Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cjs/atoms.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class JSTracer;
// clang-format off
#define FOR_EACH_ATOM(macro) \
macro(cause, "cause") \
macro(clear_cache, "clearCache") \
macro(code, "code") \
macro(column_number, "columnNumber") \
macro(connect_after, "connect_after") \
Expand Down
67 changes: 63 additions & 4 deletions cjs/importer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,50 @@ seal_import(JSContext *cx,
return true;
}

/* Clear a cached module so it can be re-imported.
* This allows extension/applet reloading without restarting.
*/
GJS_JSAPI_RETURN_CONVENTION
static bool
importer_clear_cache(JSContext *cx,
unsigned argc,
JS::Value *vp)
{
GJS_GET_THIS(cx, argc, vp, args, importer);

if (args.length() < 1 || !args[0].isString()) {
gjs_throw(cx, "clearCache requires a string argument");
return false;
}

JS::RootedString name_str(cx, args[0].toString());
JS::UniqueChars name(JS_EncodeStringToUTF8(cx, name_str));
if (!name)
return false;

// Check if the property exists
bool has_prop;
if (!JS_HasOwnProperty(cx, importer, name.get(), &has_prop))
return false;

if (!has_prop) {
args.rval().setBoolean(false);
return true;
}

gjs_debug(GJS_DEBUG_IMPORTER,
"Clearing cached import '%s'",
name.get());

// Delete the cached module property
JS::ObjectOpResult result;
if (!JS_DeleteProperty(cx, importer, name.get(), result))
return false;

args.rval().setBoolean(result.succeed());
return true;
}

/* An import failed. Delete the property pointing to the import
* from the parent namespace. In complicated situations this might
* not be sufficient to get us fully back to a sane state. If:
Expand Down Expand Up @@ -431,9 +475,23 @@ static bool attempt_import(JSContext* cx, JS::HandleObject obj,

GjsAutoChar full_path = g_file_get_parse_name(file);

return define_meta_properties(cx, module_obj, full_path, module_name,
obj) &&
seal_import(cx, obj, module_id, module_name);
if (!define_meta_properties(cx, module_obj, full_path, module_name, obj))
return false;

// Only seal imports on the root importer (where parent is null).
// Sub-importers (like xlet importers) remain unsealed so their modules
// can be cleared from cache and re-imported for xlet reloading.
const GjsAtoms& atoms = GjsContextPrivate::atoms(cx);
JS::RootedValue parent(cx);
if (!JS_GetPropertyById(cx, obj, atoms.parent_module(), &parent))
return false;

if (parent.isNull()) {
if (!seal_import(cx, obj, module_id, module_name))
return false;
}

return true;
}

GJS_JSAPI_RETURN_CONVENTION
Expand Down Expand Up @@ -728,7 +786,7 @@ importer_resolve(JSContext *context,

const GjsAtoms& atoms = GjsContextPrivate::atoms(context);
if (id == atoms.module_init() || id == atoms.to_string() ||
id == atoms.value_of()) {
id == atoms.value_of() || id == atoms.clear_cache()) {
*resolved = false;
return true;
}
Expand Down Expand Up @@ -768,6 +826,7 @@ static const JSPropertySpec gjs_importer_proto_props[] = {

JSFunctionSpec gjs_importer_proto_funcs[] = {
JS_FN("toString", importer_to_string, 0, 0),
JS_FN("clearCache", importer_clear_cache, 1, 0),
JS_FS_END};

[[nodiscard]] static const std::vector<std::string>& gjs_get_search_path() {
Expand Down
Loading