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