@salve-software/react-native-nitro-jsdom 2.0.0 → 2.1.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/android/CMakeLists.txt +2 -0
- package/cpp/HybridHtmlSandbox.cpp +7 -4
- package/cpp/HybridHtmlSandbox.hpp +1 -1
- package/cpp/lexbor/LexborDocument.cpp +3 -3
- package/cpp/lexbor/LexborDocument.hpp +3 -2
- package/cpp/quickjs/DOMBindings.cpp +5 -0
- package/cpp/quickjs/QuickJSRuntime.hpp +1 -0
- package/cpp/quickjs/bindings/CSSOMBindings.cpp +50 -0
- package/cpp/quickjs/bindings/CustomElementsBindings.cpp +1 -1
- package/cpp/quickjs/bindings/DocumentBindings.cpp +8 -0
- package/cpp/quickjs/bindings/ElementBindings.cpp +221 -1
- package/cpp/quickjs/bindings/EventBindings.cpp +8 -4
- package/cpp/quickjs/bindings/FormBindings.cpp +71 -0
- package/cpp/quickjs/bindings/IntlBindings.cpp +352 -0
- package/cpp/quickjs/bindings/IntlBindings.hpp +29 -0
- package/cpp/quickjs/bindings/UrlBindings.cpp +34 -0
- package/cpp/quickjs/bindings/WindowBindings.cpp +9 -0
- package/cpp/quickjs/bindings/WindowNamedPropertiesBindings.cpp +241 -0
- package/cpp/quickjs/bindings/WindowNamedPropertiesBindings.hpp +30 -0
- package/lib/commonjs/classes/JSDOM/JSDOM.class.js +2 -1
- package/lib/commonjs/classes/JSDOM/JSDOM.class.js.map +1 -1
- package/lib/module/classes/JSDOM/JSDOM.class.js +2 -1
- package/lib/module/classes/JSDOM/JSDOM.class.js.map +1 -1
- package/lib/typescript/src/classes/JSDOM/JSDOM.class.d.ts.map +1 -1
- package/lib/typescript/src/classes/JSDOM/types/IJSDOMOptions.d.ts +5 -4
- package/lib/typescript/src/classes/JSDOM/types/IJSDOMOptions.d.ts.map +1 -1
- package/lib/typescript/src/specs/HtmlSandbox.nitro.d.ts +1 -1
- package/lib/typescript/src/specs/HtmlSandbox.nitro.d.ts.map +1 -1
- package/nitrogen/generated/shared/c++/HybridHtmlSandboxSpec.hpp +1 -1
- package/package.json +1 -1
- package/src/classes/JSDOM/JSDOM.class.ts +2 -1
- package/src/classes/JSDOM/types/IJSDOMOptions.ts +5 -4
- package/src/specs/HtmlSandbox.nitro.ts +1 -1
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
#include "IntlBindings.hpp"
|
|
2
|
+
#include <cstring>
|
|
3
|
+
|
|
4
|
+
namespace margelo::nitro::nitrojsdom {
|
|
5
|
+
|
|
6
|
+
namespace {
|
|
7
|
+
|
|
8
|
+
const char* kIntlBootstrapScript = R"JS(
|
|
9
|
+
(function() {
|
|
10
|
+
var LOCALE_DATA = {
|
|
11
|
+
en: {
|
|
12
|
+
months: {
|
|
13
|
+
long: ['January','February','March','April','May','June','July','August','September','October','November','December'],
|
|
14
|
+
short: ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'],
|
|
15
|
+
narrow: ['J','F','M','A','M','J','J','A','S','O','N','D'],
|
|
16
|
+
},
|
|
17
|
+
weekdays: {
|
|
18
|
+
long: ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'],
|
|
19
|
+
short: ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'],
|
|
20
|
+
narrow: ['S','M','T','W','T','F','S'],
|
|
21
|
+
},
|
|
22
|
+
dayPeriod: ['AM', 'PM'],
|
|
23
|
+
decimal: '.',
|
|
24
|
+
group: ',',
|
|
25
|
+
dateOrder: 'MDY',
|
|
26
|
+
currencySpace: false,
|
|
27
|
+
},
|
|
28
|
+
pt: {
|
|
29
|
+
months: {
|
|
30
|
+
long: ['janeiro','fevereiro','março','abril','maio','junho','julho','agosto','setembro','outubro','novembro','dezembro'],
|
|
31
|
+
short: ['jan','fev','mar','abr','mai','jun','jul','ago','set','out','nov','dez'],
|
|
32
|
+
narrow: ['J','F','M','A','M','J','J','A','S','O','N','D'],
|
|
33
|
+
},
|
|
34
|
+
weekdays: {
|
|
35
|
+
long: ['domingo','segunda-feira','terça-feira','quarta-feira','quinta-feira','sexta-feira','sábado'],
|
|
36
|
+
short: ['dom','seg','ter','qua','qui','sex','sáb'],
|
|
37
|
+
narrow: ['D','S','T','Q','Q','S','S'],
|
|
38
|
+
},
|
|
39
|
+
dayPeriod: ['AM', 'PM'],
|
|
40
|
+
decimal: ',',
|
|
41
|
+
group: '.',
|
|
42
|
+
dateOrder: 'DMY',
|
|
43
|
+
currencySpace: true,
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
var CURRENCY_SYMBOLS = {
|
|
48
|
+
USD: '$', EUR: '€', GBP: '£', JPY: '¥', BRL: 'R$', CAD: 'CA$', AUD: 'A$', CHF: 'CHF', CNY: 'CN¥', INR: '₹', MXN: 'MX$',
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
var CURRENCY_DECIMALS = {
|
|
52
|
+
JPY: 0, KRW: 0, VND: 0, CLP: 0, ISK: 0, UGX: 0, XAF: 0, XOF: 0, XPF: 0,
|
|
53
|
+
BHD: 3, IQD: 3, JOD: 3, KWD: 3, OMR: 3, TND: 3,
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
function currencyDecimals(code) {
|
|
57
|
+
return CURRENCY_DECIMALS[code] !== undefined ? CURRENCY_DECIMALS[code] : 2;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function localeLanguage(locales) {
|
|
61
|
+
var tag = Array.isArray(locales) ? locales[0] : locales;
|
|
62
|
+
tag = String(tag || 'en');
|
|
63
|
+
return tag.split(/[-_]/)[0].toLowerCase();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function resolveLocale(locales) {
|
|
67
|
+
var lang = localeLanguage(locales);
|
|
68
|
+
return LOCALE_DATA[lang] || LOCALE_DATA.en;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function groupInteger(digits, groupSep) {
|
|
72
|
+
var out = '';
|
|
73
|
+
var count = 0;
|
|
74
|
+
for (var i = digits.length - 1; i >= 0; i--) {
|
|
75
|
+
out = digits.charAt(i) + out;
|
|
76
|
+
count++;
|
|
77
|
+
if (count % 3 === 0 && i !== 0) out = groupSep + out;
|
|
78
|
+
}
|
|
79
|
+
return out;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function formatFixed(value, minFrac, maxFrac, useGrouping, data) {
|
|
83
|
+
var negative = value < 0;
|
|
84
|
+
var abs = Math.abs(value);
|
|
85
|
+
var fixed = abs.toFixed(maxFrac);
|
|
86
|
+
var parts = fixed.split('.');
|
|
87
|
+
var intPart = parts[0];
|
|
88
|
+
var fracPart = parts[1] || '';
|
|
89
|
+
|
|
90
|
+
while (fracPart.length > minFrac && fracPart.charAt(fracPart.length - 1) === '0') {
|
|
91
|
+
fracPart = fracPart.slice(0, -1);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
var out = useGrouping ? groupInteger(intPart, data.group) : intPart;
|
|
95
|
+
if (fracPart.length > 0) out += data.decimal + fracPart;
|
|
96
|
+
return (negative ? '-' : '') + out;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function NumberFormat(locales, options) {
|
|
100
|
+
this._data = resolveLocale(locales);
|
|
101
|
+
options = options || {};
|
|
102
|
+
this._style = options.style || 'decimal';
|
|
103
|
+
if (this._style === 'currency') {
|
|
104
|
+
if (!options.currency) throw new TypeError('Currency code is required with currency style.');
|
|
105
|
+
this._currency = options.currency;
|
|
106
|
+
}
|
|
107
|
+
var currDecimals = this._style === 'currency' ? currencyDecimals(this._currency) : undefined;
|
|
108
|
+
var defaultMinFrac = this._style === 'currency' ? currDecimals : 0;
|
|
109
|
+
var defaultMaxFrac = this._style === 'currency' ? currDecimals : (this._style === 'percent' ? 0 : 3);
|
|
110
|
+
this._minFrac = options.minimumFractionDigits !== undefined ? options.minimumFractionDigits : defaultMinFrac;
|
|
111
|
+
this._maxFrac = options.maximumFractionDigits !== undefined ? options.maximumFractionDigits : Math.max(defaultMaxFrac, this._minFrac);
|
|
112
|
+
this._useGrouping = options.useGrouping !== undefined ? !!options.useGrouping : true;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
NumberFormat.prototype.format = function(value) {
|
|
116
|
+
value = Number(value);
|
|
117
|
+
if (this._style === 'percent') value = value * 100;
|
|
118
|
+
var formatted = formatFixed(value, this._minFrac, this._maxFrac, this._useGrouping, this._data);
|
|
119
|
+
if (this._style === 'percent') return formatted + '%';
|
|
120
|
+
if (this._style === 'currency') {
|
|
121
|
+
var symbol = CURRENCY_SYMBOLS[this._currency] || this._currency;
|
|
122
|
+
return symbol + (this._data.currencySpace ? ' ' : '') + formatted;
|
|
123
|
+
}
|
|
124
|
+
return formatted;
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
NumberFormat.prototype.resolvedOptions = function() {
|
|
128
|
+
return {
|
|
129
|
+
style: this._style,
|
|
130
|
+
currency: this._style === 'currency' ? this._currency : undefined,
|
|
131
|
+
minimumFractionDigits: this._minFrac,
|
|
132
|
+
maximumFractionDigits: this._maxFrac,
|
|
133
|
+
useGrouping: this._useGrouping,
|
|
134
|
+
};
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
function pad2(n) { return (n < 10 ? '0' : '') + n; }
|
|
138
|
+
|
|
139
|
+
var DATE_STYLE_OPTIONS = {
|
|
140
|
+
short: { year: '2-digit', month: 'numeric', day: 'numeric' },
|
|
141
|
+
medium: { year: 'numeric', month: 'short', day: 'numeric' },
|
|
142
|
+
long: { year: 'numeric', month: 'long', day: 'numeric' },
|
|
143
|
+
full: { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' },
|
|
144
|
+
};
|
|
145
|
+
var TIME_STYLE_OPTIONS = {
|
|
146
|
+
short: { hour: 'numeric', minute: 'numeric' },
|
|
147
|
+
medium: { hour: 'numeric', minute: 'numeric', second: 'numeric' },
|
|
148
|
+
long: { hour: 'numeric', minute: 'numeric', second: 'numeric' },
|
|
149
|
+
full: { hour: 'numeric', minute: 'numeric', second: 'numeric' },
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
function DateTimeFormat(locales, options) {
|
|
153
|
+
this._data = resolveLocale(locales);
|
|
154
|
+
options = options || {};
|
|
155
|
+
|
|
156
|
+
if (options.dateStyle || options.timeStyle) {
|
|
157
|
+
var withDate = options.dateStyle ? (DATE_STYLE_OPTIONS[options.dateStyle] || DATE_STYLE_OPTIONS.medium) : {};
|
|
158
|
+
var withTime = options.timeStyle ? (TIME_STYLE_OPTIONS[options.timeStyle] || TIME_STYLE_OPTIONS.short) : {};
|
|
159
|
+
options = Object.assign({}, withDate, withTime, options);
|
|
160
|
+
} else if (Object.keys(options).length === 0) {
|
|
161
|
+
options = { year: 'numeric', month: 'numeric', day: 'numeric' };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
this._options = options;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function datePartFor(date, o, data) {
|
|
168
|
+
var textual = o.month === 'long' || o.month === 'short' || o.month === 'narrow';
|
|
169
|
+
var day = o.day ? (o.day === '2-digit' ? pad2(date.getDate()) : String(date.getDate())) : '';
|
|
170
|
+
var year = o.year ? (o.year === '2-digit' ? pad2(date.getFullYear() % 100) : String(date.getFullYear())) : '';
|
|
171
|
+
var month = o.month
|
|
172
|
+
? (textual ? data.months[o.month][date.getMonth()] : (o.month === '2-digit' ? pad2(date.getMonth() + 1) : String(date.getMonth() + 1)))
|
|
173
|
+
: '';
|
|
174
|
+
|
|
175
|
+
if (!month && !day && !year) return '';
|
|
176
|
+
|
|
177
|
+
if (textual) {
|
|
178
|
+
if (data.dateOrder === 'MDY') {
|
|
179
|
+
var head = day ? month + ' ' + day : month;
|
|
180
|
+
return year ? head + ', ' + year : head;
|
|
181
|
+
}
|
|
182
|
+
return [day, month, year].filter(Boolean).join(' ');
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
var ordered = data.dateOrder === 'MDY' ? [month, day, year] : [day, month, year];
|
|
186
|
+
return ordered.filter(Boolean).join('/');
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
DateTimeFormat.prototype.format = function(date) {
|
|
190
|
+
date = date === undefined ? new Date() : (date instanceof Date ? date : new Date(date));
|
|
191
|
+
var o = this._options;
|
|
192
|
+
var data = this._data;
|
|
193
|
+
var pieces = [];
|
|
194
|
+
|
|
195
|
+
var datePart = datePartFor(date, o, data);
|
|
196
|
+
if (o.weekday) {
|
|
197
|
+
var w = date.getDay();
|
|
198
|
+
var weekdayName = data.weekdays[o.weekday === 'narrow' ? 'narrow' : (o.weekday === 'long' ? 'long' : 'short')][w];
|
|
199
|
+
pieces.push(datePart ? weekdayName + ', ' + datePart : weekdayName);
|
|
200
|
+
} else if (datePart) {
|
|
201
|
+
pieces.push(datePart);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (o.hour) {
|
|
205
|
+
var h = date.getHours();
|
|
206
|
+
var hour12 = o.hour12 !== false;
|
|
207
|
+
var hourVal = hour12 ? (h % 12 === 0 ? 12 : h % 12) : h;
|
|
208
|
+
var timeSegs = [o.hour === '2-digit' ? pad2(hourVal) : String(hourVal)];
|
|
209
|
+
if (o.minute) timeSegs.push(pad2(date.getMinutes()));
|
|
210
|
+
if (o.second) timeSegs.push(pad2(date.getSeconds()));
|
|
211
|
+
var timeStr = timeSegs.join(':');
|
|
212
|
+
if (hour12) timeStr += ' ' + (h < 12 ? data.dayPeriod[0] : data.dayPeriod[1]);
|
|
213
|
+
pieces.push(timeStr);
|
|
214
|
+
} else if (o.minute) {
|
|
215
|
+
var timeSegs2 = [pad2(date.getMinutes())];
|
|
216
|
+
if (o.second) timeSegs2.push(pad2(date.getSeconds()));
|
|
217
|
+
pieces.push(timeSegs2.join(':'));
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return pieces.join(', ');
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
DateTimeFormat.prototype.resolvedOptions = function() {
|
|
224
|
+
return Object.assign({}, this._options);
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
function pluralCategoryFor(n, lang) {
|
|
228
|
+
n = Math.abs(Number(n));
|
|
229
|
+
if (lang === 'pt') return (n === 0 || n === 1) ? 'one' : 'other';
|
|
230
|
+
return n === 1 ? 'one' : 'other';
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function PluralRules(locales, options) {
|
|
234
|
+
this._lang = localeLanguage(locales);
|
|
235
|
+
options = options || {};
|
|
236
|
+
this._type = options.type || 'cardinal';
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
PluralRules.prototype.select = function(n) {
|
|
240
|
+
return pluralCategoryFor(n, this._lang);
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
PluralRules.prototype.resolvedOptions = function() {
|
|
244
|
+
return { locale: this._lang, type: this._type, pluralCategories: ['one', 'other'] };
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
var RTF_UNITS = {
|
|
248
|
+
en: {
|
|
249
|
+
year: ['year', 'years'], quarter: ['quarter', 'quarters'], month: ['month', 'months'],
|
|
250
|
+
week: ['week', 'weeks'], day: ['day', 'days'], hour: ['hour', 'hours'],
|
|
251
|
+
minute: ['minute', 'minutes'], second: ['second', 'seconds'],
|
|
252
|
+
},
|
|
253
|
+
pt: {
|
|
254
|
+
year: ['ano', 'anos'], quarter: ['trimestre', 'trimestres'], month: ['mês', 'meses'],
|
|
255
|
+
week: ['semana', 'semanas'], day: ['dia', 'dias'], hour: ['hora', 'horas'],
|
|
256
|
+
minute: ['minuto', 'minutos'], second: ['segundo', 'segundos'],
|
|
257
|
+
},
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
var RTF_PATTERNS = {
|
|
261
|
+
en: { past: '{0} {1} ago', future: 'in {0} {1}' },
|
|
262
|
+
pt: { past: 'há {0} {1}', future: 'em {0} {1}' },
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
var RTF_AUTO = {
|
|
266
|
+
en: {
|
|
267
|
+
day: { '-1': 'yesterday', '0': 'today', '1': 'tomorrow' },
|
|
268
|
+
week: { '-1': 'last week', '0': 'this week', '1': 'next week' },
|
|
269
|
+
month: { '-1': 'last month', '0': 'this month', '1': 'next month' },
|
|
270
|
+
year: { '-1': 'last year', '0': 'this year', '1': 'next year' },
|
|
271
|
+
second: { '0': 'now' },
|
|
272
|
+
},
|
|
273
|
+
pt: {
|
|
274
|
+
day: { '-1': 'ontem', '0': 'hoje', '1': 'amanhã' },
|
|
275
|
+
week: { '-1': 'semana passada', '0': 'esta semana', '1': 'semana que vem' },
|
|
276
|
+
month: { '-1': 'mês passado', '0': 'este mês', '1': 'próximo mês' },
|
|
277
|
+
year: { '-1': 'ano passado', '0': 'este ano', '1': 'próximo ano' },
|
|
278
|
+
second: { '0': 'agora' },
|
|
279
|
+
},
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
var RTF_UNIT_ALIASES = {
|
|
283
|
+
years: 'year', quarters: 'quarter', months: 'month', weeks: 'week',
|
|
284
|
+
days: 'day', hours: 'hour', minutes: 'minute', seconds: 'second',
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
function RelativeTimeFormat(locales, options) {
|
|
288
|
+
this._lang = RTF_UNITS[localeLanguage(locales)] ? localeLanguage(locales) : 'en';
|
|
289
|
+
options = options || {};
|
|
290
|
+
this._numeric = options.numeric || 'always';
|
|
291
|
+
this._style = options.style || 'long';
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
RelativeTimeFormat.prototype.format = function(value, unit) {
|
|
295
|
+
value = Number(value);
|
|
296
|
+
unit = RTF_UNIT_ALIASES[unit] || unit;
|
|
297
|
+
var units = RTF_UNITS[this._lang][unit];
|
|
298
|
+
if (!units) throw new RangeError('Invalid unit argument for RelativeTimeFormat.format()');
|
|
299
|
+
|
|
300
|
+
if (this._numeric === 'auto') {
|
|
301
|
+
var autoData = RTF_AUTO[this._lang][unit];
|
|
302
|
+
if (autoData && autoData[String(value)] !== undefined) {
|
|
303
|
+
return autoData[String(value)];
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
var n = Math.abs(value);
|
|
308
|
+
var unitName = units[pluralCategoryFor(n, this._lang) === 'one' ? 0 : 1];
|
|
309
|
+
var pattern = value < 0 ? RTF_PATTERNS[this._lang].past : RTF_PATTERNS[this._lang].future;
|
|
310
|
+
return pattern.replace('{0}', String(n)).replace('{1}', unitName);
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
RelativeTimeFormat.prototype.resolvedOptions = function() {
|
|
314
|
+
return { locale: this._lang, style: this._style, numeric: this._numeric };
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
globalThis.Intl = globalThis.Intl || {};
|
|
318
|
+
globalThis.Intl.NumberFormat = NumberFormat;
|
|
319
|
+
globalThis.Intl.DateTimeFormat = DateTimeFormat;
|
|
320
|
+
globalThis.Intl.PluralRules = PluralRules;
|
|
321
|
+
globalThis.Intl.RelativeTimeFormat = RelativeTimeFormat;
|
|
322
|
+
|
|
323
|
+
function hasOwnKeys(o) { return !!o && Object.keys(o).length > 0; }
|
|
324
|
+
|
|
325
|
+
Number.prototype.toLocaleString = function(locales, options) {
|
|
326
|
+
return new NumberFormat(locales, options).format(this);
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
Date.prototype.toLocaleString = function(locales, options) {
|
|
330
|
+
return new DateTimeFormat(locales, hasOwnKeys(options) ? options : { year: 'numeric', month: 'numeric', day: 'numeric', hour: 'numeric', minute: 'numeric', second: 'numeric' }).format(this);
|
|
331
|
+
};
|
|
332
|
+
Date.prototype.toLocaleDateString = function(locales, options) {
|
|
333
|
+
return new DateTimeFormat(locales, hasOwnKeys(options) ? options : { year: 'numeric', month: 'numeric', day: 'numeric' }).format(this);
|
|
334
|
+
};
|
|
335
|
+
Date.prototype.toLocaleTimeString = function(locales, options) {
|
|
336
|
+
return new DateTimeFormat(locales, hasOwnKeys(options) ? options : { hour: 'numeric', minute: 'numeric', second: 'numeric' }).format(this);
|
|
337
|
+
};
|
|
338
|
+
})();
|
|
339
|
+
)JS";
|
|
340
|
+
|
|
341
|
+
} // namespace
|
|
342
|
+
|
|
343
|
+
void IntlBindings::install(JSContext* ctx) {
|
|
344
|
+
JSValue result = JS_Eval(ctx, kIntlBootstrapScript, strlen(kIntlBootstrapScript),
|
|
345
|
+
"<intl-bootstrap>", JS_EVAL_TYPE_GLOBAL);
|
|
346
|
+
if (JS_IsException(result)) {
|
|
347
|
+
JS_FreeValue(ctx, JS_GetException(ctx));
|
|
348
|
+
}
|
|
349
|
+
JS_FreeValue(ctx, result);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
} // namespace margelo::nitro::nitrojsdom
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include "quickjs.h"
|
|
4
|
+
|
|
5
|
+
namespace margelo::nitro::nitrojsdom {
|
|
6
|
+
|
|
7
|
+
// Registers a pure-JS globalThis.Intl.NumberFormat/DateTimeFormat/
|
|
8
|
+
// PluralRules/RelativeTimeFormat (no ICU — QuickJS ships none). Locale data
|
|
9
|
+
// is a small hand-built table (en + pt, the two this project's users
|
|
10
|
+
// actually need), not real CLDR data. Also rewires Number.prototype.
|
|
11
|
+
// toLocaleString and Date.prototype.toLocaleString/toLocaleDateString/
|
|
12
|
+
// toLocaleTimeString to go through these instead of QuickJS's own
|
|
13
|
+
// locale-blind built-ins.
|
|
14
|
+
//
|
|
15
|
+
// RelativeTimeFormat's `style` option ('long'/'short'/'narrow') is accepted
|
|
16
|
+
// but 'short'/'narrow' render identically to 'long' — there's no abbreviated
|
|
17
|
+
// unit-name table, same trade-off DateTimeFormat already made collapsing
|
|
18
|
+
// timeStyle 'long'/'full'. `numeric: 'auto'` only has special phrasing
|
|
19
|
+
// ("yesterday", "next week", ..., plus "now"/"agora" for second: 0) for
|
|
20
|
+
// day/week/month/year/second — hour/minute/quarter always render
|
|
21
|
+
// numerically, matching real Intl's own behavior for units without a
|
|
22
|
+
// natural auto phrase. Auto phrasing only ever applies to exact supported
|
|
23
|
+
// integer values (e.g. -1/0/1) — a fractional value like 1.2 always falls
|
|
24
|
+
// through to numeric formatting rather than rounding into a phrase.
|
|
25
|
+
struct IntlBindings {
|
|
26
|
+
static void install(JSContext* ctx);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
} // namespace margelo::nitro::nitrojsdom
|
|
@@ -278,6 +278,40 @@ const char* kUrlBootstrapScript = R"JS(
|
|
|
278
278
|
};
|
|
279
279
|
|
|
280
280
|
globalThis.URL = URL;
|
|
281
|
+
|
|
282
|
+
function isAnchorElement(el) {
|
|
283
|
+
return !!el && (el.tagName === 'A' || el.tagName === 'AREA');
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function anchorUrl(el) {
|
|
287
|
+
var raw = el.getAttribute('href');
|
|
288
|
+
if (raw === null) return null;
|
|
289
|
+
try { return new URL(raw, document.baseURI); } catch (e) { return null; }
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
Object.defineProperty(Element.prototype, 'href', {
|
|
293
|
+
configurable: true,
|
|
294
|
+
get: function() {
|
|
295
|
+
if (!isAnchorElement(this)) return undefined;
|
|
296
|
+
var u = anchorUrl(this);
|
|
297
|
+
return u ? u.href : '';
|
|
298
|
+
},
|
|
299
|
+
set: function(value) {
|
|
300
|
+
if (!isAnchorElement(this)) return;
|
|
301
|
+
this.setAttribute('href', String(value));
|
|
302
|
+
},
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
['protocol', 'username', 'password', 'hostname', 'port', 'pathname', 'search', 'hash', 'host', 'origin'].forEach(function(part) {
|
|
306
|
+
Object.defineProperty(Element.prototype, part, {
|
|
307
|
+
configurable: true,
|
|
308
|
+
get: function() {
|
|
309
|
+
if (!isAnchorElement(this)) return undefined;
|
|
310
|
+
var u = anchorUrl(this);
|
|
311
|
+
return u ? u[part] : '';
|
|
312
|
+
},
|
|
313
|
+
});
|
|
314
|
+
});
|
|
281
315
|
})();
|
|
282
316
|
)JS";
|
|
283
317
|
|
|
@@ -354,6 +354,15 @@ const char* kLocationBootstrapScript = R"JS(
|
|
|
354
354
|
globalThis.Location = Location;
|
|
355
355
|
globalThis.location = new Location(globalThis.__initialHref);
|
|
356
356
|
delete globalThis.__initialHref;
|
|
357
|
+
|
|
358
|
+
if (typeof document !== 'undefined') {
|
|
359
|
+
Object.defineProperty(document, 'location', {
|
|
360
|
+
get: function() { return location; },
|
|
361
|
+
set: function(v) { location.href = v; },
|
|
362
|
+
enumerable: true,
|
|
363
|
+
configurable: true,
|
|
364
|
+
});
|
|
365
|
+
}
|
|
357
366
|
})();
|
|
358
367
|
)JS";
|
|
359
368
|
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
#include "WindowNamedPropertiesBindings.hpp"
|
|
2
|
+
#include <cstring>
|
|
3
|
+
|
|
4
|
+
namespace margelo::nitro::nitrojsdom {
|
|
5
|
+
|
|
6
|
+
namespace {
|
|
7
|
+
|
|
8
|
+
const char* kWindowNamedPropertiesBootstrapScript = R"JS(
|
|
9
|
+
(function() {
|
|
10
|
+
var NAME_ATTR_TAGS = { embed: 1, form: 1, img: 1, object: 1, iframe: 1, frame: 1 };
|
|
11
|
+
|
|
12
|
+
// registry: key -> element[] currently claiming it. owned: key -> true once
|
|
13
|
+
// this module has written globalThis[key] (so it knows it's safe to delete
|
|
14
|
+
// or reassign later without clobbering something else's global).
|
|
15
|
+
var registry = Object.create(null);
|
|
16
|
+
var owned = Object.create(null);
|
|
17
|
+
|
|
18
|
+
function isConnected(el) {
|
|
19
|
+
var docEl = document.documentElement;
|
|
20
|
+
var n = el;
|
|
21
|
+
while (n) {
|
|
22
|
+
if (n === docEl) return true;
|
|
23
|
+
n = n.parentNode;
|
|
24
|
+
}
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function refreshKey(key) {
|
|
29
|
+
var els = registry[key];
|
|
30
|
+
if (!els || els.length === 0) {
|
|
31
|
+
if (owned[key]) { delete globalThis[key]; delete owned[key]; }
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (!owned[key] && (key in globalThis)) return; // don't clobber a real global
|
|
35
|
+
globalThis[key] = els.length === 1 ? els[0] : els.slice();
|
|
36
|
+
owned[key] = true;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function addToRegistry(key, el) {
|
|
40
|
+
if (!key) return;
|
|
41
|
+
var els = registry[key] || (registry[key] = []);
|
|
42
|
+
if (els.indexOf(el) === -1) els.push(el);
|
|
43
|
+
refreshKey(key);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function removeFromRegistry(key, el) {
|
|
47
|
+
if (!key) return;
|
|
48
|
+
var els = registry[key];
|
|
49
|
+
if (!els) return;
|
|
50
|
+
var idx = els.indexOf(el);
|
|
51
|
+
if (idx !== -1) els.splice(idx, 1);
|
|
52
|
+
refreshKey(key);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function keysFor(el) {
|
|
56
|
+
var keys = [];
|
|
57
|
+
var id = el.getAttribute ? el.getAttribute('id') : null;
|
|
58
|
+
if (id) keys.push(id);
|
|
59
|
+
var tag = el.tagName ? el.tagName.toLowerCase() : '';
|
|
60
|
+
if (NAME_ATTR_TAGS[tag]) {
|
|
61
|
+
var name = el.getAttribute('name');
|
|
62
|
+
if (name) keys.push(name);
|
|
63
|
+
}
|
|
64
|
+
return keys;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function registerIfConnected(el) {
|
|
68
|
+
if (!el || el.nodeType !== 1 || !isConnected(el)) return;
|
|
69
|
+
keysFor(el).forEach(function(k) { addToRegistry(k, el); });
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function unregister(el) {
|
|
73
|
+
if (!el || el.nodeType !== 1) return;
|
|
74
|
+
keysFor(el).forEach(function(k) { removeFromRegistry(k, el); });
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function registerSubtree(root) {
|
|
78
|
+
if (!root) return;
|
|
79
|
+
if (root.nodeType === 1) registerIfConnected(root);
|
|
80
|
+
if (typeof root.querySelectorAll !== 'function') return;
|
|
81
|
+
var found = root.querySelectorAll('*');
|
|
82
|
+
for (var i = 0; i < found.length; i++) registerIfConnected(found[i]);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function unregisterSubtree(root) {
|
|
86
|
+
if (!root) return;
|
|
87
|
+
if (root.nodeType === 1) unregister(root);
|
|
88
|
+
if (typeof root.querySelectorAll !== 'function') return;
|
|
89
|
+
var found = root.querySelectorAll('*');
|
|
90
|
+
for (var i = 0; i < found.length; i++) unregister(found[i]);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function collectSubtreeKeys(root) {
|
|
94
|
+
var pending = [];
|
|
95
|
+
function collect(el) {
|
|
96
|
+
if (!el || el.nodeType !== 1) return;
|
|
97
|
+
keysFor(el).forEach(function(k) { pending.push({ key: k, el: el }); });
|
|
98
|
+
}
|
|
99
|
+
if (!root) return pending;
|
|
100
|
+
collect(root);
|
|
101
|
+
if (typeof root.querySelectorAll === 'function') {
|
|
102
|
+
var found = root.querySelectorAll('*');
|
|
103
|
+
for (var i = 0; i < found.length; i++) collect(found[i]);
|
|
104
|
+
}
|
|
105
|
+
return pending;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function commitUnregister(pending) {
|
|
109
|
+
pending.forEach(function(entry) { removeFromRegistry(entry.key, entry.el); });
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ── Initial population: elements already present in the parsed document ──
|
|
113
|
+
if (document.documentElement) registerSubtree(document.documentElement);
|
|
114
|
+
|
|
115
|
+
// ── appendChild / removeChild ─────────────────────────────────────────
|
|
116
|
+
var DOCUMENT_FRAGMENT_NODE = 11;
|
|
117
|
+
|
|
118
|
+
var origAppendChild = Node.prototype.appendChild;
|
|
119
|
+
Node.prototype.appendChild = function(child) {
|
|
120
|
+
var moved = (child && child.nodeType === DOCUMENT_FRAGMENT_NODE)
|
|
121
|
+
? Array.prototype.slice.call(child.childNodes)
|
|
122
|
+
: [child];
|
|
123
|
+
var result = origAppendChild.call(this, child);
|
|
124
|
+
moved.forEach(registerSubtree);
|
|
125
|
+
return result;
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
var origRemoveChild = Node.prototype.removeChild;
|
|
129
|
+
Node.prototype.removeChild = function(child) {
|
|
130
|
+
var pending = collectSubtreeKeys(child);
|
|
131
|
+
var willRemove = child && child.parentNode === this;
|
|
132
|
+
var result = origRemoveChild.call(this, child);
|
|
133
|
+
if (willRemove) commitUnregister(pending);
|
|
134
|
+
return result;
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
var origRemove = Element.prototype.remove;
|
|
138
|
+
Element.prototype.remove = function() {
|
|
139
|
+
var pending = collectSubtreeKeys(this);
|
|
140
|
+
var hadParent = this.parentNode !== null;
|
|
141
|
+
var result = origRemove.call(this);
|
|
142
|
+
if (hadParent) commitUnregister(pending);
|
|
143
|
+
return result;
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
// ── innerHTML ────────────────────────────────────────────────────────
|
|
147
|
+
var innerHTMLDesc = Object.getOwnPropertyDescriptor(Element.prototype, 'innerHTML');
|
|
148
|
+
if (innerHTMLDesc && innerHTMLDesc.set) {
|
|
149
|
+
var origInnerHTMLSet = innerHTMLDesc.set;
|
|
150
|
+
Object.defineProperty(Element.prototype, 'innerHTML', {
|
|
151
|
+
get: innerHTMLDesc.get,
|
|
152
|
+
set: function(html) {
|
|
153
|
+
unregisterSubtree(this); // walk the OLD content before it's replaced
|
|
154
|
+
origInnerHTMLSet.call(this, html);
|
|
155
|
+
registerSubtree(this); // register the NEW content
|
|
156
|
+
},
|
|
157
|
+
enumerable: innerHTMLDesc.enumerable,
|
|
158
|
+
configurable: true,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
var textContentDesc = Object.getOwnPropertyDescriptor(Node.prototype, 'textContent');
|
|
163
|
+
if (textContentDesc && textContentDesc.set) {
|
|
164
|
+
var origTextContentSet = textContentDesc.set;
|
|
165
|
+
Object.defineProperty(Node.prototype, 'textContent', {
|
|
166
|
+
get: textContentDesc.get,
|
|
167
|
+
set: function(value) {
|
|
168
|
+
unregisterSubtree(this);
|
|
169
|
+
origTextContentSet.call(this, value);
|
|
170
|
+
},
|
|
171
|
+
enumerable: textContentDesc.enumerable,
|
|
172
|
+
configurable: true,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// ── insertAdjacentHTML ──────────────────────────────────────────────
|
|
177
|
+
var origInsertAdjacentHTML = Element.prototype.insertAdjacentHTML;
|
|
178
|
+
Element.prototype.insertAdjacentHTML = function(position, html) {
|
|
179
|
+
origInsertAdjacentHTML.call(this, position, html);
|
|
180
|
+
var pos = String(position).toLowerCase();
|
|
181
|
+
registerSubtree((pos === 'beforebegin' || pos === 'afterend') ? (this.parentNode || this) : this);
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
// ── element.id (native accessor property — bypasses setAttribute) ──
|
|
185
|
+
var idDesc = Object.getOwnPropertyDescriptor(Element.prototype, 'id');
|
|
186
|
+
if (idDesc && idDesc.set) {
|
|
187
|
+
var origIdSet = idDesc.set;
|
|
188
|
+
Object.defineProperty(Element.prototype, 'id', {
|
|
189
|
+
get: idDesc.get,
|
|
190
|
+
set: function(value) {
|
|
191
|
+
var oldValue = this.getAttribute('id');
|
|
192
|
+
origIdSet.call(this, value);
|
|
193
|
+
if (oldValue) removeFromRegistry(oldValue, this);
|
|
194
|
+
registerIfConnected(this);
|
|
195
|
+
},
|
|
196
|
+
enumerable: idDesc.enumerable,
|
|
197
|
+
configurable: true,
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// ── setAttribute / removeAttribute (id/name changes) ───────────────
|
|
202
|
+
function isTrackedAttr(el, name) {
|
|
203
|
+
var lower = String(name).toLowerCase();
|
|
204
|
+
if (lower === 'id') return true;
|
|
205
|
+
var tag = el.tagName ? el.tagName.toLowerCase() : '';
|
|
206
|
+
return lower === 'name' && !!NAME_ATTR_TAGS[tag];
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
var origSetAttribute = Element.prototype.setAttribute;
|
|
210
|
+
Element.prototype.setAttribute = function(name, value) {
|
|
211
|
+
var tracked = isTrackedAttr(this, name);
|
|
212
|
+
var oldValue = tracked ? this.getAttribute(name) : null;
|
|
213
|
+
origSetAttribute.call(this, name, value);
|
|
214
|
+
if (tracked) {
|
|
215
|
+
if (oldValue) removeFromRegistry(oldValue, this);
|
|
216
|
+
registerIfConnected(this);
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
var origRemoveAttribute = Element.prototype.removeAttribute;
|
|
221
|
+
Element.prototype.removeAttribute = function(name) {
|
|
222
|
+
var tracked = isTrackedAttr(this, name);
|
|
223
|
+
var oldValue = tracked ? this.getAttribute(name) : null;
|
|
224
|
+
origRemoveAttribute.call(this, name);
|
|
225
|
+
if (tracked && oldValue) removeFromRegistry(oldValue, this);
|
|
226
|
+
};
|
|
227
|
+
})();
|
|
228
|
+
)JS";
|
|
229
|
+
|
|
230
|
+
} // namespace
|
|
231
|
+
|
|
232
|
+
void WindowNamedPropertiesBindings::install(JSContext* ctx) {
|
|
233
|
+
JSValue result = JS_Eval(ctx, kWindowNamedPropertiesBootstrapScript, strlen(kWindowNamedPropertiesBootstrapScript),
|
|
234
|
+
"<window-named-properties-bootstrap>", JS_EVAL_TYPE_GLOBAL);
|
|
235
|
+
if (JS_IsException(result)) {
|
|
236
|
+
JS_FreeValue(ctx, JS_GetException(ctx));
|
|
237
|
+
}
|
|
238
|
+
JS_FreeValue(ctx, result);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
} // namespace margelo::nitro::nitrojsdom
|