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