@@ -216,6 +216,50 @@ static bool seal_import(JSContext* cx, JS::HandleObject obj, JS::HandleId id,
216216 return true ;
217217}
218218
219+ /* Clear a cached module so it can be re-imported.
220+ * This allows extension/applet reloading without restarting.
221+ */
222+ GJS_JSAPI_RETURN_CONVENTION
223+ static bool
224+ importer_clear_cache (JSContext *cx,
225+ unsigned argc,
226+ JS::Value *vp)
227+ {
228+ GJS_GET_THIS (cx, argc, vp, args, importer);
229+
230+ if (args.length () < 1 || !args[0 ].isString ()) {
231+ gjs_throw (cx, " clearCache requires a string argument" );
232+ return false ;
233+ }
234+
235+ JS::RootedString name_str (cx, args[0 ].toString ());
236+ JS::UniqueChars name (JS_EncodeStringToUTF8 (cx, name_str));
237+ if (!name)
238+ return false ;
239+
240+ // Check if the property exists
241+ bool has_prop;
242+ if (!JS_HasOwnProperty (cx, importer, name.get (), &has_prop))
243+ return false ;
244+
245+ if (!has_prop) {
246+ args.rval ().setBoolean (false );
247+ return true ;
248+ }
249+
250+ gjs_debug (GJS_DEBUG_IMPORTER,
251+ " Clearing cached import '%s'" ,
252+ name.get ());
253+
254+ // Delete the cached module property
255+ JS::ObjectOpResult result;
256+ if (!JS_DeleteProperty (cx, importer, name.get (), result))
257+ return false ;
258+
259+ args.rval ().setBoolean (result.succeed ());
260+ return true ;
261+ }
262+
219263/* An import failed. Delete the property pointing to the import from the parent
220264 * namespace. In complicated situations this might not be sufficient to get us
221265 * fully back to a sane state. If:
@@ -412,9 +456,23 @@ static bool attempt_import(JSContext* cx, JS::HandleObject obj,
412456
413457 Gjs::AutoChar full_path{g_file_get_parse_name (file)};
414458
415- return define_meta_properties (cx, module_obj, full_path, module_name,
416- obj) &&
417- seal_import (cx, obj, module_id, module_name);
459+ if (!define_meta_properties (cx, module_obj, full_path, module_name, obj))
460+ return false ;
461+
462+ // Only seal imports on the root importer (where parent is null).
463+ // Sub-importers (like xlet importers) remain unsealed so their modules
464+ // can be cleared from cache and re-imported for xlet reloading.
465+ const GjsAtoms& atoms = GjsContextPrivate::atoms (cx);
466+ JS::RootedValue parent (cx);
467+ if (!JS_GetPropertyById (cx, obj, atoms.parent_module (), &parent))
468+ return false ;
469+
470+ if (parent.isNull ()) {
471+ if (!seal_import (cx, obj, module_id, module_name))
472+ return false ;
473+ }
474+
475+ return true ;
418476}
419477
420478GJS_JSAPI_RETURN_CONVENTION
@@ -696,7 +754,7 @@ static bool importer_resolve(JSContext* cx, JS::HandleObject obj,
696754
697755 const GjsAtoms& atoms = GjsContextPrivate::atoms (cx);
698756 if (id == atoms.module_init () || id == atoms.to_string () ||
699- id == atoms.value_of ()) {
757+ id == atoms.value_of () || id == atoms. clear_cache () ) {
700758 *resolved = false ;
701759 return true ;
702760 }
@@ -736,6 +794,7 @@ static const JSPropertySpec gjs_importer_proto_props[] = {
736794
737795JSFunctionSpec gjs_importer_proto_funcs[] = {
738796 JS_FN (" toString" , importer_to_string, 0 , 0 ),
797+ JS_FN (" clearCache" , importer_clear_cache, 1 , 0 ),
739798 JS_FS_END};
740799
741800[[nodiscard]]
0 commit comments