Branch data Line data Source code
1 : : /* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
2 : : // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later
3 : : // SPDX-FileCopyrightText: 2017 Philip Chimento <philip.chimento@gmail.com>
4 : :
5 : : #include <config.h>
6 : :
7 : : #include <stddef.h> // for size_t
8 : : #include <string.h>
9 : :
10 : : #include <string>
11 : : #include <vector> // for vector
12 : :
13 : : #include <gio/gio.h>
14 : : #include <glib-object.h>
15 : : #include <glib.h>
16 : :
17 : : #include <js/CallAndConstruct.h>
18 : : #include <js/CallArgs.h>
19 : : #include <js/CharacterEncoding.h> // for ConstUTF8CharsZ
20 : : #include <js/Class.h>
21 : : #include <js/CompilationAndEvaluation.h>
22 : : #include <js/CompileOptions.h>
23 : : #include <js/Conversions.h>
24 : : #include <js/ErrorReport.h> // for JS_ReportOutOfMemory
25 : : #include <js/Exception.h>
26 : : #include <js/GCVector.h> // for RootedVector
27 : : #include <js/GlobalObject.h> // for CurrentGlobalOrNull
28 : : #include <js/Id.h>
29 : : #include <js/Modules.h>
30 : : #include <js/Object.h>
31 : : #include <js/Promise.h>
32 : : #include <js/PropertyAndElement.h>
33 : : #include <js/PropertyDescriptor.h>
34 : : #include <js/RootingAPI.h>
35 : : #include <js/ScriptPrivate.h>
36 : : #include <js/SourceText.h>
37 : : #include <js/String.h>
38 : : #include <js/TypeDecls.h>
39 : : #include <js/Utility.h> // for UniqueChars
40 : : #include <js/Value.h>
41 : : #include <js/ValueArray.h>
42 : : #include <jsapi.h> // for JS_GetFunctionObject, JS_Ne...
43 : : #include <jsfriendapi.h> // for NewFunctionWithReserved
44 : : #include <mozilla/Maybe.h>
45 : :
46 : : #include "gjs/atoms.h"
47 : : #include "gjs/auto.h"
48 : : #include "gjs/context-private.h"
49 : : #include "gjs/deprecation.h"
50 : : #include "gjs/gerror-result.h"
51 : : #include "gjs/global.h"
52 : : #include "gjs/jsapi-util-args.h"
53 : : #include "gjs/jsapi-util.h"
54 : : #include "gjs/macros.h"
55 : : #include "gjs/mem-private.h"
56 : : #include "gjs/module.h"
57 : : #include "gjs/native.h"
58 : : #include "util/log.h"
59 : : #include "util/misc.h"
60 : :
61 : : namespace mozilla {
62 : : union Utf8Unit;
63 : : }
64 : :
65 : : class GjsScriptModule {
66 : : Gjs::AutoChar m_name;
67 : :
68 : : // Reserved slots
69 : : static const size_t POINTER = 0;
70 : :
71 : 908 : GjsScriptModule(const char* name) : m_name(g_strdup(name)) {
72 : 454 : GJS_INC_COUNTER(module);
73 : 454 : }
74 : :
75 : 449 : ~GjsScriptModule() { GJS_DEC_COUNTER(module); }
76 : :
77 : : GjsScriptModule(GjsScriptModule&) = delete;
78 : : GjsScriptModule& operator=(GjsScriptModule&) = delete;
79 : :
80 : : /* Private data accessors */
81 : :
82 : 293897 : [[nodiscard]] static inline GjsScriptModule* priv(JSObject* module) {
83 : 293897 : return JS::GetMaybePtrFromReservedSlot<GjsScriptModule>(
84 : 293897 : module, GjsScriptModule::POINTER);
85 : : }
86 : :
87 : : /* Creates a JS module object. Use instead of the class's constructor */
88 : 454 : [[nodiscard]] static JSObject* create(JSContext* cx, const char* name) {
89 : 454 : JSObject* module = JS_NewObject(cx, &GjsScriptModule::klass);
90 : 454 : JS::SetReservedSlot(module, GjsScriptModule::POINTER,
91 : 454 : JS::PrivateValue(new GjsScriptModule(name)));
92 : 454 : return module;
93 : : }
94 : :
95 : : /* Defines the empty module as a property on the importer */
96 : : GJS_JSAPI_RETURN_CONVENTION
97 : : bool
98 : 454 : define_import(JSContext *cx,
99 : : JS::HandleObject module,
100 : : JS::HandleObject importer,
101 : : JS::HandleId name) const
102 : : {
103 [ - + ]: 454 : if (!JS_DefinePropertyById(cx, importer, name, module,
104 : : GJS_MODULE_PROP_FLAGS & ~JSPROP_PERMANENT)) {
105 : 0 : gjs_debug(GJS_DEBUG_IMPORTER, "Failed to define '%s' in importer",
106 : : m_name.get());
107 : 0 : return false;
108 : : }
109 : :
110 : 454 : return true;
111 : : }
112 : :
113 : : /* Carries out the actual execution of the module code */
114 : : GJS_JSAPI_RETURN_CONVENTION
115 : 454 : bool evaluate_import(JSContext* cx, JS::HandleObject module,
116 : : const char* source, size_t source_len,
117 : : const char* filename, const char* uri) {
118 : 454 : JS::SourceText<mozilla::Utf8Unit> buf;
119 [ - + ]: 454 : if (!buf.init(cx, source, source_len, JS::SourceOwnership::Borrowed))
120 : 0 : return false;
121 : :
122 : 454 : JS::RootedObjectVector scope_chain(cx);
123 [ - + ]: 454 : if (!scope_chain.append(module)) {
124 : 0 : JS_ReportOutOfMemory(cx);
125 : 0 : return false;
126 : : }
127 : :
128 : 454 : JS::CompileOptions options(cx);
129 : 454 : options.setFileAndLine(filename, 1).setNonSyntacticScope(true);
130 : :
131 : 454 : JS::RootedObject priv(cx, build_private(cx, uri));
132 [ - + ]: 454 : if (!priv)
133 : 0 : return false;
134 : :
135 : 454 : JS::RootedScript script(cx, JS::Compile(cx, options, buf));
136 [ + + ]: 454 : if (!script)
137 : 1 : return false;
138 : :
139 : 453 : JS::SetScriptPrivate(script, JS::ObjectValue(*priv));
140 : 453 : JS::RootedValue ignored_retval(cx);
141 [ + + ]: 453 : if (!JS_ExecuteScript(cx, scope_chain, script, &ignored_retval))
142 : 3 : return false;
143 : :
144 : 450 : GjsContextPrivate* gjs = GjsContextPrivate::from_cx(cx);
145 : 450 : gjs->schedule_gc_if_needed();
146 : :
147 : 450 : gjs_debug(GJS_DEBUG_IMPORTER, "Importing module %s succeeded",
148 : : m_name.get());
149 : :
150 : 450 : return true;
151 : 454 : }
152 : :
153 : : /* Loads JS code from a file and imports it */
154 : : GJS_JSAPI_RETURN_CONVENTION
155 : : bool
156 : 454 : import_file(JSContext *cx,
157 : : JS::HandleObject module,
158 : : GFile *file)
159 : : {
160 : 454 : Gjs::AutoError error;
161 : 454 : Gjs::AutoChar script;
162 : 454 : size_t script_len = 0;
163 : :
164 [ - + ]: 454 : if (!(g_file_load_contents(file, nullptr, script.out(), &script_len,
165 : : nullptr, &error)))
166 : 0 : return gjs_throw_gerror_message(cx, error);
167 : 454 : g_assert(script);
168 : :
169 : 454 : Gjs::AutoChar full_path{g_file_get_parse_name(file)};
170 : 454 : Gjs::AutoChar uri{g_file_get_uri(file)};
171 : 454 : return evaluate_import(cx, module, script, script_len, full_path, uri);
172 : 454 : }
173 : :
174 : : /* JSClass operations */
175 : :
176 : : GJS_JSAPI_RETURN_CONVENTION
177 : : bool
178 : 292540 : resolve_impl(JSContext *cx,
179 : : JS::HandleObject module,
180 : : JS::HandleId id,
181 : : bool *resolved)
182 : : {
183 : 292540 : JS::RootedObject lexical(cx, JS_ExtensibleLexicalEnvironment(module));
184 [ - + ]: 292540 : if (!lexical) {
185 : 0 : *resolved = false;
186 : 0 : return true; /* nothing imported yet */
187 : : }
188 : :
189 : 292540 : JS::Rooted<mozilla::Maybe<JS::PropertyDescriptor>> maybe_desc(cx);
190 : 292540 : JS::RootedObject holder(cx);
191 [ - + ]: 292540 : if (!JS_GetPropertyDescriptorById(cx, lexical, id, &maybe_desc,
192 : : &holder))
193 : 0 : return false;
194 [ + + ]: 292540 : if (maybe_desc.isNothing())
195 : 292538 : return true;
196 : :
197 : : /* The property is present in the lexical environment. This should not
198 : : * be supported according to ES6. For compatibility with earlier GJS,
199 : : * we treat it as if it were a real property, but warn about it. */
200 : :
201 [ + + ]: 12 : _gjs_warn_deprecated_once_per_callsite(
202 : : cx, GjsDeprecationMessageId::ModuleExportedLetOrConst,
203 : 4 : {gjs_debug_id(id), m_name.get()});
204 : :
205 : 2 : JS::Rooted<JS::PropertyDescriptor> desc(cx, maybe_desc.value());
206 : 2 : return JS_DefinePropertyById(cx, module, id, desc);
207 : 292540 : }
208 : :
209 : : GJS_JSAPI_RETURN_CONVENTION
210 : : static bool
211 : 292540 : resolve(JSContext *cx,
212 : : JS::HandleObject module,
213 : : JS::HandleId id,
214 : : bool *resolved)
215 : : {
216 : 292540 : return priv(module)->resolve_impl(cx, module, id, resolved);
217 : : }
218 : :
219 : 449 : static void finalize(JS::GCContext*, JSObject* module) {
220 [ + - ]: 449 : delete priv(module);
221 : 449 : }
222 : :
223 : : static constexpr JSClassOps class_ops = {
224 : : nullptr, // addProperty
225 : : nullptr, // deleteProperty
226 : : nullptr, // enumerate
227 : : nullptr, // newEnumerate
228 : : &GjsScriptModule::resolve,
229 : : nullptr, // mayResolve
230 : : &GjsScriptModule::finalize,
231 : : };
232 : :
233 : : static constexpr JSClass klass = {
234 : : "GjsScriptModule",
235 : : JSCLASS_HAS_RESERVED_SLOTS(1) | JSCLASS_BACKGROUND_FINALIZE,
236 : : &GjsScriptModule::class_ops,
237 : : };
238 : :
239 : : public:
240 : : /*
241 : : * Creates a JS object to pass to JS::SetScriptPrivate as a script's
242 : : * private.
243 : : */
244 : : GJS_JSAPI_RETURN_CONVENTION
245 : 712 : static JSObject* build_private(JSContext* cx, const char* script_uri) {
246 : 712 : JS::RootedObject priv(cx, JS_NewPlainObject(cx));
247 : 712 : const GjsAtoms& atoms = GjsContextPrivate::atoms(cx);
248 : :
249 : 712 : JS::RootedValue val(cx);
250 [ + - ]: 1424 : if (!gjs_string_from_utf8(cx, script_uri, &val) ||
251 [ - + - + ]: 1424 : !JS_SetPropertyById(cx, priv, atoms.uri(), val))
252 : 0 : return nullptr;
253 : :
254 : 712 : return priv;
255 : 712 : }
256 : :
257 : : /* Carries out the import operation */
258 : : GJS_JSAPI_RETURN_CONVENTION
259 : : static JSObject *
260 : 454 : import(JSContext *cx,
261 : : JS::HandleObject importer,
262 : : JS::HandleId id,
263 : : const char *name,
264 : : GFile *file)
265 : : {
266 : 454 : JS::RootedObject module(cx, GjsScriptModule::create(cx, name));
267 : 454 : if (!module ||
268 [ + - + - ]: 908 : !priv(module)->define_import(cx, module, importer, id) ||
269 [ + + + + ]: 908 : !priv(module)->import_file(cx, module, file))
270 : 4 : return nullptr;
271 : :
272 : 450 : return module;
273 : 454 : }
274 : : };
275 : :
276 : : /**
277 : : * gjs_script_module_build_private:
278 : : * @cx: the #JSContext
279 : : * @uri: the URI this script module is loaded from
280 : : *
281 : : * To support dynamic imports from scripts, we need to provide private data when
282 : : * we compile scripts which is compatible with our module resolution hooks in
283 : : * modules/internal/loader.js
284 : : *
285 : : * Returns: a JSObject which can be used for a JSScript's private data.
286 : : */
287 : 258 : JSObject* gjs_script_module_build_private(JSContext* cx, const char* uri) {
288 : 258 : return GjsScriptModule::build_private(cx, uri);
289 : : }
290 : :
291 : : /**
292 : : * gjs_module_import:
293 : : * @cx: the JS context
294 : : * @importer: the JS importer object, parent of the module to be imported
295 : : * @id: module name in the form of a jsid
296 : : * @name: module name, used for logging and identification
297 : : * @file: location of the file to import
298 : : *
299 : : * Carries out an import of a GJS module.
300 : : * Defines a property @name on @importer pointing to the module object, which
301 : : * is necessary in the case of cyclic imports.
302 : : * This property is not permanent; the caller is responsible for making it
303 : : * permanent if the import succeeds.
304 : : *
305 : : * Returns: the JS module object, or nullptr on failure.
306 : : */
307 : : JSObject *
308 : 454 : gjs_module_import(JSContext *cx,
309 : : JS::HandleObject importer,
310 : : JS::HandleId id,
311 : : const char *name,
312 : : GFile *file)
313 : : {
314 : 454 : return GjsScriptModule::import(cx, importer, id, name, file);
315 : : }
316 : :
317 : : decltype(GjsScriptModule::klass) constexpr GjsScriptModule::klass;
318 : : decltype(GjsScriptModule::class_ops) constexpr GjsScriptModule::class_ops;
319 : :
320 : : /**
321 : : * gjs_get_native_registry:
322 : : * @global: The JS global object
323 : : *
324 : : * Retrieves a global's native registry from the NATIVE_REGISTRY slot.
325 : : * Registries are JS Map objects created with JS::NewMapObject instead of
326 : : * GCHashMaps (used elsewhere in GJS) because the objects need to be exposed to
327 : : * internal JS code and accessed from native C++ code.
328 : : *
329 : : * Returns: the native module registry, a JS Map object.
330 : : */
331 : 33398 : JSObject* gjs_get_native_registry(JSObject* global) {
332 : : JS::Value native_registry =
333 : 33398 : gjs_get_global_slot(global, GjsGlobalSlot::NATIVE_REGISTRY);
334 : :
335 : 33398 : g_assert(native_registry.isObject());
336 : 33398 : return &native_registry.toObject();
337 : : }
338 : :
339 : : /**
340 : : * gjs_get_module_registry:
341 : : * @global: the JS global object
342 : : *
343 : : * Retrieves a global's module registry from the MODULE_REGISTRY slot.
344 : : * Registries are JS Maps. See gjs_get_native_registry() for more detail.
345 : : *
346 : : * Returns: the module registry, a JS Map object
347 : : */
348 : 12704 : JSObject* gjs_get_module_registry(JSObject* global) {
349 : : JS::Value esm_registry =
350 : 12704 : gjs_get_global_slot(global, GjsGlobalSlot::MODULE_REGISTRY);
351 : :
352 : 12704 : g_assert(esm_registry.isObject());
353 : 12704 : return &esm_registry.toObject();
354 : : }
355 : :
356 : : /**
357 : : * gjs_get_source_map_registry:
358 : : * @global: The JS global object
359 : : *
360 : : * Retrieves a global's source map registry from the SOURCE_MAP_REGISTRY slot.
361 : : * Registries are JS Maps.
362 : : *
363 : : * Returns: the source map registry, a JS Map object
364 : : */
365 : 246 : JSObject* gjs_get_source_map_registry(JSObject* global) {
366 : : JS::Value source_map_registry =
367 : 246 : gjs_get_global_slot(global, GjsGlobalSlot::SOURCE_MAP_REGISTRY);
368 : :
369 : 246 : g_assert(source_map_registry.isObject());
370 : 246 : return &source_map_registry.toObject();
371 : : }
372 : :
373 : : /**
374 : : * gjs_module_load:
375 : : * @cx: the current JSContext
376 : : * @identifier: specifier of the module to load
377 : : * @file_uri: URI to load the module from
378 : : *
379 : : * Loads and registers a module given a specifier and URI.
380 : : *
381 : : * Returns: whether an error occurred while resolving the specifier.
382 : : */
383 : 642 : JSObject* gjs_module_load(JSContext* cx, const char* identifier,
384 : : const char* file_uri) {
385 : 642 : g_assert((gjs_global_is_type(cx, GjsGlobalType::DEFAULT) ||
386 : : gjs_global_is_type(cx, GjsGlobalType::INTERNAL)) &&
387 : : "gjs_module_load can only be called from module-enabled "
388 : : "globals.");
389 : :
390 : 642 : JS::RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
391 : : JS::RootedValue v_loader(
392 : 642 : cx, gjs_get_global_slot(global, GjsGlobalSlot::MODULE_LOADER));
393 : 642 : g_assert(v_loader.isObject());
394 : 642 : JS::RootedObject loader(cx, &v_loader.toObject());
395 : :
396 : 642 : JS::ConstUTF8CharsZ id_chars(identifier, strlen(identifier));
397 : 642 : JS::ConstUTF8CharsZ uri_chars(file_uri, strlen(file_uri));
398 : 642 : JS::RootedString id(cx, JS_NewStringCopyUTF8Z(cx, id_chars));
399 [ - + ]: 642 : if (!id)
400 : 0 : return nullptr;
401 : 642 : JS::RootedString uri(cx, JS_NewStringCopyUTF8Z(cx, uri_chars));
402 [ - + ]: 642 : if (!uri)
403 : 0 : return nullptr;
404 : :
405 : 642 : JS::RootedValueArray<2> args(cx);
406 : 642 : args[0].setString(id);
407 : 642 : args[1].setString(uri);
408 : :
409 : 642 : gjs_debug(GJS_DEBUG_IMPORTER,
410 : : "Module load hook for module '%s' (%s), global %p", identifier,
411 : 642 : file_uri, global.get());
412 : :
413 : 642 : JS::RootedValue result(cx);
414 [ + + ]: 642 : if (!JS::Call(cx, loader, "moduleLoadHook", args, &result))
415 : 1 : return nullptr;
416 : :
417 : 641 : g_assert(result.isObject() && "Module hook failed to return an object!");
418 : 641 : return &result.toObject();
419 : 642 : }
420 : :
421 : : /**
422 : : * import_native_module_sync:
423 : : * @identifier: the specifier for the module to import
424 : : *
425 : : * JS function exposed as `import.meta.importSync` in internal modules only.
426 : : *
427 : : * Synchronously imports native "modules" from the import global's
428 : : * native registry. This function does not do blocking I/O so it is
429 : : * safe to call it synchronously for accessing native "modules" within
430 : : * modules. This function is always called within the import global's
431 : : * realm.
432 : : *
433 : : * Compare gjs_import_native_module() for the legacy importer.
434 : : *
435 : : * Returns: the imported JS module object.
436 : : */
437 : 9712 : static bool import_native_module_sync(JSContext* cx, unsigned argc,
438 : : JS::Value* vp) {
439 : 9712 : JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
440 : 9712 : JS::UniqueChars id;
441 [ - + ]: 9712 : if (!gjs_parse_call_args(cx, "importSync", args, "s", "identifier", &id))
442 : 0 : return false;
443 : :
444 : 9712 : Gjs::AutoMainRealm ar{cx};
445 : 9712 : JS::RootedObject global{cx, JS::CurrentGlobalOrNull(cx)};
446 : :
447 : 9712 : JS::AutoSaveExceptionState exc_state(cx);
448 : :
449 : 9712 : JS::RootedObject native_registry(cx, gjs_get_native_registry(global));
450 : 9712 : JS::RootedObject v_module(cx);
451 : :
452 : 9712 : JS::RootedId key(cx, gjs_intern_string_to_id(cx, id.get()));
453 [ - + ]: 9712 : if (!gjs_global_registry_get(cx, native_registry, key, &v_module))
454 : 0 : return false;
455 : :
456 [ + + ]: 9712 : if (v_module) {
457 : 9611 : args.rval().setObject(*v_module);
458 : 9611 : return true;
459 : : }
460 : :
461 : 101 : JS::RootedObject native_obj(cx);
462 [ - + ]: 101 : if (!Gjs::NativeModuleDefineFuncs::get().define(cx, id.get(),
463 : : &native_obj)) {
464 : 0 : gjs_throw(cx, "Failed to load native module: %s", id.get());
465 : 0 : return false;
466 : : }
467 : :
468 [ - + ]: 101 : if (!gjs_global_registry_set(cx, native_registry, key, native_obj))
469 : 0 : return false;
470 : :
471 : 101 : args.rval().setObject(*native_obj);
472 : 101 : return true;
473 : 9712 : }
474 : :
475 : : /**
476 : : * gjs_populate_module_meta:
477 : : * @cx: the current JSContext
478 : : * @private_ref: the JS private value for the #Module object, as a JS Object
479 : : * @meta: the JS `import.meta` object
480 : : *
481 : : * Hook SpiderMonkey calls to populate the `import.meta` object.
482 : : * Defines a property `import.meta.url`, and additionally a method
483 : : * `import.meta.importSync` if this is an internal module.
484 : : *
485 : : * Returns: whether an error occurred while populating the module meta.
486 : : */
487 : 164 : bool gjs_populate_module_meta(JSContext* cx, JS::HandleValue private_ref,
488 : : JS::HandleObject meta) {
489 : 164 : g_assert(private_ref.isObject());
490 : 164 : JS::RootedObject module(cx, &private_ref.toObject());
491 : :
492 : 164 : gjs_debug(GJS_DEBUG_IMPORTER, "Module metadata hook for module %p",
493 : 164 : &private_ref.toObject());
494 : :
495 : 164 : const GjsAtoms& atoms = GjsContextPrivate::atoms(cx);
496 : 164 : JS::RootedValue specifier{cx};
497 [ + - ]: 328 : if (!JS_GetProperty(cx, module, "id", &specifier) ||
498 [ - + - + ]: 328 : !JS_DefinePropertyById(cx, meta, atoms.url(), specifier,
499 : : GJS_MODULE_PROP_FLAGS))
500 : 0 : return false;
501 : :
502 : 164 : JS::RootedValue v_internal(cx);
503 [ - + ]: 164 : if (!JS_GetPropertyById(cx, module, atoms.internal(), &v_internal))
504 : 0 : return false;
505 [ + + ]: 164 : if (JS::ToBoolean(v_internal)) {
506 : 159 : gjs_debug(GJS_DEBUG_IMPORTER, "Defining meta.importSync for module %p",
507 : 159 : &private_ref.toObject());
508 [ - + ]: 159 : if (!JS_DefineFunctionById(cx, meta, atoms.importSync(),
509 : : import_native_module_sync, 1,
510 : : GJS_MODULE_PROP_FLAGS))
511 : 0 : return false;
512 : : }
513 : :
514 : 164 : return true;
515 : 164 : }
516 : :
517 : : // Canonicalize specifier so that differently-spelled specifiers referring to
518 : : // the same module don't result in duplicate entries in the registry
519 : 11680 : static bool canonicalize_specifier(JSContext* cx,
520 : : JS::MutableHandleString specifier) {
521 : 11680 : JS::UniqueChars specifier_utf8 = JS_EncodeStringToUTF8(cx, specifier);
522 [ - + ]: 11680 : if (!specifier_utf8)
523 : 0 : return false;
524 : :
525 : 11680 : Gjs::AutoChar scheme, host, path, query;
526 [ - + ]: 11680 : if (!g_uri_split(specifier_utf8.get(), G_URI_FLAGS_NONE, scheme.out(),
527 : : nullptr, host.out(), nullptr, path.out(), query.out(),
528 : : nullptr, nullptr))
529 : 0 : return false;
530 : :
531 [ + + ]: 11680 : if (g_strcmp0(scheme, "gi")) {
532 : : // canonicalize without the query portion to avoid it being encoded
533 : 11342 : Gjs::AutoChar for_file_uri{g_uri_join(G_URI_FLAGS_NONE, scheme.get(),
534 : 11342 : nullptr, host.get(), -1,
535 : 22684 : path.get(), nullptr, nullptr)};
536 : 11342 : Gjs::AutoUnref<GFile> file{g_file_new_for_uri(for_file_uri.get())};
537 : 11342 : for_file_uri = g_file_get_uri(file);
538 : 11342 : host.reset();
539 : 11342 : path.reset();
540 [ - + ]: 11342 : if (!g_uri_split(for_file_uri.get(), G_URI_FLAGS_NONE, nullptr, nullptr,
541 : : host.out(), nullptr, path.out(), nullptr, nullptr,
542 : : nullptr))
543 : 0 : return false;
544 [ + - + - ]: 11342 : }
545 : :
546 : : Gjs::AutoChar canonical_specifier{
547 : 11680 : g_uri_join(G_URI_FLAGS_NONE, scheme.get(), nullptr, host.get(), -1,
548 : 23360 : path.get(), query.get(), nullptr)};
549 : 11680 : JS::ConstUTF8CharsZ chars{canonical_specifier, strlen(canonical_specifier)};
550 : 11680 : JS::RootedString new_specifier{cx, JS_NewStringCopyUTF8Z(cx, chars)};
551 [ - + ]: 11680 : if (!new_specifier)
552 : 0 : return false;
553 : :
554 : 11680 : specifier.set(new_specifier);
555 : 11680 : return true;
556 : 11680 : }
557 : :
558 : : /**
559 : : * gjs_module_resolve:
560 : : * @cx: the current JSContext
561 : : * @importing_module_priv: the private JS Object of the #Module object
562 : : * initiating the import, or a JS null value
563 : : * @module_request: the module request object
564 : : *
565 : : * This function resolves import specifiers. It is called internally by
566 : : * SpiderMonkey as a hook.
567 : : *
568 : : * Returns: whether an error occurred while resolving the specifier.
569 : : */
570 : 11637 : JSObject* gjs_module_resolve(JSContext* cx,
571 : : JS::HandleValue importing_module_priv,
572 : : JS::HandleObject module_request) {
573 : 11637 : g_assert((gjs_global_is_type(cx, GjsGlobalType::DEFAULT) ||
574 : : gjs_global_is_type(cx, GjsGlobalType::INTERNAL)) &&
575 : : "gjs_module_resolve can only be called from module-enabled "
576 : : "globals.");
577 : : JS::RootedString specifier(
578 : 11637 : cx, JS::GetModuleRequestSpecifier(cx, module_request));
579 : :
580 : 11637 : JS::RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
581 : : JS::RootedValue v_loader(
582 : 11637 : cx, gjs_get_global_slot(global, GjsGlobalSlot::MODULE_LOADER));
583 : 11637 : g_assert(v_loader.isObject());
584 : 11637 : JS::RootedObject loader(cx, &v_loader.toObject());
585 : :
586 [ - + ]: 11637 : if (!canonicalize_specifier(cx, &specifier))
587 : 0 : return nullptr;
588 : :
589 : 11637 : JS::RootedValueArray<2> args(cx);
590 : 11637 : args[0].set(importing_module_priv);
591 : 11637 : args[1].setString(specifier);
592 : :
593 : 23274 : gjs_debug(GJS_DEBUG_IMPORTER,
594 : : "Module resolve hook for module %s (relative to %s), global %p",
595 : 23274 : gjs_debug_string(specifier).c_str(),
596 : 23274 : gjs_debug_value(importing_module_priv).c_str(), global.get());
597 : :
598 : 11637 : JS::RootedValue result(cx);
599 [ + + ]: 11637 : if (!JS::Call(cx, loader, "moduleResolveHook", args, &result))
600 : 4 : return nullptr;
601 : :
602 : 11633 : g_assert(result.isObject() && "resolve hook failed to return an object!");
603 : 11633 : return &result.toObject();
604 : 11637 : }
605 : :
606 : : // Call JS::FinishDynamicModuleImport() with the values stashed in the function.
607 : : // Can fail in JS::FinishDynamicModuleImport(), but will assert if anything
608 : : // fails in fetching the stashed values, since that would be a serious GJS bug.
609 : : GJS_JSAPI_RETURN_CONVENTION
610 : 43 : static bool finish_import(JSContext* cx, JS::HandleObject evaluation_promise,
611 : : const JS::CallArgs& args) {
612 : 43 : GjsContextPrivate* priv = GjsContextPrivate::from_cx(cx);
613 : 43 : priv->main_loop_release();
614 : :
615 : 43 : JS::Value callback_priv = js::GetFunctionNativeReserved(&args.callee(), 0);
616 : 43 : g_assert(callback_priv.isObject() && "Wrong private value");
617 : 43 : JS::RootedObject callback_data(cx, &callback_priv.toObject());
618 : :
619 : 43 : JS::RootedValue importing_module_priv(cx);
620 : 43 : JS::RootedValue v_module_request(cx);
621 : 43 : JS::RootedValue v_internal_promise(cx);
622 : : bool ok GJS_USED_ASSERT =
623 [ + - ]: 86 : JS_GetProperty(cx, callback_data, "priv", &importing_module_priv) &&
624 [ + - + - ]: 86 : JS_GetProperty(cx, callback_data, "promise", &v_internal_promise) &&
625 : 43 : JS_GetProperty(cx, callback_data, "module_request", &v_module_request);
626 : 43 : g_assert(ok && "Wrong properties on private value");
627 : :
628 : 43 : g_assert(v_module_request.isObject() && "Wrong type for module request");
629 : 43 : g_assert(v_internal_promise.isObject() && "Wrong type for promise");
630 : :
631 : 43 : JS::RootedObject module_request(cx, &v_module_request.toObject());
632 : 43 : JS::RootedObject internal_promise(cx, &v_internal_promise.toObject());
633 : :
634 : 43 : args.rval().setUndefined();
635 : :
636 : 43 : return JS::FinishDynamicModuleImport(cx, evaluation_promise,
637 : : importing_module_priv, module_request,
638 : 43 : internal_promise);
639 : 43 : }
640 : :
641 : : // Failing a JSAPI function may result either in an exception pending on the
642 : : // context, in which case we must call JS::FinishDynamicModuleImport() to reject
643 : : // the internal promise; or in an uncatchable exception such as OOM, in which
644 : : // case we must not call JS::FinishDynamicModuleImport().
645 : : GJS_JSAPI_RETURN_CONVENTION
646 : 2 : static bool fail_import(JSContext* cx, const JS::CallArgs& args) {
647 [ + - ]: 2 : if (JS_IsExceptionPending(cx))
648 : 2 : return finish_import(cx, nullptr, args);
649 : 0 : return false;
650 : : }
651 : :
652 : : GJS_JSAPI_RETURN_CONVENTION
653 : 6 : static bool import_rejected(JSContext* cx, unsigned argc, JS::Value* vp) {
654 : 6 : JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
655 : :
656 : 6 : gjs_debug(GJS_DEBUG_IMPORTER, "Async import promise rejected");
657 : :
658 : : // Throw the value that the promise is rejected with, so that
659 : : // FinishDynamicModuleImport will reject the internal_promise with it.
660 : 6 : JS_SetPendingException(cx, args.get(0),
661 : : JS::ExceptionStackBehavior::DoNotCapture);
662 : :
663 : 6 : return finish_import(cx, nullptr, args);
664 : : }
665 : :
666 : : GJS_JSAPI_RETURN_CONVENTION
667 : 37 : static bool import_resolved(JSContext* cx, unsigned argc, JS::Value* vp) {
668 : 37 : JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
669 : :
670 : 37 : gjs_debug(GJS_DEBUG_IMPORTER, "Async import promise resolved");
671 : :
672 : 37 : Gjs::AutoMainRealm ar{cx};
673 : :
674 : 37 : g_assert(args[0].isObject());
675 : 37 : JS::RootedObject module(cx, &args[0].toObject());
676 : :
677 : 37 : JS::RootedValue evaluation_promise(cx);
678 [ + + ]: 72 : if (!JS::ModuleLink(cx, module) ||
679 [ - + + + ]: 72 : !JS::ModuleEvaluate(cx, module, &evaluation_promise))
680 : 2 : return fail_import(cx, args);
681 : :
682 : 35 : g_assert(evaluation_promise.isObject() &&
683 : : "got weird value from JS::ModuleEvaluate");
684 : : JS::RootedObject evaluation_promise_object(cx,
685 : 35 : &evaluation_promise.toObject());
686 : 35 : return finish_import(cx, evaluation_promise_object, args);
687 : 37 : }
688 : :
689 : 43 : bool gjs_dynamic_module_resolve(JSContext* cx,
690 : : JS::HandleValue importing_module_priv,
691 : : JS::HandleObject module_request,
692 : : JS::HandleObject internal_promise) {
693 : 43 : g_assert(gjs_global_is_type(cx, GjsGlobalType::DEFAULT) &&
694 : : "gjs_dynamic_module_resolve can only be called from the default "
695 : : "global.");
696 : :
697 : 43 : JS::RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
698 : 43 : g_assert(global && "gjs_dynamic_module_resolve must be in a realm");
699 : :
700 : : JS::RootedValue v_loader(
701 : 43 : cx, gjs_get_global_slot(global, GjsGlobalSlot::MODULE_LOADER));
702 : 43 : g_assert(v_loader.isObject());
703 : 43 : JS::RootedObject loader(cx, &v_loader.toObject());
704 : : JS::RootedString specifier(
705 : 43 : cx, JS::GetModuleRequestSpecifier(cx, module_request));
706 : :
707 [ - + ]: 43 : if (!canonicalize_specifier(cx, &specifier))
708 : 0 : return false;
709 : :
710 : 43 : JS::RootedObject callback_data(cx, JS_NewPlainObject(cx));
711 : 43 : if (!callback_data ||
712 [ + - ]: 43 : !JS_DefineProperty(cx, callback_data, "module_request", module_request,
713 : 43 : JSPROP_PERMANENT) ||
714 [ + - ]: 43 : !JS_DefineProperty(cx, callback_data, "promise", internal_promise,
715 [ + - ]: 86 : JSPROP_PERMANENT) ||
716 [ - + - + ]: 86 : !JS_DefineProperty(cx, callback_data, "priv", importing_module_priv,
717 : : JSPROP_PERMANENT))
718 : 0 : return false;
719 : :
720 [ + + ]: 43 : if (importing_module_priv.isObject()) {
721 : 40 : gjs_debug(GJS_DEBUG_IMPORTER,
722 : : "Async module resolve hook for module %s (relative to %p), "
723 : : "global %p",
724 : 80 : gjs_debug_string(specifier).c_str(),
725 : 40 : &importing_module_priv.toObject(), global.get());
726 : : } else {
727 : 3 : gjs_debug(GJS_DEBUG_IMPORTER,
728 : : "Async module resolve hook for module %s (unknown path), "
729 : : "global %p",
730 : 6 : gjs_debug_string(specifier).c_str(), global.get());
731 : : }
732 : :
733 : 43 : JS::RootedValueArray<2> args(cx);
734 : 43 : args[0].set(importing_module_priv);
735 : 43 : args[1].setString(specifier);
736 : :
737 : 43 : JS::RootedValue result(cx);
738 [ - + ]: 43 : if (!JS::Call(cx, loader, "moduleResolveAsyncHook", args, &result))
739 : 0 : return JS::FinishDynamicModuleImport(cx, nullptr, importing_module_priv,
740 : 0 : module_request, internal_promise);
741 : :
742 : : // Release in finish_import
743 : 43 : GjsContextPrivate* priv = GjsContextPrivate::from_cx(cx);
744 : 43 : priv->main_loop_hold();
745 : :
746 : : JS::RootedObject resolved(
747 : 43 : cx, JS_GetFunctionObject(js::NewFunctionWithReserved(
748 : 43 : cx, import_resolved, 1, 0, "async import resolved")));
749 [ - + ]: 43 : if (!resolved)
750 : 0 : return false;
751 : : JS::RootedObject rejected(
752 : 43 : cx, JS_GetFunctionObject(js::NewFunctionWithReserved(
753 : 43 : cx, import_rejected, 1, 0, "async import rejected")));
754 [ - + ]: 43 : if (!rejected)
755 : 0 : return false;
756 : 43 : js::SetFunctionNativeReserved(resolved, 0, JS::ObjectValue(*callback_data));
757 : 43 : js::SetFunctionNativeReserved(rejected, 0, JS::ObjectValue(*callback_data));
758 : :
759 : 43 : JS::RootedObject promise(cx, &result.toObject());
760 : :
761 : : // Calling JS::FinishDynamicModuleImport() at the end of the resolve and
762 : : // reject handlers will also call the module resolve hook. The module will
763 : : // already have been resolved, but that is how SpiderMonkey obtains the
764 : : // module object.
765 : 43 : return JS::AddPromiseReactions(cx, promise, resolved, rejected);
766 : 43 : }
|