Branch data Line data Source code
1 : : // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later
2 : : // SPDX-FileCopyrightText: 2020 Evan Welsh <contact@evanwelsh.com>
3 : :
4 : : #include <config.h>
5 : :
6 : : #include <stddef.h> // for size_t
7 : : #include <string.h>
8 : :
9 : : #include <memory> // for unique_ptr
10 : :
11 : : #include <gio/gio.h>
12 : : #include <glib-object.h>
13 : : #include <glib.h>
14 : :
15 : : #include <js/CallAndConstruct.h> // for JS_CallFunction
16 : : #include <js/CallArgs.h>
17 : : #include <js/CharacterEncoding.h>
18 : : #include <js/CompileOptions.h>
19 : : #include <js/ErrorReport.h> // for JSEXN_ERR
20 : : #include <js/Exception.h>
21 : : #include <js/Id.h> // for PropertyKey
22 : : #include <js/Modules.h>
23 : : #include <js/Promise.h>
24 : : #include <js/PropertyAndElement.h>
25 : : #include <js/PropertyDescriptor.h>
26 : : #include <js/Realm.h>
27 : : #include <js/RootingAPI.h>
28 : : #include <js/SourceText.h>
29 : : #include <js/String.h>
30 : : #include <js/TypeDecls.h>
31 : : #include <js/Utility.h> // for UniqueChars
32 : : #include <js/Value.h>
33 : : #include <js/ValueArray.h>
34 : : #include <jsapi.h> // for JS_NewPlainObject, JS_ObjectIsFunction
35 : : #include <jsfriendapi.h> // for JS_GetObjectFunction, SetFunctionNativeReserved
36 : :
37 : : #include "gjs/auto.h"
38 : : #include "gjs/context-private.h"
39 : : #include "gjs/engine.h"
40 : : #include "gjs/gerror-result.h"
41 : : #include "gjs/global.h"
42 : : #include "gjs/internal.h"
43 : : #include "gjs/jsapi-util-args.h"
44 : : #include "gjs/jsapi-util.h"
45 : : #include "gjs/macros.h"
46 : : #include "gjs/module.h"
47 : : #include "util/log.h"
48 : : #include "util/misc.h"
49 : :
50 : : namespace mozilla {
51 : : union Utf8Unit;
52 : : }
53 : :
54 : : // NOTE: You have to be very careful in this file to only do operations within
55 : : // the correct global!
56 : :
57 : : /**
58 : : * gjs_load_internal_module:
59 : : * @cx: the current JSContext
60 : : * @identifier: the identifier of the internal module
61 : : *
62 : : * Loads a module source from an internal resource,
63 : : * resource:///org/gnome/gjs/modules/internal/{#identifier}.js, registers it in
64 : : * the internal global's module registry, and proceeds to compile, initialize,
65 : : * and evaluate the module.
66 : : *
67 : : * Returns: whether an error occurred while loading or evaluating the module.
68 : : */
69 : 257 : bool gjs_load_internal_module(JSContext* cx, const char* identifier) {
70 : : Gjs::AutoChar full_path(g_strdup_printf(
71 : 257 : "resource:///org/gnome/gjs/modules/internal/%s.js", identifier));
72 : :
73 : 257 : gjs_debug(GJS_DEBUG_IMPORTER, "Loading internal module '%s' (%s)",
74 : : identifier, full_path.get());
75 : :
76 : 257 : Gjs::AutoChar script;
77 : : size_t script_len;
78 [ - + ]: 257 : if (!gjs_load_internal_source(cx, full_path, script.out(), &script_len))
79 : 0 : return false;
80 : :
81 : 257 : JS::SourceText<mozilla::Utf8Unit> buf;
82 [ - + ]: 257 : if (!buf.init(cx, script.get(), script_len, JS::SourceOwnership::Borrowed))
83 : 0 : return false;
84 : :
85 : 257 : JS::CompileOptions options(cx);
86 : 257 : options.setIntroductionType("Internal Module Bootstrap");
87 : 257 : options.setFileAndLine(full_path, 1);
88 : 257 : options.setSelfHostingMode(false);
89 : :
90 : 257 : Gjs::AutoInternalRealm ar{cx};
91 : 257 : GjsContextPrivate* gjs = GjsContextPrivate::from_cx(cx);
92 : 257 : JS::RootedObject internal_global{cx, gjs->internal_global()};
93 : 257 : JS::RootedObject module{cx, JS::CompileModule(cx, options, buf)};
94 [ - + ]: 257 : if (!module)
95 : 0 : return false;
96 : :
97 : 257 : JS::RootedObject registry{cx, gjs_get_module_registry(internal_global)};
98 : :
99 : 257 : JS::RootedId key{cx, gjs_intern_string_to_id(cx, full_path)};
100 [ - + ]: 257 : if (key.isVoid())
101 : 0 : return false;
102 : :
103 : 257 : JS::RootedValue ignore{cx};
104 : 257 : if (!gjs_global_registry_set(cx, registry, key, module) ||
105 [ + - + - ]: 514 : !JS::ModuleLink(cx, module) ||
106 [ - + - + ]: 514 : !JS::ModuleEvaluate(cx, module, &ignore)) {
107 : 0 : return false;
108 : : }
109 : :
110 : 257 : return true;
111 : 257 : }
112 : :
113 : 0 : static bool handle_wrong_args(JSContext* cx) {
114 : 0 : gjs_log_exception(cx);
115 : 0 : g_error("Wrong invocation of internal code");
116 : : return false;
117 : : }
118 : :
119 : : /**
120 : : * gjs_internal_set_global_module_loader:
121 : : * @global: the JS global object
122 : : * @loader: the JS module loader object to store in @global
123 : : *
124 : : * JS function exposed as `setGlobalModuleLoader` in the internal global scope.
125 : : *
126 : : * Sets the MODULE_LOADER slot of @global. @loader should be an instance of
127 : : * ModuleLoader or InternalModuleLoader. Its `moduleResolveHook` and
128 : : * `moduleLoadHook` properties will be called.
129 : : *
130 : : * Returns: JS undefined
131 : : */
132 : 514 : bool gjs_internal_set_global_module_loader(JSContext* cx, unsigned argc,
133 : : JS::Value* vp) {
134 : 514 : JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
135 : 514 : JS::RootedObject global(cx), loader(cx);
136 [ - + ]: 514 : if (!gjs_parse_call_args(cx, "setGlobalModuleLoader", args, "oo", "global",
137 : : &global, "loader", &loader))
138 : 0 : return handle_wrong_args(cx);
139 : :
140 : 514 : gjs_set_global_slot(global, GjsGlobalSlot::MODULE_LOADER,
141 : 514 : JS::ObjectValue(*loader));
142 : :
143 : 514 : args.rval().setUndefined();
144 : 514 : return true;
145 : 514 : }
146 : :
147 : : /**
148 : : * compile_module:
149 : : * @cx: the current JSContext
150 : : * @uri: The URI of the module
151 : : * @source: The source text of the module
152 : : * @v_module_out: (out): Return location for the module as a JS value
153 : : *
154 : : * Compiles the a module source text into an internal #Module object given the
155 : : * module's URI as the first argument.
156 : : *
157 : : * Returns: whether an error occurred while compiling the module.
158 : : */
159 : 3913 : static bool compile_module(JSContext* cx, const JS::UniqueChars& uri,
160 : : JS::HandleString source,
161 : : JS::MutableHandleValue v_module_out) {
162 : 3913 : JS::CompileOptions options(cx);
163 : 3913 : options.setFileAndLine(uri.get(), 1).setSourceIsLazy(false);
164 : :
165 : : size_t text_len;
166 : : char16_t* text;
167 [ - + ]: 3913 : if (!gjs_string_get_char16_data(cx, source, &text, &text_len))
168 : 0 : return false;
169 : :
170 : 3913 : JS::SourceText<char16_t> buf;
171 [ - + ]: 3913 : if (!buf.init(cx, text, text_len, JS::SourceOwnership::TakeOwnership))
172 : 0 : return false;
173 : :
174 : 3913 : JS::RootedObject new_module(cx, JS::CompileModule(cx, options, buf));
175 [ + + ]: 3913 : if (!new_module)
176 : 1 : return false;
177 : :
178 : 3912 : v_module_out.setObject(*new_module);
179 : 3912 : return true;
180 : 3913 : }
181 : :
182 : : /**
183 : : * gjs_internal_compile_internal_module:
184 : : * @uri: The URI of the module (JS string)
185 : : * @source: The source text of the module (JS string)
186 : : *
187 : : * JS function exposed as `compileInternalModule` in the internal global scope.
188 : : *
189 : : * Compiles a module source text within the internal global's realm.
190 : : *
191 : : * NOTE: Modules compiled with this function can only be executed
192 : : * within the internal global's realm.
193 : : *
194 : : * Returns: The compiled JS module object.
195 : : */
196 : 2056 : bool gjs_internal_compile_internal_module(JSContext* cx, unsigned argc,
197 : : JS::Value* vp) {
198 : 2056 : JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
199 : :
200 : 2056 : Gjs::AutoInternalRealm ar{cx};
201 : :
202 : 2056 : JS::UniqueChars uri;
203 : 2056 : JS::RootedString source(cx);
204 [ - + ]: 2056 : if (!gjs_parse_call_args(cx, "compileInternalModule", args, "sS", "uri",
205 : : &uri, "source", &source))
206 : 0 : return handle_wrong_args(cx);
207 : :
208 : 2056 : return compile_module(cx, uri, source, args.rval());
209 : 2056 : }
210 : :
211 : : /**
212 : : * gjs_internal_compile_module:
213 : : * @uri: The URI of the module (JS string)
214 : : * @source: The source text of the module (JS string)
215 : : *
216 : : * JS function exposed as `compileModule` in the internal global scope.
217 : : *
218 : : * Compiles a module source text within the main realm.
219 : : *
220 : : * NOTE: Modules compiled with this function can only be executed
221 : : * within the main realm.
222 : : *
223 : : * Returns: The compiled JS module object.
224 : : */
225 : 1857 : bool gjs_internal_compile_module(JSContext* cx, unsigned argc, JS::Value* vp) {
226 : 1857 : JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
227 : :
228 : 1857 : Gjs::AutoMainRealm ar{cx};
229 : :
230 : 1857 : JS::UniqueChars uri;
231 : 1857 : JS::RootedString source(cx);
232 [ - + ]: 1857 : if (!gjs_parse_call_args(cx, "compileModule", args, "sS", "uri", &uri,
233 : : "source", &source))
234 : 0 : return handle_wrong_args(cx);
235 : :
236 : 1857 : return compile_module(cx, uri, source, args.rval());
237 : 1857 : }
238 : :
239 : : /**
240 : : * gjs_internal_set_module_private:
241 : : * @module: The JS module object
242 : : * @private: The JS module private object for @module
243 : : *
244 : : * JS function exposed as `setModulePrivate` in the internal global scope.
245 : : *
246 : : * Sets the private object of an internal #Module object.
247 : : *
248 : : * Returns: JS undefined
249 : : */
250 : 3912 : bool gjs_internal_set_module_private(JSContext* cx, unsigned argc,
251 : : JS::Value* vp) {
252 : 3912 : JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
253 : 3912 : JS::RootedObject module(cx), private_obj(cx);
254 [ - + ]: 3912 : if (!gjs_parse_call_args(cx, "setModulePrivate", args, "oo", "module",
255 : : &module, "private", &private_obj))
256 : 0 : return handle_wrong_args(cx);
257 : :
258 : 3912 : JS::SetModulePrivate(module, JS::ObjectValue(*private_obj));
259 : 3912 : return true;
260 : 3912 : }
261 : :
262 : : /**
263 : : * gjs_internal_get_registry:
264 : : * @global: The JS global object
265 : : *
266 : : * JS function exposed as `getRegistry` in the internal global scope.
267 : : *
268 : : * Retrieves the module registry for @global.
269 : : *
270 : : * Returns: the module registry, a JS Map object.
271 : : */
272 : 12321 : bool gjs_internal_get_registry(JSContext* cx, unsigned argc, JS::Value* vp) {
273 : 12321 : JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
274 : 12321 : JS::RootedObject global(cx);
275 [ - + ]: 12321 : if (!gjs_parse_call_args(cx, "getRegistry", args, "o", "global", &global))
276 : 0 : return handle_wrong_args(cx);
277 : :
278 : 12321 : JSAutoRealm ar(cx, global);
279 : :
280 : 12321 : JS::RootedObject registry(cx, gjs_get_module_registry(global));
281 : 12321 : args.rval().setObject(*registry);
282 : 12321 : return true;
283 : 12321 : }
284 : :
285 : : /**
286 : : * gjs_internal_get_source_map_registry:
287 : : * @global: The JS global object
288 : : *
289 : : * JS function exposed as `getSourceMapRegistry` in the internal global scope.
290 : : *
291 : : * Retrieves the source map registry for @global.
292 : : *
293 : : * Returns: the source map registry, a JS Map object.
294 : : */
295 : 13 : bool gjs_internal_get_source_map_registry(JSContext* cx, unsigned argc,
296 : : JS::Value* vp) {
297 : 13 : JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
298 : 13 : JS::RootedObject global{cx};
299 [ - + ]: 13 : if (!gjs_parse_call_args(cx, "getSourceMapRegistry", args, "o", "global",
300 : : &global))
301 : 0 : return handle_wrong_args(cx);
302 : :
303 : 13 : JSAutoRealm ar{cx, global};
304 : :
305 : 13 : JSObject* registry = gjs_get_source_map_registry(global);
306 : 13 : args.rval().setObject(*registry);
307 : 13 : return true;
308 : 13 : }
309 : :
310 : : /**
311 : : * gjs_uri_object:
312 : : * @cx: the current JSContext
313 : : * @uri: the URI to parse into a JS URI object
314 : : * @rval: (out): handle to a JSValue where the URI object will be stored
315 : : *
316 : : * Parses @uri and creates a JS object with the various parsed parts available
317 : : * as properties. See type `Uri` in modules/internal/environment.d.ts.
318 : : *
319 : : * Basically a JS wrapper for g_uri_parse() for use in the internal global scope
320 : : * where we don't have access to gobject-introspection.
321 : : *
322 : : * Returns: false if an exception is pending, otherwise true
323 : : */
324 : 35300 : static bool gjs_uri_object(JSContext* cx, const char* uri,
325 : : JS::MutableHandleValue rval) {
326 : : using AutoHashTable =
327 : 0 : Gjs::AutoPointer<GHashTable, GHashTable, g_hash_table_destroy>;
328 : 0 : using AutoURI = Gjs::AutoPointer<GUri, GUri, g_uri_unref>;
329 : :
330 : 35300 : Gjs::AutoError error;
331 : 35300 : AutoURI parsed = g_uri_parse(uri, G_URI_FLAGS_NONE, &error);
332 [ + + ]: 35300 : if (!parsed) {
333 : 11267 : Gjs::AutoMainRealm ar{cx};
334 : :
335 : 11267 : gjs_throw_custom(cx, JSEXN_ERR, "ImportError",
336 : : "Attempted to import invalid URI: %s (%s)", uri,
337 : 11267 : error->message);
338 : 11267 : return false;
339 : 11267 : }
340 : :
341 : 24033 : JS::RootedObject query_obj(cx, JS_NewPlainObject(cx));
342 [ - + ]: 24033 : if (!query_obj)
343 : 0 : return false;
344 : :
345 : 24033 : const char* raw_query = g_uri_get_query(parsed);
346 [ + + ]: 24033 : if (raw_query) {
347 : : AutoHashTable query =
348 : 7 : g_uri_parse_params(raw_query, -1, "&", G_URI_PARAMS_NONE, &error);
349 [ - + ]: 7 : if (!query) {
350 : 0 : Gjs::AutoMainRealm ar{cx};
351 : :
352 : 0 : gjs_throw_custom(cx, JSEXN_ERR, "ImportError",
353 : : "Attempted to import invalid URI: %s (%s)", uri,
354 : 0 : error->message);
355 : 0 : return false;
356 : 0 : }
357 : :
358 : : GHashTableIter iter;
359 : 7 : g_hash_table_iter_init(&iter, query);
360 : :
361 : : void* key_ptr;
362 : : void* value_ptr;
363 [ + + ]: 17 : while (g_hash_table_iter_next(&iter, &key_ptr, &value_ptr)) {
364 : 10 : auto* key = static_cast<const char*>(key_ptr);
365 : 10 : auto* value = static_cast<const char*>(value_ptr);
366 : :
367 : 10 : JS::ConstUTF8CharsZ value_chars{value, strlen(value)};
368 : : JS::RootedString value_str(cx,
369 : 10 : JS_NewStringCopyUTF8Z(cx, value_chars));
370 [ + - - + : 10 : if (!value_str || !JS_DefineProperty(cx, query_obj, key, value_str,
- + ]
371 : : JSPROP_ENUMERATE))
372 : 0 : return false;
373 [ + - ]: 10 : }
374 [ + - ]: 7 : }
375 : :
376 : 24033 : JS::RootedObject return_obj(cx, JS_NewPlainObject(cx));
377 [ - + ]: 24033 : if (!return_obj)
378 : 0 : return false;
379 : :
380 : 24033 : JS::RootedValue v_uri{cx};
381 : 24033 : Gjs::AutoChar uri_string{g_uri_to_string(parsed)};
382 [ - + ]: 24033 : if (!gjs_string_from_utf8(cx, uri_string, &v_uri))
383 : 0 : return false;
384 : :
385 : : // JS_NewStringCopyZ() used here and below because the URI components are
386 : : // %-encoded, meaning ASCII-only
387 : : JS::RootedString scheme(cx,
388 : 24033 : JS_NewStringCopyZ(cx, g_uri_get_scheme(parsed)));
389 [ - + ]: 24033 : if (!scheme)
390 : 0 : return false;
391 : :
392 : 24033 : JS::RootedString host(cx, JS_NewStringCopyZ(cx, g_uri_get_host(parsed)));
393 [ - + ]: 24033 : if (!host)
394 : 0 : return false;
395 : :
396 : 24033 : JS::RootedString path(cx, JS_NewStringCopyZ(cx, g_uri_get_path(parsed)));
397 [ - + ]: 24033 : if (!path)
398 : 0 : return false;
399 : :
400 : : Gjs::AutoChar no_query_str{
401 : 24033 : g_uri_to_string_partial(parsed, G_URI_HIDE_QUERY)};
402 : 24033 : JS::RootedString uri_no_query{cx, JS_NewStringCopyZ(cx, no_query_str)};
403 [ - + ]: 24033 : if (!uri_no_query)
404 : 0 : return false;
405 : :
406 : 24033 : if (!JS_DefineProperty(cx, return_obj, "uri", uri_no_query,
407 : 24033 : JSPROP_ENUMERATE) ||
408 [ + - ]: 24033 : !JS_DefineProperty(cx, return_obj, "uriWithQuery", v_uri,
409 : 24033 : JSPROP_ENUMERATE) ||
410 [ + - ]: 24033 : !JS_DefineProperty(cx, return_obj, "scheme", scheme,
411 : 24033 : JSPROP_ENUMERATE) ||
412 [ + - ]: 24033 : !JS_DefineProperty(cx, return_obj, "host", host, JSPROP_ENUMERATE) ||
413 [ + - + - ]: 72099 : !JS_DefineProperty(cx, return_obj, "path", path, JSPROP_ENUMERATE) ||
414 [ - + - + ]: 48066 : !JS_DefineProperty(cx, return_obj, "query", query_obj,
415 : : JSPROP_ENUMERATE))
416 : 0 : return false;
417 : :
418 : 24033 : rval.setObject(*return_obj);
419 : 24033 : return true;
420 : 35300 : }
421 : :
422 : 25922 : bool gjs_internal_parse_uri(JSContext* cx, unsigned argc, JS::Value* vp) {
423 : 25922 : JS::CallArgs args = CallArgsFromVp(argc, vp);
424 : 25922 : JS::RootedString string_arg{cx};
425 [ - + ]: 25922 : if (!gjs_parse_call_args(cx, "parseURI", args, "S", "uri", &string_arg))
426 : 0 : return handle_wrong_args(cx);
427 : :
428 : 25922 : JS::UniqueChars uri = JS_EncodeStringToUTF8(cx, string_arg);
429 [ - + ]: 25922 : if (!uri)
430 : 0 : return false;
431 : :
432 : 25922 : return gjs_uri_object(cx, uri.get(), args.rval());
433 : 25922 : }
434 : :
435 : 9378 : bool gjs_internal_resolve_relative_resource_or_file(JSContext* cx,
436 : : unsigned argc,
437 : : JS::Value* vp) {
438 : 9378 : JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
439 : 9378 : JS::UniqueChars uri, relative_path;
440 [ - + ]: 9378 : if (!gjs_parse_call_args(cx, "resolveRelativeResourceOrFile", args, "ss",
441 : : "uri", &uri, "relativePath", &relative_path))
442 : 0 : return handle_wrong_args(cx);
443 : :
444 : 9378 : Gjs::AutoUnref<GFile> module_file{g_file_new_for_uri(uri.get())};
445 : :
446 : : Gjs::AutoChar output_uri{g_uri_resolve_relative(
447 : 9378 : uri.get(), relative_path.get(), G_URI_FLAGS_NONE, nullptr)};
448 : :
449 : 9378 : return gjs_uri_object(cx, output_uri.get(), args.rval());
450 : 9378 : }
451 : :
452 : 4232 : bool gjs_internal_load_resource_or_file(JSContext* cx, unsigned argc,
453 : : JS::Value* vp) {
454 : 4232 : JS::CallArgs args = CallArgsFromVp(argc, vp);
455 : 4232 : JS::UniqueChars uri;
456 [ - + ]: 4232 : if (!gjs_parse_call_args(cx, "loadResourceOrFile", args, "s", "uri", &uri))
457 : 0 : return handle_wrong_args(cx);
458 : :
459 : 4232 : Gjs::AutoUnref<GFile> file{g_file_new_for_uri(uri.get())};
460 : :
461 : : char* contents;
462 : : size_t length;
463 : 4232 : Gjs::AutoError error;
464 [ - + ]: 4232 : if (!g_file_load_contents(file, /* cancellable = */ nullptr, &contents,
465 : : &length, /* etag_out = */ nullptr, &error)) {
466 : 0 : Gjs::AutoMainRealm ar{cx};
467 : :
468 : 0 : gjs_throw_custom(cx, JSEXN_ERR, "ImportError",
469 : : "Unable to load file from: %s (%s)", uri.get(),
470 : 0 : error->message);
471 : 0 : return false;
472 : 0 : }
473 : :
474 : 4232 : JS::ConstUTF8CharsZ contents_chars{contents, length};
475 : : JS::RootedString contents_str(cx,
476 : 4232 : JS_NewStringCopyUTF8Z(cx, contents_chars));
477 : 4232 : g_free(contents);
478 [ - + ]: 4232 : if (!contents_str)
479 : 0 : return false;
480 : :
481 : 4232 : args.rval().setString(contents_str);
482 : 4232 : return true;
483 : 4232 : }
484 : :
485 : 1895 : bool gjs_internal_uri_exists(JSContext* cx, unsigned argc, JS::Value* vp) {
486 : 1895 : JS::CallArgs args = CallArgsFromVp(argc, vp);
487 : 1895 : JS::UniqueChars uri;
488 [ - + ]: 1895 : if (!gjs_parse_call_args(cx, "uriExists", args, "!s", "uri", &uri))
489 : 0 : return handle_wrong_args(cx);
490 : :
491 : 1895 : Gjs::AutoUnref<GFile> file{g_file_new_for_uri(uri.get())};
492 : :
493 : 1895 : args.rval().setBoolean(g_file_query_exists(file, nullptr));
494 : 1895 : return true;
495 : 1895 : }
496 : :
497 : 5 : bool gjs_internal_atob(JSContext* cx, unsigned argc, JS::Value* vp) {
498 : 5 : JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
499 : 5 : JS::UniqueChars text;
500 : : size_t result_len;
501 [ - + ]: 5 : if (!gjs_parse_call_args(cx, "atob", args, "!s", "text", &text))
502 : 0 : return handle_wrong_args(cx);
503 : :
504 : : Gjs::AutoChar decoded =
505 : 5 : reinterpret_cast<char*>(g_base64_decode(text.get(), &result_len));
506 : 5 : JS::ConstUTF8CharsZ contents_chars{decoded, result_len};
507 : : JS::RootedString contents_str{cx,
508 : 5 : JS_NewStringCopyUTF8Z(cx, contents_chars)};
509 : :
510 [ - + ]: 5 : if (!contents_str)
511 : 0 : return false;
512 : :
513 : 5 : args.rval().setString(contents_str);
514 : 5 : return true;
515 : 5 : }
516 : :
517 : : class PromiseData {
518 : : public:
519 : : JSContext* cx;
520 : :
521 : : private:
522 : : JS::PersistentRooted<JSFunction*> m_resolve;
523 : : JS::PersistentRooted<JSFunction*> m_reject;
524 : :
525 : 19 : JS::HandleFunction resolver() { return m_resolve; }
526 : 1 : JS::HandleFunction rejecter() { return m_reject; }
527 : :
528 : : public:
529 : 20 : explicit PromiseData(JSContext* a_cx, JSFunction* resolve,
530 : : JSFunction* reject)
531 : 20 : : cx(a_cx), m_resolve(cx, resolve), m_reject(cx, reject) {}
532 : :
533 : 20 : static PromiseData* from_ptr(void* ptr) {
534 : 20 : return static_cast<PromiseData*>(ptr);
535 : : }
536 : :
537 : : // Adapted from SpiderMonkey js::RejectPromiseWithPendingError()
538 : : // https://searchfox.org/mozilla-central/rev/95cf843de977805a3951f9137f5ff1930599d94e/js/src/builtin/Promise.cpp#4435
539 : 1 : void reject_with_pending_exception() {
540 : 1 : JS::RootedValue exception(cx);
541 : 1 : bool ok GJS_USED_ASSERT = JS_GetPendingException(cx, &exception);
542 : 1 : g_assert(ok && "Cannot reject a promise with an uncatchable exception");
543 : :
544 : 1 : JS::RootedValueArray<1> args(cx);
545 : 1 : args[0].set(exception);
546 : 1 : JS::RootedValue ignored_rval(cx);
547 : 1 : ok = JS_CallFunction(cx, /* this_obj = */ nullptr, rejecter(), args,
548 : : &ignored_rval);
549 : 1 : g_assert(ok && "Failed rejecting promise");
550 : 1 : }
551 : :
552 : 19 : void resolve(JS::Value result) {
553 : 19 : JS::RootedValueArray<1> args(cx);
554 : 19 : args[0].set(result);
555 : 19 : JS::RootedValue ignored_rval(cx);
556 : 19 : bool ok GJS_USED_ASSERT = JS_CallFunction(
557 : : cx, /* this_obj = */ nullptr, resolver(), args, &ignored_rval);
558 : 19 : g_assert(ok && "Failed resolving promise");
559 : 19 : }
560 : : };
561 : :
562 : 20 : static void load_async_callback(GObject* file, GAsyncResult* res, void* data) {
563 : 20 : std::unique_ptr<PromiseData> promise(PromiseData::from_ptr(data));
564 : :
565 : 20 : GjsContextPrivate* gjs = GjsContextPrivate::from_cx(promise->cx);
566 : 20 : gjs->main_loop_release();
567 : :
568 : 20 : Gjs::AutoMainRealm ar{gjs};
569 : :
570 : : char* contents;
571 : : size_t length;
572 : 20 : Gjs::AutoError error;
573 [ + + ]: 20 : if (!g_file_load_contents_finish(G_FILE(file), res, &contents, &length,
574 : : /* etag_out = */ nullptr, &error)) {
575 : 1 : Gjs::AutoChar uri{g_file_get_uri(G_FILE(file))};
576 : 1 : gjs_throw_custom(promise->cx, JSEXN_ERR, "ImportError",
577 : : "Unable to load file async from: %s (%s)", uri.get(),
578 : 1 : error->message);
579 : 1 : promise->reject_with_pending_exception();
580 : 1 : return;
581 : 1 : }
582 : :
583 : 19 : JS::RootedValue text(promise->cx);
584 : 19 : bool ok = gjs_string_from_utf8_n(promise->cx, contents, length, &text);
585 : 19 : g_free(contents);
586 [ - + ]: 19 : if (!ok) {
587 : 0 : promise->reject_with_pending_exception();
588 : 0 : return;
589 : : }
590 : :
591 : 19 : promise->resolve(text);
592 [ + - + + : 22 : }
+ + + + ]
593 : :
594 : : GJS_JSAPI_RETURN_CONVENTION
595 : 20 : static bool load_async_executor(JSContext* cx, unsigned argc, JS::Value* vp) {
596 : 20 : JS::CallArgs args = CallArgsFromVp(argc, vp);
597 : 20 : JS::RootedObject resolve(cx), reject(cx);
598 [ - + ]: 20 : if (!gjs_parse_call_args(cx, "executor", args, "oo", "resolve", &resolve,
599 : : "reject", &reject))
600 : 0 : return handle_wrong_args(cx);
601 : :
602 : 20 : g_assert(JS_ObjectIsFunction(resolve) && "Executor called weirdly");
603 : 20 : g_assert(JS_ObjectIsFunction(reject) && "Executor called weirdly");
604 : :
605 : 20 : JS::Value priv_value = js::GetFunctionNativeReserved(&args.callee(), 0);
606 : 20 : g_assert(!priv_value.isNull() && "Executor called twice");
607 : 20 : Gjs::AutoUnref<GFile> file{G_FILE(priv_value.toPrivate())};
608 : 20 : g_assert(file && "Executor called twice");
609 : : // We now own the GFile, and will pass the reference to the GAsyncResult, so
610 : : // remove it from the executor's private slot so it doesn't become dangling
611 : 20 : js::SetFunctionNativeReserved(&args.callee(), 0, JS::NullValue());
612 : :
613 : 20 : auto* data = new PromiseData(cx, JS_GetObjectFunction(resolve),
614 : 40 : JS_GetObjectFunction(reject));
615 : :
616 : : // Hold the main loop until this function resolves...
617 : 20 : GjsContextPrivate::from_cx(cx)->main_loop_hold();
618 : 20 : g_file_load_contents_async(file, nullptr, load_async_callback, data);
619 : :
620 : 20 : args.rval().setUndefined();
621 : 20 : return true;
622 : 20 : }
623 : :
624 : 20 : bool gjs_internal_load_resource_or_file_async(JSContext* cx, unsigned argc,
625 : : JS::Value* vp) {
626 : 20 : JS::CallArgs args = CallArgsFromVp(argc, vp);
627 : 20 : JS::UniqueChars uri;
628 [ - + ]: 20 : if (!gjs_parse_call_args(cx, "loadResourceOrFileAsync", args, "s", "uri",
629 : : &uri))
630 : 0 : return handle_wrong_args(cx);
631 : :
632 : 20 : Gjs::AutoUnref<GFile> file{g_file_new_for_uri(uri.get())};
633 : :
634 : : JS::RootedObject executor(cx,
635 : 20 : JS_GetFunctionObject(js::NewFunctionWithReserved(
636 : : cx, load_async_executor, 2, 0,
637 : 20 : "loadResourceOrFileAsync executor")));
638 [ - + ]: 20 : if (!executor)
639 : 0 : return false;
640 : :
641 : : // Stash the file object for the callback to find later; executor owns it
642 : 20 : js::SetFunctionNativeReserved(executor, 0, JS::PrivateValue(file.copy()));
643 : :
644 : 20 : JSObject* promise = JS::NewPromiseObject(cx, executor);
645 [ - + ]: 20 : if (!promise)
646 : 0 : return false;
647 : :
648 : 20 : args.rval().setObject(*promise);
649 : 20 : return true;
650 : 20 : }
|