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 : 241 : bool gjs_load_internal_module(JSContext* cx, const char* identifier) {
71 : : GjsAutoChar full_path(g_strdup_printf(
72 : 241 : "resource:///org/gnome/gjs/modules/internal/%s.js", identifier));
73 : :
74 : 241 : gjs_debug(GJS_DEBUG_IMPORTER, "Loading internal module '%s' (%s)",
75 : : identifier, full_path.get());
76 : :
77 : 241 : GjsAutoChar script;
78 : : size_t script_len;
79 [ - + ]: 241 : if (!gjs_load_internal_source(cx, full_path, script.out(), &script_len))
80 : 0 : return false;
81 : :
82 : 241 : JS::SourceText<mozilla::Utf8Unit> buf;
83 [ - + ]: 241 : if (!buf.init(cx, script.get(), script_len, JS::SourceOwnership::Borrowed))
84 : 0 : return false;
85 : :
86 : 241 : JS::CompileOptions options(cx);
87 : 241 : options.setIntroductionType("Internal Module Bootstrap");
88 : 241 : options.setFileAndLine(full_path, 1);
89 : 241 : options.setSelfHostingMode(false);
90 : :
91 : 241 : Gjs::AutoInternalRealm ar{cx};
92 : :
93 : 241 : JS::RootedValue ignored(cx);
94 : 241 : return JS::Evaluate(cx, options, buf, &ignored);
95 : 241 : }
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 : 241 : bool gjs_internal_set_global_module_loader(JSContext* cx, unsigned argc,
114 : : JS::Value* vp) {
115 : 241 : JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
116 : 241 : JS::RootedObject global(cx), loader(cx);
117 [ - + ]: 241 : if (!gjs_parse_call_args(cx, "setGlobalModuleLoader", args, "oo", "global",
118 : : &global, "loader", &loader))
119 : 0 : return handle_wrong_args(cx);
120 : :
121 : 241 : gjs_set_global_slot(global, GjsGlobalSlot::MODULE_LOADER,
122 : 241 : JS::ObjectValue(*loader));
123 : :
124 : 241 : args.rval().setUndefined();
125 : 241 : return true;
126 : 241 : }
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 : 1735 : static bool compile_module(JSContext* cx, const JS::UniqueChars& uri,
142 : : JS::HandleString source,
143 : : JS::MutableHandleValue v_module_out) {
144 : 1735 : JS::CompileOptions options(cx);
145 : 1735 : options.setFileAndLine(uri.get(), 1).setSourceIsLazy(false);
146 : :
147 : : size_t text_len;
148 : : char16_t* text;
149 [ - + ]: 1735 : if (!gjs_string_get_char16_data(cx, source, &text, &text_len))
150 : 0 : return false;
151 : :
152 : 1735 : JS::SourceText<char16_t> buf;
153 [ - + ]: 1735 : if (!buf.init(cx, text, text_len, JS::SourceOwnership::TakeOwnership))
154 : 0 : return false;
155 : :
156 : 1735 : JS::RootedObject new_module(cx, JS::CompileModule(cx, options, buf));
157 [ + + ]: 1735 : if (!new_module)
158 : 1 : return false;
159 : :
160 : 1734 : v_module_out.setObject(*new_module);
161 : 1734 : return true;
162 : 1735 : }
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 : 1735 : bool gjs_internal_compile_module(JSContext* cx, unsigned argc, JS::Value* vp) {
208 : 1735 : JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
209 : :
210 : 1735 : Gjs::AutoMainRealm ar{cx};
211 : :
212 : 1735 : JS::UniqueChars uri;
213 : 1735 : JS::RootedString source(cx);
214 [ - + ]: 1735 : if (!gjs_parse_call_args(cx, "compileModule", args, "sS", "uri", &uri,
215 : : "source", &source))
216 : 0 : return handle_wrong_args(cx);
217 : :
218 : 1735 : return compile_module(cx, uri, source, args.rval());
219 : 1735 : }
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 : 1734 : bool gjs_internal_set_module_private(JSContext* cx, unsigned argc,
234 : : JS::Value* vp) {
235 : 1734 : JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
236 : 1734 : JS::RootedObject module(cx), private_obj(cx);
237 [ - + ]: 1734 : if (!gjs_parse_call_args(cx, "setModulePrivate", args, "oo", "module",
238 : : &module, "private", &private_obj))
239 : 0 : return handle_wrong_args(cx);
240 : :
241 : 1734 : JS::SetModulePrivate(module, JS::ObjectValue(*private_obj));
242 : 1734 : return true;
243 : 1734 : }
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 : 5142 : bool gjs_internal_get_registry(JSContext* cx, unsigned argc, JS::Value* vp) {
257 : 5142 : JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
258 : 5142 : JS::RootedObject global(cx);
259 [ - + ]: 5142 : if (!gjs_parse_call_args(cx, "getRegistry", args, "o", "global", &global))
260 : 0 : return handle_wrong_args(cx);
261 : :
262 : 5142 : JSAutoRealm ar(cx, global);
263 : :
264 : 5142 : JS::RootedObject registry(cx, gjs_get_module_registry(global));
265 : 5142 : args.rval().setObject(*registry);
266 : 5142 : return true;
267 : 5142 : }
268 : :
269 : 7468 : bool gjs_internal_parse_uri(JSContext* cx, unsigned argc, JS::Value* vp) {
270 : : using AutoHashTable =
271 : : GjsAutoPointer<GHashTable, GHashTable, g_hash_table_destroy>;
272 : : using AutoURI = GjsAutoPointer<GUri, GUri, g_uri_unref>;
273 : :
274 : 7468 : JS::CallArgs args = CallArgsFromVp(argc, vp);
275 : 7468 : JS::RootedString string_arg(cx);
276 [ - + ]: 7468 : if (!gjs_parse_call_args(cx, "parseUri", args, "S", "uri", &string_arg))
277 : 0 : return handle_wrong_args(cx);
278 : :
279 : 7468 : JS::UniqueChars uri = JS_EncodeStringToUTF8(cx, string_arg);
280 [ - + ]: 7468 : if (!uri)
281 : 0 : return false;
282 : :
283 : 7468 : GjsAutoError error;
284 : 7468 : AutoURI parsed = g_uri_parse(uri.get(), G_URI_FLAGS_NONE, &error);
285 [ + + ]: 7468 : if (!parsed) {
286 : 2627 : Gjs::AutoMainRealm ar{cx};
287 : :
288 : 2627 : gjs_throw_custom(cx, JSEXN_ERR, "ImportError",
289 : : "Attempted to import invalid URI: %s (%s)", uri.get(),
290 : 2627 : error->message);
291 : 2627 : return false;
292 : 2627 : }
293 : :
294 : 4841 : JS::RootedObject query_obj(cx, JS_NewPlainObject(cx));
295 [ - + ]: 4841 : if (!query_obj)
296 : 0 : return false;
297 : :
298 : 4841 : const char* raw_query = g_uri_get_query(parsed);
299 [ - + ]: 4841 : if (raw_query) {
300 : : AutoHashTable query =
301 : 0 : g_uri_parse_params(raw_query, -1, "&", G_URI_PARAMS_NONE, &error);
302 [ # # ]: 0 : 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 : 0 : g_hash_table_iter_init(&iter, query);
313 : :
314 : : void* key_ptr;
315 : : void* value_ptr;
316 [ # # ]: 0 : while (g_hash_table_iter_next(&iter, &key_ptr, &value_ptr)) {
317 : 0 : auto* key = static_cast<const char*>(key_ptr);
318 : 0 : auto* value = static_cast<const char*>(value_ptr);
319 : :
320 : 0 : JS::ConstUTF8CharsZ value_chars{value, strlen(value)};
321 : : JS::RootedString value_str(cx,
322 : 0 : JS_NewStringCopyUTF8Z(cx, value_chars));
323 [ # # # # : 0 : if (!value_str || !JS_DefineProperty(cx, query_obj, key, value_str,
# # ]
324 : : JSPROP_ENUMERATE))
325 : 0 : return false;
326 [ # # ]: 0 : }
327 [ # # ]: 0 : }
328 : :
329 : 4841 : JS::RootedObject return_obj(cx, JS_NewPlainObject(cx));
330 [ - + ]: 4841 : 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 : 4841 : JS_NewStringCopyZ(cx, g_uri_get_scheme(parsed)));
337 [ - + ]: 4841 : if (!scheme)
338 : 0 : return false;
339 : :
340 : 4841 : JS::RootedString host(cx, JS_NewStringCopyZ(cx, g_uri_get_host(parsed)));
341 [ - + ]: 4841 : if (!host)
342 : 0 : return false;
343 : :
344 : 4841 : JS::RootedString path(cx, JS_NewStringCopyZ(cx, g_uri_get_path(parsed)));
345 [ - + ]: 4841 : if (!path)
346 : 0 : return false;
347 : :
348 : 4841 : if (!JS_DefineProperty(cx, return_obj, "uri", string_arg,
349 : 4841 : JSPROP_ENUMERATE) ||
350 [ + - ]: 4841 : !JS_DefineProperty(cx, return_obj, "scheme", scheme,
351 : 4841 : JSPROP_ENUMERATE) ||
352 [ + - ]: 4841 : !JS_DefineProperty(cx, return_obj, "host", host, JSPROP_ENUMERATE) ||
353 [ + - + - ]: 14523 : !JS_DefineProperty(cx, return_obj, "path", path, JSPROP_ENUMERATE) ||
354 [ - + - + ]: 9682 : !JS_DefineProperty(cx, return_obj, "query", query_obj,
355 : : JSPROP_ENUMERATE))
356 : 0 : return false;
357 : :
358 : 4841 : args.rval().setObject(*return_obj);
359 : 4841 : return true;
360 : 7468 : }
361 : :
362 : 1798 : bool gjs_internal_resolve_relative_resource_or_file(JSContext* cx,
363 : : unsigned argc,
364 : : JS::Value* vp) {
365 : 1798 : JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
366 : 1798 : JS::UniqueChars uri, relative_path;
367 [ - + ]: 1798 : if (!gjs_parse_call_args(cx, "resolveRelativeResourceOrFile", args, "ss",
368 : : "uri", &uri, "relativePath", &relative_path))
369 : 0 : return handle_wrong_args(cx);
370 : :
371 : 1798 : GjsAutoUnref<GFile> module_file = g_file_new_for_uri(uri.get());
372 : 1798 : GjsAutoUnref<GFile> module_parent_file = g_file_get_parent(module_file);
373 : :
374 [ + - ]: 1798 : if (module_parent_file) {
375 : : GjsAutoUnref<GFile> output = g_file_resolve_relative_path(
376 : 1798 : module_parent_file, relative_path.get());
377 : 1798 : GjsAutoChar output_uri = g_file_get_uri(output);
378 : :
379 : 1798 : JS::ConstUTF8CharsZ uri_chars(output_uri, strlen(output_uri));
380 : 1798 : JS::RootedString retval(cx, JS_NewStringCopyUTF8Z(cx, uri_chars));
381 [ - + ]: 1798 : if (!retval)
382 : 0 : return false;
383 : :
384 : 1798 : args.rval().setString(retval);
385 : 1798 : return true;
386 : 1798 : }
387 : :
388 : 0 : args.rval().setNull();
389 : 0 : return true;
390 : 1798 : }
391 : :
392 : 1672 : bool gjs_internal_load_resource_or_file(JSContext* cx, unsigned argc,
393 : : JS::Value* vp) {
394 : 1672 : JS::CallArgs args = CallArgsFromVp(argc, vp);
395 : 1672 : JS::UniqueChars uri;
396 [ - + ]: 1672 : if (!gjs_parse_call_args(cx, "loadResourceOrFile", args, "s", "uri", &uri))
397 : 0 : return handle_wrong_args(cx);
398 : :
399 : 1672 : GjsAutoUnref<GFile> file = g_file_new_for_uri(uri.get());
400 : :
401 : : char* contents;
402 : : size_t length;
403 : 1672 : GjsAutoError error;
404 [ - + ]: 1672 : if (!g_file_load_contents(file, /* cancellable = */ nullptr, &contents,
405 : : &length, /* etag_out = */ nullptr, &error)) {
406 : 0 : Gjs::AutoMainRealm ar{cx};
407 : :
408 : 0 : gjs_throw_custom(cx, JSEXN_ERR, "ImportError",
409 : : "Unable to load file from: %s (%s)", uri.get(),
410 : 0 : error->message);
411 : 0 : return false;
412 : 0 : }
413 : :
414 : 1672 : JS::ConstUTF8CharsZ contents_chars{contents, length};
415 : : JS::RootedString contents_str(cx,
416 : 1672 : JS_NewStringCopyUTF8Z(cx, contents_chars));
417 : 1672 : g_free(contents);
418 [ - + ]: 1672 : if (!contents_str)
419 : 0 : return false;
420 : :
421 : 1672 : args.rval().setString(contents_str);
422 : 1672 : return true;
423 : 1672 : }
424 : :
425 : 827 : bool gjs_internal_uri_exists(JSContext* cx, unsigned argc, JS::Value* vp) {
426 : 827 : JS::CallArgs args = CallArgsFromVp(argc, vp);
427 : 827 : JS::UniqueChars uri;
428 [ - + ]: 827 : if (!gjs_parse_call_args(cx, "uriExists", args, "!s", "uri", &uri))
429 : 0 : return handle_wrong_args(cx);
430 : :
431 : 827 : GjsAutoUnref<GFile> file = g_file_new_for_uri(uri.get());
432 : :
433 : 827 : args.rval().setBoolean(g_file_query_exists(file, nullptr));
434 : 827 : return true;
435 : 827 : }
436 : :
437 : : class PromiseData {
438 : : public:
439 : : JSContext* cx;
440 : :
441 : : private:
442 : : JS::Heap<JSFunction*> m_resolve;
443 : : JS::Heap<JSFunction*> m_reject;
444 : :
445 : 11 : JS::HandleFunction resolver() {
446 : 11 : return JS::HandleFunction::fromMarkedLocation(m_resolve.address());
447 : : }
448 : 1 : JS::HandleFunction rejecter() {
449 : 1 : return JS::HandleFunction::fromMarkedLocation(m_reject.address());
450 : : }
451 : :
452 : 0 : static void trace(JSTracer* trc, void* data) {
453 : 0 : auto* self = PromiseData::from_ptr(data);
454 : 0 : JS::TraceEdge(trc, &self->m_resolve, "loadResourceOrFileAsync resolve");
455 : 0 : JS::TraceEdge(trc, &self->m_reject, "loadResourceOrFileAsync reject");
456 : 0 : }
457 : :
458 : : public:
459 : 12 : explicit PromiseData(JSContext* a_cx, JSFunction* resolve,
460 : : JSFunction* reject)
461 : 12 : : cx(a_cx), m_resolve(resolve), m_reject(reject) {
462 : 12 : JS_AddExtraGCRootsTracer(cx, &PromiseData::trace, this);
463 : 12 : }
464 : :
465 : 12 : ~PromiseData() {
466 : 12 : JS_RemoveExtraGCRootsTracer(cx, &PromiseData::trace, this);
467 : 12 : }
468 : :
469 : 12 : static PromiseData* from_ptr(void* ptr) {
470 : 12 : return static_cast<PromiseData*>(ptr);
471 : : }
472 : :
473 : : // Adapted from SpiderMonkey js::RejectPromiseWithPendingError()
474 : : // https://searchfox.org/mozilla-central/rev/95cf843de977805a3951f9137f5ff1930599d94e/js/src/builtin/Promise.cpp#4435
475 : 1 : void reject_with_pending_exception() {
476 : 1 : JS::RootedValue exception(cx);
477 : 1 : bool ok GJS_USED_ASSERT = JS_GetPendingException(cx, &exception);
478 : 1 : g_assert(ok && "Cannot reject a promise with an uncatchable exception");
479 : :
480 : 1 : JS::RootedValueArray<1> args(cx);
481 : 1 : args[0].set(exception);
482 : 1 : JS::RootedValue ignored_rval(cx);
483 : 1 : ok = JS_CallFunction(cx, /* this_obj = */ nullptr, rejecter(), args,
484 : : &ignored_rval);
485 : 1 : g_assert(ok && "Failed rejecting promise");
486 : 1 : }
487 : :
488 : 11 : void resolve(JS::Value result) {
489 : 11 : JS::RootedValueArray<1> args(cx);
490 : 11 : args[0].set(result);
491 : 11 : JS::RootedValue ignored_rval(cx);
492 : 11 : bool ok GJS_USED_ASSERT = JS_CallFunction(
493 : : cx, /* this_obj = */ nullptr, resolver(), args, &ignored_rval);
494 : 11 : g_assert(ok && "Failed resolving promise");
495 : 11 : }
496 : : };
497 : :
498 : 12 : static void load_async_callback(GObject* file, GAsyncResult* res, void* data) {
499 : 12 : std::unique_ptr<PromiseData> promise(PromiseData::from_ptr(data));
500 : :
501 : 12 : GjsContextPrivate* gjs = GjsContextPrivate::from_cx(promise->cx);
502 : 12 : gjs->main_loop_release();
503 : :
504 : 12 : Gjs::AutoMainRealm ar{gjs};
505 : :
506 : : char* contents;
507 : : size_t length;
508 : 12 : GjsAutoError error;
509 [ + + ]: 12 : if (!g_file_load_contents_finish(G_FILE(file), res, &contents, &length,
510 : : /* etag_out = */ nullptr, &error)) {
511 : 1 : GjsAutoChar uri = g_file_get_uri(G_FILE(file));
512 : 1 : gjs_throw_custom(promise->cx, JSEXN_ERR, "ImportError",
513 : : "Unable to load file from: %s (%s)", uri.get(),
514 : 1 : error->message);
515 : 1 : promise->reject_with_pending_exception();
516 : 1 : return;
517 : 1 : }
518 : :
519 : 11 : JS::RootedValue text(promise->cx);
520 : 11 : bool ok = gjs_string_from_utf8_n(promise->cx, contents, length, &text);
521 : 11 : g_free(contents);
522 [ - + ]: 11 : if (!ok) {
523 : 0 : promise->reject_with_pending_exception();
524 : 0 : return;
525 : : }
526 : :
527 : 11 : promise->resolve(text);
528 [ + - + + : 14 : }
+ + + + ]
529 : :
530 : : GJS_JSAPI_RETURN_CONVENTION
531 : 12 : static bool load_async_executor(JSContext* cx, unsigned argc, JS::Value* vp) {
532 : 12 : JS::CallArgs args = CallArgsFromVp(argc, vp);
533 : 12 : JS::RootedObject resolve(cx), reject(cx);
534 [ - + ]: 12 : if (!gjs_parse_call_args(cx, "executor", args, "oo", "resolve", &resolve,
535 : : "reject", &reject))
536 : 0 : return handle_wrong_args(cx);
537 : :
538 : 12 : g_assert(JS_ObjectIsFunction(resolve) && "Executor called weirdly");
539 : 12 : g_assert(JS_ObjectIsFunction(reject) && "Executor called weirdly");
540 : :
541 : 12 : JS::Value priv_value = js::GetFunctionNativeReserved(&args.callee(), 0);
542 : 12 : g_assert(!priv_value.isNull() && "Executor called twice");
543 : 12 : GjsAutoUnref<GFile> file = G_FILE(priv_value.toPrivate());
544 : 12 : g_assert(file && "Executor called twice");
545 : : // We now own the GFile, and will pass the reference to the GAsyncResult, so
546 : : // remove it from the executor's private slot so it doesn't become dangling
547 : 12 : js::SetFunctionNativeReserved(&args.callee(), 0, JS::NullValue());
548 : :
549 : 12 : auto* data = new PromiseData(cx, JS_GetObjectFunction(resolve),
550 : 24 : JS_GetObjectFunction(reject));
551 : :
552 : : // Hold the main loop until this function resolves...
553 : 12 : GjsContextPrivate::from_cx(cx)->main_loop_hold();
554 : 12 : g_file_load_contents_async(file, nullptr, load_async_callback, data);
555 : :
556 : 12 : args.rval().setUndefined();
557 : 12 : return true;
558 : 12 : }
559 : :
560 : 12 : bool gjs_internal_load_resource_or_file_async(JSContext* cx, unsigned argc,
561 : : JS::Value* vp) {
562 : 12 : JS::CallArgs args = CallArgsFromVp(argc, vp);
563 : 12 : JS::UniqueChars uri;
564 [ - + ]: 12 : if (!gjs_parse_call_args(cx, "loadResourceOrFileAsync", args, "s", "uri",
565 : : &uri))
566 : 0 : return handle_wrong_args(cx);
567 : :
568 : 12 : GjsAutoUnref<GFile> file = g_file_new_for_uri(uri.get());
569 : :
570 : : JS::RootedObject executor(cx,
571 : 12 : JS_GetFunctionObject(js::NewFunctionWithReserved(
572 : : cx, load_async_executor, 2, 0,
573 : 12 : "loadResourceOrFileAsync executor")));
574 [ - + ]: 12 : if (!executor)
575 : 0 : return false;
576 : :
577 : : // Stash the file object for the callback to find later; executor owns it
578 : 12 : js::SetFunctionNativeReserved(executor, 0, JS::PrivateValue(file.copy()));
579 : :
580 : 12 : JSObject* promise = JS::NewPromiseObject(cx, executor);
581 [ - + ]: 12 : if (!promise)
582 : 0 : return false;
583 : :
584 : 12 : args.rval().setObject(*promise);
585 : 12 : return true;
586 : 12 : }
|