diff options
author | Philip Chimento <philip.chimento@gmail.com> | 2023-03-05 18:26:25 +0000 |
---|---|---|
committer | Philip Chimento <philip.chimento@gmail.com> | 2023-03-05 18:26:25 +0000 |
commit | ad36414e00d4d9c1b0160e1ec3041fd1607dcd77 (patch) | |
tree | b9ece2e1fda25100814480aaea1c21cf0701bdd4 | |
parent | 12de7257e2923c5285614d51f06d751ebc0c60a8 (diff) | |
parent | 52adc35e38b2475fd6468982bb9ed31d50304f36 (diff) | |
download | gjs-ad36414e00d4d9c1b0160e1ec3041fd1607dcd77.tar.gz |
Merge branch 'february-maintenance' into 'master'
February maintenance
See merge request GNOME/gjs!828
29 files changed, 188 insertions, 80 deletions
diff --git a/gi/repo.cpp b/gi/repo.cpp index 27e78b5e..20f445df 100644 --- a/gi/repo.cpp +++ b/gi/repo.cpp @@ -126,16 +126,16 @@ static bool resolve_namespace_object(JSContext* context, JS::RootedObject gi_namespace(context, gjs_create_ns(context, ns_name.get())); + JS::RootedValue override(context); + if (!lookup_override_function(context, ns_id, &override)) + return false; + /* Define the property early, to avoid reentrancy issues if the override module looks for namespaces that import this */ if (!JS_DefinePropertyById(context, repo_obj, ns_id, gi_namespace, GJS_MODULE_PROP_FLAGS)) return false; - JS::RootedValue override(context); - if (!lookup_override_function(context, ns_id, &override)) - return false; - JS::RootedValue result(context); if (!override.isUndefined() && !JS_CallFunctionValue (context, gi_namespace, /* thisp */ @@ -556,9 +556,12 @@ lookup_override_function(JSContext *cx, goto fail; } + // If the override module is present, it must have a callable _init(). An + // override module without _init() is probably unintentional. (function + // being undefined means there was no override module.) if (!gjs_object_require_property(cx, module, "override module", atoms.init(), function) || - !function.isObjectOrNull()) { + !function.isObject() || !JS::IsCallable(&function.toObject())) { gjs_throw(cx, "Unexpected value for _init in overrides module"); goto fail; } diff --git a/gjs/console.cpp b/gjs/console.cpp index 3d53ade0..c2e15789 100644 --- a/gjs/console.cpp +++ b/gjs/console.cpp @@ -355,7 +355,7 @@ int main(int argc, char** argv) { if (coverage_prefixes) gjs_coverage_enable(); - GjsAutoUnref<GjsContext> js_context(static_cast<GjsContext*>(g_object_new( + GjsAutoUnref<GjsContext> js_context(GJS_CONTEXT(g_object_new( GJS_TYPE_CONTEXT, "search-path", include_path.get(), "program-name", program_name, "program-path", program_path.get(), "profiler-enabled", enable_profiler, "exec-as-module", exec_as_module, nullptr))); diff --git a/gjs/context-private.h b/gjs/context-private.h index d8b7b85d..147feae4 100644 --- a/gjs/context-private.h +++ b/gjs/context-private.h @@ -17,7 +17,6 @@ #include <utility> // for pair #include <vector> -#include <gio/gio.h> #include <glib-object.h> #include <glib.h> @@ -248,13 +247,11 @@ class GjsContextPrivate : public JS::JobQueue { JS::HandleObject allocation_site, JS::HandleObject incumbent_global) override; void runJobs(JSContext* cx) override; - void runJobs(JSContext* cx, GCancellable* cancellable); [[nodiscard]] bool empty() const override { return m_job_queue.empty(); } js::UniquePtr<JS::JobQueue::SavedJobQueue> saveJobQueue( JSContext* cx) override; - GJS_JSAPI_RETURN_CONVENTION bool run_jobs_fallible( - GCancellable* cancellable = nullptr); + GJS_JSAPI_RETURN_CONVENTION bool run_jobs_fallible(); void register_unhandled_promise_rejection(uint64_t id, GjsAutoChar&& stack); void unregister_unhandled_promise_rejection(uint64_t id); void warn_about_unhandled_promise_rejections(); diff --git a/gjs/context.cpp b/gjs/context.cpp index 94a45c8b..59ac8afd 100644 --- a/gjs/context.cpp +++ b/gjs/context.cpp @@ -531,6 +531,12 @@ static bool on_context_module_rejected_log_exception(JSContext* cx, unsigned argc, JS::Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + JSString* id = + JS_GetFunctionDisplayId(JS_GetObjectFunction(&args.callee())); + gjs_debug(GJS_DEBUG_IMPORTER, "Module evaluation promise rejected: %s", + gjs_debug_string(id).c_str()); + JS::HandleValue error = args.get(0); GjsContextPrivate* gjs_cx = GjsContextPrivate::from_cx(cx); @@ -547,6 +553,12 @@ static bool on_context_module_rejected_log_exception(JSContext* cx, static bool on_context_module_resolved(JSContext* cx, unsigned argc, JS::Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + JSString* id = + JS_GetFunctionDisplayId(JS_GetObjectFunction(&args.callee())); + gjs_debug(GJS_DEBUG_IMPORTER, "Module evaluation promise resolved: %s", + gjs_debug_string(id).c_str()); + args.rval().setUndefined(); GjsContextPrivate::from_cx(cx)->main_loop_release(); @@ -565,12 +577,12 @@ static bool add_promise_reactions(JSContext* cx, JS::HandleValue promise, JS::RootedFunction on_rejected( cx, - js::NewFunctionWithReserved(cx, reject, 1, 0, resolved_tag.c_str())); + js::NewFunctionWithReserved(cx, reject, 1, 0, rejected_tag.c_str())); if (!on_rejected) return false; JS::RootedFunction on_resolved( cx, - js::NewFunctionWithReserved(cx, resolve, 1, 0, rejected_tag.c_str())); + js::NewFunctionWithReserved(cx, resolve, 1, 0, resolved_tag.c_str())); if (!on_resolved) return false; @@ -606,6 +618,12 @@ static void load_context_module(JSContext* cx, const char* uri, [](JSContext* cx, unsigned argc, JS::Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + JSString* id = + JS_GetFunctionDisplayId(JS_GetObjectFunction(&args.callee())); + gjs_debug(GJS_DEBUG_IMPORTER, + "Module evaluation promise rejected: %s", + gjs_debug_string(id).c_str()); + JS::HandleValue error = args.get(0); // Abort because this module is required. gjs_log_exception_full(cx, error, nullptr, G_LOG_LEVEL_ERROR); @@ -613,7 +631,7 @@ static void load_context_module(JSContext* cx, const char* uri, GjsContextPrivate::from_cx(cx)->main_loop_release(); return false; }, - "context"); + debug_identifier); if (!ok) { gjs_log_exception(cx); @@ -988,15 +1006,10 @@ bool GjsContextPrivate::should_exit(uint8_t* exit_code_p) const { return m_should_exit; } -void GjsContextPrivate::start_draining_job_queue(void) { - gjs_debug(GJS_DEBUG_CONTEXT, "Starting promise job dispatcher"); - m_dispatcher.start(); -} +void GjsContextPrivate::start_draining_job_queue(void) { m_dispatcher.start(); } void GjsContextPrivate::stop_draining_job_queue(void) { m_draining_job_queue = false; - - gjs_debug(GJS_DEBUG_CONTEXT, "Stopping promise job dispatcher"); m_dispatcher.stop(); } @@ -1015,7 +1028,7 @@ bool GjsContextPrivate::enqueuePromiseJob(JSContext* cx [[maybe_unused]], g_assert(cx == m_cx); g_assert(from_cx(cx) == this); - gjs_debug(GJS_DEBUG_CONTEXT, + gjs_debug(GJS_DEBUG_MAINLOOP, "Enqueue job %s, promise=%s, allocation site=%s", gjs_debug_object(job).c_str(), gjs_debug_object(promise).c_str(), gjs_debug_object(allocation_site).c_str()); @@ -1032,12 +1045,10 @@ bool GjsContextPrivate::enqueuePromiseJob(JSContext* cx [[maybe_unused]], // Override of JobQueue::runJobs(). Called by js::RunJobs(), and when execution // of the job queue was interrupted by the debugger and is resuming. -void GjsContextPrivate::runJobs(JSContext* cx) { runJobs(cx, nullptr); } - -void GjsContextPrivate::runJobs(JSContext* cx, GCancellable* cancellable) { +void GjsContextPrivate::runJobs(JSContext* cx) { g_assert(cx == m_cx); g_assert(from_cx(cx) == this); - if (!run_jobs_fallible(cancellable)) + if (!run_jobs_fallible()) gjs_log_exception(cx); } @@ -1053,7 +1064,7 @@ void GjsContextPrivate::runJobs(JSContext* cx, GCancellable* cancellable) { * Returns: false if one of the jobs threw an uncatchable exception; * otherwise true. */ -bool GjsContextPrivate::run_jobs_fallible(GCancellable* cancellable) { +bool GjsContextPrivate::run_jobs_fallible() { bool retval = true; if (m_draining_job_queue || m_should_exit) @@ -1070,8 +1081,11 @@ bool GjsContextPrivate::run_jobs_fallible(GCancellable* cancellable) { * it's crucial to recheck the queue length during each iteration. */ for (size_t ix = 0; ix < m_job_queue.length(); ix++) { /* A previous job might have set this flag. e.g., System.exit(). */ - if (m_should_exit || g_cancellable_is_cancelled(cancellable)) + if (m_should_exit || !m_dispatcher.is_running()) { + gjs_debug(GJS_DEBUG_MAINLOOP, "Stopping jobs because of %s", + m_should_exit ? "exit" : "main loop cancel"); break; + } job = m_job_queue[ix]; @@ -1085,7 +1099,7 @@ bool GjsContextPrivate::run_jobs_fallible(GCancellable* cancellable) { m_job_queue[ix] = nullptr; { JSAutoRealm ar(m_cx, job); - gjs_debug(GJS_DEBUG_CONTEXT, "handling job %s", + gjs_debug(GJS_DEBUG_MAINLOOP, "handling job %zu, %s", ix, gjs_debug_object(job).c_str()); if (!JS::Call(m_cx, JS::UndefinedHandleValue, job, args, &rval)) { /* Uncatchable exception - return false so that @@ -1104,6 +1118,7 @@ bool GjsContextPrivate::run_jobs_fallible(GCancellable* cancellable) { gjs_log_exception_uncaught(m_cx); } } + gjs_debug(GJS_DEBUG_MAINLOOP, "Completed job %zu", ix); } m_draining_job_queue = false; @@ -1380,6 +1395,7 @@ bool GjsContextPrivate::set_main_loop_hook(JSObject* callable) { bool GjsContextPrivate::run_main_loop_hook() { JS::RootedObject hook(m_cx, m_main_loop_hook.get()); m_main_loop_hook = nullptr; + gjs_debug(GJS_DEBUG_MAINLOOP, "Running and clearing main loop hook"); JS::RootedValue ignored_rval(m_cx); return JS::Call(m_cx, JS::NullHandleValue, hook, JS::HandleValueArray::empty(), &ignored_rval); @@ -1490,7 +1506,7 @@ bool GjsContextPrivate::eval_module(const char* identifier, ok = add_promise_reactions( m_cx, evaluation_promise, on_context_module_resolved, - on_context_module_rejected_log_exception, "context"); + on_context_module_rejected_log_exception, identifier); } bool exiting = false; diff --git a/gjs/mainloop.cpp b/gjs/mainloop.cpp index b69d45a8..3ad3f5c7 100644 --- a/gjs/mainloop.cpp +++ b/gjs/mainloop.cpp @@ -20,6 +20,7 @@ bool MainLoop::spin(GjsContextPrivate* gjs) { if (gjs->should_exit(nullptr)) { // Return false to indicate the loop is exiting due to an exit call, // the queue is likely not empty + debug("Not spinning loop because System.exit called"); exit(); return false; } @@ -27,6 +28,7 @@ bool MainLoop::spin(GjsContextPrivate* gjs) { GjsAutoPointer<GMainContext, GMainContext, g_main_context_unref> main_context(g_main_context_ref_thread_default()); + debug("Spinning loop until released or hook cleared"); do { bool blocking = can_block(); @@ -36,6 +38,7 @@ bool MainLoop::spin(GjsContextPrivate* gjs) { // If System.exit() has not been called if (gjs->should_exit(nullptr)) { + debug("Stopped spinning loop because System.exit called"); exit(); return false; } diff --git a/gjs/mainloop.h b/gjs/mainloop.h index f374060c..a228d761 100644 --- a/gjs/mainloop.h +++ b/gjs/mainloop.h @@ -8,6 +8,8 @@ #include <glib.h> +#include "util/log.h" + class GjsContextPrivate; namespace Gjs { @@ -20,6 +22,10 @@ class MainLoop { grefcount m_hold_count; bool m_exiting; + void debug(const char* msg) { + gjs_debug(GJS_DEBUG_MAINLOOP, "Main loop instance %p: %s", this, msg); + } + [[nodiscard]] bool can_block() { // Don't block if exiting if (m_exiting) @@ -51,6 +57,7 @@ class MainLoop { if (m_exiting) return; + debug("hold"); g_ref_count_inc(&m_hold_count); } @@ -59,6 +66,7 @@ class MainLoop { if (m_exiting) return; + debug("release"); bool zero [[maybe_unused]] = g_ref_count_dec(&m_hold_count); g_assert(!zero && "main loop released too many times"); } diff --git a/gjs/profiler.cpp b/gjs/profiler.cpp index 39c19621..1a5560f7 100644 --- a/gjs/profiler.cpp +++ b/gjs/profiler.cpp @@ -653,7 +653,7 @@ gjs_profiler_stop(GjsProfiler *self) static gboolean gjs_profiler_sigusr2(void *data) { - auto context = static_cast<GjsContext *>(data); + GjsContext* context = GJS_CONTEXT(data); GjsProfiler *current_profiler = gjs_context_get_profiler(context); if (current_profiler) { diff --git a/gjs/promise.cpp b/gjs/promise.cpp index 39939aa9..f248b019 100644 --- a/gjs/promise.cpp +++ b/gjs/promise.cpp @@ -6,6 +6,8 @@ #include <stddef.h> // for size_t +#include <string> + #include <gio/gio.h> #include <glib-object.h> @@ -22,6 +24,7 @@ #include "gjs/jsapi-util.h" #include "gjs/macros.h" #include "gjs/promise.h" +#include "util/log.h" /** * promise.cpp - This file implements a custom GSource, PromiseJobQueueSource, @@ -79,12 +82,8 @@ class PromiseJobDispatcher::Source : public GSource { // next one to execute. (it will starve the other sources) g_source_set_ready_time(this, -1); - // A reference to the current cancellable is needed in case any - // jobs reset PromiseJobDispatcher and thus replace the cancellable. - GjsAutoUnref<GCancellable> cancellable(m_cancellable, - GjsAutoTakeOwnership{}); // Drain the job queue. - m_gjs->runJobs(m_gjs->context(), cancellable); + m_gjs->runJobs(m_gjs->context()); return G_SOURCE_CONTINUE; } @@ -135,6 +134,8 @@ class PromiseJobDispatcher::Source : public GSource { if (!g_cancellable_is_cancelled(m_cancellable)) return; + gjs_debug(GJS_DEBUG_MAINLOOP, "Uncancelling promise job dispatcher"); + if (is_running()) g_source_remove_child_source(this, m_cancellable_source); else @@ -179,10 +180,14 @@ void PromiseJobDispatcher::start() { if (is_running()) return; + gjs_debug(GJS_DEBUG_MAINLOOP, "Starting promise job dispatcher"); g_source_attach(m_source.get(), m_main_context); } -void PromiseJobDispatcher::stop() { m_source->cancel(); } +void PromiseJobDispatcher::stop() { + gjs_debug(GJS_DEBUG_MAINLOOP, "Stopping promise job dispatcher"); + m_source->cancel(); +} }; // namespace Gjs @@ -212,6 +217,9 @@ bool set_main_loop_hook(JSContext* cx, unsigned argc, JS::Value* vp) { return false; } + gjs_debug(GJS_DEBUG_MAINLOOP, "Set main loop hook to %s", + gjs_debug_object(callback).c_str()); + GjsContextPrivate* priv = GjsContextPrivate::from_cx(cx); if (!priv->set_main_loop_hook(callback)) { gjs_throw( diff --git a/gjs/stack.cpp b/gjs/stack.cpp index b3d0d9c3..3292eae4 100644 --- a/gjs/stack.cpp +++ b/gjs/stack.cpp @@ -31,7 +31,7 @@ gjs_dumpstack(void) GList *iter; for (iter = contexts; iter; iter = iter->next) { - GjsAutoUnref<GjsContext> context(static_cast<GjsContext*>(iter->data)); + GjsAutoUnref<GjsContext> context(GJS_CONTEXT(iter->data)); gjs_context_print_stack_stderr(context); } } diff --git a/installed-tests/js/jsunit.gresources.xml b/installed-tests/js/jsunit.gresources.xml index 209274fc..75c54c90 100644 --- a/installed-tests/js/jsunit.gresources.xml +++ b/installed-tests/js/jsunit.gresources.xml @@ -13,6 +13,10 @@ <file>modules/badOverrides/Gio.js</file> <file>modules/badOverrides/Regress.js</file> <file>modules/badOverrides/WarnLib.js</file> + <file>modules/badOverrides2/GIMarshallingTests.js</file> + <file>modules/badOverrides2/Gio.js</file> + <file>modules/badOverrides2/Regress.js</file> + <file>modules/badOverrides2/WarnLib.js</file> <file>modules/subBadInit/__init__.js</file> <file>modules/subErrorInit/__init__.js</file> <file>modules/data.txt</file> diff --git a/installed-tests/js/meson.build b/installed-tests/js/meson.build index 5520a19d..6db887d2 100644 --- a/installed-tests/js/meson.build +++ b/installed-tests/js/meson.build @@ -127,6 +127,7 @@ jasmine_tests = [ 'GObjectValue', 'GTypeClass', 'Importer', + 'Importer2', 'Introspection', 'Lang', 'LegacyByteArray', diff --git a/installed-tests/js/minijasmine.js b/installed-tests/js/minijasmine.js index 36f953bc..cd00f611 100644 --- a/installed-tests/js/minijasmine.js +++ b/installed-tests/js/minijasmine.js @@ -10,7 +10,6 @@ function _filterStack(stack) { return stack.split('\n') .filter(stackLine => stackLine.indexOf('resource:///org/gjs/jsunit') === -1) - .filter(stackLine => stackLine.indexOf('<jasmine-start>') === -1) .join('\n'); } diff --git a/installed-tests/js/modules/badOverrides2/.eslintrc.yml b/installed-tests/js/modules/badOverrides2/.eslintrc.yml new file mode 100644 index 00000000..b1c10f5d --- /dev/null +++ b/installed-tests/js/modules/badOverrides2/.eslintrc.yml @@ -0,0 +1,8 @@ +--- +# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later +# SPDX-FileCopyrightText: 2018 Philip Chimento <philip.chimento@gmail.com> +rules: + no-throw-literal: 'off' # these are intended to be bad code + no-unused-vars: + - error + - varsIgnorePattern: ^_init$ diff --git a/installed-tests/js/modules/badOverrides2/GIMarshallingTests.js b/installed-tests/js/modules/badOverrides2/GIMarshallingTests.js new file mode 100644 index 00000000..6cf2dfa9 --- /dev/null +++ b/installed-tests/js/modules/badOverrides2/GIMarshallingTests.js @@ -0,0 +1,5 @@ +// Sabotage the import of imports.gi.GIMarshallingTests! +// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later +// SPDX-FileCopyrightText: 2023 Philip Chimento <philip.chimento@gmail.com> + +var _init = {thingsThatThisObjectIsNot: ['callable']}; diff --git a/installed-tests/js/modules/badOverrides2/Gio.js b/installed-tests/js/modules/badOverrides2/Gio.js new file mode 100644 index 00000000..9758a7c0 --- /dev/null +++ b/installed-tests/js/modules/badOverrides2/Gio.js @@ -0,0 +1,5 @@ +// Sabotage the import of imports.gi.Gio! +// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later +// SPDX-FileCopyrightText: 2023 Philip Chimento <philip.chimento@gmail.com> + +var _init = null; diff --git a/installed-tests/js/modules/badOverrides2/Regress.js b/installed-tests/js/modules/badOverrides2/Regress.js new file mode 100644 index 00000000..e4252f67 --- /dev/null +++ b/installed-tests/js/modules/badOverrides2/Regress.js @@ -0,0 +1,5 @@ +// Sabotage the import of imports.gi.Regress! +// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later +// SPDX-FileCopyrightText: 2023 Philip Chimento <philip.chimento@gmail.com> + +var _init; diff --git a/installed-tests/js/modules/badOverrides2/WarnLib.js b/installed-tests/js/modules/badOverrides2/WarnLib.js new file mode 100644 index 00000000..b2c93b05 --- /dev/null +++ b/installed-tests/js/modules/badOverrides2/WarnLib.js @@ -0,0 +1,3 @@ +// Sabotage the import of imports.gi.WarnLib! +// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later +// SPDX-FileCopyrightText: 2023 Philip Chimento <philip.chimento@gmail.com> diff --git a/installed-tests/js/testESModules.js b/installed-tests/js/testESModules.js index 30db17e4..e727c790 100644 --- a/installed-tests/js/testESModules.js +++ b/installed-tests/js/testESModules.js @@ -27,26 +27,32 @@ describe('ES module imports', function () { it('import with version parameter', function () { expect(gi.require('GObject', '2.0')).toBe(gi.require('GObject')); + expect(imports.gi.versions['GObject']).toBe('2.0'); }); it('import again with other version parameter', function () { expect(() => gi.require('GObject', '1.75')).toThrow(); + expect(imports.gi.versions['GObject']).toBe('2.0'); }); it('import for the first time with wrong version', function () { expect(() => gi.require('Gtk', '1.75')).toThrow(); + expect(imports.gi.versions['Gtk']).not.toBeDefined(); }); it('import with another version after a failed import', function () { expect(gi.require('Gtk', '3.0').toString()).toEqual('[object GIRepositoryNamespace]'); + expect(imports.gi.versions['Gtk']).toBe('3.0'); }); it('import nonexistent module', function () { expect(() => gi.require('PLib')).toThrow(); + expect(imports.gi.versions['PLib']).not.toBeDefined(); }); it('GObject introspection import via URL scheme', function () { expect(Gio.toString()).toEqual('[object GIRepositoryNamespace]'); + expect(imports.gi.versions['Gio']).toBe('2.0'); }); it('import.meta.url', function () { diff --git a/installed-tests/js/testImporter.js b/installed-tests/js/testImporter.js index 8380af20..f03bf634 100644 --- a/installed-tests/js/testImporter.js +++ b/installed-tests/js/testImporter.js @@ -32,7 +32,7 @@ describe('GI importer', function () { expect(() => imports.gi.Regress).toThrow('💩'); }); - it("throws an exception when the overrides _init isn't a function", function () { + it('throws an exception when the overrides _init is a primitive', function () { expect(() => imports.gi.Gio).toThrowError(/_init/); }); }); diff --git a/installed-tests/js/testImporter2.js b/installed-tests/js/testImporter2.js new file mode 100644 index 00000000..b4e71225 --- /dev/null +++ b/installed-tests/js/testImporter2.js @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later +// SPDX-FileCopyrightText: 2023 Philip Chimento <philip.chimento@gmail.com> + +// This test is in a separate file instead of testImporter.js, because it tests +// loading overrides for g-i modules, and in the original file we have literally +// run out of g-i modules to override -- at least, the ones that we can assume +// will be present on any system where GJS is compiled. + +describe('GI importer', function () { + describe('on failure', function () { + // For these tests, we provide special overrides files to sabotage the + // import, at the path resource:///org/gjs/jsunit/modules/badOverrides2. + let oldSearchPath; + beforeAll(function () { + oldSearchPath = imports.overrides.searchPath.slice(); + imports.overrides.searchPath = ['resource:///org/gjs/jsunit/modules/badOverrides2']; + }); + + afterAll(function () { + imports.overrides.searchPath = oldSearchPath; + }); + + it("throws an exception when the overrides _init isn't a function", function () { + expect(() => imports.gi.GIMarshallingTests).toThrowError(/_init/); + }); + + it('throws an exception when the overrides _init is null', function () { + expect(() => imports.gi.Gio).toThrowError(/_init/); + }); + + it('throws an exception when the overrides _init is undefined', function () { + expect(() => imports.gi.Regress).toThrowError(/_init/); + }); + + it('throws an exception when the overrides _init is missing', function () { + expect(() => imports.gi.WarnLib).toThrowError(/_init/); + }); + }); +}); diff --git a/installed-tests/minijasmine.cpp b/installed-tests/minijasmine.cpp index 9d1c65f9..3036fb87 100644 --- a/installed-tests/minijasmine.cpp +++ b/installed-tests/minijasmine.cpp @@ -33,7 +33,6 @@ main(int argc, char **argv) if (argc < 2) g_error("Need a test file"); - /* The fact that this isn't the default is kind of lame... */ g_setenv("GJS_DEBUG_OUTPUT", "stderr", false); setlocale(LC_ALL, ""); diff --git a/modules/core/overrides/GLib.js b/modules/core/overrides/GLib.js index 023cf795..2d51b575 100644 --- a/modules/core/overrides/GLib.js +++ b/modules/core/overrides/GLib.js @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2011 Giovanni Campagna -const ByteArray = imports.byteArray; +const ByteArray = imports._byteArrayNative; const {setMainLoopHook} = imports._promiseNative; let GLib; @@ -100,15 +100,12 @@ function _packVariant(signature, value) { } if (arrayType[0] === 'y') { // special case for array of bytes - let bytes; if (typeof value === 'string') { - let byteArray = ByteArray.fromString(value); - if (byteArray[byteArray.length - 1] !== 0) - byteArray = Uint8Array.of(...byteArray, 0); - bytes = ByteArray.toGBytes(byteArray); - } else { - bytes = new GLib.Bytes(value); + value = ByteArray.fromString(value); + if (value[value.length - 1] !== 0) + value = Uint8Array.of(...value, 0); } + const bytes = new GLib.Bytes(value); return GLib.Variant.new_from_bytes(new GLib.VariantType('ay'), bytes, true); } diff --git a/modules/esm/_encoding/encoding.js b/modules/esm/_encoding/encoding.js index 41261b96..60cc0494 100644 --- a/modules/esm/_encoding/encoding.js +++ b/modules/esm/_encoding/encoding.js @@ -1,8 +1,6 @@ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2021 Evan Welsh <contact@evanwelsh.com> -const Encoding = import.meta.importSync('_encodingNative'); - import {getEncodingFromLabel} from './encodingMap.js'; class TextDecoder { @@ -128,6 +126,7 @@ class TextDecoder { input = new Uint8Array(buffer, byteOffset + 3, byteLength - 3); } + const Encoding = import.meta.importSync('_encodingNative'); return Encoding.decode(input, this._internalEncoding, this.fatal); } } @@ -142,11 +141,13 @@ class TextEncoder { } encode(input = '') { + const Encoding = import.meta.importSync('_encodingNative'); // The TextEncoder specification only allows for UTF-8 encoding. return Encoding.encode(`${input}`, 'utf-8'); } encodeInto(input = '', output = new Uint8Array()) { + const Encoding = import.meta.importSync('_encodingNative'); // The TextEncoder specification only allows for UTF-8 encoding. return Encoding.encodeInto(`${input}`, output); } diff --git a/modules/esm/_timers.js b/modules/esm/_timers.js index 94da6f8f..ca20b71d 100644 --- a/modules/esm/_timers.js +++ b/modules/esm/_timers.js @@ -6,11 +6,6 @@ // Note: implicit coercion with + is used to perform the ToNumber algorithm from // the timers specification -import GLib from 'gi://GLib'; -import GObject from 'gi://GObject'; - -const PromiseNative = import.meta.importSync('_promiseNative'); - /** * @param {number} delay a number value (in milliseconds) */ @@ -52,9 +47,9 @@ function checkThis(thisArg) { * @returns {GLib.Source} */ function createTimeoutSource(timeout, handler) { - const source = GLib.timeout_source_new(timeout); - source.set_priority(GLib.PRIORITY_DEFAULT); - GObject.source_set_closure(source, handler); + const source = imports.gi.GLib.timeout_source_new(timeout); + source.set_priority(imports.gi.GLib.PRIORITY_DEFAULT); + imports.gi.GObject.source_set_closure(source, handler); return source; } @@ -72,13 +67,13 @@ function setTimeout(callback, delay = 0, ...args) { const boundCallback = callback.bind(globalThis, ...args); const source = createTimeoutSource(delay, () => { if (!timeouts.has(source)) - return GLib.SOURCE_REMOVE; + return imports.gi.GLib.SOURCE_REMOVE; boundCallback(); releaseSource(source); - PromiseNative.drainMicrotaskQueue(); + import.meta.importSync('_promiseNative').drainMicrotaskQueue(); - return GLib.SOURCE_REMOVE; + return imports.gi.GLib.SOURCE_REMOVE; }); addSource(source); @@ -98,12 +93,12 @@ function setInterval(callback, delay = 0, ...args) { const boundCallback = callback.bind(globalThis, ...args); const source = createTimeoutSource(delay, () => { if (!timeouts.has(source)) - return GLib.SOURCE_REMOVE; + return imports.gi.GLib.SOURCE_REMOVE; boundCallback(); - PromiseNative.drainMicrotaskQueue(); + import.meta.importSync('_promiseNative').drainMicrotaskQueue(); - return GLib.SOURCE_CONTINUE; + return imports.gi.GLib.SOURCE_CONTINUE; }); addSource(source); diff --git a/modules/esm/console.js b/modules/esm/console.js index 74a34666..1532624d 100644 --- a/modules/esm/console.js +++ b/modules/esm/console.js @@ -1,9 +1,6 @@ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2021 Evan Welsh <contact@evanwelsh.com> -import GLib from 'gi://GLib'; -import GjsPrivate from 'gi://GjsPrivate'; - const DEFAULT_LOG_DOMAIN = 'Gjs-Console'; // A line-by-line implementation of https://console.spec.whatwg.org/. @@ -112,7 +109,7 @@ class Console { */ clear() { this.#groupIndentation = ''; - GjsPrivate.clear_terminal(); + imports.gi.GjsPrivate.clear_terminal(); } /** @@ -268,7 +265,7 @@ class Console { * @returns {void} */ time(label) { - this.#timeLabels[label] = GLib.get_monotonic_time(); + this.#timeLabels[label] = imports.gi.GLib.get_monotonic_time(); } /** @@ -288,7 +285,7 @@ class Console { `No time log found for label: '${label}'.`, ]); } else { - const durationMs = (GLib.get_monotonic_time() - startTime) / 1000; + const durationMs = (imports.gi.GLib.get_monotonic_time() - startTime) / 1000; const concat = `${label}: ${durationMs.toFixed(3)} ms`; data.unshift(concat); @@ -314,7 +311,7 @@ class Console { } else { delete this.#timeLabels[label]; - const durationMs = (GLib.get_monotonic_time() - startTime) / 1000; + const durationMs = (imports.gi.GLib.get_monotonic_time() - startTime) / 1000; const concat = `${label}: ${durationMs.toFixed(3)} ms`; this.#printer('timeEnd', [concat]); @@ -500,6 +497,7 @@ class Console { * @returns {void} */ #printer(logLevel, args, options) { + const GLib = imports.gi.GLib; let severity; switch (logLevel) { diff --git a/modules/esm/gi.js b/modules/esm/gi.js index 126ce7e7..b49cf120 100644 --- a/modules/esm/gi.js +++ b/modules/esm/gi.js @@ -8,17 +8,24 @@ const Gi = { if (namespace === 'versions') throw new Error('Cannot import namespace "versions", use the version parameter of Gi.require to specify versions.'); + let oldVersion = gi.versions[namespace]; if (version !== undefined) gi.versions[namespace] = version; - const module = gi[namespace]; + try { + const module = gi[namespace]; - if (version !== undefined && version !== module.__version__) { - throw new Error(`Version ${module.__version__} of GI module ${ - namespace} already loaded, cannot load version ${version}`); - } + if (version !== undefined && version !== module.__version__) { + throw new Error(`Version ${module.__version__} of GI module ${ + namespace} already loaded, cannot load version ${version}`); + } - return module; + return module; + } catch (error) { + // Roll back change to versions object if import failed + gi.versions[namespace] = oldVersion; + throw error; + } }, }; Object.freeze(Gi); diff --git a/test/gjs-tests.cpp b/test/gjs-tests.cpp index e48734aa..7d46e286 100644 --- a/test/gjs-tests.cpp +++ b/test/gjs-tests.cpp @@ -814,10 +814,8 @@ gjstest_test_func_util_misc_strv_concat_pointers(void) static void gjstest_test_profiler_start_stop(void) { - GjsAutoUnref<GjsContext> context = - static_cast<GjsContext *>(g_object_new(GJS_TYPE_CONTEXT, - "profiler-enabled", TRUE, - nullptr)); + GjsAutoUnref<GjsContext> context = GJS_CONTEXT( + g_object_new(GJS_TYPE_CONTEXT, "profiler-enabled", TRUE, nullptr)); GjsProfiler *profiler = gjs_context_get_profiler(context); gjs_profiler_set_filename(profiler, "dont-conflict-with-other-test.syscap"); diff --git a/util/log.cpp b/util/log.cpp index ce4b02dd..4a910342 100644 --- a/util/log.cpp +++ b/util/log.cpp @@ -53,6 +53,8 @@ static const char* topic_to_prefix(GjsDebugTopic topic) { return "JS CAIRO"; case GJS_DEBUG_KEEP_ALIVE: return "JS KP ALV"; + case GJS_DEBUG_MAINLOOP: + return "JS MAINLOOP"; case GJS_DEBUG_GREPO: return "JS G REPO"; case GJS_DEBUG_GNAMESPACE: @@ -18,6 +18,7 @@ typedef enum { GJS_DEBUG_NATIVE, GJS_DEBUG_CAIRO, GJS_DEBUG_KEEP_ALIVE, + GJS_DEBUG_MAINLOOP, GJS_DEBUG_GREPO, GJS_DEBUG_GNAMESPACE, GJS_DEBUG_GOBJECT, |