@@ -228,6 +228,50 @@ seal_import(JSContext *cx,
228228 return true ;
229229}
230230
231+ /* Clear a cached module so it can be re-imported.
232+ * This allows extension/applet reloading without restarting.
233+ */
234+ GJS_JSAPI_RETURN_CONVENTION
235+ static bool
236+ importer_clear_cache (JSContext *cx,
237+ unsigned argc,
238+ JS::Value *vp)
239+ {
240+ GJS_GET_THIS (cx, argc, vp, args, importer);
241+
242+ if (args.length () < 1 || !args[0 ].isString ()) {
243+ gjs_throw (cx, " clearCache requires a string argument" );
244+ return false ;
245+ }
246+
247+ JS::RootedString name_str (cx, args[0 ].toString ());
248+ JS::UniqueChars name (JS_EncodeStringToUTF8 (cx, name_str));
249+ if (!name)
250+ return false ;
251+
252+ // Check if the property exists
253+ bool has_prop;
254+ if (!JS_HasOwnProperty (cx, importer, name.get (), &has_prop))
255+ return false ;
256+
257+ if (!has_prop) {
258+ args.rval ().setBoolean (false );
259+ return true ;
260+ }
261+
262+ gjs_debug (GJS_DEBUG_IMPORTER,
263+ " Clearing cached import '%s'" ,
264+ name.get ());
265+
266+ // Delete the cached module property
267+ JS::ObjectOpResult result;
268+ if (!JS_DeleteProperty (cx, importer, name.get (), result))
269+ return false ;
270+
271+ args.rval ().setBoolean (result.succeed ());
272+ return true ;
273+ }
274+
231275/* An import failed. Delete the property pointing to the import
232276 * from the parent namespace. In complicated situations this might
233277 * not be sufficient to get us fully back to a sane state. If:
@@ -431,9 +475,23 @@ static bool attempt_import(JSContext* cx, JS::HandleObject obj,
431475
432476 GjsAutoChar full_path = g_file_get_parse_name (file);
433477
434- return define_meta_properties (cx, module_obj, full_path, module_name,
435- obj) &&
436- seal_import (cx, obj, module_id, module_name);
478+ if (!define_meta_properties (cx, module_obj, full_path, module_name, obj))
479+ return false ;
480+
481+ // Only seal imports on the root importer (where parent is null).
482+ // Sub-importers (like xlet importers) remain unsealed so their modules
483+ // can be cleared from cache and re-imported for xlet reloading.
484+ const GjsAtoms& atoms = GjsContextPrivate::atoms (cx);
485+ JS::RootedValue parent (cx);
486+ if (!JS_GetPropertyById (cx, obj, atoms.parent_module (), &parent))
487+ return false ;
488+
489+ if (parent.isNull ()) {
490+ if (!seal_import (cx, obj, module_id, module_name))
491+ return false ;
492+ }
493+
494+ return true ;
437495}
438496
439497GJS_JSAPI_RETURN_CONVENTION
@@ -728,7 +786,7 @@ importer_resolve(JSContext *context,
728786
729787 const GjsAtoms& atoms = GjsContextPrivate::atoms (context);
730788 if (id == atoms.module_init () || id == atoms.to_string () ||
731- id == atoms.value_of ()) {
789+ id == atoms.value_of () || id == atoms. clear_cache () ) {
732790 *resolved = false ;
733791 return true ;
734792 }
@@ -768,6 +826,7 @@ static const JSPropertySpec gjs_importer_proto_props[] = {
768826
769827JSFunctionSpec gjs_importer_proto_funcs[] = {
770828 JS_FN (" toString" , importer_to_string, 0 , 0 ),
829+ JS_FN (" clearCache" , importer_clear_cache, 1 , 0 ),
771830 JS_FS_END};
772831
773832[[nodiscard]] static const std::vector<std::string>& gjs_get_search_path () {
0 commit comments