@salve-software/react-native-nitro-jsdom 1.0.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.
Files changed (116) hide show
  1. package/LICENSE +21 -0
  2. package/NitroJsdom.podspec +63 -0
  3. package/README.md +95 -0
  4. package/android/CMakeLists.txt +86 -0
  5. package/android/build.gradle +148 -0
  6. package/android/fix-prefab.gradle +51 -0
  7. package/android/gradle.properties +5 -0
  8. package/android/src/main/AndroidManifest.xml +2 -0
  9. package/android/src/main/cpp/cpp-adapter.cpp +9 -0
  10. package/android/src/main/java/com/nitrojsdom/NitroJsdomPackage.kt +20 -0
  11. package/cpp/HybridHtmlSandbox.cpp +171 -0
  12. package/cpp/HybridHtmlSandbox.hpp +47 -0
  13. package/cpp/lexbor/LexborDocument.cpp +365 -0
  14. package/cpp/lexbor/LexborDocument.hpp +71 -0
  15. package/cpp/quickjs/DOMBindings.cpp +57 -0
  16. package/cpp/quickjs/DOMBindings.hpp +21 -0
  17. package/cpp/quickjs/DOMBindingsInternal.cpp +157 -0
  18. package/cpp/quickjs/DOMBindingsInternal.hpp +83 -0
  19. package/cpp/quickjs/MutationObservers.cpp +546 -0
  20. package/cpp/quickjs/MutationObservers.hpp +127 -0
  21. package/cpp/quickjs/QuickJSRuntime.cpp +475 -0
  22. package/cpp/quickjs/QuickJSRuntime.hpp +110 -0
  23. package/cpp/quickjs/Storage.cpp +130 -0
  24. package/cpp/quickjs/Storage.hpp +40 -0
  25. package/cpp/quickjs/bindings/ClassListBindings.cpp +241 -0
  26. package/cpp/quickjs/bindings/ClassListBindings.hpp +29 -0
  27. package/cpp/quickjs/bindings/DatasetBindings.cpp +120 -0
  28. package/cpp/quickjs/bindings/DatasetBindings.hpp +20 -0
  29. package/cpp/quickjs/bindings/DocumentBindings.cpp +155 -0
  30. package/cpp/quickjs/bindings/DocumentBindings.hpp +19 -0
  31. package/cpp/quickjs/bindings/ElementBindings.cpp +1106 -0
  32. package/cpp/quickjs/bindings/ElementBindings.hpp +19 -0
  33. package/cpp/quickjs/bindings/EventBindings.cpp +352 -0
  34. package/cpp/quickjs/bindings/EventBindings.hpp +21 -0
  35. package/cpp/quickjs/bindings/FetchBindings.cpp +347 -0
  36. package/cpp/quickjs/bindings/FetchBindings.hpp +16 -0
  37. package/cpp/quickjs/bindings/StyleBindings.cpp +245 -0
  38. package/cpp/quickjs/bindings/StyleBindings.hpp +22 -0
  39. package/cpp/quickjs/bindings/TimerBindings.cpp +94 -0
  40. package/cpp/quickjs/bindings/TimerBindings.hpp +16 -0
  41. package/cpp/quickjs/bindings/WindowBindings.cpp +211 -0
  42. package/cpp/quickjs/bindings/WindowBindings.hpp +16 -0
  43. package/ios/Bridge.h +8 -0
  44. package/lib/commonjs/classes/JSDOM/JSDOM.class.js +138 -0
  45. package/lib/commonjs/classes/JSDOM/JSDOM.class.js.map +1 -0
  46. package/lib/commonjs/classes/JSDOM/index.js +17 -0
  47. package/lib/commonjs/classes/JSDOM/index.js.map +1 -0
  48. package/lib/commonjs/classes/JSDOM/types/IFetchOptions.js +2 -0
  49. package/lib/commonjs/classes/JSDOM/types/IFetchOptions.js.map +1 -0
  50. package/lib/commonjs/classes/JSDOM/types/IJSDOMOptions.js +6 -0
  51. package/lib/commonjs/classes/JSDOM/types/IJSDOMOptions.js.map +1 -0
  52. package/lib/commonjs/classes/JSDOM/types/index.js +2 -0
  53. package/lib/commonjs/classes/JSDOM/types/index.js.map +1 -0
  54. package/lib/commonjs/classes/index.js +17 -0
  55. package/lib/commonjs/classes/index.js.map +1 -0
  56. package/lib/commonjs/index.js +17 -0
  57. package/lib/commonjs/index.js.map +1 -0
  58. package/lib/commonjs/package.json +1 -0
  59. package/lib/commonjs/specs/HtmlSandbox.nitro.js +6 -0
  60. package/lib/commonjs/specs/HtmlSandbox.nitro.js.map +1 -0
  61. package/lib/module/classes/JSDOM/JSDOM.class.js +133 -0
  62. package/lib/module/classes/JSDOM/JSDOM.class.js.map +1 -0
  63. package/lib/module/classes/JSDOM/index.js +4 -0
  64. package/lib/module/classes/JSDOM/index.js.map +1 -0
  65. package/lib/module/classes/JSDOM/types/IFetchOptions.js +2 -0
  66. package/lib/module/classes/JSDOM/types/IFetchOptions.js.map +1 -0
  67. package/lib/module/classes/JSDOM/types/IJSDOMOptions.js +4 -0
  68. package/lib/module/classes/JSDOM/types/IJSDOMOptions.js.map +1 -0
  69. package/lib/module/classes/JSDOM/types/index.js +2 -0
  70. package/lib/module/classes/JSDOM/types/index.js.map +1 -0
  71. package/lib/module/classes/index.js +4 -0
  72. package/lib/module/classes/index.js.map +1 -0
  73. package/lib/module/index.js +4 -0
  74. package/lib/module/index.js.map +1 -0
  75. package/lib/module/specs/HtmlSandbox.nitro.js +4 -0
  76. package/lib/module/specs/HtmlSandbox.nitro.js.map +1 -0
  77. package/lib/typescript/src/classes/JSDOM/JSDOM.class.d.ts +74 -0
  78. package/lib/typescript/src/classes/JSDOM/JSDOM.class.d.ts.map +1 -0
  79. package/lib/typescript/src/classes/JSDOM/index.d.ts +3 -0
  80. package/lib/typescript/src/classes/JSDOM/index.d.ts.map +1 -0
  81. package/lib/typescript/src/classes/JSDOM/types/IFetchOptions.d.ts +14 -0
  82. package/lib/typescript/src/classes/JSDOM/types/IFetchOptions.d.ts.map +1 -0
  83. package/lib/typescript/src/classes/JSDOM/types/IJSDOMOptions.d.ts +57 -0
  84. package/lib/typescript/src/classes/JSDOM/types/IJSDOMOptions.d.ts.map +1 -0
  85. package/lib/typescript/src/classes/JSDOM/types/index.d.ts +3 -0
  86. package/lib/typescript/src/classes/JSDOM/types/index.d.ts.map +1 -0
  87. package/lib/typescript/src/classes/index.d.ts +2 -0
  88. package/lib/typescript/src/classes/index.d.ts.map +1 -0
  89. package/lib/typescript/src/index.d.ts +2 -0
  90. package/lib/typescript/src/index.d.ts.map +1 -0
  91. package/lib/typescript/src/specs/HtmlSandbox.nitro.d.ts +13 -0
  92. package/lib/typescript/src/specs/HtmlSandbox.nitro.d.ts.map +1 -0
  93. package/nitro.json +26 -0
  94. package/nitrogen/generated/.gitattributes +1 -0
  95. package/nitrogen/generated/android/NitroJsdom+autolinking.cmake +81 -0
  96. package/nitrogen/generated/android/NitroJsdom+autolinking.gradle +27 -0
  97. package/nitrogen/generated/android/NitroJsdomOnLoad.cpp +49 -0
  98. package/nitrogen/generated/android/NitroJsdomOnLoad.hpp +34 -0
  99. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrojsdom/NitroJsdomOnLoad.kt +35 -0
  100. package/nitrogen/generated/ios/NitroJsdom+autolinking.rb +62 -0
  101. package/nitrogen/generated/ios/NitroJsdom-Swift-Cxx-Bridge.cpp +17 -0
  102. package/nitrogen/generated/ios/NitroJsdom-Swift-Cxx-Bridge.hpp +27 -0
  103. package/nitrogen/generated/ios/NitroJsdom-Swift-Cxx-Umbrella.hpp +38 -0
  104. package/nitrogen/generated/ios/NitroJsdomAutolinking.mm +35 -0
  105. package/nitrogen/generated/ios/NitroJsdomAutolinking.swift +16 -0
  106. package/nitrogen/generated/shared/c++/HybridHtmlSandboxSpec.cpp +26 -0
  107. package/nitrogen/generated/shared/c++/HybridHtmlSandboxSpec.hpp +73 -0
  108. package/package.json +131 -0
  109. package/src/classes/JSDOM/JSDOM.class.ts +145 -0
  110. package/src/classes/JSDOM/index.ts +2 -0
  111. package/src/classes/JSDOM/types/IFetchOptions.ts +14 -0
  112. package/src/classes/JSDOM/types/IJSDOMOptions.ts +57 -0
  113. package/src/classes/JSDOM/types/index.ts +2 -0
  114. package/src/classes/index.ts +1 -0
  115. package/src/index.ts +1 -0
  116. package/src/specs/HtmlSandbox.nitro.ts +33 -0
@@ -0,0 +1,347 @@
1
+ #include "FetchBindings.hpp"
2
+ #include "../DOMBindingsInternal.hpp"
3
+ #include "../QuickJSRuntime.hpp"
4
+ #include <cstring>
5
+ #include <optional>
6
+ #include <string>
7
+
8
+ namespace margelo::nitro::nitrojsdom {
9
+
10
+ namespace {
11
+
12
+ JSValue js_fetch_native(JSContext* ctx, JSValue, int argc, JSValue* argv) {
13
+ auto* rctx = get_ctx(ctx);
14
+ if (!rctx || !rctx->fetch_callback) {
15
+ JSValue result = JS_NewObject(ctx);
16
+ JS_SetPropertyStr(ctx, result, "error", JS_NewString(ctx, "fetch is not available: no onFetch handler configured"));
17
+ return result;
18
+ }
19
+
20
+ auto toStr = [&](JSValue v, bool& had_exception) -> std::string {
21
+ JSValue s = JS_ToString(ctx, v);
22
+ if (JS_IsException(s)) { had_exception = true; return ""; }
23
+ const char* c = JS_ToCString(ctx, s);
24
+ std::string out = c ? c : "";
25
+ if (c) JS_FreeCString(ctx, c);
26
+ JS_FreeValue(ctx, s);
27
+ return out;
28
+ };
29
+
30
+ bool ex = false;
31
+ std::string url = argc >= 1 ? toStr(argv[0], ex) : "";
32
+ if (ex) return JS_EXCEPTION;
33
+ std::string method = argc >= 2 ? toStr(argv[1], ex) : "GET";
34
+ if (ex) return JS_EXCEPTION;
35
+ std::string headersJson = argc >= 3 ? toStr(argv[2], ex) : "{}";
36
+ if (ex) return JS_EXCEPTION;
37
+ std::optional<std::string> body;
38
+ if (argc >= 4 && !JS_IsUndefined(argv[3]) && !JS_IsNull(argv[3])) {
39
+ body = toStr(argv[3], ex);
40
+ if (ex) return JS_EXCEPTION;
41
+ }
42
+
43
+ std::string response;
44
+ try {
45
+ response = rctx->fetch_callback(url, method, headersJson, body);
46
+ } catch (const std::exception& e) {
47
+ return JS_ThrowInternalError(ctx, "fetch callback threw: %s", e.what());
48
+ } catch (...) {
49
+ return JS_ThrowInternalError(ctx, "fetch callback threw an unknown exception");
50
+ }
51
+
52
+ JSValue parsed = JS_ParseJSON(ctx, response.c_str(), response.size(), "<fetch-response>");
53
+ if (JS_IsException(parsed)) {
54
+ JS_FreeValue(ctx, JS_GetException(ctx));
55
+ JSValue result = JS_NewObject(ctx);
56
+ JS_SetPropertyStr(ctx, result, "error", JS_NewString(ctx, "fetch: invalid response from native layer"));
57
+ return result;
58
+ }
59
+ return parsed;
60
+ }
61
+
62
+ const char* kFetchBootstrapScript = R"JS(
63
+ (function() {
64
+ function normalizeHeaders(h) {
65
+ var out = {};
66
+ if (!h) return out;
67
+ if (Array.isArray(h)) {
68
+ for (var i = 0; i < h.length; i++) {
69
+ var pair = h[i];
70
+ if (Array.isArray(pair) && pair.length >= 2)
71
+ out[String(pair[0]).toLowerCase()] = String(pair[1]);
72
+ }
73
+ } else if (typeof h.entries === 'function') {
74
+ for (var pair of h.entries()) out[String(pair[0]).toLowerCase()] = String(pair[1]);
75
+ } else {
76
+ for (var k in h) if (Object.prototype.hasOwnProperty.call(h, k)) out[String(k).toLowerCase()] = String(h[k]);
77
+ }
78
+ return out;
79
+ }
80
+
81
+ function Headers(init) {
82
+ this._map = {};
83
+ var norm = normalizeHeaders(init);
84
+ for (var k in norm) this._map[String(k).toLowerCase()] = String(norm[k]);
85
+ }
86
+ Headers.prototype.get = function(name) {
87
+ var v = this._map[String(name).toLowerCase()];
88
+ return v === undefined ? null : v;
89
+ };
90
+ Headers.prototype.has = function(name) {
91
+ return Object.prototype.hasOwnProperty.call(this._map, String(name).toLowerCase());
92
+ };
93
+ Headers.prototype.set = function(name, value) {
94
+ this._map[String(name).toLowerCase()] = String(value);
95
+ };
96
+ Headers.prototype.forEach = function(cb) {
97
+ for (var k in this._map) cb(this._map[k], k, this);
98
+ };
99
+ Headers.prototype.entries = function() {
100
+ var self = this;
101
+ var keys = Object.keys(this._map);
102
+ var i = 0;
103
+ var iter = {
104
+ next: function() {
105
+ if (i >= keys.length) return { done: true, value: undefined };
106
+ var k = keys[i++];
107
+ return { done: false, value: [k, self._map[k]] };
108
+ }
109
+ };
110
+ iter[Symbol.iterator] = function() { return iter; };
111
+ return iter;
112
+ };
113
+ globalThis.Headers = Headers;
114
+
115
+ function Response(bodyText, init) {
116
+ init = init || {};
117
+ this._bodyText = (bodyText === undefined || bodyText === null) ? '' : bodyText;
118
+ this.status = init.status !== undefined ? init.status : 200;
119
+ this.statusText = init.statusText !== undefined ? init.statusText : '';
120
+ this.ok = this.status >= 200 && this.status < 300;
121
+ this.headers = init.headers instanceof Headers ? init.headers : new Headers(init.headers);
122
+ this.bodyUsed = false;
123
+ }
124
+ Response.prototype.text = function() {
125
+ this.bodyUsed = true;
126
+ return Promise.resolve(this._bodyText);
127
+ };
128
+ Response.prototype.json = function() {
129
+ this.bodyUsed = true;
130
+ try {
131
+ return Promise.resolve(JSON.parse(this._bodyText));
132
+ } catch (e) {
133
+ return Promise.reject(e);
134
+ }
135
+ };
136
+ Response.prototype.clone = function() {
137
+ return new Response(this._bodyText, { status: this.status, statusText: this.statusText, headers: this.headers });
138
+ };
139
+ globalThis.Response = Response;
140
+
141
+ globalThis.fetch = function(input, init) {
142
+ return new Promise(function(resolve, reject) {
143
+ try {
144
+ var url = typeof input === 'string' ? input : (input && input.url);
145
+ init = init || {};
146
+ var method = (init.method || 'GET').toUpperCase();
147
+ var headers = normalizeHeaders(init.headers);
148
+ var headersJson = JSON.stringify(headers);
149
+ var body = (init.body === undefined || init.body === null) ? undefined : String(init.body);
150
+
151
+ var raw = __nativeFetchSync(url, method, headersJson, body);
152
+ if (raw.error) {
153
+ reject(new TypeError(String(raw.error)));
154
+ return;
155
+ }
156
+
157
+ var respHeaders = {};
158
+ try { respHeaders = JSON.parse(raw.headersJson || '{}'); } catch (e) {}
159
+
160
+ resolve(new Response(raw.body, {
161
+ status: raw.status,
162
+ statusText: raw.statusText,
163
+ headers: respHeaders,
164
+ }));
165
+ } catch (e) {
166
+ reject(e);
167
+ }
168
+ });
169
+ };
170
+
171
+ // ── XMLHttpRequest ──────────────────────────────────────────────────────
172
+ // send() is fully synchronous: __nativeFetchSync blocks the calling thread
173
+ // until the response arrives, so all readyState transitions and events
174
+ // fire within the same call frame, before send() returns.
175
+
176
+ function XMLHttpRequest() {
177
+ this.readyState = XMLHttpRequest.UNSENT;
178
+ this.status = 0;
179
+ this.statusText = '';
180
+ this.responseText = '';
181
+ this.responseURL = '';
182
+ this._method = null;
183
+ this._url = null;
184
+ this._requestHeaders = {};
185
+ this._responseHeaders = {};
186
+ this._listeners = {};
187
+ this.onreadystatechange = null;
188
+ this.onload = null;
189
+ this.onerror = null;
190
+ this.onabort = null;
191
+ this.onloadend = null;
192
+ }
193
+
194
+ XMLHttpRequest.UNSENT = 0;
195
+ XMLHttpRequest.OPENED = 1;
196
+ XMLHttpRequest.HEADERS_RECEIVED = 2;
197
+ XMLHttpRequest.LOADING = 3;
198
+ XMLHttpRequest.DONE = 4;
199
+
200
+ XMLHttpRequest.prototype.UNSENT = 0;
201
+ XMLHttpRequest.prototype.OPENED = 1;
202
+ XMLHttpRequest.prototype.HEADERS_RECEIVED = 2;
203
+ XMLHttpRequest.prototype.LOADING = 3;
204
+ XMLHttpRequest.prototype.DONE = 4;
205
+
206
+ Object.defineProperty(XMLHttpRequest.prototype, 'response', {
207
+ get: function() { return this.responseText; }
208
+ });
209
+
210
+ XMLHttpRequest.prototype._setReadyState = function(state) {
211
+ this.readyState = state;
212
+ this._dispatch('readystatechange');
213
+ };
214
+
215
+ XMLHttpRequest.prototype._dispatch = function(type) {
216
+ var evt = new Event(type);
217
+ evt.target = this;
218
+ var handlerName = 'on' + type;
219
+ if (typeof this[handlerName] === 'function') {
220
+ try { this[handlerName](evt); } catch (e) {}
221
+ }
222
+ var listeners = this._listeners[type];
223
+ if (listeners) {
224
+ listeners.slice().forEach(function(cb) {
225
+ try { cb(evt); } catch (e) {}
226
+ });
227
+ }
228
+ };
229
+
230
+ XMLHttpRequest.prototype.open = function(method, url) {
231
+ this._method = String(method || 'GET').toUpperCase();
232
+ this._url = String(url || '');
233
+ this._requestHeaders = {};
234
+ this._responseHeaders = {};
235
+ this.status = 0;
236
+ this.statusText = '';
237
+ this.responseText = '';
238
+ this.responseURL = '';
239
+ this._setReadyState(XMLHttpRequest.OPENED);
240
+ };
241
+
242
+ XMLHttpRequest.prototype.setRequestHeader = function(name, value) {
243
+ if (this.readyState !== XMLHttpRequest.OPENED) {
244
+ var err = new Error('InvalidStateError: setRequestHeader() requires open() to have been called first');
245
+ err.name = 'InvalidStateError';
246
+ throw err;
247
+ }
248
+ this._requestHeaders[String(name).toLowerCase()] = String(value);
249
+ };
250
+
251
+ XMLHttpRequest.prototype.send = function(body) {
252
+ if (this.readyState !== XMLHttpRequest.OPENED) {
253
+ var err = new Error('InvalidStateError: send() requires open() to have been called first');
254
+ err.name = 'InvalidStateError';
255
+ throw err;
256
+ }
257
+
258
+ var headersJson = JSON.stringify(this._requestHeaders);
259
+ var bodyArg = (body === undefined || body === null) ? undefined : String(body);
260
+
261
+ var raw = __nativeFetchSync(this._url, this._method, headersJson, bodyArg);
262
+
263
+ if (raw.error) {
264
+ this.status = 0;
265
+ this.statusText = '';
266
+ this.responseText = '';
267
+ this.responseURL = '';
268
+ this._responseHeaders = {};
269
+ this._setReadyState(XMLHttpRequest.DONE);
270
+ this._dispatch('error');
271
+ this._dispatch('loadend');
272
+ return;
273
+ }
274
+
275
+ var respHeaders = {};
276
+ try { respHeaders = JSON.parse(raw.headersJson || '{}'); } catch (e) {}
277
+ var normalized = {};
278
+ for (var k in respHeaders) normalized[String(k).toLowerCase()] = String(respHeaders[k]);
279
+ this._responseHeaders = normalized;
280
+ this.status = raw.status;
281
+ this.statusText = raw.statusText || '';
282
+ this.responseURL = this._url;
283
+
284
+ this._setReadyState(XMLHttpRequest.HEADERS_RECEIVED);
285
+ this._setReadyState(XMLHttpRequest.LOADING);
286
+ this.responseText = raw.body || '';
287
+ this._setReadyState(XMLHttpRequest.DONE);
288
+ this._dispatch('load');
289
+ this._dispatch('loadend');
290
+ };
291
+
292
+ XMLHttpRequest.prototype.abort = function() {
293
+ if (this.readyState === XMLHttpRequest.UNSENT || this.readyState === XMLHttpRequest.DONE) {
294
+ return;
295
+ }
296
+ this.status = 0;
297
+ this.statusText = '';
298
+ this.responseText = '';
299
+ this._setReadyState(XMLHttpRequest.DONE);
300
+ this._dispatch('abort');
301
+ this._dispatch('loadend');
302
+ };
303
+
304
+ XMLHttpRequest.prototype.getAllResponseHeaders = function() {
305
+ var lines = [];
306
+ for (var k in this._responseHeaders) lines.push(k + ': ' + this._responseHeaders[k]);
307
+ return lines.join('\r\n');
308
+ };
309
+
310
+ XMLHttpRequest.prototype.getResponseHeader = function(name) {
311
+ var v = this._responseHeaders[String(name).toLowerCase()];
312
+ return v === undefined ? null : v;
313
+ };
314
+
315
+ XMLHttpRequest.prototype.addEventListener = function(type, cb) {
316
+ if (typeof cb !== 'function') return;
317
+ if (!this._listeners[type]) this._listeners[type] = [];
318
+ this._listeners[type].push(cb);
319
+ };
320
+
321
+ XMLHttpRequest.prototype.removeEventListener = function(type, cb) {
322
+ var arr = this._listeners[type];
323
+ if (!arr) return;
324
+ var idx = arr.indexOf(cb);
325
+ if (idx !== -1) arr.splice(idx, 1);
326
+ };
327
+
328
+ globalThis.XMLHttpRequest = XMLHttpRequest;
329
+ })();
330
+ )JS";
331
+
332
+ } // namespace
333
+
334
+ void FetchBindings::install(JSContext* ctx) {
335
+ JSValue global = JS_GetGlobalObject(ctx);
336
+ JS_SetPropertyStr(ctx, global, "__nativeFetchSync", JS_NewCFunction(ctx, js_fetch_native, "__nativeFetchSync", 4));
337
+ JS_FreeValue(ctx, global);
338
+
339
+ JSValue bootstrap_result = JS_Eval(ctx, kFetchBootstrapScript, strlen(kFetchBootstrapScript),
340
+ "<fetch-bootstrap>", JS_EVAL_TYPE_GLOBAL);
341
+ if (JS_IsException(bootstrap_result)) {
342
+ JS_FreeValue(ctx, JS_GetException(ctx));
343
+ }
344
+ JS_FreeValue(ctx, bootstrap_result);
345
+ }
346
+
347
+ } // namespace margelo::nitro::nitrojsdom
@@ -0,0 +1,16 @@
1
+ #pragma once
2
+
3
+ // fetch()/Headers/Response/XMLHttpRequest. The native side (__nativeFetchSync)
4
+ // bridges to the RN onFetch callback synchronously; the JS-level bootstrap
5
+ // script builds the spec-shaped wrapper objects on top of it.
6
+
7
+ #include "quickjs.h"
8
+
9
+ namespace margelo::nitro::nitrojsdom {
10
+
11
+ class FetchBindings {
12
+ public:
13
+ static void install(JSContext* ctx);
14
+ };
15
+
16
+ } // namespace margelo::nitro::nitrojsdom
@@ -0,0 +1,245 @@
1
+ #include "StyleBindings.hpp"
2
+ #include "../DOMBindingsInternal.hpp"
3
+ #include <lexbor/dom/dom.h>
4
+ #include <algorithm>
5
+ #include <utility>
6
+ #include <vector>
7
+
8
+ namespace margelo::nitro::nitrojsdom {
9
+
10
+ namespace {
11
+
12
+ JSClassID js_style_class_id = 0;
13
+
14
+ lxb_dom_element_t* unwrap_style(JSContext* ctx, JSValue val) {
15
+ return static_cast<lxb_dom_element_t*>(JS_GetOpaque(val, js_style_class_id));
16
+ }
17
+
18
+ std::string get_style_attr(lxb_dom_element_t* el) {
19
+ size_t len = 0;
20
+ const lxb_char_t* val = lxb_dom_element_get_attribute(el,
21
+ reinterpret_cast<const lxb_char_t*>("style"), 5, &len);
22
+ return val ? std::string(reinterpret_cast<const char*>(val), len) : "";
23
+ }
24
+
25
+ void set_style_attr(lxb_dom_element_t* el, const std::string& css) {
26
+ if (css.empty()) {
27
+ lxb_dom_element_remove_attribute(el, reinterpret_cast<const lxb_char_t*>("style"), 5);
28
+ } else {
29
+ lxb_dom_element_set_attribute(el,
30
+ reinterpret_cast<const lxb_char_t*>("style"), 5,
31
+ reinterpret_cast<const lxb_char_t*>(css.data()), css.size());
32
+ }
33
+ }
34
+
35
+ std::string trim_ws(const std::string& s) {
36
+ size_t a = s.find_first_not_of(" \t\n\r");
37
+ if (a == std::string::npos) return "";
38
+ size_t b = s.find_last_not_of(" \t\n\r");
39
+ return s.substr(a, b - a + 1);
40
+ }
41
+
42
+ // Parses "color: red; font-size: 12px" into ordered (name, value) pairs.
43
+ std::vector<std::pair<std::string, std::string>> parse_style_decls(const std::string& css) {
44
+ std::vector<std::pair<std::string, std::string>> decls;
45
+ size_t pos = 0;
46
+ while (pos < css.size()) {
47
+ size_t semi = std::string::npos;
48
+ int depth = 0;
49
+ for (size_t i = pos; i < css.size(); i++) {
50
+ char c = css[i];
51
+ if (c == '(') depth++;
52
+ else if (c == ')' && depth > 0) depth--;
53
+ else if (c == ';' && depth == 0) { semi = i; break; }
54
+ }
55
+ std::string decl = css.substr(pos, semi == std::string::npos ? std::string::npos : semi - pos);
56
+ pos = (semi == std::string::npos) ? css.size() : semi + 1;
57
+
58
+ size_t colon = decl.find(':');
59
+ if (colon == std::string::npos) continue;
60
+ std::string name = trim_ws(decl.substr(0, colon));
61
+ std::string value = trim_ws(decl.substr(colon + 1));
62
+ if (!name.empty()) decls.emplace_back(name, value);
63
+ }
64
+ return decls;
65
+ }
66
+
67
+ std::string serialize_style_decls(const std::vector<std::pair<std::string, std::string>>& decls) {
68
+ std::string out;
69
+ for (auto& kv : decls) { out += kv.first; out += ": "; out += kv.second; out += "; "; }
70
+ if (!out.empty()) out.pop_back();
71
+ return out;
72
+ }
73
+
74
+ int js_style_get_own_property(JSContext* ctx, JSPropertyDescriptor* desc, JSValue obj, JSAtom prop) {
75
+ auto* el = unwrap_style(ctx, obj);
76
+ if (!el) return 0;
77
+ JSValue key_val = JS_AtomToString(ctx, prop);
78
+ const char* key = JS_ToCString(ctx, key_val);
79
+ JS_FreeValue(ctx, key_val);
80
+ if (!key) return 0;
81
+ std::string prop_name = camel_to_attr_suffix(key);
82
+ JS_FreeCString(ctx, key);
83
+
84
+ auto decls = parse_style_decls(get_style_attr(el));
85
+ for (auto& kv : decls) {
86
+ if (kv.first == prop_name) {
87
+ if (desc) {
88
+ desc->flags = JS_PROP_ENUMERABLE | JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE;
89
+ desc->value = JS_NewStringLen(ctx, kv.second.data(), kv.second.size());
90
+ desc->getter = JS_UNDEFINED;
91
+ desc->setter = JS_UNDEFINED;
92
+ }
93
+ return 1;
94
+ }
95
+ }
96
+ return 0;
97
+ }
98
+
99
+ int js_style_define_own_property(JSContext* ctx, JSValue this_obj, JSAtom prop, JSValue val,
100
+ JSValue, JSValue, int) {
101
+ auto* el = unwrap_style(ctx, this_obj);
102
+ if (!el) return 0;
103
+ JSValue key_val = JS_AtomToString(ctx, prop);
104
+ const char* key = JS_ToCString(ctx, key_val);
105
+ JS_FreeValue(ctx, key_val);
106
+ if (!key) return 0;
107
+ std::string prop_name = camel_to_attr_suffix(key);
108
+ JS_FreeCString(ctx, key);
109
+
110
+ const char* str = JS_ToCString(ctx, val);
111
+ if (str) {
112
+ auto decls = parse_style_decls(get_style_attr(el));
113
+ bool found = false;
114
+ for (auto& kv : decls) { if (kv.first == prop_name) { kv.second = str; found = true; break; } }
115
+ if (!found) decls.emplace_back(prop_name, str);
116
+ set_style_attr(el, serialize_style_decls(decls));
117
+ JS_FreeCString(ctx, str);
118
+ }
119
+ return 1;
120
+ }
121
+
122
+ int js_style_delete_property(JSContext* ctx, JSValue obj, JSAtom prop) {
123
+ auto* el = unwrap_style(ctx, obj);
124
+ if (!el) return 1;
125
+ JSValue key_val = JS_AtomToString(ctx, prop);
126
+ const char* key = JS_ToCString(ctx, key_val);
127
+ JS_FreeValue(ctx, key_val);
128
+ if (!key) return 1;
129
+ std::string prop_name = camel_to_attr_suffix(key);
130
+ JS_FreeCString(ctx, key);
131
+
132
+ auto decls = parse_style_decls(get_style_attr(el));
133
+ decls.erase(std::remove_if(decls.begin(), decls.end(),
134
+ [&](auto& kv) { return kv.first == prop_name; }), decls.end());
135
+ set_style_attr(el, serialize_style_decls(decls));
136
+ return 1;
137
+ }
138
+
139
+ int js_style_get_own_property_names(JSContext* ctx, JSPropertyEnum** ptab, uint32_t* plen, JSValue obj) {
140
+ *ptab = nullptr;
141
+ *plen = 0;
142
+ auto* el = unwrap_style(ctx, obj);
143
+ if (!el) return 0;
144
+ auto decls = parse_style_decls(get_style_attr(el));
145
+
146
+ auto* tab = static_cast<JSPropertyEnum*>(js_malloc(ctx, sizeof(JSPropertyEnum) * (decls.empty() ? 1 : decls.size())));
147
+ if (!tab) return -1;
148
+ for (size_t i = 0; i < decls.size(); i++) {
149
+ tab[i].is_enumerable = 1;
150
+ tab[i].atom = JS_NewAtom(ctx, attr_suffix_to_camel(decls[i].first).c_str());
151
+ }
152
+ *ptab = tab;
153
+ *plen = (uint32_t)decls.size();
154
+ return 0;
155
+ }
156
+
157
+ JSClassExoticMethods js_style_exotic = {
158
+ .get_own_property = js_style_get_own_property,
159
+ .get_own_property_names = js_style_get_own_property_names,
160
+ .delete_property = js_style_delete_property,
161
+ .define_own_property = js_style_define_own_property,
162
+ };
163
+
164
+ JSClassDef js_style_class = { "CSSStyleDeclaration", .finalizer = nullptr, .exotic = &js_style_exotic };
165
+
166
+ JSValue js_style_get_cssText(JSContext* ctx, JSValue this_val) {
167
+ auto* el = unwrap_style(ctx, this_val);
168
+ if (!el) return JS_NewString(ctx, "");
169
+ return JS_NewString(ctx, get_style_attr(el).c_str());
170
+ }
171
+
172
+ JSValue js_style_set_cssText(JSContext* ctx, JSValue this_val, JSValue val) {
173
+ auto* el = unwrap_style(ctx, this_val);
174
+ if (!el) return JS_UNDEFINED;
175
+ const char* str = JS_ToCString(ctx, val);
176
+ if (str) { set_style_attr(el, str); JS_FreeCString(ctx, str); }
177
+ return JS_UNDEFINED;
178
+ }
179
+
180
+ JSValue js_style_setProperty(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
181
+ auto* el = unwrap_style(ctx, this_val);
182
+ if (!el || argc < 2) return JS_UNDEFINED;
183
+ const char* name = JS_ToCString(ctx, argv[0]);
184
+ const char* value = JS_ToCString(ctx, argv[1]);
185
+ if (name && value) {
186
+ auto decls = parse_style_decls(get_style_attr(el));
187
+ bool found = false;
188
+ for (auto& kv : decls) { if (kv.first == name) { kv.second = value; found = true; break; } }
189
+ if (!found) decls.emplace_back(name, value);
190
+ set_style_attr(el, serialize_style_decls(decls));
191
+ }
192
+ if (name) JS_FreeCString(ctx, name);
193
+ if (value) JS_FreeCString(ctx, value);
194
+ return JS_UNDEFINED;
195
+ }
196
+
197
+ JSValue js_style_getPropertyValue(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
198
+ auto* el = unwrap_style(ctx, this_val);
199
+ if (!el || argc < 1) return JS_NewString(ctx, "");
200
+ const char* name = JS_ToCString(ctx, argv[0]);
201
+ if (!name) return JS_NewString(ctx, "");
202
+ auto decls = parse_style_decls(get_style_attr(el));
203
+ std::string result;
204
+ for (auto& kv : decls) { if (kv.first == name) { result = kv.second; break; } }
205
+ JS_FreeCString(ctx, name);
206
+ return JS_NewString(ctx, result.c_str());
207
+ }
208
+
209
+ JSValue js_style_removeProperty(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
210
+ auto* el = unwrap_style(ctx, this_val);
211
+ if (!el || argc < 1) return JS_NewString(ctx, "");
212
+ const char* name = JS_ToCString(ctx, argv[0]);
213
+ if (!name) return JS_NewString(ctx, "");
214
+ auto decls = parse_style_decls(get_style_attr(el));
215
+ std::string removed;
216
+ decls.erase(std::remove_if(decls.begin(), decls.end(), [&](auto& kv) {
217
+ if (kv.first == name) { removed = kv.second; return true; }
218
+ return false;
219
+ }), decls.end());
220
+ set_style_attr(el, serialize_style_decls(decls));
221
+ JS_FreeCString(ctx, name);
222
+ return JS_NewString(ctx, removed.c_str());
223
+ }
224
+
225
+ } // namespace
226
+
227
+ void StyleBindings::install(JSContext* ctx) {
228
+ if (js_style_class_id == 0) JS_NewClassID(&js_style_class_id);
229
+ JS_NewClass(JS_GetRuntime(ctx), js_style_class_id, &js_style_class);
230
+
231
+ JSValue proto = JS_NewObject(ctx);
232
+ JS_SetPropertyStr(ctx, proto, "setProperty", JS_NewCFunction(ctx, js_style_setProperty, "setProperty", 2));
233
+ JS_SetPropertyStr(ctx, proto, "getPropertyValue", JS_NewCFunction(ctx, js_style_getPropertyValue, "getPropertyValue", 1));
234
+ JS_SetPropertyStr(ctx, proto, "removeProperty", JS_NewCFunction(ctx, js_style_removeProperty, "removeProperty", 1));
235
+ define_prop(ctx, proto, "cssText", js_style_get_cssText, js_style_set_cssText);
236
+ JS_SetClassProto(ctx, js_style_class_id, proto);
237
+ }
238
+
239
+ JSValue StyleBindings::make(JSContext* ctx, lxb_dom_element_t* el) {
240
+ JSValue obj = JS_NewObjectClass(ctx, js_style_class_id);
241
+ JS_SetOpaque(obj, el);
242
+ return obj;
243
+ }
244
+
245
+ } // namespace margelo::nitro::nitrojsdom
@@ -0,0 +1,22 @@
1
+ #pragma once
2
+
3
+ // element.style, a CSSStyleDeclaration-like object mirroring the `style`
4
+ // attribute. Same exotic-property technique as DatasetBindings: individual
5
+ // longhand properties (backgroundColor) go through get/define/delete_property
6
+ // against the parsed `style` attribute text; cssText/setProperty/etc. live on
7
+ // the class prototype, a plain object that doesn't re-enter the exotic hooks.
8
+
9
+ #include "quickjs.h"
10
+
11
+ struct lxb_dom_element;
12
+ typedef struct lxb_dom_element lxb_dom_element_t;
13
+
14
+ namespace margelo::nitro::nitrojsdom {
15
+
16
+ class StyleBindings {
17
+ public:
18
+ static void install(JSContext* ctx);
19
+ static JSValue make(JSContext* ctx, lxb_dom_element_t* el);
20
+ };
21
+
22
+ } // namespace margelo::nitro::nitrojsdom