cjs-5.2.0/0000755000175000017500000000000014144444702012437 5ustar jpeisachjpeisachcjs-5.2.0/debian/0000755000175000017500000000000014144444702013661 5ustar jpeisachjpeisachcjs-5.2.0/installed-tests/0000755000175000017500000000000014144444702015556 5ustar jpeisachjpeisachcjs-5.2.0/installed-tests/debugger-test.sh0000755000175000017500000000113314144444702020654 0ustar jpeisachjpeisach#!/bin/sh if test "$GJS_USE_UNINSTALLED_FILES" = "1"; then gjs="$TOP_BUILDDIR/cjs-console" else gjs=cjs-console fi echo 1..1 DEBUGGER_SCRIPT="$1" JS_SCRIPT="$1.js" EXPECTED_OUTPUT="$1.output" THE_DIFF=$("$gjs" -d "$JS_SCRIPT" < "$DEBUGGER_SCRIPT" | sed \ -e "s#$1#$(basename $1)#g" \ -e "s/0x[0-9a-f]\{4,16\}/0xADDR/g" \ | diff -u "$EXPECTED_OUTPUT" -) if test $? -ne 0; then echo "not ok 1 - $1 # command failed" exit 1 fi if test -n "$THE_DIFF"; then echo "not ok 1 - $1" echo "$THE_DIFF" | while read line; do echo "#$line"; done else echo "ok 1 - $1" fi cjs-5.2.0/installed-tests/minijasmine.cpp0000644000175000017500000001141214144444702020564 0ustar jpeisachjpeisach/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* * Copyright (c) 2016 Philip Chimento * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #include // for setlocale, LC_ALL #include // for exit #include #include #include #include #include [[noreturn]] static void bail_out(GjsContext* gjs_context, const char* msg) { g_object_unref(gjs_context); g_print("Bail out! %s\n", msg); exit(1); } int 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, ""); if (g_getenv("GJS_USE_UNINSTALLED_FILES") != NULL) { g_irepository_prepend_search_path(g_getenv("TOP_BUILDDIR")); } else { g_irepository_prepend_search_path(INSTTESTDIR); g_irepository_prepend_library_path(INSTTESTDIR); } const char *coverage_prefix = g_getenv("GJS_UNIT_COVERAGE_PREFIX"); const char *coverage_output_path = g_getenv("GJS_UNIT_COVERAGE_OUTPUT"); const char *search_path[] = { "resource:///org/gjs/jsunit", NULL }; if (coverage_prefix) gjs_coverage_enable(); GjsContext *cx = gjs_context_new_with_search_path((char **)search_path); GjsCoverage *coverage = NULL; if (coverage_prefix) { const char *coverage_prefixes[2] = { coverage_prefix, NULL }; if (!coverage_output_path) { bail_out(cx, "GJS_UNIT_COVERAGE_OUTPUT is required when using GJS_UNIT_COVERAGE_PREFIX"); } GFile *output = g_file_new_for_commandline_arg(coverage_output_path); coverage = gjs_coverage_new(coverage_prefixes, cx, output); g_object_unref(output); } GError *error = NULL; bool success; int code; success = gjs_context_eval(cx, "imports.minijasmine;", -1, "", &code, &error); if (!success) bail_out(cx, error->message); success = gjs_context_eval_file(cx, argv[1], &code, &error); if (!success) bail_out(cx, error->message); /* jasmineEnv.execute() queues up all the tests and runs them * asynchronously. This should start after the main loop starts, otherwise * we will hit the main loop only after several tests have already run. For * consistency we should guarantee that there is a main loop running during * all tests. */ const char *start_suite_script = "const GLib = imports.gi.GLib;\n" "GLib.idle_add(GLib.PRIORITY_DEFAULT, function () {\n" " try {\n" " window._jasmineEnv.execute();\n" " } catch (e) {\n" " print('Bail out! Exception occurred inside Jasmine:', e);\n" " window._jasmineRetval = 1;\n" " window._jasmineMain.quit();\n" " }\n" " return GLib.SOURCE_REMOVE;\n" "});\n" "window._jasmineMain.run();\n" "window._jasmineRetval;"; success = gjs_context_eval(cx, start_suite_script, -1, "", &code, &error); if (!success) bail_out(cx, error->message); if (code != 0) g_print("# Test script failed; see test log for assertions\n"); if (coverage) { gjs_coverage_write_statistics(coverage); g_clear_object(&coverage); } gjs_memory_report("before destroying context", false); g_object_unref(cx); gjs_memory_report("after destroying context", true); /* For TAP, should actually be return 0; as a nonzero return code would * indicate an error in the test harness. But that would be quite silly * when running the tests outside of the TAP driver. */ return code; } cjs-5.2.0/installed-tests/scripts/0000755000175000017500000000000014144444702017245 5ustar jpeisachjpeisachcjs-5.2.0/installed-tests/scripts/common.sh0000755000175000017500000000161714144444702021101 0ustar jpeisachjpeisach#!/bin/sh if test "$GJS_USE_UNINSTALLED_FILES" = "1"; then gjs="$TOP_BUILDDIR/cjs-console" else gjs="cjs-console" fi # Avoid interference in the profiler tests from stray environment variable unset GJS_ENABLE_PROFILER total=0 report () { exit_code=$? total=$((total + 1)) if test $exit_code -eq 0; then echo "ok $total - $1" else echo "not ok $total - $1 [EXIT CODE: $exit_code]" fi } report_timeout () { exit_code=$? total=$((total + 1)) if test $exit_code -eq 0 -o $exit_code -eq 124; then echo "ok $total - $1" else echo "not ok $total - $1 [EXIT CODE: $exit_code]" fi } report_xfail () { exit_code=$? total=$((total + 1)) if test $exit_code -ne 0; then echo "ok $total - $1" else echo "not ok $total - $1" fi } skip () { total=$((total + 1)) echo "ok $total - $1 # SKIP $2" } cjs-5.2.0/installed-tests/scripts/testExamples.sh0000755000175000017500000000167514144444702022273 0ustar jpeisachjpeisach#!/bin/bash DIR="$( cd "$( dirname "${0}" )" && pwd )" source "${DIR}"/common.sh # Run the examples $gjs examples/gio-cat.js meson.build report "run the gio-cat.js example" if [[ -n "${ENABLE_GTK}" ]]; then export graphical_gjs="xvfb-run -a dbus-run-session -- $gjs" eval timeout 5s $graphical_gjs examples/calc.js report_timeout "run the calc.js example" eval timeout 5s $graphical_gjs examples/gtk.js report_timeout "run the gtk.js example" eval timeout 5s $graphical_gjs examples/gtk-application.js report_timeout "run the gtk-application.js example" eval timeout 5s $graphical_gjs examples/gettext.js report_timeout "run the gettext.js example" else skip "run the calc.js example" "running without GTK" skip "run the gtk.js example" "running without GTK" skip "run the gtk-application.js example" "running without GTK" skip "run the gettext.js example" "running without GTK" fi echo "1..$total" cjs-5.2.0/installed-tests/scripts/testCommandLine.sh0000755000175000017500000002313114144444702022672 0ustar jpeisachjpeisach#!/bin/sh if test "$GJS_USE_UNINSTALLED_FILES" = "1"; then gjs="$TOP_BUILDDIR/cjs-console" else gjs="cjs-console" fi # Avoid interference in the profiler tests from stray environment variable unset GJS_ENABLE_PROFILER # Avoid interference in the warning tests from G_DEBUG=fatal-warnings/criticals OLD_G_DEBUG="$G_DEBUG" # This JS script should exit immediately with code 42. If that is not working, # then it will exit after 3 seconds as a fallback, with code 0. cat <exit.js const GLib = imports.gi.GLib; let loop = GLib.MainLoop.new(null, false); GLib.idle_add(GLib.PRIORITY_LOW, () => imports.system.exit(42)); GLib.timeout_add_seconds(GLib.PRIORITY_HIGH, 3, () => loop.quit()); loop.run(); EOF # this JS script fails if either 1) --help is not passed to it, or 2) the string # "sentinel" is not in its search path cat <help.js const System = imports.system; if (imports.searchPath.indexOf('sentinel') == -1) System.exit(1); if (ARGV.indexOf('--help') == -1) System.exit(1); System.exit(0); EOF # this JS script should print one string (jobs are run before the interpreter # finishes) and should not print the other (jobs should not be run after the # interpreter is instructed to quit) cat <promise.js const System = imports.system; Promise.resolve().then(() => { print('Should be printed'); System.exit(42); }); Promise.resolve().then(() => print('Should not be printed')); EOF # this JS script should not cause an unhandled promise rejection cat <awaitcatch.js async function foo() { throw new Error('foo'); } async function bar() { try { await foo(); } catch (e) {} } bar(); EOF total=0 report () { exit_code=$? total=$((total + 1)) if test $exit_code -eq 0; then echo "ok $total - $1" else echo "not ok $total - $1" fi } report_xfail () { exit_code=$? total=$((total + 1)) if test $exit_code -eq 23; then echo "not ok $total - $1 (leaked memory)" elif test $exit_code -ne 0; then echo "ok $total - $1 (exit code $exit_code)" else echo "not ok $total - $1" fi } skip () { total=$((total + 1)) echo "ok $total - $1 # SKIP $2" } $gjs --invalid-option >/dev/null 2>/dev/null report_xfail "Invalid option should exit with failure" $gjs --invalid-option 2>&1 | grep -q invalid-option report "Invalid option should print a relevant message" # Test that System.exit() works in gjs-console $gjs -c 'imports.system.exit(0)' report "System.exit(0) should exit successfully" $gjs -c 'imports.system.exit(42)' test $? -eq 42 report "System.exit(42) should exit with the correct exit code" # FIXME: should check -eq 42 specifically, but in debug mode we will be # hitting an assertion. For this reason, skip when running under valgrind # since nothing will be freed. Also suppress LSan for the same reason. echo "# VALGRIND = $VALGRIND" if test -z $VALGRIND; then ASAN_OPTIONS=detect_leaks=0 $gjs exit.js test $? -ne 0 report "System.exit() should still exit across an FFI boundary" else skip "System.exit() should still exit across an FFI boundary" "running under valgrind" fi # ensure the encoding of argv is being properly handled $gjs -c 'imports.system.exit((ARGV[0] !== "Valentín") ? 1 : 0)' "Valentín" report "Basic unicode encoding (accents, etc) should be functioning properly for ARGV and imports." $gjs -c 'imports.system.exit((ARGV[0] !== "☭") ? 1 : 0)' "☭" report "Unicode encoding for symbols should be functioning properly for ARGV and imports." # gjs --help prints GJS help $gjs --help >/dev/null report "--help should succeed" test -n "$($gjs --help)" report "--help should print something" # print GJS help even if it's not the first argument $gjs -I . --help >/dev/null report "should succeed when --help is not first arg" test -n "$($gjs -I . --help)" report "should print something when --help is not first arg" # --help before a script file name prints GJS help $gjs --help help.js >/dev/null report "--help should succeed before a script file" test -n "$($gjs --help help.js)" report "--help should print something before a script file" # --help before a -c argument prints GJS help script='imports.system.exit(1)' $gjs --help -c "$script" >/dev/null report "--help should succeed before -c" test -n "$($gjs --help -c "$script")" report "--help should print something before -c" # --help after a script file name is passed to the script $gjs -I sentinel help.js --help report "--help after script file should be passed to script" test -z "$($gjs -I sentinel help.js --help)" report "--help after script file should not print anything" # --help after a -c argument is passed to the script script='if(ARGV[0] !== "--help") imports.system.exit(1)' $gjs -c "$script" --help report "--help after -c should be passed to script" test -z "$($gjs -c "$script" --help)" report "--help after -c should not print anything" # -I after a program is not consumed by GJS # Temporary behaviour: still consume the argument, but give a warning # "$gjs" help.js --help -I sentinel # report_xfail "-I after script file should not be added to search path" # fi G_DEBUG=$(echo "$G_DEBUG" | sed -e 's/fatal-warnings,\{0,1\}//') $gjs help.js --help -I sentinel 2>&1 | grep -q 'Cjs-WARNING.*--include-path' report "-I after script should succeed but give a warning" $gjs -c 'imports.system.exit(0)' --coverage-prefix=foo --coverage-output=foo 2>&1 | grep -q 'Cjs-WARNING.*--coverage-prefix' report "--coverage-prefix after script should succeed but give a warning" $gjs -c 'imports.system.exit(0)' --coverage-prefix=foo --coverage-output=foo 2>&1 | grep -q 'Cjs-WARNING.*--coverage-output' report "--coverage-output after script should succeed but give a warning" rm -f foo/coverage.lcov G_DEBUG="$OLD_G_DEBUG" for version_arg in --version --jsversion; do # --version and --jsversion work $gjs $version_arg >/dev/null report "$version_arg should work" test -n "$($gjs $version_arg)" report "$version_arg should print something" # --version and --jsversion after a script go to the script script="if(ARGV[0] !== '$version_arg') imports.system.exit(1)" $gjs -c "$script" $version_arg report "$version_arg after -c should be passed to script" test -z "$($gjs -c "$script" $version_arg)" report "$version_arg after -c should not print anything" done # --profile rm -f gjs-*.syscap foo.syscap $gjs -c 'imports.system.exit(0)' && ! stat gjs-*.syscap > /dev/null 2>&1 report "no profiling data should be dumped without --profile" # Skip some tests if built without profiler support if $gjs --profile -c 1 2>&1 | grep -q 'Cjs-Message.*Profiler is disabled'; then reason="profiler is disabled" skip "--profile should dump profiling data to the default file name" "$reason" skip "--profile with argument should dump profiling data to the named file" "$reason" skip "GJS_ENABLE_PROFILER=1 should enable the profiler" "$reason" else rm -f gjs-*.syscap $gjs --profile -c 'imports.system.exit(0)' && stat gjs-*.syscap > /dev/null 2>&1 report "--profile should dump profiling data to the default file name" rm -f gjs-*.syscap $gjs --profile=foo.syscap -c 'imports.system.exit(0)' && test -f foo.syscap report "--profile with argument should dump profiling data to the named file" rm -f foo.syscap && rm -f gjs-*.syscap GJS_ENABLE_PROFILER=1 $gjs -c 'imports.system.exit(0)' && stat gjs-*.syscap > /dev/null 2>&1 report "GJS_ENABLE_PROFILER=1 should enable the profiler" rm -f gjs-*.syscap fi # interpreter handles queued promise jobs correctly output=$($gjs promise.js) test $? -eq 42 report "interpreter should exit with the correct exit code from a queued promise job" test -n "$output" -a -z "${output##*Should be printed*}" report "interpreter should run queued promise jobs before finishing" test -n "${output##*Should not be printed*}" report "interpreter should stop running jobs when one calls System.exit()" $gjs -c "Promise.resolve().then(() => { throw new Error(); });" 2>&1 | grep -q 'Cjs-WARNING.*Unhandled promise rejection.*[sS]tack trace' report "unhandled promise rejection should be reported" test -z "$($gjs awaitcatch.js)" report "catching an await expression should not cause unhandled rejection" # https://gitlab.gnome.org/GNOME/gjs/issues/18 G_DEBUG=$(echo "$G_DEBUG" | sed -e 's/fatal-warnings,\{0,1\}//') $gjs -c "(async () => await true)(); void foobar;" 2>&1 | grep -q 'ReferenceError: foobar is not defined' report "main program exceptions are not swallowed by queued promise jobs" G_DEBUG="$OLD_G_DEBUG" # https://gitlab.gnome.org/GNOME/gjs/issues/26 $gjs -c 'new imports.gi.Gio.Subprocess({argv: ["true"]}).init(null);' report "object unref from other thread after shutdown should not race" # https://gitlab.gnome.org/GNOME/gjs/issues/212 if test -n "$ENABLE_GTK"; then G_DEBUG=$(echo "$G_DEBUG" | sed -e 's/fatal-warnings,\{0,1\}//' -e 's/fatal-criticals,\{0,1\}//') $gjs -c 'imports.gi.versions.Gtk = "3.0"; const Gtk = imports.gi.Gtk; const GObject = imports.gi.GObject; Gtk.init(null); let BadWidget = GObject.registerClass(class BadWidget extends Gtk.Widget { vfunc_destroy() {}; }); let w = new BadWidget ();' report "avoid crashing when GTK vfuncs are called on context destroy" G_DEBUG="$OLD_G_DEBUG" else skip "avoid crashing when GTK vfuncs are called on context destroy" "GTK disabled" fi # https://gitlab.gnome.org/GNOME/gjs/-/issues/322 $gjs --coverage-prefix=$(pwd) --coverage-output=$(pwd) awaitcatch.js grep -q TN: coverage.lcov report "coverage prefix is treated as an absolute path" rm -f coverage.lcov rm -f exit.js help.js promise.js awaitcatch.js echo "1..$total" cjs-5.2.0/installed-tests/scripts/testWarnings.sh0000755000175000017500000000152114144444702022273 0ustar jpeisachjpeisach#!/bin/sh if test "$GJS_USE_UNINSTALLED_FILES" = "1"; then gjs="$TOP_BUILDDIR/cjs-console" else gjs="cjs-console" fi total=0 report () { exit_code=$? total=$((total + 1)) if test $exit_code -eq 0; then echo "ok $total - $1" else echo "not ok $total - $1" fi } $gjs -c 'imports.signals.addSignalMethods({connect: "foo"})' 2>&1 | \ grep -q 'addSignalMethods is replacing existing .* connect method' report "overwriting method with Signals.addSignalMethods() should warn" $gjs -c 'imports.gi.GLib.get_home_dir("foobar")' 2>&1 | \ grep -q 'Too many arguments to .*: expected 0, got 1' report "passing too many arguments to a GI function should warn" $gjs -c '**' 2>&1 | \ grep -q 'SyntaxError.*@ :1' report "file and line number are logged for syntax errors" echo "1..$total" cjs-5.2.0/installed-tests/debugger.test.in0000644000175000017500000000017114144444702020647 0ustar jpeisachjpeisach[Test] Type=session Exec=@installed_tests_execdir@/debugger-test.sh @installed_tests_execdir@/debugger/@name@ Output=TAP cjs-5.2.0/installed-tests/minijasmine.test.in0000644000175000017500000000015614144444702021371 0ustar jpeisachjpeisach[Test] Type=session Exec=@installed_tests_execdir@/minijasmine @installed_tests_execdir@/js/@name@ Output=TAP cjs-5.2.0/installed-tests/extra/0000755000175000017500000000000014144444702016701 5ustar jpeisachjpeisachcjs-5.2.0/installed-tests/extra/cjs.supp0000644000175000017500000000557614144444702020406 0ustar jpeisachjpeisach# Valgrind suppressions file for GJS # This is intended to be used in addition to GLib's glib.supp file. # SpiderMonkey leaks { mozjs-thread-stack-init Memcheck:Leak match-leak-kinds: possible fun:calloc fun:_dl_allocate_tls fun:pthread_create@@GLIBC_2.2.5 fun:_ZN7mozilla9TimeStamp20ComputeProcessUptimeEv fun:_ZN7mozilla9TimeStamp15ProcessCreationEPb fun:_ZN2JS6detail25InitWithFailureDiagnosticEb fun:_Z7JS_Initv } # Various things that I don't believe are related to GJS { gtk-style-context Memcheck:Leak match-leak-kinds: possible fun:malloc fun:g_malloc ... fun:gtk_css_node_declaration_make_writable ... fun:gtk_style_constructed } { gtk-style-context2 Memcheck:Leak match-leak-kinds: possible fun:malloc fun:g_malloc ... fun:gtk_css_node_declaration_make_writable_resize ... fun:gtk_style_constructed } # https://bugs.freedesktop.org/show_bug.cgi?id=105466 { freedesktop-bug-105466 Memcheck:Leak match-leak-kinds: definite fun:malloc ... fun:FcConfigSubstituteWithPat fun:_cairo_ft_resolve_pattern fun:_cairo_ft_font_face_get_implementation fun:cairo_scaled_font_create fun:_cairo_gstate_ensure_scaled_font fun:_cairo_gstate_get_scaled_font fun:_cairo_default_context_get_scaled_font fun:cairo_show_text } # Data that Cairo keeps around for the process lifetime # This could be freed by calling cairo_debug_reset_static_data(), but it's # not a good idea to call that function in production, because certain versions # of Cairo have bugs that cause it to fail assertions and crash. { cairo-static-data Memcheck:Leak match-leak-kinds: definite fun:malloc ... fun:FcPatternDuplicate fun:_cairo_ft_font_face_create_for_pattern fun:_cairo_ft_font_face_create_for_toy fun:_cairo_toy_font_face_create_impl_face fun:_cairo_toy_font_face_init fun:cairo_toy_font_face_create ... fun:_cairo_gstate_ensure_font_face fun:_cairo_gstate_ensure_scaled_font fun:_cairo_gstate_get_scaled_font fun:_cairo_default_context_get_scaled_font ... fun:cairo_show_text } # https://gitlab.gnome.org/GNOME/gobject-introspection/issues/265 { gobject-introspection-default-repository Memcheck:Leak match-leak-kinds: definite fun:realloc ... fun:build_typelib_key fun:register_internal } # Workaround for https://github.com/mesonbuild/meson/issues/4427 # When fixed, valgrind should already not trace bash { bash-workaround Memcheck:Leak match-leak-kinds: definite fun:malloc fun:xmalloc fun:set_default_locale fun:main } # https://gitlab.gnome.org/GNOME/glib/-/issues/1911 { g-type-register-static Memcheck:Leak match-leak-kinds:possible fun:malloc ... fun:g_type_register_static } { g-type-register-static-calloc Memcheck:Leak match-leak-kinds:possible fun:calloc ... fun:g_type_register_static } cjs-5.2.0/installed-tests/extra/lsan.supp0000644000175000017500000000044614144444702020553 0ustar jpeisachjpeisach# SpiderMonkey leaks a mutex for each GC helper thread. leak:js::HelperThread::threadLoop # https://bugs.freedesktop.org/show_bug.cgi?id=105466 leak:libfontconfig.so.1 # https://bugzilla.mozilla.org/show_bug.cgi?id=1478679 leak:js::coverage::LCovSource::writeScript leak:js/src/util/Text.cpp cjs-5.2.0/installed-tests/extra/gjs.supp0000644000175000017500000000546714144444702020411 0ustar jpeisachjpeisach# Valgrind suppressions file for GJS # This is intended to be used in addition to GLib's glib.supp file. # We leak a small wrapper in GJS for each registered GType. { gtype-wrapper-new Memcheck:Leak match-leak-kinds: definite fun:_Znwm fun:gjs_gtype_create_gtype_wrapper } { gtype-wrapper-qdata Memcheck:Leak match-leak-kinds: possible ... fun:type_set_qdata_W fun:g_type_set_qdata fun:gjs_gtype_create_gtype_wrapper } { g_type_register_fundamental never freed Memcheck:Leak fun:calloc ... fun:g_type_register_fundamental ... } # SpiderMonkey leaks { mozjs-thread-stack-init Memcheck:Leak match-leak-kinds: possible fun:calloc fun:allocate_dtv fun:_dl_allocate_tls fun:allocate_stack fun:pthread_create@@GLIBC_2.2.5 fun:_ZN7mozilla9TimeStamp20ComputeProcessUptimeEv fun:_ZN7mozilla9TimeStamp15ProcessCreationERb fun:_ZN2JS6detail25InitWithFailureDiagnosticEb fun:JS_Init } { mozjs-thread-stack-new-context Memcheck:Leak match-leak-kinds: possible fun:calloc fun:allocate_dtv fun:_dl_allocate_tls fun:allocate_stack fun:pthread_create@@GLIBC_2.2.5 fun:_ZN2js6Thread6createEPFPvS1_ES1_ fun:init fun:_ZN2js23GlobalHelperThreadState17ensureInitializedEv fun:_ZN9JSRuntime4initEjj fun:init fun:NewContext fun:_Z13JS_NewContextjjP9JSContext } { mozjs-gc-helper-thread-mutex-guard Memcheck:Leak match-leak-kinds: definite fun:malloc fun:js_malloc fun:js_new > fun:_ZN2js5Mutex14heldMutexStackEv.part.* fun:heldMutexStack fun:_ZN2js5Mutex4lockEv fun:LockGuard fun:_ZN2js25AutoLockHelperThreadStateC1EON7mozilla6detail19GuardObjectNotifierE fun:_ZN2js12HelperThread10threadLoopEv fun:callMain<0*> fun:_ZN2js6detail16ThreadTrampolineIRFvPvEJPNS_12HelperThreadEEE5StartES2_ fun:start_thread fun:clone } # SpiderMonkey data races # These are in SpiderMonkey's atomics / thread barrier stuff so presumably # locking is working correctly and helgrind just can't detect it? { mozjs-helgrind-atomic-load-1 Helgrind:Race fun:load fun:load fun:operator unsigned int } { mozjs-helgrind-atomic-load-2 Helgrind:Race fun:load fun:load fun:operator bool } { mozjs-helgrind-atomic-store Helgrind:Race fun:store fun:store fun:operator= } # Presumably this one is OK since the function is called "thread safe"?! { mozjs-helgrind-thread-safe-lookup Helgrind:Race ... fun:lookup fun:readonlyThreadsafeLookup fun:readonlyThreadsafeLookup } { mozjs-helgrind-jit-code Helgrind:Race obj:* ... fun:_ZL13EnterBaselineP9JSContextRN2js3jit12EnterJitDataE } { mozjs-helgrind-probably-jit-code Helgrind:Race obj:* obj:* obj:* obj:* obj:* } cjs-5.2.0/installed-tests/extra/unittest.gdb0000644000175000017500000000004614144444702021236 0ustar jpeisachjpeisachrun if ($_exitcode == 0) quit end cjs-5.2.0/installed-tests/js/0000755000175000017500000000000014144444702016172 5ustar jpeisachjpeisachcjs-5.2.0/installed-tests/js/testPrint.js0000644000175000017500000000145514144444702020531 0ustar jpeisachjpeisachdescribe('print', function () { it('can be spied upon', function () { spyOn(globalThis, 'print'); print('foo'); expect(print).toHaveBeenCalledWith('foo'); }); }); describe('printerr', function () { it('can be spied upon', function () { spyOn(globalThis, 'printerr'); printerr('foo'); expect(printerr).toHaveBeenCalledWith('foo'); }); }); describe('log', function () { it('can be spied upon', function () { spyOn(globalThis, 'log'); log('foo'); expect(log).toHaveBeenCalledWith('foo'); }); }); describe('logError', function () { it('can be spied upon', function () { spyOn(globalThis, 'logError'); logError('foo', 'bar'); expect(logError).toHaveBeenCalledWith('foo', 'bar'); }); }); cjs-5.2.0/installed-tests/js/testLegacyClass.js0000644000175000017500000005517214144444702021634 0ustar jpeisachjpeisach// -*- mode: js; indent-tabs-mode: nil -*- /* eslint-disable no-restricted-properties */ const Lang = imports.lang; const NormalClass = new Lang.Class({ Name: 'NormalClass', _init() { this.one = 1; }, }); let Subclassed = []; const MetaClass = new Lang.Class({ Name: 'MetaClass', Extends: Lang.Class, _init(params) { Subclassed.push(params.Name); this.parent(params); if (params.Extended) { this.prototype.dynamic_method = this.wrapFunction('dynamic_method', function () { return 73; }); this.DYNAMIC_CONSTANT = 2; } }, }); const CustomMetaOne = new MetaClass({ Name: 'CustomMetaOne', Extends: NormalClass, Extended: false, _init() { this.parent(); this.two = 2; }, }); const CustomMetaTwo = new MetaClass({ Name: 'CustomMetaTwo', Extends: NormalClass, Extended: true, _init() { this.parent(); this.two = 2; }, }); // This should inherit CustomMeta, even though // we use Lang.Class const CustomMetaSubclass = new Lang.Class({ Name: 'CustomMetaSubclass', Extends: CustomMetaOne, Extended: true, _init() { this.parent(); this.three = 3; }, }); describe('A metaclass', function () { it('has its constructor called each time a class is created with it', function () { expect(Subclassed).toEqual(['CustomMetaOne', 'CustomMetaTwo', 'CustomMetaSubclass']); }); it('is an instance of Lang.Class', function () { expect(NormalClass instanceof Lang.Class).toBeTruthy(); expect(MetaClass instanceof Lang.Class).toBeTruthy(); }); it('produces instances that are instances of itself and Lang.Class', function () { expect(CustomMetaOne instanceof Lang.Class).toBeTruthy(); expect(CustomMetaOne instanceof MetaClass).toBeTruthy(); }); it('can dynamically define properties in its constructor', function () { expect(CustomMetaTwo.DYNAMIC_CONSTANT).toEqual(2); expect(CustomMetaOne.DYNAMIC_CONSTANT).not.toBeDefined(); }); describe('instance', function () { let instanceOne, instanceTwo; beforeEach(function () { instanceOne = new CustomMetaOne(); instanceTwo = new CustomMetaTwo(); }); it('gets all the properties from its class and metaclass', function () { expect(instanceOne).toEqual(jasmine.objectContaining({one: 1, two: 2})); expect(instanceTwo).toEqual(jasmine.objectContaining({one: 1, two: 2})); }); it('gets dynamically defined properties from metaclass', function () { expect(() => instanceOne.dynamic_method()).toThrow(); expect(instanceTwo.dynamic_method()).toEqual(73); }); }); it('can be instantiated with Lang.Class but still get the appropriate metaclass', function () { expect(CustomMetaSubclass instanceof MetaClass).toBeTruthy(); expect(CustomMetaSubclass.DYNAMIC_CONSTANT).toEqual(2); let instance = new CustomMetaSubclass(); expect(instance).toEqual(jasmine.objectContaining({one: 1, two: 2, three: 3})); expect(instance.dynamic_method()).toEqual(73); }); it('can be detected with Lang.getMetaClass', function () { expect(Lang.getMetaClass({ Extends: CustomMetaOne, })).toBe(MetaClass); }); }); const MagicBase = new Lang.Class({ Name: 'MagicBase', _init(a, buffer) { if (buffer) buffer.push(a); this.a = a; }, foo(a, buffer) { buffer.push(a); return a * 3; }, bar(a) { return a * 5; }, }); const Magic = new Lang.Class({ Name: 'Magic', Extends: MagicBase, _init(a, b, buffer) { this.parent(a, buffer); if (buffer) buffer.push(b); this.b = b; }, foo(a, b, buffer) { let val = this.parent(a, buffer); buffer.push(b); return val * 2; }, bar(a, buffer) { this.foo(a, 2 * a, buffer); return this.parent(a); }, }); const Accessor = new Lang.Class({ Name: 'AccessorMagic', _init(val) { this._val = val; }, get value() { return this._val; }, set value(val) { if (val !== 42) throw TypeError('Value is not a magic number'); this._val = val; }, }); const AbstractBase = new Lang.Class({ Name: 'AbstractBase', Abstract: true, _init() { this.foo = 42; }, }); describe('Class framework', function () { it('calls _init constructors', function () { let newMagic = new MagicBase('A'); expect(newMagic.a).toEqual('A'); }); it('calls parent constructors', function () { let buffer = []; let newMagic = new Magic('a', 'b', buffer); expect(buffer).toEqual(['a', 'b']); buffer = []; let val = newMagic.foo(10, 20, buffer); expect(buffer).toEqual([10, 20]); expect(val).toEqual(10 * 6); }); it('sets the right constructor properties', function () { expect(Magic.prototype.constructor).toBe(Magic); let newMagic = new Magic(); expect(newMagic.constructor).toBe(Magic); }); it('sets up instanceof correctly', function () { let newMagic = new Magic(); expect(newMagic instanceof Magic).toBeTruthy(); expect(newMagic instanceof MagicBase).toBeTruthy(); }); it('has a name', function () { expect(Magic.name).toEqual('Magic'); }); it('reports a sensible value for toString()', function () { let newMagic = new MagicBase(); expect(newMagic.toString()).toEqual('[object MagicBase]'); }); it('allows overriding toString()', function () { const ToStringOverride = new Lang.Class({ Name: 'ToStringOverride', toString() { let oldToString = this.parent(); return `${oldToString}; hello`; }, }); let override = new ToStringOverride(); expect(override.toString()).toEqual('[object ToStringOverride]; hello'); }); it('is not configurable', function () { let newMagic = new MagicBase(); delete newMagic.foo; expect(newMagic.foo).toBeDefined(); }); it('allows accessors for properties', function () { let newAccessor = new Accessor(11); expect(newAccessor.value).toEqual(11); expect(() => (newAccessor.value = 12)).toThrow(); newAccessor.value = 42; expect(newAccessor.value).toEqual(42); }); it('raises an exception when creating an abstract class', function () { expect(() => new AbstractBase()).toThrow(); }); it('inherits properties from abstract base classes', function () { const AbstractImpl = new Lang.Class({ Name: 'AbstractImpl', Extends: AbstractBase, _init() { this.parent(); this.bar = 42; }, }); let newAbstract = new AbstractImpl(); expect(newAbstract.foo).toEqual(42); expect(newAbstract.bar).toEqual(42); }); it('inherits constructors from abstract base classes', function () { const AbstractImpl = new Lang.Class({ Name: 'AbstractImpl', Extends: AbstractBase, }); let newAbstract = new AbstractImpl(); expect(newAbstract.foo).toEqual(42); }); it('allows ES6 classes to inherit from abstract base classes', function () { class AbstractImpl extends AbstractBase {} let newAbstract = new AbstractImpl(); expect(newAbstract.foo).toEqual(42); }); it('lets methods call other methods without clobbering __caller__', function () { let newMagic = new Magic(); let buffer = []; let res = newMagic.bar(10, buffer); expect(buffer).toEqual([10, 20]); expect(res).toEqual(50); }); it('allows custom return values from constructors', function () { const CustomConstruct = new Lang.Class({ Name: 'CustomConstruct', _construct(one, two) { return [one, two]; }, }); let instance = new CustomConstruct(1, 2); expect(instance instanceof Array).toBeTruthy(); expect(instance instanceof CustomConstruct).toBeFalsy(); expect(instance).toEqual([1, 2]); }); it('allows symbol-named methods', function () { const SymbolClass = new Lang.Class({ Name: 'SymbolClass', *[Symbol.iterator]() { yield* [1, 2, 3]; }, }); let instance = new SymbolClass(); expect([...instance]).toEqual([1, 2, 3]); }); }); const AnInterface = new Lang.Interface({ Name: 'AnInterface', required: Lang.Interface.UNIMPLEMENTED, optional() { return 'AnInterface.optional()'; }, optionalGeneric() { return 'AnInterface.optionalGeneric()'; }, argumentGeneric(arg) { return `AnInterface.argumentGeneric(${arg})`; }, usesThis() { return this._interfacePrivateMethod(); }, _interfacePrivateMethod() { return 'interface private method'; }, get some_prop() { return 'AnInterface.some_prop getter'; }, set some_prop(value) { this.some_prop_setter_called = true; }, }); const InterfaceRequiringOtherInterface = new Lang.Interface({ Name: 'InterfaceRequiringOtherInterface', Requires: [AnInterface], optional(...args) { return `InterfaceRequiringOtherInterface.optional()\n${ AnInterface.prototype.optional.apply(this, args)}`; }, optionalGeneric() { return `InterfaceRequiringOtherInterface.optionalGeneric()\n${ AnInterface.optionalGeneric(this)}`; }, }); const ObjectImplementingAnInterface = new Lang.Class({ Name: 'ObjectImplementingAnInterface', Implements: [AnInterface], _init() { this.parent(); }, required() {}, optional(...args) { return AnInterface.prototype.optional.apply(this, args); }, optionalGeneric() { return AnInterface.optionalGeneric(this); }, argumentGeneric(arg) { return AnInterface.argumentGeneric(this, `${arg} (hello from class)`); }, }); const InterfaceRequiringClassAndInterface = new Lang.Interface({ Name: 'InterfaceRequiringClassAndInterface', Requires: [ObjectImplementingAnInterface, InterfaceRequiringOtherInterface], }); const MinimalImplementationOfAnInterface = new Lang.Class({ Name: 'MinimalImplementationOfAnInterface', Implements: [AnInterface], required() {}, }); const ImplementationOfTwoInterfaces = new Lang.Class({ Name: 'ImplementationOfTwoInterfaces', Implements: [AnInterface, InterfaceRequiringOtherInterface], required() {}, optional(...args) { return InterfaceRequiringOtherInterface.prototype.optional.apply(this, args); }, optionalGeneric() { return InterfaceRequiringOtherInterface.optionalGeneric(this); }, }); describe('An interface', function () { it('is an instance of Lang.Interface', function () { expect(AnInterface instanceof Lang.Interface).toBeTruthy(); expect(InterfaceRequiringOtherInterface instanceof Lang.Interface).toBeTruthy(); }); it('has a name', function () { expect(AnInterface.name).toEqual('AnInterface'); }); it('cannot be instantiated', function () { expect(() => new AnInterface()).toThrow(); }); it('can be implemented by a class', function () { let obj; expect(() => { obj = new ObjectImplementingAnInterface(); }).not.toThrow(); expect(obj.constructor.implements(AnInterface)).toBeTruthy(); }); it("can be implemented by a class's superclass", function () { const ChildWhoseParentImplementsAnInterface = new Lang.Class({ Name: 'ChildWhoseParentImplementsAnInterface', Extends: ObjectImplementingAnInterface, }); let obj = new ChildWhoseParentImplementsAnInterface(); expect(obj.constructor.implements(AnInterface)).toBeTruthy(); }); it("doesn't disturb a class's constructor", function () { let obj = new ObjectImplementingAnInterface(); expect(obj.constructor).toEqual(ObjectImplementingAnInterface); }); it('can have its required method implemented', function () { let implementer = new ObjectImplementingAnInterface(); expect(() => implementer.required()).not.toThrow(); }); it('must have a name', function () { expect(() => new Lang.Interface({ required: Lang.Interface.UNIMPLEMENTED, })).toThrow(); }); it('must have its required methods implemented', function () { expect(() => new Lang.Class({ Name: 'MyBadObject', Implements: [AnInterface], })).toThrow(); }); it('does not have to have its optional methods implemented', function () { let obj; expect(() => (obj = new MinimalImplementationOfAnInterface())).not.toThrow(); expect(obj.constructor.implements(AnInterface)).toBeTruthy(); }); it('can have its optional method deferred to by the implementation', function () { let obj = new MinimalImplementationOfAnInterface(); expect(obj.optional()).toEqual('AnInterface.optional()'); }); it('can be chained up to by a class', function () { let obj = new ObjectImplementingAnInterface(); expect(obj.optional()).toEqual('AnInterface.optional()'); }); it('can include arguments when being chained up to by a class', function () { let obj = new ObjectImplementingAnInterface(); expect(obj.argumentGeneric('arg')) .toEqual('AnInterface.argumentGeneric(arg (hello from class))'); }); it('can have its property getter deferred to', function () { let obj = new ObjectImplementingAnInterface(); expect(obj.some_prop).toEqual('AnInterface.some_prop getter'); }); it('can have its property setter deferred to', function () { let obj = new ObjectImplementingAnInterface(); obj.some_prop = 'foobar'; expect(obj.some_prop_setter_called).toBeTruthy(); }); it('can have its property getter overridden', function () { const ObjectWithGetter = new Lang.Class({ Name: 'ObjectWithGetter', Implements: [AnInterface], required() {}, get some_prop() { return 'ObjectWithGetter.some_prop getter'; }, }); let obj = new ObjectWithGetter(); expect(obj.some_prop).toEqual('ObjectWithGetter.some_prop getter'); }); it('can have its property setter overridden', function () { const ObjectWithSetter = new Lang.Class({ Name: 'ObjectWithSetter', Implements: [AnInterface], required() {}, set some_prop(value) { /* setter without getter */// jshint ignore:line this.overridden_some_prop_setter_called = true; }, }); let obj = new ObjectWithSetter(); obj.some_prop = 'foobar'; expect(obj.overridden_some_prop_setter_called).toBeTruthy(); expect(obj.some_prop_setter_called).not.toBeDefined(); }); it('can require another interface', function () { let obj; expect(() => { obj = new ImplementationOfTwoInterfaces(); }).not.toThrow(); expect(obj.constructor.implements(AnInterface)).toBeTruthy(); expect(obj.constructor.implements(InterfaceRequiringOtherInterface)).toBeTruthy(); }); it('can have empty requires', function () { expect(() => new Lang.Interface({ Name: 'InterfaceWithEmptyRequires', Requires: [], })).not.toThrow(); }); it('can chain up to another interface', function () { let obj = new ImplementationOfTwoInterfaces(); expect(obj.optional()) .toEqual('InterfaceRequiringOtherInterface.optional()\nAnInterface.optional()'); }); it('can be chained up to with a generic', function () { let obj = new ObjectImplementingAnInterface(); expect(obj.optionalGeneric()).toEqual('AnInterface.optionalGeneric()'); }); it('can chain up to another interface with a generic', function () { let obj = new ImplementationOfTwoInterfaces(); expect(obj.optionalGeneric()) .toEqual('InterfaceRequiringOtherInterface.optionalGeneric()\nAnInterface.optionalGeneric()'); }); it('has its optional function defer to that of the last interface', function () { const MinimalImplementationOfTwoInterfaces = new Lang.Class({ Name: 'MinimalImplementationOfTwoInterfaces', Implements: [AnInterface, InterfaceRequiringOtherInterface], required() {}, }); let obj = new MinimalImplementationOfTwoInterfaces(); expect(obj.optionalGeneric()) .toEqual('InterfaceRequiringOtherInterface.optionalGeneric()\nAnInterface.optionalGeneric()'); }); it('must have all its required interfaces implemented', function () { expect(() => new Lang.Class({ Name: 'ObjectWithNotEnoughInterfaces', Implements: [InterfaceRequiringOtherInterface], required() {}, })).toThrow(); }); it('must have all its required interfaces implemented in the correct order', function () { expect(() => new Lang.Class({ Name: 'ObjectWithInterfacesInWrongOrder', Implements: [InterfaceRequiringOtherInterface, AnInterface], required() {}, })).toThrow(); }); it('can have its implementation on a parent class', function () { let obj; expect(() => { const ObjectInheritingFromInterfaceImplementation = new Lang.Class({ Name: 'ObjectInheritingFromInterfaceImplementation', Extends: ObjectImplementingAnInterface, Implements: [InterfaceRequiringOtherInterface], }); obj = new ObjectInheritingFromInterfaceImplementation(); }).not.toThrow(); expect(obj.constructor.implements(AnInterface)).toBeTruthy(); expect(obj.constructor.implements(InterfaceRequiringOtherInterface)).toBeTruthy(); }); it('can require its implementor to be a subclass of some class', function () { let obj; expect(() => { const ObjectImplementingInterfaceRequiringParentObject = new Lang.Class({ Name: 'ObjectImplementingInterfaceRequiringParentObject', Extends: ObjectImplementingAnInterface, Implements: [InterfaceRequiringOtherInterface, InterfaceRequiringClassAndInterface], }); obj = new ObjectImplementingInterfaceRequiringParentObject(); }).not.toThrow(); expect(obj.constructor.implements(AnInterface)).toBeTruthy(); expect(obj.constructor.implements(InterfaceRequiringOtherInterface)).toBeTruthy(); expect(obj.constructor.implements(InterfaceRequiringClassAndInterface)).toBeTruthy(); }); it('must be implemented by an object which subclasses the required class', function () { expect(() => new Lang.Class({ Name: 'ObjectWithoutRequiredParent', Implements: [AnInterface, InterfaceRequiringOtherInterface, InterfaceRequiringClassAndInterface], required() {}, })).toThrow(); }); it('can have methods that call others of its methods', function () { let obj = new ObjectImplementingAnInterface(); expect(obj.usesThis()).toEqual('interface private method'); }); it('is implemented by a subclass of a class that implements it', function () { const SubObject = new Lang.Class({ Name: 'SubObject', Extends: ObjectImplementingAnInterface, }); let obj = new SubObject(); expect(obj.constructor.implements(AnInterface)).toBeTruthy(); }); it('can be reimplemented by a subclass of a class that implements it', function () { const SubImplementer = new Lang.Class({ Name: 'SubImplementer', Extends: ObjectImplementingAnInterface, Implements: [AnInterface], }); let obj = new SubImplementer(); expect(obj.constructor.implements(AnInterface)).toBeTruthy(); expect(() => obj.required()).not.toThrow(); }); it('tells what it is with toString()', function () { expect(AnInterface.toString()).toEqual('[interface Interface for AnInterface]'); }); }); describe('ES6 class inheriting from Lang.Class', function () { let Shiny, Legacy; beforeEach(function () { Legacy = new Lang.Class({ Name: 'Legacy', _init(someval) { this.constructorCalledWith = someval; }, instanceMethod() {}, chainUpToMe() {}, overrideMe() {}, get property() { return this._property + 1; }, set property(value) { this._property = value - 2; }, }); Legacy.staticMethod = function () {}; spyOn(Legacy, 'staticMethod'); spyOn(Legacy.prototype, 'instanceMethod'); spyOn(Legacy.prototype, 'chainUpToMe'); spyOn(Legacy.prototype, 'overrideMe'); Shiny = class extends Legacy { chainUpToMe() { super.chainUpToMe(); } overrideMe() {} }; }); it('calls a static method on the parent class', function () { Shiny.staticMethod(); expect(Legacy.staticMethod).toHaveBeenCalled(); }); it('calls a method on the parent class', function () { let instance = new Shiny(); instance.instanceMethod(); expect(Legacy.prototype.instanceMethod).toHaveBeenCalled(); }); it("passes arguments to the parent class's constructor", function () { let instance = new Shiny(42); expect(instance.constructorCalledWith).toEqual(42); }); it('chains up to a method on the parent class', function () { let instance = new Shiny(); instance.chainUpToMe(); expect(Legacy.prototype.chainUpToMe).toHaveBeenCalled(); }); it('overrides a method on the parent class', function () { let instance = new Shiny(); instance.overrideMe(); expect(Legacy.prototype.overrideMe).not.toHaveBeenCalled(); }); it('sets and gets a property from the parent class', function () { let instance = new Shiny(); instance.property = 42; expect(instance.property).toEqual(41); }); }); cjs-5.2.0/installed-tests/js/testGObjectDestructionAccess.js0000644000175000017500000000755214144444702024324 0ustar jpeisachjpeisach// -*- mode: js; indent-tabs-mode: nil -*- imports.gi.versions.Gtk = '3.0'; const GLib = imports.gi.GLib; const Gtk = imports.gi.Gtk; describe('Access to destroyed GObject', function () { let destroyedWindow; beforeAll(function () { Gtk.init(null); }); beforeEach(function () { destroyedWindow = new Gtk.Window({type: Gtk.WindowType.TOPLEVEL}); destroyedWindow.destroy(); }); it('Get property', function () { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Window (0x*'); void destroyedWindow.title; GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectDestructionAccess.js', 0, 'testExceptionInDestroyedObjectPropertyGet'); }); it('Set property', function () { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Window (0x*'); destroyedWindow.title = 'I am dead'; GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectDestructionAccess.js', 0, 'testExceptionInDestroyedObjectPropertySet'); }); it('Access to getter method', function () { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Window (0x*'); GLib.test_expect_message('Gtk', GLib.LogLevelFlags.LEVEL_CRITICAL, '*GTK_IS_WINDOW*'); void destroyedWindow.get_title(); GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectDestructionAccess.js', 0, 'testExceptionInDestroyedObjectMethodGet'); }); it('Access to setter method', function () { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Window (0x*'); GLib.test_expect_message('Gtk', GLib.LogLevelFlags.LEVEL_CRITICAL, '*GTK_IS_WINDOW*'); destroyedWindow.set_title('I am dead'); GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectDestructionAccess.js', 0, 'testExceptionInDestroyedObjectMethodSet'); }); it('Proto function connect', function () { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Window (0x*'); destroyedWindow.connect('foo-signal', () => {}); GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectDestructionAccess.js', 0, 'testExceptionInDestroyedObjectConnect'); }); it('Proto function connect_after', function () { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Window (0x*'); destroyedWindow.connect_after('foo-signal', () => {}); GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectDestructionAccess.js', 0, 'testExceptionInDestroyedObjectConnectAfter'); }); it('Proto function emit', function () { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Window (0x*'); destroyedWindow.emit('foo-signal'); GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectDestructionAccess.js', 0, 'testExceptionInDestroyedObjectEmit'); }); it('Proto function toString', function () { expect(destroyedWindow.toString()).toMatch( /\[object \(FINALIZED\) instance wrapper GIName:Gtk.Window jsobj@0x[a-f0-9]+ native@0x[a-f0-9]+\]/); }); it('Proto function toString before/after', function () { var validWindow = new Gtk.Window({type: Gtk.WindowType.TOPLEVEL}); expect(validWindow.toString()).toMatch( /\[object instance wrapper GIName:Gtk.Window jsobj@0x[a-f0-9]+ native@0x[a-f0-9]+\]/); validWindow.destroy(); expect(validWindow.toString()).toMatch( /\[object \(FINALIZED\) instance wrapper GIName:Gtk.Window jsobj@0x[a-f0-9]+ native@0x[a-f0-9]+\]/); }); }); cjs-5.2.0/installed-tests/js/minijasmine.js0000644000175000017500000000743714144444702021046 0ustar jpeisachjpeisach#!/usr/bin/env gjs const GLib = imports.gi.GLib; function _removeNewlines(str) { let allNewlines = /\n/g; return str.replace(allNewlines, '\\n'); } function _filterStack(stack) { if (!stack) return 'No stack'; return stack.split('\n') .filter(stackLine => stackLine.indexOf('resource:///org/gjs/jsunit') === -1) .filter(stackLine => stackLine.indexOf('') === -1) .join('\n'); } function _setTimeoutInternal(continueTimeout, func, time) { return GLib.timeout_add(GLib.PRIORITY_DEFAULT, time, function () { func(); return continueTimeout; }); } function _clearTimeoutInternal(id) { if (id > 0) GLib.source_remove(id); } // Install the browser setTimeout/setInterval API on the global object globalThis.setTimeout = _setTimeoutInternal.bind(undefined, GLib.SOURCE_REMOVE); globalThis.setInterval = _setTimeoutInternal.bind(undefined, GLib.SOURCE_CONTINUE); globalThis.clearTimeout = globalThis.clearInterval = _clearTimeoutInternal; let jasmineRequire = imports.jasmine.getJasmineRequireObj(); let jasmineCore = jasmineRequire.core(jasmineRequire); globalThis._jasmineEnv = jasmineCore.getEnv(); globalThis._jasmineMain = GLib.MainLoop.new(null, false); globalThis._jasmineRetval = 0; // Install Jasmine API on the global object let jasmineInterface = jasmineRequire.interface(jasmineCore, globalThis._jasmineEnv); Object.assign(globalThis, jasmineInterface); // Reporter that outputs according to the Test Anything Protocol // See http://testanything.org/tap-specification.html class TapReporter { constructor() { this._failedSuites = []; this._specCount = 0; } jasmineStarted(info) { print(`1..${info.totalSpecsDefined}`); } jasmineDone() { this._failedSuites.forEach(failure => { failure.failedExpectations.forEach(result => { print('not ok - An error was thrown outside a test'); print(`# ${result.message}`); }); }); globalThis._jasmineMain.quit(); } suiteDone(result) { if (result.failedExpectations && result.failedExpectations.length > 0) { globalThis._jasmineRetval = 1; this._failedSuites.push(result); } if (result.status === 'disabled') print('# Suite was disabled:', result.fullName); } specStarted() { this._specCount++; } specDone(result) { let tapReport; if (result.status === 'failed') { globalThis._jasmineRetval = 1; tapReport = 'not ok'; } else { tapReport = 'ok'; } tapReport += ` ${this._specCount} ${result.fullName}`; if (result.status === 'pending' || result.status === 'disabled') { let reason = result.pendingReason || result.status; tapReport += ` # SKIP ${reason}`; } print(tapReport); // Print additional diagnostic info on failure if (result.status === 'failed' && result.failedExpectations) { result.failedExpectations.forEach(failedExpectation => { print('# Message:', _removeNewlines(failedExpectation.message)); print('# Stack:'); let stackTrace = _filterStack(failedExpectation.stack).trim(); print(stackTrace.split('\n').map(str => `# ${str}`).join('\n')); }); } } } globalThis._jasmineEnv.addReporter(new TapReporter()); // If we're running the tests in certain JS_GC_ZEAL modes, then some will time // out if the CI machine is under a certain load. In that case increase the // default timeout. const gcZeal = GLib.getenv('JS_GC_ZEAL'); if (gcZeal && (gcZeal === '2' || gcZeal.startsWith('2,') || gcZeal === '4')) jasmine.DEFAULT_TIMEOUT_INTERVAL *= 5; cjs-5.2.0/installed-tests/js/testSignals.js0000644000175000017500000001016014144444702021026 0ustar jpeisachjpeisach/* eslint-disable no-restricted-properties */ const GLib = imports.gi.GLib; const Lang = imports.lang; const Signals = imports.signals; const Foo = new Lang.Class({ Name: 'Foo', Implements: [Signals.WithSignals], _init() {}, }); describe('Legacy object with signals', function () { testSignals(Foo); }); class FooWithoutSignals {} Signals.addSignalMethods(FooWithoutSignals.prototype); describe('Object with signals added', function () { testSignals(FooWithoutSignals); }); function testSignals(klass) { let foo, bar; beforeEach(function () { foo = new klass(); bar = jasmine.createSpy('bar'); }); it('calls a signal handler when a signal is emitted', function () { foo.connect('bar', bar); foo.emit('bar', 'This is a', 'This is b'); expect(bar).toHaveBeenCalledWith(foo, 'This is a', 'This is b'); }); it('does not call a signal handler after the signal is disconnected', function () { let id = foo.connect('bar', bar); foo.emit('bar', 'This is a', 'This is b'); bar.calls.reset(); foo.disconnect(id); // this emission should do nothing foo.emit('bar', 'Another a', 'Another b'); expect(bar).not.toHaveBeenCalled(); }); it('can disconnect a signal handler during signal emission', function () { var toRemove = []; let firstId = foo.connect('bar', function (theFoo) { theFoo.disconnect(toRemove[0]); theFoo.disconnect(toRemove[1]); }); toRemove.push(foo.connect('bar', bar)); toRemove.push(foo.connect('bar', bar)); // emit signal; what should happen is that the second two handlers are // disconnected before they get invoked foo.emit('bar'); expect(bar).not.toHaveBeenCalled(); // clean up the last handler foo.disconnect(firstId); // poke in private implementation to sanity-check no handlers left expect(foo._signalConnections.length).toEqual(0); }); it('distinguishes multiple signals', function () { let bonk = jasmine.createSpy('bonk'); foo.connect('bar', bar); foo.connect('bonk', bonk); foo.connect('bar', bar); foo.emit('bar'); expect(bar).toHaveBeenCalledTimes(2); expect(bonk).not.toHaveBeenCalled(); foo.emit('bonk'); expect(bar).toHaveBeenCalledTimes(2); expect(bonk).toHaveBeenCalledTimes(1); foo.emit('bar'); expect(bar).toHaveBeenCalledTimes(4); expect(bonk).toHaveBeenCalledTimes(1); foo.disconnectAll(); bar.calls.reset(); bonk.calls.reset(); // these post-disconnect emissions should do nothing foo.emit('bar'); foo.emit('bonk'); expect(bar).not.toHaveBeenCalled(); expect(bonk).not.toHaveBeenCalled(); }); it('determines if a signal is connected on a JS object', function () { let id = foo.connect('bar', bar); expect(foo.signalHandlerIsConnected(id)).toEqual(true); foo.disconnect(id); expect(foo.signalHandlerIsConnected(id)).toEqual(false); }); describe('with exception in signal handler', function () { let bar2; beforeEach(function () { bar.and.throwError('Exception we are throwing on purpose'); bar2 = jasmine.createSpy('bar'); foo.connect('bar', bar); foo.connect('bar', bar2); GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: Exception in callback for signal: *'); foo.emit('bar'); }); it('does not affect other callbacks', function () { expect(bar).toHaveBeenCalledTimes(1); expect(bar2).toHaveBeenCalledTimes(1); }); it('does not disconnect the callback', function () { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: Exception in callback for signal: *'); foo.emit('bar'); expect(bar).toHaveBeenCalledTimes(2); expect(bar2).toHaveBeenCalledTimes(2); }); }); } cjs-5.2.0/installed-tests/js/jsunit.gresources.xml0000644000175000017500000000164414144444702022415 0ustar jpeisachjpeisach complex3.ui complex4.ui jasmine.js minijasmine.js modules/alwaysThrows.js modules/badOverrides/GIMarshallingTests.js modules/badOverrides/Gio.js modules/badOverrides/Regress.js modules/badOverrides/WarnLib.js modules/foobar.js modules/lexicalScope.js modules/modunicode.js modules/mutualImport/a.js modules/mutualImport/b.js modules/overrides/GIMarshallingTests.js modules/subA/subB/__init__.js modules/subA/subB/baz.js modules/subA/subB/foobar.js cjs-5.2.0/installed-tests/js/testRegress.js0000644000175000017500000017613014144444702021052 0ustar jpeisachjpeisachconst Regress = imports.gi.Regress; // We use Gio to have some objects that we know exist imports.gi.versions.Gtk = '3.0'; const GLib = imports.gi.GLib; const Gio = imports.gi.Gio; const GObject = imports.gi.GObject; describe('Life, the Universe and Everything', function () { it('includes null return value', function () { expect(Regress.test_return_allow_none()).toBeNull(); expect(Regress.test_return_nullable()).toBeNull(); }); it('includes booleans', function () { expect(Regress.test_boolean(false)).toBe(false); expect(Regress.test_boolean(true)).toBe(true); expect(Regress.test_boolean_true(true)).toBe(true); expect(Regress.test_boolean_false(false)).toBe(false); }); [8, 16, 32, 64].forEach(bits => { it(`includes ${bits}-bit integers`, function () { const method = `test_int${bits}`; expect(Regress[method](42)).toBe(42); expect(Regress[method](-42)).toBe(-42); }); it(`includes unsigned ${bits}-bit integers`, function () { expect(Regress[`test_uint${bits}`](42)).toBe(42); }); }); ['short', 'int', 'long', 'ssize', 'float', 'double'].forEach(type => { it(`includes ${type}s`, function () { const method = `test_${type}`; expect(Regress[method](42)).toBe(42); expect(Regress[method](-42)).toBe(-42); }); }); ['ushort', 'uint', 'ulong', 'size'].forEach(type => { it(`includes ${type}s`, function () { expect(Regress[`test_${type}`](42)).toBe(42); }); }); describe('No implicit conversion to unsigned', function () { ['uint8', 'uint16', 'uint32', 'uint64', 'uint', 'size'].forEach(type => { it(`for ${type}`, function () { expect(() => Regress[`test_${type}`](-42)).toThrow(); }); }); }); it('includes wide characters', function () { expect(Regress.test_unichar('c')).toBe('c'); expect(Regress.test_unichar('')).toBe(''); expect(Regress.test_unichar('\u2665')).toBe('\u2665'); }); it('includes time_t', function () { const now = Math.floor(new Date().getTime() / 1000); const bounced = Math.floor(Regress.test_timet(now)); expect(bounced).toEqual(now); }); it('includes GTypes', function () { expect(Regress.test_gtype(GObject.TYPE_NONE)).toBe(GObject.TYPE_NONE); expect(Regress.test_gtype(String)).toBe(GObject.TYPE_STRING); expect(Regress.test_gtype(GObject.Object)).toBe(GObject.Object.$gtype); }); it('closures', function () { const callback = jasmine.createSpy('callback').and.returnValue(42); expect(Regress.test_closure(callback)).toEqual(42); expect(callback).toHaveBeenCalledWith(); }); it('closures with one argument', function () { const callback = jasmine.createSpy('callback') .and.callFake(someValue => someValue); expect(Regress.test_closure_one_arg(callback, 42)).toEqual(42); expect(callback).toHaveBeenCalledWith(42); }); it('closure with GLib.Variant argument', function () { const callback = jasmine.createSpy('callback') .and.returnValue(new GLib.Variant('s', 'hello')); const variant = new GLib.Variant('i', 42); expect(Regress.test_closure_variant(callback, variant).deepUnpack()) .toEqual('hello'); expect(callback).toHaveBeenCalledWith(variant); }); describe('GValue marshalling', function () { it('integer in', function () { expect(Regress.test_int_value_arg(42)).toEqual(42); }); it('integer out', function () { expect(Regress.test_value_return(42)).toEqual(42); }); }); // See testCairo.js for the following tests, since that will be skipped if // we are building without Cairo support: // Regress.test_cairo_context_full_return() // Regress.test_cairo_context_none_in() // Regress.test_cairo_surface_none_return() // Regress.test_cairo_surface_full_return() // Regress.test_cairo_surface_none_in() // Regress.test_cairo_surface_full_out() // Regress.TestObj.emit_sig_with_foreign_struct() it('integer GLib.Variant', function () { const ivar = Regress.test_gvariant_i(); expect(ivar.get_type_string()).toEqual('i'); expect(ivar.unpack()).toEqual(1); }); it('string GLib.Variant', function () { const svar = Regress.test_gvariant_s(); expect(String.fromCharCode(svar.classify())).toEqual('s'); expect(svar.unpack()).toEqual('one'); }); it('dictionary GLib.Variant', function () { const asvvar = Regress.test_gvariant_asv(); expect(asvvar.recursiveUnpack()).toEqual({name: 'foo', timeout: 10}); }); it('variant GLib.Variant', function () { const vvar = Regress.test_gvariant_v(); expect(vvar.unpack()).toEqual(jasmine.any(GLib.Variant)); expect(vvar.recursiveUnpack()).toEqual('contents'); }); it('string array GLib.Variant', function () { const asvar = Regress.test_gvariant_as(); expect(asvar.deepUnpack()).toEqual(['one', 'two', 'three']); }); describe('UTF-8 strings', function () { const CONST_STR = 'const ♥ utf8'; const NONCONST_STR = 'nonconst ♥ utf8'; it('as return types', function () { expect(Regress.test_utf8_const_return()).toEqual(CONST_STR); expect(Regress.test_utf8_nonconst_return()).toEqual(NONCONST_STR); }); it('as in parameters', function () { Regress.test_utf8_const_in(CONST_STR); }); it('as out parameters', function () { expect(Regress.test_utf8_out()).toEqual(NONCONST_STR); }); xit('as in-out parameters', function () { expect(Regress.test_utf8_inout(CONST_STR)).toEqual(NONCONST_STR); }).pend('https://gitlab.gnome.org/GNOME/gobject-introspection/issues/192'); }); it('return values in filename encoding', function () { const filenames = Regress.test_filename_return(); expect(filenames).toEqual(['\u00e5\u00e4\u00f6', '/etc/fstab']); }); describe('Various configurations of arguments', function () { it('in after out', function () { const str = 'hello'; const len = Regress.test_int_out_utf8(str); expect(len).toEqual(str.length); }); it('multiple number args', function () { const [times2, times3] = Regress.test_multi_double_args(2.5); expect(times2).toEqual(5); expect(times3).toEqual(7.5); }); it('multiple string out parameters', function () { const [first, second] = Regress.test_utf8_out_out(); expect(first).toEqual('first'); expect(second).toEqual('second'); }); it('strings as return value and output parameter', function () { const [first, second] = Regress.test_utf8_out_nonconst_return(); expect(first).toEqual('first'); expect(second).toEqual('second'); }); it('nullable string in parameter', function () { expect(() => Regress.test_utf8_null_in(null)).not.toThrow(); }); it('nullable string out parameter', function () { expect(Regress.test_utf8_null_out()).toBeNull(); }); }); ['int', 'gint8', 'gint16', 'gint32', 'gint64'].forEach(inttype => { it(`arrays of ${inttype} in`, function () { expect(Regress[`test_array_${inttype}_in`]([1, 2, 3, 4])).toEqual(10); }); }); it('implicit conversions from strings to int arrays', function () { expect(Regress.test_array_gint8_in('\x01\x02\x03\x04')).toEqual(10); expect(Regress.test_array_gint16_in('\x01\x02\x03\x04')).toEqual(10); expect(Regress.test_array_gint16_in('\u0100\u0200\u0300\u0400')).toEqual(2560); }); it('out arrays of integers', function () { expect(Regress.test_array_int_out()).toEqual([0, 1, 2, 3, 4]); }); xit('inout arrays of integers', function () { expect(Regress.test_array_int_inout([0, 1, 2, 3, 4])).toEqual([2, 3, 4, 5]); }).pend('https://gitlab.gnome.org/GNOME/gobject-introspection/issues/192'); describe('String arrays', function () { it('marshalling in', function () { expect(Regress.test_strv_in(['1', '2', '3'])).toBeTruthy(); expect(Regress.test_strv_in(['4', '5', '6'])).toBeFalsy(); // Ensure that primitives throw without SEGFAULT expect(() => Regress.test_strv_in(1)).toThrow(); expect(() => Regress.test_strv_in('')).toThrow(); expect(() => Regress.test_strv_in(false)).toThrow(); // Second two are deliberately not strings expect(() => Regress.test_strv_in(['1', 2, 3])).toThrow(); }); it('marshalling out', function () { expect(Regress.test_strv_out()) .toEqual(['thanks', 'for', 'all', 'the', 'fish']); }); it('marshalling return value with container transfer', function () { expect(Regress.test_strv_out_container()).toEqual(['1', '2', '3']); }); it('marshalling out parameter with container transfer', function () { expect(Regress.test_strv_outarg()).toEqual(['1', '2', '3']); }); }); it('GType arrays', function () { expect(Regress.test_array_gtype_in([Gio.SimpleAction, Gio.Icon, GObject.TYPE_BOXED])) .toEqual('[GSimpleAction,GIcon,GBoxed,]'); expect(() => Regress.test_array_gtype_in(42)).toThrow(); expect(() => Regress.test_array_gtype_in([undefined])).toThrow(); // 80 is G_TYPE_OBJECT, but we don't want it to work expect(() => Regress.test_array_gtype_in([80])).toThrow(); }); describe('Fixed arrays of integers', function () { it('marshals as an in parameter', function () { expect(Regress.test_array_fixed_size_int_in([1, 2, 3, 4])).toEqual(10); }); it('marshals as an out parameter', function () { expect(Regress.test_array_fixed_size_int_out()).toEqual([0, 1, 2, 3, 4]); }); it('marshals as a return value', function () { expect(Regress.test_array_fixed_size_int_return()).toEqual([0, 1, 2, 3, 4]); }); }); it("string array that's const in C", function () { expect(Regress.test_strv_out_c()).toEqual(['thanks', 'for', 'all', 'the', 'fish']); }); describe('arrays of integers with length parameter', function () { it('marshals as a return value with transfer full', function () { expect(Regress.test_array_int_full_out()).toEqual([0, 1, 2, 3, 4]); }); it('marshals as a return value with transfer none', function () { expect(Regress.test_array_int_none_out()).toEqual([1, 2, 3, 4, 5]); }); it('marshalls as a nullable in parameter', function () { expect(() => Regress.test_array_int_null_in(null)).not.toThrow(); }); it('marshals as a nullable return value', function () { expect(Regress.test_array_int_null_out()).toEqual([]); }); }); ['glist', 'gslist'].forEach(list => { describe(`${list} types`, function () { const STR_LIST = ['1', '2', '3']; it('return with transfer-none', function () { expect(Regress[`test_${list}_nothing_return`]()).toEqual(STR_LIST); expect(Regress[`test_${list}_nothing_return2`]()).toEqual(STR_LIST); }); it('return with transfer-container', function () { expect(Regress[`test_${list}_container_return`]()).toEqual(STR_LIST); }); it('return with transfer-full', function () { expect(Regress[`test_${list}_everything_return`]()).toEqual(STR_LIST); }); it('in with transfer-none', function () { Regress[`test_${list}_nothing_in`](STR_LIST); Regress[`test_${list}_nothing_in2`](STR_LIST); }); it('nullable in', function () { expect(() => Regress[`test_${list}_null_in`]([])).not.toThrow(); }); it('nullable out', function () { expect(Regress[`test_${list}_null_out`]()).toEqual([]); }); xit('in with transfer-container', function () { Regress[`test_${list}_container_in`](STR_LIST); }).pend('Function not added to gobject-introspection test suite yet'); }); }); it('GList of GTypes in with transfer container', function () { expect(() => Regress.test_glist_gtype_container_in([Regress.TestObj, Regress.TestSubObj])) .not.toThrow(); }); describe('GHash type', function () { const EXPECTED_HASH = {baz: 'bat', foo: 'bar', qux: 'quux'}; it('null GHash out', function () { expect(Regress.test_ghash_null_return()).toBeNull(); }); it('out GHash', function () { expect(Regress.test_ghash_nothing_return()).toEqual(EXPECTED_HASH); expect(Regress.test_ghash_nothing_return2()).toEqual(EXPECTED_HASH); }); const GVALUE_HASH_TABLE = { 'integer': 12, 'boolean': true, 'string': 'some text', 'strings': ['first', 'second', 'third'], 'flags': Regress.TestFlags.FLAG1 | Regress.TestFlags.FLAG3, 'enum': Regress.TestEnum.VALUE2, }; it('with GValue value type out', function () { expect(Regress.test_ghash_gvalue_return()).toEqual(GVALUE_HASH_TABLE); }); xit('with GValue value type in', function () { expect(() => Regress.test_ghash_gvalue_in(GVALUE_HASH_TABLE)).not.toThrow(); }).pend('https://gitlab.gnome.org/GNOME/gjs/issues/272'); it('marshals as a return value with transfer container', function () { expect(Regress.test_ghash_container_return()).toEqual(EXPECTED_HASH); }); it('marshals as a return value with transfer full', function () { expect(Regress.test_ghash_everything_return()).toEqual(EXPECTED_HASH); }); it('null GHash in', function () { Regress.test_ghash_null_in(null); }); it('null GHashTable out', function () { expect(Regress.test_ghash_null_out()).toBeNull(); }); it('in GHash', function () { Regress.test_ghash_nothing_in(EXPECTED_HASH); Regress.test_ghash_nothing_in2(EXPECTED_HASH); }); it('nested GHash', function () { const EXPECTED_NESTED_HASH = {wibble: EXPECTED_HASH}; expect(Regress.test_ghash_nested_everything_return()) .toEqual(EXPECTED_NESTED_HASH); expect(Regress.test_ghash_nested_everything_return2()) .toEqual(EXPECTED_NESTED_HASH); }); }); describe('GArray', function () { it('marshals as a return value with transfer container', function () { expect(Regress.test_garray_container_return()).toEqual(['regress']); }); it('marshals as a return value with transfer full', function () { expect(Regress.test_garray_full_return()).toEqual(['regress']); }); }); it('enum parameter', function () { expect(Regress.test_enum_param(Regress.TestEnum.VALUE1)).toEqual('value1'); expect(Regress.test_enum_param(Regress.TestEnum.VALUE3)).toEqual('value3'); }); it('unsigned enum parameter', function () { expect(Regress.test_unsigned_enum_param(Regress.TestEnumUnsigned.VALUE1)) .toEqual('value1'); expect(Regress.test_unsigned_enum_param(Regress.TestEnumUnsigned.VALUE2)) .toEqual('value2'); }); it('flags parameter', function () { expect(Regress.global_get_flags_out()).toEqual(Regress.TestFlags.FLAG1 | Regress.TestFlags.FLAG3); }); describe('Simple introspected struct', function () { let struct; beforeEach(function () { struct = new Regress.TestStructA(); struct.some_int = 42; struct.some_int8 = 43; struct.some_double = 42.5; struct.some_enum = Regress.TestEnum.VALUE3; }); it('sets fields correctly', function () { expect(struct.some_int).toEqual(42); expect(struct.some_int8).toEqual(43); expect(struct.some_double).toEqual(42.5); expect(struct.some_enum).toEqual(Regress.TestEnum.VALUE3); }); it('can clone', function () { const b = struct.clone(); expect(b.some_int).toEqual(42); expect(b.some_int8).toEqual(43); expect(b.some_double).toEqual(42.5); expect(b.some_enum).toEqual(Regress.TestEnum.VALUE3); }); it('can be modified by a method', function () { const c = Regress.TestStructA.parse('foobar'); expect(c.some_int).toEqual(23); }); describe('constructors', function () { beforeEach(function () { struct = new Regress.TestStructA({ some_int: 42, some_int8: 43, some_double: 42.5, some_enum: Regress.TestEnum.VALUE3, }); }); it('"copies" an object from a hash of field values', function () { expect(struct.some_int).toEqual(42); expect(struct.some_int8).toEqual(43); expect(struct.some_double).toEqual(42.5); expect(struct.some_enum).toEqual(Regress.TestEnum.VALUE3); }); it('catches bad field names', function () { expect(() => new Regress.TestStructA({junk: 42})).toThrow(); }); it('copies an object from another object of the same type', function () { const copy = new Regress.TestStructA(struct); expect(copy.some_int).toEqual(42); expect(copy.some_int8).toEqual(43); expect(copy.some_double).toEqual(42.5); expect(copy.some_enum).toEqual(Regress.TestEnum.VALUE3); }); }); }); it('out arrays of structs', function () { const array = Regress.test_array_struct_out(); const ints = array.map(struct => struct.some_int); expect(ints).toEqual([22, 33, 44]); }); describe('Introspected nested struct', function () { let struct; beforeEach(function () { struct = new Regress.TestStructB(); struct.some_int8 = 43; struct.nested_a.some_int8 = 66; }); it('sets fields correctly', function () { expect(struct.some_int8).toEqual(43); expect(struct.nested_a.some_int8).toEqual(66); }); it('can clone', function () { const b = struct.clone(); expect(b.some_int8).toEqual(43); expect(b.nested_a.some_int8).toEqual(66); }); }); // Bare GObject pointer, not currently supported (and possibly not ever) xdescribe('Struct with non-basic member', function () { it('sets fields correctly', function () { const struct = new Regress.TestStructC(); struct.another_int = 43; struct.obj = new GObject.Object(); expect(struct.another_int).toEqual(43); expect(struct.obj).toEqual(jasmine.any(GObject.Object)); }); }); describe('Struct with annotated fields', function () { xit('sets fields correctly', function () { const testObjList = [new Regress.TestObj(), new Regress.TestObj()]; const testStructList = [new Regress.TestStructA(), new Regress.TestStructA()]; const struct = new Regress.TestStructD(); struct.array1 = testStructList; struct.array2 = testObjList; struct.field = testObjList[0]; struct.list = testObjList; struct.garray = testObjList; expect(struct.array1).toEqual(testStructList); expect(struct.array2).toEqual(testObjList); expect(struct.field).toEqual(testObjList[0]); expect(struct.list).toEqual(testObjList); expect(struct.garray).toEqual(testObjList); }).pend('https://gitlab.gnome.org/GNOME/gjs/issues/83'); }); describe('Struct with array of anonymous unions', function () { xit('sets fields correctly', function () { const struct = new Regress.TestStructE(); struct.some_type = GObject.Object.$gtype; for (let ix = 0; ix < 1; ix++) { struct.some_union[ix].v_int = 42; struct.some_union[ix].v_uint = 43; struct.some_union[ix].v_long = 44; struct.some_union[ix].v_ulong = 45; struct.some_union[ix].v_int64 = 46; struct.some_union[ix].v_uint64 = 47; struct.some_union[ix].v_float = 48.5; struct.some_union[ix].v_double = 49.5; struct.some_union[ix].v_pointer = null; } expect(struct.some_type).toEqual(GObject.Object.$gtype); for (let ix = 0; ix < 1; ix++) { expect(struct.some_union[ix].v_int).toEqual(42); expect(struct.some_union[ix].v_uint).toEqual(43); expect(struct.some_union[ix].v_long).toEqual(44); expect(struct.some_union[ix].v_ulong).toEqual(45); expect(struct.some_union[ix].v_int64).toEqual(46); expect(struct.some_union[ix].v_uint64).toEqual(47); expect(struct.some_union[ix].v_float).toEqual(48.5); expect(struct.some_union[ix].v_double).toEqual(49.5); expect(struct.some_union[ix].v_pointer).toBeNull(); } }).pend('https://gitlab.gnome.org/GNOME/gjs/issues/273'); }); // Bare int pointers, not currently supported (and possibly not ever) xdescribe('Struct with const/volatile members', function () { it('sets fields correctly', function () { const struct = new Regress.TestStructF(); struct.ref_count = 1; struct.data1 = null; struct.data2 = null; struct.data3 = null; struct.data4 = null; struct.data5 = null; struct.data6 = null; struct.data7 = 42; expect(struct.ref_count).toEqual(1); expect(struct.data1).toBeNull(); expect(struct.data2).toBeNull(); expect(struct.data3).toBeNull(); expect(struct.data4).toBeNull(); expect(struct.data5).toBeNull(); expect(struct.data6).toBeNull(); expect(struct.data7).toEqual(42); }); }); describe('Introspected simple boxed struct', function () { let struct; beforeEach(function () { struct = new Regress.TestSimpleBoxedA(); struct.some_int = 42; struct.some_int8 = 43; struct.some_double = 42.5; struct.some_enum = Regress.TestEnum.VALUE3; }); it('sets fields correctly', function () { expect(struct.some_int).toEqual(42); expect(struct.some_int8).toEqual(43); expect(struct.some_double).toEqual(42.5); expect(struct.some_enum).toEqual(Regress.TestEnum.VALUE3); }); it('can be passed to a method', function () { const other = new Regress.TestSimpleBoxedA({ some_int: 42, some_int8: 43, some_double: 42.5, }); expect(other.equals(struct)).toBeTruthy(); }); it('can be returned from a method', function () { const other = Regress.TestSimpleBoxedA.const_return(); expect(other.some_int).toEqual(5); expect(other.some_int8).toEqual(6); expect(other.some_double).toEqual(7); }); describe('constructors', function () { beforeEach(function () { struct = new Regress.TestSimpleBoxedA({ some_int: 42, some_int8: 43, some_double: 42.5, some_enum: Regress.TestEnum.VALUE3, }); }); it('"copies" an object from a hash of field values', function () { expect(struct.some_int).toEqual(42); expect(struct.some_int8).toEqual(43); expect(struct.some_double).toEqual(42.5); expect(struct.some_enum).toEqual(Regress.TestEnum.VALUE3); }); it('catches bad field names', function () { expect(() => new Regress.TestSimpleBoxedA({junk: 42})).toThrow(); }); it('copies an object from another object of the same type', function () { const copy = new Regress.TestSimpleBoxedA(struct); expect(copy).toEqual(jasmine.any(Regress.TestSimpleBoxedA)); expect(copy.some_int).toEqual(42); expect(copy.some_int8).toEqual(43); expect(copy.some_double).toEqual(42.5); expect(copy.some_enum).toEqual(Regress.TestEnum.VALUE3); }); }); }); describe('Introspected boxed nested struct', function () { let struct; beforeEach(function () { struct = new Regress.TestSimpleBoxedB(); struct.some_int8 = 42; struct.nested_a.some_int = 43; }); it('reads fields and nested fields', function () { expect(struct.some_int8).toEqual(42); expect(struct.nested_a.some_int).toEqual(43); }); it('assigns nested struct field from an instance', function () { struct.nested_a = new Regress.TestSimpleBoxedA({some_int: 53}); expect(struct.nested_a.some_int).toEqual(53); }); it('assigns nested struct field directly from a hash of field values', function () { struct.nested_a = {some_int: 63}; expect(struct.nested_a.some_int).toEqual(63); }); describe('constructors', function () { it('constructs with a nested hash of field values', function () { const simple2 = new Regress.TestSimpleBoxedB({ some_int8: 42, nested_a: { some_int: 43, some_int8: 44, some_double: 43.5, }, }); expect(simple2.some_int8).toEqual(42); expect(simple2.nested_a.some_int).toEqual(43); expect(simple2.nested_a.some_int8).toEqual(44); expect(simple2.nested_a.some_double).toEqual(43.5); }); it('copies an object from another object of the same type', function () { const copy = new Regress.TestSimpleBoxedB(struct); expect(copy.some_int8).toEqual(42); expect(copy.nested_a.some_int).toEqual(43); }); }); }); describe('Introspected boxed types', function () { describe('Opaque', function () { it('constructs from a default constructor', function () { const boxed = new Regress.TestBoxed(); expect(boxed).toEqual(jasmine.any(Regress.TestBoxed)); }); it('sets fields correctly', function () { const boxed = new Regress.TestBoxed(); boxed.some_int8 = 42; expect(boxed.some_int8).toEqual(42); }); it('constructs from a static constructor', function () { const boxed = Regress.TestBoxed.new_alternative_constructor1(42); expect(boxed.some_int8).toEqual(42); }); it('constructs from a static constructor with different args', function () { const boxed = Regress.TestBoxed.new_alternative_constructor2(40, 2); expect(boxed.some_int8).toEqual(42); }); it('constructs from a static constructor with differently typed args', function () { const boxed = Regress.TestBoxed.new_alternative_constructor3('42'); expect(boxed.some_int8).toEqual(42); }); it('constructs from a another object of the same type', function () { const boxed = new Regress.TestBoxed({some_int8: 42}); const copy = new Regress.TestBoxed(boxed); expect(copy.some_int8).toEqual(42); expect(copy.equals(boxed)).toBeTruthy(); }); it('ensures methods are named correctly', function () { const boxed = new Regress.TestBoxed(); expect(boxed.s_not_a_method).not.toBeDefined(); expect(boxed.not_a_method).not.toBeDefined(); expect(() => Regress.test_boxeds_not_a_method(boxed)).not.toThrow(); }); it('ensures static methods are named correctly', function () { expect(Regress.TestBoxed.s_not_a_static).not.toBeDefined(); expect(Regress.TestBoxed.not_a_static).not.toBeDefined(); expect(Regress.test_boxeds_not_a_static).not.toThrow(); }); }); describe('Simple', function () { it('sets fields correctly', function () { const boxed = new Regress.TestBoxedB(); boxed.some_int8 = 7; boxed.some_long = 5; expect(boxed.some_int8).toEqual(7); expect(boxed.some_long).toEqual(5); }); it('constructs from a static constructor', function () { const boxed = Regress.TestBoxedB.new(7, 5); expect(boxed.some_int8).toEqual(7); expect(boxed.some_long).toEqual(5); }); it('constructs from another object of the same type', function () { const boxed = Regress.TestBoxedB.new(7, 5); const copy = new Regress.TestBoxedB(boxed); expect(copy.some_int8).toEqual(7); expect(copy.some_long).toEqual(5); }); // Regress.TestBoxedB has a constructor that takes multiple arguments, // but since it is directly allocatable, we keep the old style of // passing an hash of fields. The two real world structs that have this // behavior are Clutter.Color and Clutter.ActorBox. it('constructs in backwards compatibility mode', function () { const boxed = new Regress.TestBoxedB({some_int8: 7, some_long: 5}); expect(boxed.some_int8).toEqual(7); expect(boxed.some_long).toEqual(5); }); }); describe('Refcounted', function () { it('constructs from a default constructor', function () { const boxed = new Regress.TestBoxedC(); expect(boxed.another_thing).toEqual(42); }); it('constructs from another object of the same type', function () { const boxed = new Regress.TestBoxedC({another_thing: 43}); const copy = new Regress.TestBoxedC(boxed); expect(copy.another_thing).toEqual(43); }); }); describe('Private', function () { it('constructs using a custom constructor', function () { const boxed = new Regress.TestBoxedD('abcd', 8); expect(boxed.get_magic()).toEqual(12); }); it('constructs from another object of the same type', function () { const boxed = new Regress.TestBoxedD('abcd', 8); const copy = new Regress.TestBoxedD(boxed); expect(copy.get_magic()).toEqual(12); }); it('does not construct with a default constructor', function () { expect(() => new Regress.TestBoxedD()).toThrow(); }); }); }); describe('wrong type for GBoxed', function () { let simpleBoxed, wrongObject, wrongBoxed; beforeEach(function () { simpleBoxed = new Regress.TestSimpleBoxedA(); wrongObject = new Gio.SimpleAction(); wrongBoxed = new GLib.KeyFile(); }); // simpleBoxed.equals expects a Everything.TestSimpleBoxedA it('function does not accept a GObject of the wrong type', function () { expect(() => simpleBoxed.equals(wrongObject)).toThrow(); }); it('function does not accept a GBoxed of the wrong type', function () { expect(() => simpleBoxed.equals(wrongBoxed)).toThrow(); }); it('function does accept a GBoxed of the correct type', function () { expect(simpleBoxed.equals(simpleBoxed)).toBeTruthy(); }); it('method cannot be called on a GObject', function () { expect(() => Regress.TestSimpleBoxedA.prototype.copy.call(wrongObject)) .toThrow(); }); it('method cannot be called on a GBoxed of the wrong type', function () { expect(() => Regress.TestSimpleBoxedA.protoype.copy.call(wrongBoxed)) .toThrow(); }); it('method can be called on correct GBoxed type', function () { expect(() => Regress.TestSimpleBoxedA.prototype.copy.call(simpleBoxed)) .not.toThrow(); }); }); describe('Introspected GObject', function () { let o; beforeEach(function () { o = new Regress.TestObj({ // These properties have backing public fields with different names int: 42, float: 3.1416, double: 2.71828, }); }); it('can access fields with simple types', function () { // Compare the values gotten through the GObject property getters to the // values of the backing fields expect(o.some_int8).toEqual(o.int); expect(o.some_float).toEqual(o.float); expect(o.some_double).toEqual(o.double); }); it('cannot access fields with complex types (GI limitation)', function () { expect(() => o.parent_instance).toThrow(); expect(() => o.function_ptr).toThrow(); }); it('throws when setting a read-only field', function () { expect(() => (o.some_int8 = 41)).toThrow(); }); it('has normal Object methods', function () { o.ownprop = 'foo'; // eslint-disable-next-line no-prototype-builtins expect(o.hasOwnProperty('ownprop')).toBeTruthy(); }); // it('sets write-only properties', function () { // expect(o.int).not.toEqual(0); // o.write_only = true; // expect(o.int).toEqual(0); // }); // it('gives undefined for write-only properties', function () { // expect(o.write_only).not.toBeDefined(); // }); it('constructs from constructors annotated with (constructor)', function () { expect(Regress.TestObj.new(o)).toEqual(jasmine.any(Regress.TestObj)); expect(Regress.TestObj.constructor()).toEqual(jasmine.any(Regress.TestObj)); }); it('static methods', function () { const v = Regress.TestObj.new_from_file('/enoent'); expect(v).toEqual(jasmine.any(Regress.TestObj)); }); describe('Object-valued GProperty', function () { let o1, t1, t2; beforeEach(function () { o1 = new GObject.Object(); t1 = new Regress.TestObj({bare: o1}); t2 = new Regress.TestSubObj(); t2.bare = o1; }); it('marshals correctly in the getter', function () { expect(t1.bare).toBe(o1); }); it('marshals correctly when inherited', function () { expect(t2.bare).toBe(o1); }); it('marshals into setter function', function () { const o2 = new GObject.Object(); t2.set_bare(o2); expect(t2.bare).toBe(o2); }); it('marshals null', function () { t2.unset_bare(); expect(t2.bare).toBeNull(); }); }); describe('Signal connection', function () { it('calls correct handlers with correct arguments', function () { const handler = jasmine.createSpy('handler'); const handlerId = o.connect('test', handler); handler.and.callFake(() => o.disconnect(handlerId)); o.emit('test'); expect(handler).toHaveBeenCalledTimes(1); expect(handler).toHaveBeenCalledWith(o); handler.calls.reset(); o.emit('test'); expect(handler).not.toHaveBeenCalled(); }); it('throws errors for invalid signals', function () { expect(() => o.connect('invalid-signal', () => {})).toThrow(); expect(() => o.emit('invalid-signal')).toThrow(); }); it('signal handler with static scope arg gets arg passed by reference', function () { const b = new Regress.TestSimpleBoxedA({ some_int: 42, some_int8: 43, some_double: 42.5, some_enum: Regress.TestEnum.VALUE3, }); o.connect('test-with-static-scope-arg', (signalObject, signalArg) => { signalArg.some_int = 44; }); o.emit('test-with-static-scope-arg', b); expect(b.some_int).toEqual(44); }); it('signal with object gets correct arguments', function (done) { o.connect('sig-with-obj', (self, objectParam) => { expect(objectParam.int).toEqual(3); done(); }); o.emit_sig_with_obj(); }); // See testCairo.js for a test of // Regress.TestObj::sig-with-foreign-struct. xit('signal with int64 gets correct value', function (done) { o.connect('sig-with-int64-prop', (self, number) => { expect(number).toEqual(GLib.MAXINT64); done(); return GLib.MAXINT64; }); o.emit_sig_with_int64(); }).pend('https://gitlab.gnome.org/GNOME/gjs/issues/271'); xit('signal with uint64 gets correct value', function (done) { o.connect('sig-with-uint64-prop', (self, number) => { expect(number).toEqual(GLib.MAXUINT64); done(); return GLib.MAXUINT64; }); o.emit_sig_with_uint64(); }).pend('https://gitlab.gnome.org/GNOME/gjs/issues/271'); it('signal with array len parameter is not passed correct array and no length arg', function (done) { o.connect('sig-with-array-len-prop', (signalObj, signalArray, shouldBeUndefined) => { expect(shouldBeUndefined).not.toBeDefined(); expect(signalArray).toEqual([0, 1, 2, 3, 4]); done(); }); o.emit_sig_with_array_len_prop(); }); xit('can pass parameter to signal with array len parameter via emit', function (done) { o.connect('sig-with-array-len-prop', (signalObj, signalArray) => { expect(signalArray).toEqual([0, 1, 2, 3, 4]); done(); }); o.emit('sig-with-array-len-prop', [0, 1, 2, 3, 4]); }).pend('Not yet implemented'); xit('can pass null to signal with array len parameter', function () { const handler = jasmine.createSpy('handler'); o.connect('sig-with-array-len-prop', handler); o.emit('sig-with-array-len-prop', null); expect(handler).toHaveBeenCalledWith([jasmine.any(Object), null]); }).pend('Not yet implemented'); xit('signal with int in-out parameter', function () { const handler = jasmine.createSpy('handler').and.callFake(() => 43); o.connect('sig-with-inout-int', handler); o.emit_sig_with_inout_int(); expect(handler.toHaveBeenCalledWith([jasmine.any(Object), 42])); }).pend('Not yet implemented'); // it('GError signal with GError set', function (done) { // o.connect('sig-with-gerror', (obj, e) => { // expect(e).toEqual(jasmine.any(Gio.IOErrorEnum)); // expect(e.domain).toEqual(Gio.io_error_quark()); // expect(e.code).toEqual(Gio.IOErrorEnum.FAILED); // done(); // }); // o.emit_sig_with_error(); // }); // it('GError signal with no GError set', function (done) { // o.connect('sig-with-gerror', (obj, e) => { // expect(e).toBeNull(); // done(); // }); // o.emit_sig_with_null_error(); // }); }); it('can call an instance method', function () { expect(o.instance_method()).toEqual(-1); }); it('can call a transfer-full instance method', function () { expect(() => o.instance_method_full()).not.toThrow(); }); it('can call a static method', function () { expect(Regress.TestObj.static_method(5)).toEqual(5); }); it('can call a method annotated with (method)', function () { expect(() => o.forced_method()).not.toThrow(); }); describe('Object torture signature', function () { it('0', function () { const [y, z, q] = o.torture_signature_0(42, 'foo', 7); expect(Math.floor(y)).toEqual(42); expect(z).toEqual(84); expect(q).toEqual(10); }); it('1 fail', function () { expect(() => o.torture_signature_1(42, 'foo', 7)).toThrow(); }); it('1 success', function () { const [, y, z, q] = o.torture_signature_1(11, 'barbaz', 8); expect(Math.floor(y)).toEqual(11); expect(z).toEqual(22); expect(q).toEqual(14); }); }); describe('Introspected function length', function () { it('skips over instance parameters of methods', function () { expect(o.set_bare.length).toEqual(1); }); it('skips over out and GError parameters', function () { expect(o.torture_signature_1.length).toEqual(3); }); it('does not skip over inout parameters', function () { expect(o.skip_return_val.length).toEqual(5); }); xit('skips over return value annotated with skip', function () { const [b, d, sum] = o.skip_return_val(1, 2, 3, 4, 5); expect(b).toEqual(2); expect(d).toEqual(4); expect(sum).toEqual(54); const retval = o.skip_return_val_no_out(1); expect(retval).not.toBeDefined(); }).pend('https://gitlab.gnome.org/GNOME/gjs/issues/59'); xit('skips over parameters annotated with skip', function () { expect(o.skip_param.length).toEqual(4); const [success, b, d, sum] = o.skip_param(1, 2, 3, 4); expect(success).toBeTruthy(); expect(b).toEqual(2); expect(d).toEqual(3); expect(sum).toEqual(43); }).pend('https://gitlab.gnome.org/GNOME/gjs/issues/59'); xit('skips over out parameters annotated with skip', function () { const [success, d, sum] = o.skip_out_param(1, 2, 3, 4, 5); expect(success).toBeTruthy(); expect(d).toEqual(4); expect(sum).toEqual(54); }).pend('https://gitlab.gnome.org/GNOME/gjs/issues/59'); xit('skips over inout parameters annotated with skip', function () { expect(o.skip_inout_param.length).toEqual(4); const [success, b, sum] = o.skip_inout_param(1, 2, 3, 4); expect(success).toBeTruthy(); expect(b).toEqual(2); expect(sum).toEqual(43); }).pend('https://gitlab.gnome.org/GNOME/gjs/issues/59'); it('gives number of arguments for static methods', function () { expect(Regress.TestObj.new_from_file.length).toEqual(1); }); it('skips over destroy-notify and user-data parameters', function () { expect(Regress.TestObj.new_callback.length).toEqual(1); }); }); it('virtual function', function () { expect(o.do_matrix('meaningless string')).toEqual(42); }); describe('wrong type for GObject', function () { let wrongObject, wrongBoxed, subclassObject; beforeEach(function () { wrongObject = new Gio.SimpleAction(); wrongBoxed = new GLib.KeyFile(); subclassObject = new Regress.TestSubObj(); }); // Regress.func_obj_null_in expects a Regress.TestObj it('function does not accept a GObject of the wrong type', function () { expect(() => Regress.func_obj_null_in(wrongObject)).toThrow(); }); it('function does not accept a GBoxed instead of GObject', function () { expect(() => Regress.func_obj_null_in(wrongBoxed)).toThrow(); }); it('function does not accept returned GObject of the wrong type', function () { const wrongReturnedObject = Gio.File.new_for_path('/'); expect(() => Regress.func_obj_null_in(wrongReturnedObject)).toThrow(); }); it('function accepts GObject of subclass of expected type', function () { expect(() => Regress.func_obj_null_in(subclassObject)).not.toThrow(); }); it('method cannot be called on a GObject of the wrong type', function () { expect(() => Regress.TestObj.prototype.instance_method.call(wrongObject)) .toThrow(); }); it('method cannot be called on a GBoxed', function () { expect(() => Regress.TestObj.prototype.instance_method.call(wrongBoxed)) .toThrow(); }); it('method can be called on a GObject of subclass of expected type', function () { expect(() => Regress.TestObj.prototype.instance_method.call(subclassObject)) .not.toThrow(); }); }); it('marshals a null object in', function () { expect(() => Regress.func_obj_null_in(null)).not.toThrow(); expect(() => Regress.func_obj_nullable_in(null)).not.toThrow(); }); it('marshals a null object out', function () { expect(Regress.TestObj.null_out()).toBeNull(); }); it('marshals a gpointer with a type annotation in', function () { const o2 = new GObject.Object(); expect(() => o.not_nullable_typed_gpointer_in(o2)).not.toThrow(); }); it('marshals a gpointer with an element-type annotation in', function () { expect(() => o.not_nullable_element_typed_gpointer_in([1, 2])).not.toThrow(); }); // This test is not meant to be normative; a GObject behaving like this is // doing something unsupported. However, we have been handling this so far // in a certain way, and we don't want to break user code because of badly // behaved libraries. This test ensures that any change to the behaviour // must be intentional. it('resolves properties when they are shadowed by methods', function () { expect(o.name_conflict).toEqual(42); expect(o.name_conflict).not.toEqual(jasmine.any(Function)); }); }); it('marshals a fixed-size array of objects out', function () { expect(Regress.test_array_fixed_out_objects()).toEqual([ jasmine.any(Regress.TestObj), jasmine.any(Regress.TestObj), ]); }); describe('Inherited GObject', function () { let subobj; beforeEach(function () { subobj = new Regress.TestSubObj({ int: 42, float: Math.PI, double: Math.E, }); }); it('can read fields from a parent class', function () { // see "can access fields with simple types" above expect(subobj.some_int8).toEqual(subobj.int); expect(subobj.some_float).toEqual(subobj.float); expect(subobj.some_double).toEqual(subobj.double); }); it('can be constructed from a static constructor', function () { expect(Regress.TestSubObj.new).not.toThrow(); }); it('can call an instance method that overrides the parent class', function () { expect(subobj.instance_method()).toEqual(0); }); }); // describe('Overridden properties on interfaces', function () { // it('set and get properly', function () { // const o = new Regress.TestSubObj(); // o.number = 4; // expect(o.number).toEqual(4); // }); // it('default properly', function () { // const o = new Regress.TestSubObj(); // expect(o.number).toBeDefined(); // expect(o.number).toEqual(0); // }); // it('construct properly', function () { // const o = new Regress.TestSubObj({number: 4}); // expect(o.number).toEqual(4); // }); // }); describe('Fundamental type', function () { it('constructs a subtype of a fundamental type', function () { expect(() => new Regress.TestFundamentalSubObject('plop')).not.toThrow(); }); it('constructs a subtype of a hidden (no introspection data) fundamental type', function () { expect(() => Regress.test_create_fundamental_hidden_class_instance()).not.toThrow(); }); }); it('callbacks', function () { const callback = jasmine.createSpy('callback').and.returnValue(42); expect(Regress.test_callback(callback)).toEqual(42); }); it('null / undefined callback', function () { expect(Regress.test_callback(null)).toEqual(0); expect(() => Regress.test_callback(undefined)).toThrow(); }); it('callback called more than once', function () { const callback = jasmine.createSpy('callback').and.returnValue(21); expect(Regress.test_multi_callback(callback)).toEqual(42); expect(callback).toHaveBeenCalledTimes(2); }); it('null callback called more than once', function () { expect(Regress.test_multi_callback(null)).toEqual(0); }); it('array callbacks', function () { const callback = jasmine.createSpy('callback').and.returnValue(7); expect(Regress.test_array_callback(callback)).toEqual(14); expect(callback).toHaveBeenCalledWith([-1, 0, 1, 2], ['one', 'two', 'three']); }); it('null array callback', function () { expect(() => Regress.test_array_callback(null)).toThrow(); }); xit('callback with inout array', function () { const callback = jasmine.createSpy('callback').and.callFake(arr => arr.slice(1)); expect(Regress.test_array_inout_callback(callback)).toEqual(3); expect(callback).toHaveBeenCalledWith([-2, -1, 0, 1, 2], [-1, 0, 1, 2]); }); // assertion failed, "Use gjs_value_from_explicit_array() for arrays with length param"" ['simple', 'noptr'].forEach(type => { it(`${type} callback`, function () { const callback = jasmine.createSpy('callback'); Regress[`test_${type}_callback`](callback); expect(callback).toHaveBeenCalled(); }); it('null simple callback', function () { expect(() => Regress[`test_${type}_callback`](null)).not.toThrow(); }); }); it('callback with user data', function () { const callback = jasmine.createSpy('callback').and.returnValue(7); expect(Regress.test_callback_user_data(callback)).toEqual(7); expect(callback).toHaveBeenCalled(); }); it('callback with transfer-full return value', function () { const callback = jasmine.createSpy('callback') .and.returnValue(Regress.TestObj.new_from_file('/enoent')); Regress.test_callback_return_full(callback); expect(callback).toHaveBeenCalled(); }); it('callback with destroy-notify', function () { const callback1 = jasmine.createSpy('callback').and.returnValue(42); const callback2 = jasmine.createSpy('callback').and.returnValue(58); expect(Regress.test_callback_destroy_notify(callback1)).toEqual(42); expect(callback1).toHaveBeenCalledTimes(1); expect(Regress.test_callback_destroy_notify(callback2)).toEqual(58); expect(callback2).toHaveBeenCalledTimes(1); expect(Regress.test_callback_thaw_notifications()).toEqual(100); expect(callback1).toHaveBeenCalledTimes(2); expect(callback2).toHaveBeenCalledTimes(2); }); xit('callback with destroy-notify and no user data', function () { const callback1 = jasmine.createSpy('callback').and.returnValue(42); const callback2 = jasmine.createSpy('callback').and.returnValue(58); expect(Regress.test_callback_destroy_notify_no_user_data(callback1)).toEqual(42); expect(callback1).toHaveBeenCalledTimes(1); expect(Regress.test_callback_destroy_notify_no_user_data(callback2)).toEqual(58); expect(callback2).toHaveBeenCalledTimes(1); expect(Regress.test_callback_thaw_notifications()).toEqual(100); expect(callback1).toHaveBeenCalledTimes(2); expect(callback2).toHaveBeenCalledTimes(2); }).pend('Callback with destroy-notify and no user data not currently supported'); it('async callback', function () { Regress.test_callback_async(() => 44); expect(Regress.test_callback_thaw_async()).toEqual(44); }); it('Gio.AsyncReadyCallback', function (done) { Regress.test_async_ready_callback((obj, res) => { expect(obj).toBeNull(); expect(res).toEqual(jasmine.any(Gio.SimpleAsyncResult)); done(); }); }); it('instance method taking a callback', function () { const o = new Regress.TestObj(); const callback = jasmine.createSpy('callback'); o.instance_method_callback(callback); expect(callback).toHaveBeenCalled(); }); it('constructor taking a callback', function () { const callback = jasmine.createSpy('callback').and.returnValue(42); void Regress.TestObj.new_callback(callback); expect(callback).toHaveBeenCalled(); expect(Regress.test_callback_thaw_notifications()).toEqual(42); expect(callback).toHaveBeenCalledTimes(2); }); it('hash table passed to callback', function () { const hashtable = { a: 1, b: 2, c: 3, }; const callback = jasmine.createSpy('callback'); Regress.test_hash_table_callback(hashtable, callback); expect(callback).toHaveBeenCalledWith(hashtable); }); it('GError callback', function (done) { Regress.test_gerror_callback(e => { expect(e).toEqual(jasmine.any(Gio.IOErrorEnum)); expect(e.domain).toEqual(Gio.io_error_quark()); expect(e.code).toEqual(Gio.IOErrorEnum.NOT_SUPPORTED); done(); }); }); it('null GError callback', function () { const callback = jasmine.createSpy('callback'); Regress.test_null_gerror_callback(callback); expect(callback).toHaveBeenCalledWith(null); }); it('owned GError callback', function (done) { Regress.test_owned_gerror_callback(e => { expect(e).toEqual(jasmine.any(Gio.IOErrorEnum)); expect(e.domain).toEqual(Gio.io_error_quark()); expect(e.code).toEqual(Gio.IOErrorEnum.PERMISSION_DENIED); done(); }); }); // describe('Introspected interface', function () { // const Implementor = GObject.registerClass({ // Implements: [Regress.TestInterface], // Properties: { // number: GObject.ParamSpec.override('number', Regress.TestInterface), // }, // }, class Implementor extends GObject.Object { // get number() { // return 5; // } // }); // it('correctly emits interface signals', function () { // const obj = new Implementor(); // const handler = jasmine.createSpy('handler').and.callFake(() => {}); // obj.connect('interface-signal', handler); // obj.emit_signal(); // expect(handler).toHaveBeenCalled(); // }); // }); describe('GObject with nonstandard prefix', function () { let o; beforeEach(function () { o = new Regress.TestWi8021x(); }); it('sets and gets properties', function () { expect(o.testbool).toBeTruthy(); o.testbool = false; expect(o.testbool).toBeFalsy(); }); it('constructs via a static constructor', function () { expect(Regress.TestWi8021x.new()).toEqual(jasmine.any(Regress.TestWi8021x)); }); it('calls methods', function () { expect(o.get_testbool()).toBeTruthy(); o.set_testbool(false); expect(o.get_testbool()).toBeFalsy(); }); it('calls a static method', function () { expect(Regress.TestWi8021x.static_method(21)).toEqual(42); }); }); describe('GObject.InitiallyUnowned', function () { it('constructs', function () { expect(new Regress.TestFloating()).toEqual(jasmine.any(Regress.TestFloating)); }); it('constructs via a static constructor', function () { expect(Regress.TestFloating.new()).toEqual(jasmine.any(Regress.TestFloating)); }); }); it('torture signature 0', function () { const [y, z, q] = Regress.test_torture_signature_0(42, 'foo', 7); expect(Math.floor(y)).toEqual(42); expect(z).toEqual(84); expect(q).toEqual(10); }); it('torture signature 1 fail', function () { expect(() => Regress.test_torture_signature_1(42, 'foo', 7)).toThrow(); }); it('torture signature 1 success', function () { const [, y, z, q] = Regress.test_torture_signature_1(11, 'barbaz', 8); expect(Math.floor(y)).toEqual(11); expect(z).toEqual(22); expect(q).toEqual(14); }); it('torture signature 2', function () { const [y, z, q] = Regress.test_torture_signature_2(42, () => 0, 'foo', 7); expect(Math.floor(y)).toEqual(42); expect(z).toEqual(84); expect(q).toEqual(10); }); describe('GValue boxing and unboxing', function () { it('date in', function () { const date = Regress.test_date_in_gvalue(); expect(date.get_year()).toEqual(1984); expect(date.get_month()).toEqual(GLib.DateMonth.DECEMBER); expect(date.get_day()).toEqual(5); }); it('strv in', function () { expect(Regress.test_strv_in_gvalue()).toEqual(['one', 'two', 'three']); }); it('correctly converts a NULL strv in a GValue to an empty array', function () { expect(Regress.test_null_strv_in_gvalue()).toEqual([]); }); }); it("code coverage for documentation tests that don't do anything", function () { expect(() => { Regress.test_multiline_doc_comments(); Regress.test_nested_parameter(5); Regress.test_versioning(); }).not.toThrow(); }); it('marshals an aliased type', function () { // GLib.PtrArray is not introspectable, so neither is an alias of it // Regress.introspectable_via_alias(new GLib.PtrArray()); expect(Regress.aliased_caller_alloc()).toEqual(jasmine.any(Regress.TestBoxed)); }); it('deals with a fixed-size array in a struct', function () { const struct = new Regress.TestStructFixedArray(); struct.frob(); expect(struct.just_int).toEqual(7); expect(struct.array).toEqual([42, 43, 44, 45, 46, 47, 48, 49, 50, 51]); }); it('marshals a fixed-size int array as a gpointer', function () { expect(() => Regress.has_parameter_named_attrs(0, Array(32).fill(42))).not.toThrow(); }); it('deals with a fixed-size and also zero-terminated array in a struct', function () { const x = new Regress.LikeXklConfigItem(); x.set_name('foo'); expect(x.name).toEqual([...'foo'].map(c => c.codePointAt()).concat(Array(29).fill(0))); x.set_name('*'.repeat(33)); expect(x.name).toEqual(Array(31).fill('*'.codePointAt()).concat([0])); }); it('marshals a transfer-floating GLib.Variant', function () { expect(Regress.get_variant().unpack()).toEqual(42); }); // describe('Flat array of structs', function () { // it('out parameter with transfer none', function () { // const expected = [111, 222, 333].map(some_int => // jasmine.objectContaining({some_int})); // expect(Regress.test_array_struct_out_none()).toEqual(expected); // }); // it('out parameter with transfer container', function () { // const expected = [11, 13, 17, 19, 23].map(some_int => // jasmine.objectContaining({some_int})); // expect(Regress.test_array_struct_out_container()).toEqual(expected); // }); // it('out parameter with transfer full', function () { // const expected = [2, 3, 5, 7].map(some_int => // jasmine.objectContaining({some_int})); // expect(Regress.test_array_struct_out_full_fixed()).toEqual(expected); // }); // xit('caller-allocated out parameter', function () { // // With caller-allocated array in, there's no way to supply the // // length. This happens in GLib.MainContext.query() // expect(Regress.test_array_struct_out_caller_alloc()).toEqual([]); // }).pend('Not supported'); // it('transfer-full in parameter', function () { // const array = [201, 202].map(some_int => // new Regress.TestStructA({some_int})); // expect(() => Regress.test_array_struct_in_full(array)).not.toThrow(); // }); // it('transfer-none in parameter', function () { // const array = [301, 302, 303].map(some_int => // new Regress.TestStructA({some_int})); // expect(() => Regress.test_array_struct_in_none(array)).not.toThrow(); // }); // }); }); cjs-5.2.0/installed-tests/js/testGIMarshalling.js0000644000175000017500000017047714144444702022131 0ustar jpeisachjpeisach// Load overrides for GIMarshallingTests imports.overrides.searchPath.unshift('resource:///org/gjs/jsunit/modules/overrides'); const ByteArray = imports.byteArray; const GIMarshallingTests = imports.gi.GIMarshallingTests; // We use Gio and GLib to have some objects that we know exist const Gio = imports.gi.Gio; const GLib = imports.gi.GLib; const GObject = imports.gi.GObject; // Some helpers to cut down on repetitive marshalling tests. // - options.omit: the test doesn't exist, don't create a test case // - options.skip: the test does exist, but doesn't pass, either unsupported or // a bug in GJS. Create the test case and mark it pending function testReturnValue(root, value, {omit, skip, funcName = `${root}_return`} = {}) { if (omit) return; it('marshals as a return value', function () { if (skip) pending(skip); expect(GIMarshallingTests[funcName]()).toEqual(value); }); } function testInParameter(root, value, {omit, skip, funcName = `${root}_in`} = {}) { if (omit) return; it('marshals as an in parameter', function () { if (skip) pending(skip); expect(() => GIMarshallingTests[funcName](value)).not.toThrow(); }); } function testOutParameter(root, value, {omit, skip, funcName = `${root}_out`} = {}) { if (omit) return; it('marshals as an out parameter', function () { if (skip) pending(skip); expect(GIMarshallingTests[funcName]()).toEqual(value); }); } function testInoutParameter(root, inValue, outValue, {omit, skip, funcName = `${root}_inout`} = {}) { if (omit) return; it('marshals as an inout parameter', function () { if (skip) pending(skip); expect(GIMarshallingTests[funcName](inValue)).toEqual(outValue); }); } function testSimpleMarshalling(root, value, inoutValue, options = {}) { testReturnValue(root, value, options.returnv); testInParameter(root, value, options.in); testOutParameter(root, value, options.out); testInoutParameter(root, value, inoutValue, options.inout); } function testTransferMarshalling(root, value, inoutValue, options = {}) { describe('with transfer none', function () { testSimpleMarshalling(`${root}_none`, value, inoutValue, options.none); }); describe('with transfer full', function () { const fullOptions = { in: { omit: true, // this case is not in the test suite }, inout: { skip: 'https://gitlab.gnome.org/GNOME/gobject-introspection/issues/192', }, }; Object.assign(fullOptions, options.full); testSimpleMarshalling(`${root}_full`, value, inoutValue, fullOptions); }); } function testContainerMarshalling(root, value, inoutValue, options = {}) { testTransferMarshalling(root, value, inoutValue, options); describe('with transfer container', function () { const containerOptions = { in: { omit: true, // this case is not in the test suite }, inout: { skip: 'https://gitlab.gnome.org/GNOME/gjs/issues/44', }, }; Object.assign(containerOptions, options.container); testSimpleMarshalling(`${root}_container`, value, inoutValue, containerOptions); }); } // Integer limits, defined without reference to GLib (because the GLib.MAXINT8 // etc. constants are also subject to marshalling) const Limits = { int8: { min: -(2 ** 7), max: 2 ** 7 - 1, umax: 2 ** 8 - 1, }, int16: { min: -(2 ** 15), max: 2 ** 15 - 1, umax: 2 ** 16 - 1, }, int32: { min: -(2 ** 31), max: 2 ** 31 - 1, umax: 2 ** 32 - 1, }, int64: { min: -(2 ** 63), max: 2 ** 63 - 1, umax: 2 ** 64 - 1, bit64: true, // note: unsafe, values will not be accurate! }, short: {}, int: {}, long: {}, ssize: { utype: 'size', }, }; Object.assign(Limits.short, Limits.int16); Object.assign(Limits.int, Limits.int32); // Platform dependent sizes; expand definitions as needed if (GLib.SIZEOF_LONG === 8) Object.assign(Limits.long, Limits.int64); else Object.assign(Limits.long, Limits.int32); if (GLib.SIZEOF_SSIZE_T === 8) Object.assign(Limits.ssize, Limits.int64); else Object.assign(Limits.ssize, Limits.int32); // Functions for dealing with tests that require or return unsafe 64-bit ints, // until we get BigInts. // Sometimes tests pass if we are comparing two inaccurate values in JS with // each other. That's fine for now. Then we just have to suppress the warnings. function warn64(is64bit, func, ...args) { if (is64bit) { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, '*cannot be safely stored*'); } const retval = func(...args); if (is64bit) { GLib.test_assert_expected_messages_internal('Cjs', 'testGIMarshalling.js', 0, 'Ignore message'); } return retval; } // Other times we compare an inaccurate value marshalled from JS into C, with an // accurate value in C. Those tests we have to skip. function skip64(is64bit) { if (is64bit) pending('https://gitlab.gnome.org/GNOME/gjs/issues/271'); } describe('Boolean', function () { [true, false].forEach(bool => { describe(`${bool}`, function () { testSimpleMarshalling('boolean', bool, !bool, { returnv: { funcName: `boolean_return_${bool}`, }, in: { funcName: `boolean_in_${bool}`, }, out: { funcName: `boolean_out_${bool}`, }, inout: { funcName: `boolean_inout_${bool}_${!bool}`, }, }); }); }); }); describe('Integer', function () { Object.entries(Limits).forEach(([type, {min, max, umax, bit64, utype = `u${type}`}]) => { describe(`${type}-typed`, function () { it('marshals signed value as a return value', function () { expect(warn64(bit64, GIMarshallingTests[`${type}_return_max`])).toEqual(max); expect(warn64(bit64, GIMarshallingTests[`${type}_return_min`])).toEqual(min); }); it('marshals signed value as an in parameter', function () { skip64(bit64); expect(() => GIMarshallingTests[`${type}_in_max`](max)).not.toThrow(); expect(() => GIMarshallingTests[`${type}_in_min`](min)).not.toThrow(); }); it('marshals signed value as an out parameter', function () { expect(warn64(bit64, GIMarshallingTests[`${type}_out_max`])).toEqual(max); expect(warn64(bit64, GIMarshallingTests[`${type}_out_min`])).toEqual(min); }); it('marshals as an inout parameter', function () { skip64(bit64); expect(GIMarshallingTests[`${type}_inout_max_min`](max)).toEqual(min); expect(GIMarshallingTests[`${type}_inout_min_max`](min)).toEqual(max); }); it('marshals unsigned value as a return value', function () { expect(warn64(bit64, GIMarshallingTests[`${utype}_return`])).toEqual(umax); }); it('marshals unsigned value as an in parameter', function () { skip64(bit64); expect(() => GIMarshallingTests[`${utype}_in`](umax)).not.toThrow(); }); it('marshals unsigned value as an out parameter', function () { expect(warn64(bit64, GIMarshallingTests[`${utype}_out`])).toEqual(umax); }); it('marshals unsigned value as an inout parameter', function () { skip64(bit64); expect(GIMarshallingTests[`${utype}_inout`](umax)).toEqual(0); }); }); }); }); describe('Floating point', function () { const FloatLimits = { float: { min: 2 ** -126, max: (2 - 2 ** -23) * 2 ** 127, }, double: { // GLib.MINDOUBLE is the minimum normal value, which is not the same // as the minimum denormal value Number.MIN_VALUE min: 2 ** -1022, max: Number.MAX_VALUE, }, }; Object.entries(FloatLimits).forEach(([type, {min, max}]) => { describe(`${type}-typed`, function () { it('marshals value as a return value', function () { expect(GIMarshallingTests[`${type}_return`]()).toBeCloseTo(max, 10); }); testInParameter(type, max); it('marshals value as an out parameter', function () { expect(GIMarshallingTests[`${type}_out`]()).toBeCloseTo(max, 10); }); it('marshals value as an inout parameter', function () { expect(GIMarshallingTests[`${type}_inout`](max)).toBeCloseTo(min, 10); }); }); }); }); describe('time_t', function () { testSimpleMarshalling('time_t', 1234567890, 0); }); describe('GType', function () { describe('void', function () { testSimpleMarshalling('gtype', GObject.TYPE_NONE, GObject.TYPE_INT); }); describe('string', function () { testSimpleMarshalling('gtype_string', GObject.TYPE_STRING, null, { inout: {omit: true}, }); }); it('can be implicitly converted from a GObject type alias', function () { expect(() => GIMarshallingTests.gtype_in(GObject.VoidType)).not.toThrow(); }); it('can be implicitly converted from a JS type', function () { expect(() => GIMarshallingTests.gtype_string_in(String)).not.toThrow(); }); }); describe('UTF-8 string', function () { testTransferMarshalling('utf8', 'const ♥ utf8', ''); it('marshals value as a byte array', function () { expect(() => GIMarshallingTests.utf8_as_uint8array_in('const ♥ utf8')).not.toThrow(); }); it('makes a default out value for a broken C function', function () { expect(GIMarshallingTests.utf8_dangling_out()).toBeNull(); }); }); describe('In-out array in the style of gtk_init()', function () { it('marshals null', function () { const [, newArray] = GIMarshallingTests.init_function(null); expect(newArray).toEqual([]); }); xit('marshals an inout empty array', function () { const [, newArray] = GIMarshallingTests.init_function([]); expect(newArray).toEqual([]); }).pend('https://gitlab.gnome.org/GNOME/gjs/issues/88'); xit('marshals an inout array', function () { const [, newArray] = GIMarshallingTests.init_function(['--foo', '--bar']); expect(newArray).toEqual(['--foo']); }).pend('https://gitlab.gnome.org/GNOME/gjs/issues/88'); }); describe('Fixed-size C array', function () { describe('of ints', function () { testReturnValue('array_fixed_int', [-1, 0, 1, 2]); testInParameter('array_fixed_int', [-1, 0, 1, 2]); testInoutParameter('array_fixed', [-1, 0, 1, 2], [2, 1, 0, -1]); }); describe('of shorts', function () { testReturnValue('array_fixed_short', [-1, 0, 1, 2]); testInParameter('array_fixed_short', [-1, 0, 1, 2]); }); it('marshals a struct array as an out parameter', function () { expect(GIMarshallingTests.array_fixed_out_struct()).toEqual([ jasmine.objectContaining({long_: 7, int8: 6}), jasmine.objectContaining({long_: 6, int8: 7}), ]); }); }); describe('C array with length', function () { function createStructArray(StructType = GIMarshallingTests.BoxedStruct) { return [1, 2, 3].map(num => { let struct = new StructType(); struct.long_ = num; return struct; }); } testSimpleMarshalling('array', [-1, 0, 1, 2], [-2, -1, 0, 1, 2]); it('can be returned along with other arguments', function () { let [array, sum] = GIMarshallingTests.array_return_etc(9, 5); expect(sum).toEqual(14); expect(array).toEqual([9, 0, 1, 5]); }); it('can be passed to a function with its length parameter before it', function () { expect(() => GIMarshallingTests.array_in_len_before([-1, 0, 1, 2])) .not.toThrow(); }); it('can be passed to a function with zero terminator', function () { expect(() => GIMarshallingTests.array_in_len_zero_terminated([-1, 0, 1, 2])) .not.toThrow(); }); describe('of strings', function () { testInParameter('array_string', ['foo', 'bar']); }); it('marshals a byte array as an in parameter', function () { expect(() => GIMarshallingTests.array_uint8_in('abcd')).not.toThrow(); expect(() => GIMarshallingTests.array_uint8_in([97, 98, 99, 100])).not.toThrow(); expect(() => GIMarshallingTests.array_uint8_in(ByteArray.fromString('abcd'))) .not.toThrow(); }); describe('of signed 64-bit ints', function () { testInParameter('array_int64', [-1, 0, 1, 2]); }); describe('of unsigned 64-bit ints', function () { testInParameter('array_uint64', [-1, 0, 1, 2]); }); describe('of unichars', function () { testInParameter('array_unichar', 'const ♥ utf8'); testOutParameter('array_unichar', 'const ♥ utf8'); it('marshals from an array of codepoints', function () { const codepoints = [...'const ♥ utf8'].map(c => c.codePointAt(0)); expect(() => GIMarshallingTests.array_unichar_in(codepoints)).not.toThrow(); }); }); describe('of booleans', function () { testInParameter('array_bool', [true, false, true, true]); testOutParameter('array_bool', [true, false, true, true]); it('marshals from an array of numbers', function () { expect(() => GIMarshallingTests.array_bool_in([-1, 0, 1, 2])).not.toThrow(); }); }); describe('of boxed structs', function () { testInParameter('array_struct', createStructArray()); describe('passed by value', function () { testInParameter('array_struct_value', createStructArray(), { skip: 'https://gitlab.gnome.org/GNOME/gjs/issues/44', }); }); }); describe('of simple structs', function () { testInParameter('array_simple_struct', createStructArray(GIMarshallingTests.SimpleStruct), { skip: 'https://gitlab.gnome.org/GNOME/gjs/issues/44', }); }); it('marshals two arrays with the same length parameter', function () { const keys = ['one', 'two', 'three']; const values = [1, 2, 3]; expect(() => GIMarshallingTests.multi_array_key_value_in(keys, values)).not.toThrow(); }); // Run twice to ensure that copies are correctly made for (transfer full) it('copies correctly on transfer full', function () { let array = createStructArray(); expect(() => { GIMarshallingTests.array_struct_take_in(array); GIMarshallingTests.array_struct_take_in(array); }).not.toThrow(); }); describe('of enums', function () { testInParameter('array_enum', [ GIMarshallingTests.Enum.VALUE1, GIMarshallingTests.Enum.VALUE2, GIMarshallingTests.Enum.VALUE3, ]); }); it('marshals an array with a 64-bit length parameter', function () { expect(() => GIMarshallingTests.array_in_guint64_len([-1, 0, 1, 2])).not.toThrow(); }); it('marshals an array with an 8-bit length parameter', function () { expect(() => GIMarshallingTests.array_in_guint8_len([-1, 0, 1, 2])).not.toThrow(); }); it('can be an out argument along with other arguments', function () { let [array, sum] = GIMarshallingTests.array_out_etc(9, 5); expect(sum).toEqual(14); expect(array).toEqual([9, 0, 1, 5]); }); it('can be an in-out argument along with other arguments', function () { let [array, sum] = GIMarshallingTests.array_inout_etc(9, [-1, 0, 1, 2], 5); expect(sum).toEqual(14); expect(array).toEqual([9, -1, 0, 1, 5]); }); it('does not interpret an unannotated integer as a length parameter', function () { expect(() => GIMarshallingTests.array_in_nonzero_nonlen(42, 'abcd')).not.toThrow(); }); }); describe('Zero-terminated C array', function () { describe('of strings', function () { testSimpleMarshalling('array_zero_terminated', ['0', '1', '2'], ['-1', '0', '1', '2']); }); it('marshals null as a zero-terminated array return value', function () { expect(GIMarshallingTests.array_zero_terminated_return_null()).toEqual(null); }); it('marshals an array of structs as a return value', function () { let structArray = GIMarshallingTests.array_zero_terminated_return_struct(); expect(structArray.map(e => e.long_)).toEqual([42, 43, 44]); }); it('marshals an array of unichars as a return value', function () { expect(GIMarshallingTests.array_zero_terminated_return_unichar()) .toEqual('const ♥ utf8'); }); describe('of GLib.Variants', function () { let variantArray; beforeEach(function () { variantArray = [ new GLib.Variant('i', 27), new GLib.Variant('s', 'Hello'), ]; }); ['none', 'container', 'full'].forEach(transfer => { xit(`marshals as a transfer-${transfer} in and out parameter`, function () { const returnedArray = GIMarshallingTests[`array_gvariant_${transfer}_in`](variantArray); expect(returnedArray.map(v => v.deepUnpack())).toEqual([27, 'Hello']); }).pend('https://gitlab.gnome.org/GNOME/gjs/issues/269'); }); }); }); describe('GArray', function () { describe('of ints with transfer none', function () { testReturnValue('garray_int_none', [-1, 0, 1, 2]); testInParameter('garray_int_none', [-1, 0, 1, 2]); }); it('marshals int64s as a transfer-none return value', function () { expect(warn64(true, GIMarshallingTests.garray_uint64_none_return)) .toEqual([0, Limits.int64.umax]); }); describe('of strings', function () { testContainerMarshalling('garray_utf8', ['0', '1', '2'], ['-2', '-1', '0', '1']); it('marshals as a transfer-full caller-allocated out parameter', function () { expect(GIMarshallingTests.garray_utf8_full_out_caller_allocated()) .toEqual(['0', '1', '2']); }).pend('https://gitlab.gnome.org/GNOME/gjs/issues/106'); // https://gitlab.gnome.org/GNOME/gjs/-/issues/344 // the test should be replaced with the one above when issue // https://gitlab.gnome.org/GNOME/gjs/issues/106 is fixed. it('marshals as a transfer-full caller-allocated out parameter throws errors', function () { expect(() => GIMarshallingTests.garray_utf8_full_out_caller_allocated()) .toThrowError(/Unsupported type array.*\(out caller-allocates\)/); }); }); // it('marshals boxed structs as a transfer-full return value', function () { // expect(GIMarshallingTests.garray_boxed_struct_full_return().map(e => e.long_)) // .toEqual([42, 43, 44]); // }); describe('of booleans with transfer none', function () { testInParameter('garray_bool_none', [-1, 0, 1, 2]); }); describe('of unichars', function () { it('can be passed in with transfer none', function () { expect(() => GIMarshallingTests.garray_unichar_none_in('const \u2665 utf8')) .not.toThrow(); expect(() => GIMarshallingTests.garray_unichar_none_in([0x63, 0x6f, 0x6e, 0x73, 0x74, 0x20, 0x2665, 0x20, 0x75, 0x74, 0x66, 0x38])).not.toThrow(); }); }); }); describe('GPtrArray', function () { describe('of strings', function () { testContainerMarshalling('garray_utf8', ['0', '1', '2'], ['-2', '-1', '0', '1']); }); // describe('of structs', function () { // it('can be returned with transfer full', function () { // expect(GIMarshallingTests.gptrarray_boxed_struct_full_return().map(e => e.long_)) // .toEqual([42, 43, 44]); // }); // }); }); describe('GByteArray', function () { const refByteArray = Uint8Array.from([0, 49, 0xFF, 51]); testReturnValue('bytearray_full', refByteArray); it('can be passed in with transfer none', function () { expect(() => GIMarshallingTests.bytearray_none_in(refByteArray)) .not.toThrow(); expect(() => GIMarshallingTests.bytearray_none_in([0, 49, 0xFF, 51])) .not.toThrow(); }); }); describe('GBytes', function () { const refByteArray = Uint8Array.from([0, 49, 0xFF, 51]); it('marshals as a transfer-full return value', function () { expect(GIMarshallingTests.gbytes_full_return().toArray()).toEqual(refByteArray); }); it('can be created from an array and passed in', function () { let bytes = GLib.Bytes.new([0, 49, 0xFF, 51]); expect(() => GIMarshallingTests.gbytes_none_in(bytes)).not.toThrow(); }); it('can be created by returning from a function and passed in', function () { var bytes = GIMarshallingTests.gbytes_full_return(); expect(() => GIMarshallingTests.gbytes_none_in(bytes)).not.toThrow(); expect(bytes.toArray()).toEqual(refByteArray); }); it('can be implicitly converted from a ByteArray', function () { expect(() => GIMarshallingTests.gbytes_none_in(refByteArray)) .not.toThrow(); }); it('can be created from a string and is encoded in UTF-8', function () { let bytes = GLib.Bytes.new('const \u2665 utf8'); expect(() => GIMarshallingTests.utf8_as_uint8array_in(bytes.toArray())) .not.toThrow(); }); it('turns into a GByteArray on assignment', function () { let bytes = GIMarshallingTests.gbytes_full_return(); let array = bytes.toArray(); // Array should just be holding a ref, not a copy expect(array[1]).toEqual(49); array[1] = 42; // Assignment should force to GByteArray expect(array[1]).toEqual(42); array[1] = 49; // Flip the value back // Now convert back to GBytes expect(() => GIMarshallingTests.gbytes_none_in(ByteArray.toGBytes(array))) .not.toThrow(); }); it('cannot be passed to a function expecting a byte array', function () { let bytes = GLib.Bytes.new([97, 98, 99, 100]); expect(() => GIMarshallingTests.array_uint8_in(bytes.toArray())).not.toThrow(); expect(() => GIMarshallingTests.array_uint8_in(bytes)).toThrow(); }); }); describe('GStrv', function () { testSimpleMarshalling('gstrv', ['0', '1', '2'], ['-1', '0', '1', '2']); }); ['GList', 'GSList'].forEach(listKind => { const list = listKind.toLowerCase(); describe(listKind, function () { describe('of ints with transfer none', function () { testReturnValue(`${list}_int_none`, [-1, 0, 1, 2]); testInParameter(`${list}_int_none`, [-1, 0, 1, 2]); }); if (listKind === 'GList') { describe('of unsigned 32-bit ints with transfer none', function () { testReturnValue('glist_uint32_none', [0, Limits.int32.umax]); testInParameter('glist_uint32_none', [0, Limits.int32.umax]); }); } describe('of strings', function () { testContainerMarshalling(`${list}_utf8`, ['0', '1', '2'], ['-2', '-1', '0', '1']); }); }); }); describe('GHashTable', function () { const numberDict = { '-1': -0.1, 0: 0, 1: 0.1, 2: 0.2, }; describe('with integer values', function () { const intDict = { '-1': 1, 0: 0, 1: -1, 2: -2, }; testReturnValue('ghashtable_int_none', intDict); testInParameter('ghashtable_int_none', intDict); }); describe('with string values', function () { const stringDict = { '-1': '1', 0: '0', 1: '-1', 2: '-2', }; const stringDictOut = { '-1': '1', 0: '0', 1: '1', }; testContainerMarshalling('ghashtable_utf8', stringDict, stringDictOut); }); describe('with double values', function () { testInParameter('ghashtable_double', numberDict); }); describe('with float values', function () { testInParameter('ghashtable_float', numberDict); }); describe('with 64-bit int values', function () { const int64Dict = { '-1': -1, 0: 0, 1: 1, 2: 0x100000000, }; testInParameter('ghashtable_int64', int64Dict); }); describe('with unsigned 64-bit int values', function () { const uint64Dict = { '-1': 0x100000000, 0: 0, 1: 1, 2: 2, }; testInParameter('ghashtable_uint64', uint64Dict); }); }); describe('GValue', function () { testSimpleMarshalling('gvalue', 42, '42', { inout: { skip: 'https://gitlab.gnome.org/GNOME/gobject-introspection/issues/192', }, }); xit('marshals as an int64 in parameter', function () { expect(() => GIMarshallingTests.gvalue_int64_in(Limits.int64.max)).not.toThrow(); }).pend('https://gitlab.gnome.org/GNOME/gjs/issues/271'); it('type objects can be converted from primitive-like types', function () { expect(() => GIMarshallingTests.gvalue_in_with_type(42, GObject.Int)) .not.toThrow(); expect(() => GIMarshallingTests.gvalue_in_with_type(42.5, GObject.Double)) .not.toThrow(); expect(() => GIMarshallingTests.gvalue_in_with_type(42.5, Number)) .not.toThrow(); }); it('can be passed into a function and modified', function () { expect(() => GIMarshallingTests.gvalue_in_with_modification(42)).not.toThrow(); // Let's assume this test doesn't expect that the modified GValue makes // it back to the caller; I don't see how that could be achieved. // See https://gitlab.gnome.org/GNOME/gjs/issues/80 }); xit('enum can be passed into a function and packed', function () { expect(() => GIMarshallingTests.gvalue_in_enum(GIMarshallingTests.Enum.VALUE3)) .not.toThrow(); }).pend("GJS doesn't support native enum types"); it('marshals as an int64 out parameter', function () { expect(GIMarshallingTests.gvalue_int64_out()).toEqual(Limits.int64.max); }); it('marshals as a caller-allocated out parameter', function () { expect(GIMarshallingTests.gvalue_out_caller_allocates()).toEqual(42); }); it('array can be passed into a function and packed', function () { expect(() => GIMarshallingTests.gvalue_flat_array([42, '42', true])) .not.toThrow(); }); it('array can be passed as an out argument and unpacked', function () { expect(GIMarshallingTests.return_gvalue_flat_array()) .toEqual([42, '42', true]); }); xit('array can roundtrip with GValues intact', function () { expect(GIMarshallingTests.gvalue_flat_array_round_trip(42, '42', true)) .toEqual([42, '42', true]); }).pend('https://gitlab.gnome.org/GNOME/gjs/issues/272'); it('can have its type inferred from primitive values', function () { expect(() => GIMarshallingTests.gvalue_in_with_type(42, GObject.TYPE_INT)) .not.toThrow(); expect(() => GIMarshallingTests.gvalue_in_with_type(42.5, GObject.TYPE_DOUBLE)) .not.toThrow(); expect(() => GIMarshallingTests.gvalue_in_with_type('42', GObject.TYPE_STRING)) .not.toThrow(); expect(() => GIMarshallingTests.gvalue_in_with_type(GObject.TYPE_GTYPE, GObject.TYPE_GTYPE)) .not.toThrow(); }); // supplementary tests for gvalue_in_with_type() it('can have its type inferred as a GObject type', function () { expect(() => GIMarshallingTests.gvalue_in_with_type(new Gio.SimpleAction(), Gio.SimpleAction)) .not.toThrow(); }); it('can have its type inferred as a superclass', function () { let action = new Gio.SimpleAction(); expect(() => GIMarshallingTests.gvalue_in_with_type(action, GObject.Object)) .not.toThrow(); expect(() => GIMarshallingTests.gvalue_in_with_type(action, GObject.TYPE_OBJECT)) .not.toThrow(); }); it('can have its type inferred as an interface that it implements', function () { expect(() => GIMarshallingTests.gvalue_in_with_type(new Gio.SimpleAction(), Gio.SimpleAction)) .not.toThrow(); }); it('can have its type inferred as a boxed type', function () { let keyfile = new GLib.KeyFile(); expect(() => GIMarshallingTests.gvalue_in_with_type(keyfile, GLib.KeyFile)) .not.toThrow(); expect(() => GIMarshallingTests.gvalue_in_with_type(keyfile, GObject.TYPE_BOXED)) .not.toThrow(); let struct = new GIMarshallingTests.BoxedStruct(); expect(() => GIMarshallingTests.gvalue_in_with_type(struct, GIMarshallingTests.BoxedStruct)) .not.toThrow(); }); it('can have its type inferred as GVariant', function () { let variant = GLib.Variant.new('u', 42); expect(() => GIMarshallingTests.gvalue_in_with_type(variant, GLib.Variant)) .not.toThrow(); expect(() => GIMarshallingTests.gvalue_in_with_type(variant, GObject.TYPE_VARIANT)) .not.toThrow(); }); // it('can have its type inferred as a union type', function () { // let union = GIMarshallingTests.union_returnv(); // expect(() => GIMarshallingTests.gvalue_in_with_type(union, GIMarshallingTests.Union)) // .not.toThrow(); // }); it('can have its type inferred as a GParamSpec', function () { let paramSpec = GObject.ParamSpec.string('my-param', '', '', GObject.ParamFlags.READABLE, ''); expect(() => GIMarshallingTests.gvalue_in_with_type(paramSpec, GObject.TYPE_PARAM)) .not.toThrow(); }); // See testCairo.js for a test of GIMarshallingTests.gvalue_in_with_type() // on Cairo foreign structs, since it will be skipped if compiling without // Cairo support. }); describe('Callback', function () { describe('GClosure', function () { testInParameter('gclosure', () => 42); xit('marshals a GClosure as a return value', function () { // Currently a GObject.Closure instance is returned, upon which it's // not possible to call invoke() because that method takes a bare // pointer as an argument. expect(GIMarshallingTests.gclosure_return()()).toEqual(42); }).pend('https://gitlab.gnome.org/GNOME/gjs/issues/80'); }); it('marshals a return value', function () { expect(GIMarshallingTests.callback_return_value_only(() => 42)) .toEqual(42); }); it('marshals one out parameter', function () { expect(GIMarshallingTests.callback_one_out_parameter(() => 43)) .toEqual(43); }); it('marshals multiple out parameters', function () { expect(GIMarshallingTests.callback_multiple_out_parameters(() => [44, 45])) .toEqual([44, 45]); }); it('marshals a return value and one out parameter', function () { expect(GIMarshallingTests.callback_return_value_and_one_out_parameter(() => [46, 47])) .toEqual([46, 47]); }); it('marshals a return value and multiple out parameters', function () { expect(GIMarshallingTests.callback_return_value_and_multiple_out_parameters(() => [48, 49, 50])) .toEqual([48, 49, 50]); }); xit('marshals an array out parameter', function () { expect(GIMarshallingTests.callback_array_out_parameter(() => [50, 51])) .toEqual([50, 51]); }).pend('Function not added to gobject-introspection test suite yet'); }); describe('Raw pointers', function () { xit('can be roundtripped at least if the pointer is null', function () { expect(GIMarshallingTests.pointer_in_return(null)).toBeNull(); }).pend('https://gitlab.gnome.org/GNOME/gjs/merge_requests/46'); }); describe('Registered enum type', function () { testSimpleMarshalling('genum', GIMarshallingTests.GEnum.VALUE3, GIMarshallingTests.GEnum.VALUE1, { returnv: { funcName: 'genum_returnv', }, }); }); describe('Bare enum type', function () { testSimpleMarshalling('enum', GIMarshallingTests.Enum.VALUE3, GIMarshallingTests.Enum.VALUE1, { returnv: { funcName: 'enum_returnv', }, }); }); describe('Registered flags type', function () { testSimpleMarshalling('flags', GIMarshallingTests.Flags.VALUE2, GIMarshallingTests.Flags.VALUE1, { returnv: { funcName: 'flags_returnv', }, }); }); describe('Bare flags type', function () { testSimpleMarshalling('no_type_flags', GIMarshallingTests.NoTypeFlags.VALUE2, GIMarshallingTests.NoTypeFlags.VALUE1, { returnv: { funcName: 'no_type_flags_returnv', }, }); }); describe('Simple struct', function () { it('marshals as a return value', function () { expect(GIMarshallingTests.simple_struct_returnv()).toEqual(jasmine.objectContaining({ long_: 6, int8: 7, })); }); it('marshals as the this-argument of a method', function () { const struct = new GIMarshallingTests.SimpleStruct({ long_: 6, int8: 7, }); expect(() => struct.inv()).not.toThrow(); // was this supposed to be static? expect(() => struct.method()).not.toThrow(); }); }); describe('Pointer struct', function () { it('marshals as a return value', function () { expect(GIMarshallingTests.pointer_struct_returnv()).toEqual(jasmine.objectContaining({ long_: 42, })); }); it('marshals as the this-argument of a method', function () { const struct = new GIMarshallingTests.PointerStruct({ long_: 42, }); expect(() => struct.inv()).not.toThrow(); }); }); describe('Boxed struct', function () { it('marshals as a return value', function () { expect(GIMarshallingTests.boxed_struct_returnv()).toEqual(jasmine.objectContaining({ long_: 42, string_: 'hello', g_strv: ['0', '1', '2'], })); }); it('marshals as the this-argument of a method', function () { const struct = new GIMarshallingTests.BoxedStruct({ long_: 42, }); expect(() => struct.inv()).not.toThrow(); }); it('marshals as an out parameter', function () { expect(GIMarshallingTests.boxed_struct_out()).toEqual(jasmine.objectContaining({ long_: 42, })); }); it('marshals as an inout parameter', function () { const struct = new GIMarshallingTests.BoxedStruct({ long_: 42, }); expect(GIMarshallingTests.boxed_struct_inout(struct)).toEqual(jasmine.objectContaining({ long_: 0, })); }); }); describe('Union', function () { let union; beforeEach(function () { union = GIMarshallingTests.union_returnv(); }); xit('marshals as a return value', function () { expect(union.long_).toEqual(42); }).pend('https://gitlab.gnome.org/GNOME/gjs/issues/273'); // it('marshals as the this-argument of a method', function () { // expect(() => union.inv()).not.toThrow(); // was this supposed to be static? // expect(() => union.method()).not.toThrow(); // }); }); describe('GObject', function () { it('has a static method that can be called', function () { expect(() => GIMarshallingTests.Object.static_method()).not.toThrow(); }); it('has a method that can be called', function () { const o = new GIMarshallingTests.Object({int: 42}); expect(() => o.method()).not.toThrow(); }); it('has an overridden method that can be called', function () { const o = new GIMarshallingTests.Object({int: 0}); expect(() => o.overridden_method()).not.toThrow(); }); it('can be created from a static constructor', function () { const o = GIMarshallingTests.Object.new(42); expect(o.int).toEqual(42); }); it('can have a static constructor that fails', function () { expect(() => GIMarshallingTests.Object.new_fail(42)).toThrow(); }); describe('method', function () { let o; beforeEach(function () { o = new GIMarshallingTests.Object(); }); it('marshals an int array as an in parameter', function () { expect(() => o.method_array_in([-1, 0, 1, 2])).not.toThrow(); }); it('marshals an int array as an out parameter', function () { expect(o.method_array_out()).toEqual([-1, 0, 1, 2]); }); it('marshals an int array as an inout parameter', function () { expect(o.method_array_inout([-1, 0, 1, 2])).toEqual([-2, -1, 0, 1, 2]); }); it('marshals an int array as a return value', function () { expect(o.method_array_return()).toEqual([-1, 0, 1, 2]); }); it('with default implementation can be called', function () { o = new GIMarshallingTests.Object({int: 42}); o.method_with_default_implementation(43); expect(o.int).toEqual(43); }); }); ['none', 'full'].forEach(transfer => { ['return', 'out'].forEach(mode => { it(`marshals as a ${mode} parameter with transfer ${transfer}`, function () { expect(GIMarshallingTests.Object[`${transfer}_${mode}`]().int).toEqual(0); }); }); it(`marshals as an inout parameter with transfer ${transfer}`, function () { const o = new GIMarshallingTests.Object({int: 42}); expect(GIMarshallingTests.Object[`${transfer}_inout`](o).int).toEqual(0); }); }); it('marshals as a this value with transfer none', function () { const o = new GIMarshallingTests.Object({int: 42}); expect(() => o.none_in()).not.toThrow(); }); }); let VFuncTester = GObject.registerClass(class VFuncTester extends GIMarshallingTests.Object { vfunc_vfunc_return_value_only() { return 42; } vfunc_vfunc_one_out_parameter() { return 43; } vfunc_vfunc_multiple_out_parameters() { return [44, 45]; } vfunc_vfunc_return_value_and_one_out_parameter() { return [46, 47]; } vfunc_vfunc_return_value_and_multiple_out_parameters() { return [48, 49, 50]; } vfunc_vfunc_array_out_parameter() { return [50, 51]; } vfunc_vfunc_caller_allocated_out_parameter() { return 52; } vfunc_vfunc_meth_with_err(x) { switch (x) { case -1: return true; case 0: undefined.throwTypeError(); break; case 1: void referenceError; // eslint-disable-line no-undef break; case 2: throw new Gio.IOErrorEnum({ code: Gio.IOErrorEnum.FAILED, message: 'I FAILED, but the test passed!', }); case 3: throw new GLib.SpawnError({ code: GLib.SpawnError.TOO_BIG, message: 'This test is Too Big to Fail', }); } } vfunc_vfunc_return_enum() { return GIMarshallingTests.Enum.VALUE2; } vfunc_vfunc_out_enum() { return GIMarshallingTests.Enum.VALUE3; } vfunc_vfunc_return_object_transfer_none() { if (!this._returnObject) this._returnObject = new GIMarshallingTests.Object({int: 53}); return this._returnObject; } vfunc_vfunc_return_object_transfer_full() { return new GIMarshallingTests.Object({int: 54}); } vfunc_vfunc_out_object_transfer_none() { if (!this._outObject) this._outObject = new GIMarshallingTests.Object({int: 55}); return this._outObject; } vfunc_vfunc_out_object_transfer_full() { return new GIMarshallingTests.Object({int: 56}); } vfunc_vfunc_in_object_transfer_none(object) { void object; } vfunc_vfunc_in_object_transfer_full(object) { this._inObject = object; } }); try { VFuncTester = GObject.registerClass(class VFuncTesterInOut extends VFuncTester { vfunc_vfunc_one_inout_parameter(input) { return input * 5; } vfunc_vfunc_multiple_inout_parameters(inputA, inputB) { return [inputA * 5, inputB * -1]; } vfunc_vfunc_return_value_and_one_inout_parameter(input) { return [49, input * 5]; } vfunc_vfunc_return_value_and_multiple_inout_parameters(inputA, inputB) { return [49, inputA * 5, inputB * -1]; } }); } catch {} describe('Virtual function', function () { let tester; beforeEach(function () { tester = new VFuncTester(); }); it('marshals a return value', function () { expect(tester.vfunc_return_value_only()).toEqual(42); }); it('marshals one out parameter', function () { expect(tester.vfunc_one_out_parameter()).toEqual(43); }); it('marshals multiple out parameters', function () { expect(tester.vfunc_multiple_out_parameters()).toEqual([44, 45]); }); it('marshals a return value and one out parameter', function () { expect(tester.vfunc_return_value_and_one_out_parameter()) .toEqual([46, 47]); }); it('marshals a return value and multiple out parameters', function () { expect(tester.vfunc_return_value_and_multiple_out_parameters()) .toEqual([48, 49, 50]); }); it('marshals one inout parameter', function () { if (typeof VFuncTester.prototype.vfunc_one_inout_parameter === 'undefined') pending('https://gitlab.gnome.org/GNOME/gobject-introspection/merge_requests/201'); expect(tester.vfunc_one_inout_parameter(10)).toEqual(50); }); it('marshals multiple inout parameters', function () { if (typeof VFuncTester.prototype.vfunc_multiple_inout_parameters === 'undefined') pending('https://gitlab.gnome.org/GNOME/gobject-introspection/merge_requests/201'); expect(tester.vfunc_multiple_inout_parameters(10, 5)).toEqual([50, -5]); }); it('marshals a return value and one inout parameter', function () { if (typeof VFuncTester.prototype.vfunc_return_value_and_one_inout_parameter === 'undefined') pending('https://gitlab.gnome.org/GNOME/gobject-introspection/merge_requests/201'); expect(tester.vfunc_return_value_and_one_inout_parameter(10)) .toEqual([49, 50]); }); it('marshals a return value and multiple inout parameters', function () { if (typeof VFuncTester.prototype.vfunc_return_value_and_multiple_inout_parameters === 'undefined') pending('https://gitlab.gnome.org/GNOME/gobject-introspection/merge_requests/201'); expect(tester.vfunc_return_value_and_multiple_inout_parameters(10, -51)) .toEqual([49, 50, 51]); }); it('marshals an array out parameter', function () { expect(tester.vfunc_array_out_parameter()).toEqual([50, 51]); }); it('marshals a caller-allocated GValue out parameter', function () { expect(tester.vfunc_caller_allocated_out_parameter()).toEqual(52); }).pend('https://gitlab.gnome.org/GNOME/gjs/issues/74'); it('marshals an error out parameter when no error', function () { expect(tester.vfunc_meth_with_error(-1)).toBeTruthy(); }); it('marshals an error out parameter with a JavaScript exception', function () { expect(() => tester.vfunc_meth_with_error(0)).toThrowError(TypeError); expect(() => tester.vfunc_meth_with_error(1)).toThrowError(ReferenceError); }); it('marshals an error out parameter with a GError exception', function () { try { tester.vfunc_meth_with_error(2); fail('Exception should be thrown'); } catch (e) { expect(e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.FAILED)).toBeTruthy(); expect(e.message).toEqual('I FAILED, but the test passed!'); } try { tester.vfunc_meth_with_error(3); fail('Exception should be thrown'); } catch (e) { expect(e.matches(GLib.SpawnError, GLib.SpawnError.TOO_BIG)).toBeTruthy(); expect(e.message).toEqual('This test is Too Big to Fail'); } }); it('marshals an enum return value', function () { expect(tester.vfunc_return_enum()).toEqual(GIMarshallingTests.Enum.VALUE2); }); it('marshals an enum out parameter', function () { expect(tester.vfunc_out_enum()).toEqual(GIMarshallingTests.Enum.VALUE3); }); // These tests check what the refcount is of the returned objects; see // comments in gimarshallingtests.c. // Objects that are exposed in JS always have at least one reference (the // toggle reference.) JS never owns more than one reference. There may be // other references owned on the C side. // In any case the refs should not be floating. We never have floating refs // in JS. function testVfuncRefcount(mode, transfer, expectedRefcount, options = {}, ...args) { it(`marshals an object ${mode} parameter with transfer ${transfer}`, function () { if (options.skip) pending(options.skip); const [refcount, floating] = tester[`get_ref_info_for_vfunc_${mode}_object_transfer_${transfer}`](...args); expect(floating).toBeFalsy(); expect(refcount).toEqual(expectedRefcount); }); } // 1 reference = the object is owned only by JS. // 2 references = the object is owned by JS and the vfunc caller. testVfuncRefcount('return', 'none', 1); testVfuncRefcount('return', 'full', 2); testVfuncRefcount('out', 'none', 1); testVfuncRefcount('out', 'full', 2); testVfuncRefcount('in', 'none', 2, {}, GIMarshallingTests.Object); testVfuncRefcount('in', 'full', 1, { skip: 'https://gitlab.gnome.org/GNOME/gjs/issues/275', }, GIMarshallingTests.Object); }); const WrongVFuncTester = GObject.registerClass(class WrongVFuncTester extends GIMarshallingTests.Object { vfunc_vfunc_return_value_only() { } vfunc_vfunc_one_out_parameter() { } vfunc_vfunc_multiple_out_parameters() { } vfunc_vfunc_return_value_and_one_out_parameter() { } vfunc_vfunc_return_value_and_multiple_out_parameters() { } vfunc_vfunc_array_out_parameter() { } vfunc_vfunc_caller_allocated_out_parameter() { } vfunc_vfunc_return_enum() { } vfunc_vfunc_out_enum() { } vfunc_vfunc_return_object_transfer_none() { } vfunc_vfunc_return_object_transfer_full() { } vfunc_vfunc_out_object_transfer_none() { } vfunc_vfunc_out_object_transfer_full() { } vfunc_vfunc_in_object_transfer_none() { } }); describe('Wrong virtual functions', function () { let tester; beforeEach(function () { tester = new WrongVFuncTester(); }); it('marshals a return value', function () { expect(tester.vfunc_return_value_only()).toBeUndefined(); }).pend('https://gitlab.gnome.org/GNOME/gjs/issues/311'); it('marshals one out parameter', function () { expect(tester.vfunc_one_out_parameter()).toBeUndefined(); }).pend('https://gitlab.gnome.org/GNOME/gjs/issues/311'); it('marshals multiple out parameters', function () { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'JS ERROR: Error: Function *vfunc_vfunc_multiple_out_parameters*Array*'); expect(tester.vfunc_multiple_out_parameters()).toEqual([0, 0]); GLib.test_assert_expected_messages_internal('Cjs', 'testGIMarshalling.js', 0, 'testVFuncReturnWrongValue'); }); it('marshals a return value and one out parameter', function () { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'JS ERROR: Error: Function *vfunc_return_value_and_one_out_parameter*Array*'); expect(tester.vfunc_return_value_and_one_out_parameter()).toEqual([0, 0]); GLib.test_assert_expected_messages_internal('Cjs', 'testGIMarshalling.js', 0, 'testVFuncReturnWrongValue'); }); it('marshals a return value and multiple out parameters', function () { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'JS ERROR: Error: Function *vfunc_return_value_and_multiple_out_parameters*Array*'); expect(tester.vfunc_return_value_and_multiple_out_parameters()).toEqual([0, 0, 0]); GLib.test_assert_expected_messages_internal('Cjs', 'testGIMarshalling.js', 0, 'testVFuncReturnWrongValue'); }); it('marshals an array out parameter', function () { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'JS ERROR: Error: Expected type gfloat for Argument*undefined*'); expect(tester.vfunc_array_out_parameter()).toEqual(null); GLib.test_assert_expected_messages_internal('Cjs', 'testGIMarshalling.js', 0, 'testVFuncReturnWrongValue'); }); it('marshals an enum return value', function () { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'JS ERROR: Error: Expected type enum for Return*undefined*'); expect(tester.vfunc_return_enum()).toEqual(0); GLib.test_assert_expected_messages_internal('Cjs', 'testGIMarshalling.js', 0, 'testVFuncReturnWrongValue'); }); it('marshals an enum out parameter', function () { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'JS ERROR: Error: Expected type enum for Argument*undefined*'); expect(tester.vfunc_out_enum()).toEqual(0); GLib.test_assert_expected_messages_internal('Cjs', 'testGIMarshalling.js', 0, 'testVFuncReturnWrongValue'); }); }); describe('Inherited GObject', function () { ['SubObject', 'SubSubObject'].forEach(klass => { describe(klass, function () { it('has a parent method that can be called', function () { const o = new GIMarshallingTests.SubObject({int: 42}); expect(() => o.method()).not.toThrow(); }); it('has a method that can be called', function () { const o = new GIMarshallingTests.SubObject({int: 0}); expect(() => o.sub_method()).not.toThrow(); }); it('has an overridden method that can be called', function () { const o = new GIMarshallingTests.SubObject({int: 0}); expect(() => o.overwritten_method()).not.toThrow(); }); it('has a method with default implementation can be called', function () { const o = new GIMarshallingTests.SubObject({int: 42}); o.method_with_default_implementation(43); expect(o.int).toEqual(43); }); }); }); }); describe('Interface', function () { it('can be returned', function () { let ifaceImpl = new GIMarshallingTests.InterfaceImpl(); let itself = ifaceImpl.get_as_interface(); expect(ifaceImpl).toEqual(itself); }); it('can call an interface vfunc in C', function () { let ifaceImpl = new GIMarshallingTests.InterfaceImpl(); expect(() => ifaceImpl.test_int8_in(42)).not.toThrow(); expect(() => GIMarshallingTests.test_interface_test_int8_in(ifaceImpl, 42)) .not.toThrow(); }); it('can implement a C interface', function () { const I2Impl = GObject.registerClass({ Implements: [GIMarshallingTests.Interface2], }, class I2Impl extends GObject.Object {}); expect(() => new I2Impl()).not.toThrow(); }); it('can implement a C interface with a vfunc', function () { const I3Impl = GObject.registerClass({ Implements: [GIMarshallingTests.Interface3], }, class I3Impl extends GObject.Object { vfunc_test_variant_array_in(variantArray) { this.stuff = variantArray.map(v => v.deepUnpack()); } }); const i3 = new I3Impl(); i3.test_variant_array_in([ new GLib.Variant('b', true), new GLib.Variant('s', 'hello'), new GLib.Variant('i', 42), ]); expect(i3.stuff).toEqual([true, 'hello', 42]); }); }); describe('Configurations of return values', function () { it('can handle two out parameters', function () { expect(GIMarshallingTests.int_out_out()).toEqual([6, 7]); }); it('can handle three in and three out parameters', function () { expect(GIMarshallingTests.int_three_in_three_out(1, 2, 3)).toEqual([1, 2, 3]); }); it('can handle a return value and an out parameter', function () { expect(GIMarshallingTests.int_return_out()).toEqual([6, 7]); }); it('can handle four in parameters, two of which are nullable', function () { expect(() => GIMarshallingTests.int_two_in_utf8_two_in_with_allow_none(1, 2, '3', '4')) .not.toThrow(); expect(() => GIMarshallingTests.int_two_in_utf8_two_in_with_allow_none(1, 2, '3', null)) .not.toThrow(); expect(() => GIMarshallingTests.int_two_in_utf8_two_in_with_allow_none(1, 2, null, '4')) .not.toThrow(); expect(() => GIMarshallingTests.int_two_in_utf8_two_in_with_allow_none(1, 2, null, null)) .not.toThrow(); }); it('can handle three in parameters, one of which is nullable and one not', function () { expect(() => GIMarshallingTests.int_one_in_utf8_two_in_one_allows_none(1, '2', '3')) .not.toThrow(); expect(() => GIMarshallingTests.int_one_in_utf8_two_in_one_allows_none(1, null, '3')) .not.toThrow(); expect(() => GIMarshallingTests.int_one_in_utf8_two_in_one_allows_none(1, '2', null)) .toThrow(); }); it('can handle an array in parameter and two nullable in parameters', function () { expect(() => GIMarshallingTests.array_in_utf8_two_in([-1, 0, 1, 2], '1', '2')) .not.toThrow(); expect(() => GIMarshallingTests.array_in_utf8_two_in([-1, 0, 1, 2], '1', null)) .not.toThrow(); expect(() => GIMarshallingTests.array_in_utf8_two_in([-1, 0, 1, 2], null, '2')) .not.toThrow(); expect(() => GIMarshallingTests.array_in_utf8_two_in([-1, 0, 1, 2], null, null)) .not.toThrow(); }); it('can handle an array in parameter and two nullable in parameters, mixed with the array length', function () { expect(() => GIMarshallingTests.array_in_utf8_two_in_out_of_order('1', [-1, 0, 1, 2], '2')) .not.toThrow(); expect(() => GIMarshallingTests.array_in_utf8_two_in_out_of_order('1', [-1, 0, 1, 2], null)) .not.toThrow(); expect(() => GIMarshallingTests.array_in_utf8_two_in_out_of_order(null, [-1, 0, 1, 2], '2')) .not.toThrow(); expect(() => GIMarshallingTests.array_in_utf8_two_in_out_of_order(null, [-1, 0, 1, 2], null)) .not.toThrow(); }); }); describe('GError', function () { it('marshals a GError** signature as an exception', function () { expect(() => GIMarshallingTests.gerror()).toThrow(); }); it('marshals a GError** at the end of the signature as an exception', function () { expect(() => GIMarshallingTests.gerror_array_in([-1, 0, 1, 2])).toThrow(); }); it('marshals a GError** elsewhere in the signature as an out parameter', function () { expect(GIMarshallingTests.gerror_out()).toEqual([ jasmine.any(GLib.Error), 'we got an error, life is shit', ]); }); it('marshals a GError** elsewhere in the signature as an out parameter with transfer none', function () { expect(GIMarshallingTests.gerror_out_transfer_none()).toEqual([ jasmine.any(GLib.Error), 'we got an error, life is shit', ]); }); it('marshals GError as a return value', function () { expect(GIMarshallingTests.gerror_return()).toEqual(jasmine.any(GLib.Error)); }); }); describe('Overrides', function () { it('can add constants', function () { expect(GIMarshallingTests.OVERRIDES_CONSTANT).toEqual(7); }); it('can override a struct method', function () { const struct = new GIMarshallingTests.OverridesStruct(); expect(struct.method()).toEqual(6); }); it('can override an object constructor', function () { const obj = new GIMarshallingTests.OverridesObject(42); expect(obj.num).toEqual(42); }); it('can override an object method', function () { const obj = new GIMarshallingTests.OverridesObject(); expect(obj.method()).toEqual(6); }); }); describe('Filename', function () { testReturnValue('filename_list', []); }); describe('GObject.ParamSpec', function () { const pspec = GObject.ParamSpec.boolean('mybool', 'My Bool', 'My boolean property', GObject.ParamFlags.READWRITE, true); testInParameter('param_spec', pspec, { funcName: 'param_spec_in_bool', }); const expectedProps = { name: 'test-param', nick: 'test', blurb: 'This is a test', default_value: '42', flags: GObject.ParamFlags.READABLE, value_type: GObject.TYPE_STRING, }; testReturnValue('param_spec', jasmine.objectContaining(expectedProps)); testOutParameter('param_spec', jasmine.objectContaining(expectedProps)); }); describe('GObject properties', function () { let obj; beforeEach(function () { obj = new GIMarshallingTests.PropertiesObject(); }); function testPropertyGetSet(type, value1, value2, skip = false) { it(`gets and sets a ${type} property`, function () { if (skip) pending(skip); obj[`some_${type}`] = value1; expect(obj[`some_${type}`]).toEqual(value1); obj[`some_${type}`] = value2; expect(obj[`some_${type}`]).toEqual(value2); }); } testPropertyGetSet('boolean', true, false); testPropertyGetSet('char', 42, 64); testPropertyGetSet('uchar', 42, 64); testPropertyGetSet('int', 42, 64); testPropertyGetSet('uint', 42, 64); testPropertyGetSet('long', 42, 64); testPropertyGetSet('ulong', 42, 64); testPropertyGetSet('int64', 42, 64); testPropertyGetSet('uint64', 42, 64); it('gets and sets a float property', function () { obj.some_float = Math.E; expect(obj.some_float).toBeCloseTo(Math.E); obj.some_float = Math.PI; expect(obj.some_float).toBeCloseTo(Math.PI); }); it('gets and sets a double property', function () { obj.some_double = Math.E; expect(obj.some_double).toBeCloseTo(Math.E); obj.some_double = Math.PI; expect(obj.some_double).toBeCloseTo(Math.PI); }); testPropertyGetSet('strv', ['0', '1', '2'], []); testPropertyGetSet('boxed_struct', new GIMarshallingTests.BoxedStruct(), new GIMarshallingTests.BoxedStruct({long_: 42})); testPropertyGetSet('boxed_glist', null, null); testPropertyGetSet('gvalue', 42, 'foo'); testPropertyGetSet('variant', new GLib.Variant('b', true), new GLib.Variant('s', 'hello')); testPropertyGetSet('object', new GObject.Object(), new GIMarshallingTests.Object({int: 42})); testPropertyGetSet('flags', GIMarshallingTests.Flags.VALUE2, GIMarshallingTests.Flags.VALUE1 | GIMarshallingTests.Flags.VALUE2); testPropertyGetSet('enum', GIMarshallingTests.GEnum.VALUE2, GIMarshallingTests.GEnum.VALUE3); testPropertyGetSet('byte_array', Uint8Array.of(1, 2, 3), ByteArray.fromString('👾'), 'https://gitlab.gnome.org/GNOME/gjs/issues/276'); it('gets a read-only property', function () { expect(obj.some_readonly).toEqual(42); }); it('throws when setting a read-only property', function () { expect(() => (obj.some_readonly = 35)).toThrow(); }); }); cjs-5.2.0/installed-tests/js/complex4.ui0000644000175000017500000000171614144444702020271 0ustar jpeisachjpeisach cjs-5.2.0/installed-tests/js/testself.js0000644000175000017500000000215014144444702020357 0ustar jpeisachjpeisachdescribe('Test harness internal consistency', function () { it('', function () { var someUndefined; var someNumber = 1; var someOtherNumber = 42; var someString = 'hello'; var someOtherString = 'world'; expect(true).toBeTruthy(); expect(false).toBeFalsy(); expect(someNumber).toEqual(someNumber); expect(someString).toEqual(someString); expect(someNumber).not.toEqual(someOtherNumber); expect(someString).not.toEqual(someOtherString); expect(null).toBeNull(); expect(someNumber).not.toBeNull(); expect(someNumber).toBeDefined(); expect(someUndefined).not.toBeDefined(); expect(0 / 0).toBeNaN(); expect(someNumber).not.toBeNaN(); expect(() => { throw new Error(); }).toThrow(); expect(() => expect(true).toThrow()).toThrow(); expect(() => true).not.toThrow(); }); }); describe('SpiderMonkey features check', function () { it('Intl API was compiled into SpiderMonkey', function () { expect(Intl).toBeDefined(); }); }); cjs-5.2.0/installed-tests/js/testCairo.js0000644000175000017500000002504314144444702020471 0ustar jpeisachjpeisachimports.gi.versions.Gdk = '3.0'; imports.gi.versions.Gtk = '3.0'; const Cairo = imports.cairo; const {Gdk, GIMarshallingTests, GLib, Gtk, Regress} = imports.gi; function _ts(obj) { return obj.toString().slice(8, -1); } describe('Cairo', function () { let cr, surface; beforeEach(function () { surface = new Cairo.ImageSurface(Cairo.Format.ARGB32, 10, 10); cr = new Cairo.Context(surface); }); describe('context', function () { it('has the right type', function () { expect(cr instanceof Cairo.Context).toBeTruthy(); }); it('reports its target surface', function () { expect(_ts(cr.getTarget())).toEqual('ImageSurface'); }); it('can set its source to a pattern', function () { let pattern = Cairo.SolidPattern.createRGB(1, 2, 3); cr.setSource(pattern); expect(_ts(cr.getSource())).toEqual('SolidPattern'); }); it('can set its antialias', function () { cr.setAntialias(Cairo.Antialias.NONE); expect(cr.getAntialias()).toEqual(Cairo.Antialias.NONE); }); it('can set its fill rule', function () { cr.setFillRule(Cairo.FillRule.EVEN_ODD); expect(cr.getFillRule()).toEqual(Cairo.FillRule.EVEN_ODD); }); it('can set its line cap', function () { cr.setLineCap(Cairo.LineCap.ROUND); expect(cr.getLineCap()).toEqual(Cairo.LineCap.ROUND); }); it('can set its line join', function () { cr.setLineJoin(Cairo.LineJoin.ROUND); expect(cr.getLineJoin()).toEqual(Cairo.LineJoin.ROUND); }); it('can set its line width', function () { cr.setLineWidth(1138); expect(cr.getLineWidth()).toEqual(1138); }); it('can set its miter limit', function () { cr.setMiterLimit(42); expect(cr.getMiterLimit()).toEqual(42); }); it('can set its operator', function () { cr.setOperator(Cairo.Operator.IN); expect(cr.getOperator()).toEqual(Cairo.Operator.IN); }); it('can set its tolerance', function () { cr.setTolerance(144); expect(cr.getTolerance()).toEqual(144); }); it('has a rectangle as clip extents', function () { expect(cr.clipExtents().length).toEqual(4); }); it('has a rectangle as fill extents', function () { expect(cr.fillExtents().length).toEqual(4); }); it('has a rectangle as stroke extents', function () { expect(cr.strokeExtents().length).toEqual(4); }); it('has zero dashes initially', function () { expect(cr.getDashCount()).toEqual(0); }); it('transforms user to device coordinates', function () { expect(cr.userToDevice(0, 0).length).toEqual(2); }); it('transforms user to device distance', function () { expect(cr.userToDeviceDistance(0, 0).length).toEqual(2); }); it('transforms device to user coordinates', function () { expect(cr.deviceToUser(0, 0).length).toEqual(2); }); it('transforms device to user distance', function () { expect(cr.deviceToUserDistance(0, 0).length).toEqual(2); }); it('can call various, otherwise untested, methods without crashing', function () { expect(() => { cr.save(); cr.restore(); cr.setSourceSurface(surface, 0, 0); cr.pushGroup(); cr.popGroup(); cr.pushGroupWithContent(Cairo.Content.COLOR); cr.popGroupToSource(); cr.setSourceRGB(1, 2, 3); cr.setSourceRGBA(1, 2, 3, 4); cr.clip(); cr.clipPreserve(); cr.fill(); cr.fillPreserve(); let pattern = Cairo.SolidPattern.createRGB(1, 2, 3); cr.mask(pattern); cr.maskSurface(surface, 0, 0); cr.paint(); cr.paintWithAlpha(1); cr.setDash([1, 0.5], 1); cr.stroke(); cr.strokePreserve(); cr.inFill(0, 0); cr.inStroke(0, 0); cr.copyPage(); cr.showPage(); cr.translate(10, 10); cr.scale(10, 10); cr.rotate(180); cr.identityMatrix(); cr.showText('foobar'); cr.moveTo(0, 0); cr.setDash([], 1); cr.lineTo(1, 0); cr.lineTo(1, 1); cr.lineTo(0, 1); cr.closePath(); let path = cr.copyPath(); cr.fill(); cr.appendPath(path); cr.stroke(); }).not.toThrow(); }); it('has methods when created from a C function', function () { if (GLib.getenv('ENABLE_GTK') !== 'yes') { pending('GTK disabled'); return; } Gtk.init(null); let win = new Gtk.OffscreenWindow(); let da = new Gtk.DrawingArea(); win.add(da); da.realize(); cr = Gdk.cairo_create(da.window); expect(cr.save).toBeDefined(); expect(cr.getTarget()).toBeDefined(); }); }); describe('pattern', function () { it('has typechecks', function () { expect(() => cr.setSource({})).toThrow(); expect(() => cr.setSource(surface)).toThrow(); }); }); describe('solid pattern', function () { it('can be created from RGB static method', function () { let p1 = Cairo.SolidPattern.createRGB(1, 2, 3); expect(_ts(p1)).toEqual('SolidPattern'); cr.setSource(p1); expect(_ts(cr.getSource())).toEqual('SolidPattern'); }); it('can be created from RGBA static method', function () { let p2 = Cairo.SolidPattern.createRGBA(1, 2, 3, 4); expect(_ts(p2)).toEqual('SolidPattern'); cr.setSource(p2); expect(_ts(cr.getSource())).toEqual('SolidPattern'); }); }); describe('surface pattern', function () { it('can be created and added as a source', function () { let p1 = new Cairo.SurfacePattern(surface); expect(_ts(p1)).toEqual('SurfacePattern'); cr.setSource(p1); expect(_ts(cr.getSource())).toEqual('SurfacePattern'); }); }); describe('linear gradient', function () { it('can be created and added as a source', function () { let p1 = new Cairo.LinearGradient(1, 2, 3, 4); expect(_ts(p1)).toEqual('LinearGradient'); cr.setSource(p1); expect(_ts(cr.getSource())).toEqual('LinearGradient'); }); }); describe('radial gradient', function () { it('can be created and added as a source', function () { let p1 = new Cairo.RadialGradient(1, 2, 3, 4, 5, 6); expect(_ts(p1)).toEqual('RadialGradient'); cr.setSource(p1); expect(_ts(cr.getSource())).toEqual('RadialGradient'); }); }); describe('path', function () { it('has typechecks', function () { expect(() => cr.appendPath({})).toThrow(); expect(() => cr.appendPath(surface)).toThrow(); }); }); describe('surface', function () { it('has typechecks', function () { expect(() => new Cairo.Context({})).toThrow(); const pattern = new Cairo.SurfacePattern(surface); expect(() => new Cairo.Context(pattern)).toThrow(); }); }); describe('GI test suite', function () { describe('for context', function () { it('can be marshalled as a return value', function () { const outCr = Regress.test_cairo_context_full_return(); const outSurface = outCr.getTarget(); expect(outSurface.getFormat()).toEqual(Cairo.Format.ARGB32); expect(outSurface.getWidth()).toEqual(10); expect(outSurface.getHeight()).toEqual(10); }); it('can be marshalled as an in parameter', function () { expect(() => Regress.test_cairo_context_none_in(cr)).not.toThrow(); }); }); describe('for surface', function () { ['none', 'full'].forEach(transfer => { it(`can be marshalled as a transfer-${transfer} return value`, function () { const outSurface = Regress[`test_cairo_surface_${transfer}_return`](); expect(outSurface.getFormat()).toEqual(Cairo.Format.ARGB32); expect(outSurface.getWidth()).toEqual(10); expect(outSurface.getHeight()).toEqual(10); }); }); it('can be marshalled as an in parameter', function () { expect(() => Regress.test_cairo_surface_none_in(surface)).not.toThrow(); }); it('can be marshalled as an out parameter', function () { const outSurface = Regress.test_cairo_surface_full_out(); expect(outSurface.getFormat()).toEqual(Cairo.Format.ARGB32); expect(outSurface.getWidth()).toEqual(10); expect(outSurface.getHeight()).toEqual(10); }); }); it('can be marshalled through a signal handler', function () { let o = new Regress.TestObj(); let foreignSpy = jasmine.createSpy('sig-with-foreign-struct'); o.connect('sig-with-foreign-struct', foreignSpy); o.emit_sig_with_foreign_struct(); expect(foreignSpy).toHaveBeenCalledWith(o, cr); }); it('can have its type inferred as a foreign struct', function () { expect(() => GIMarshallingTests.gvalue_in_with_type(cr, Cairo.Context)) .not.toThrow(); expect(() => GIMarshallingTests.gvalue_in_with_type(surface, Cairo.Surface)) .not.toThrow(); }); }); }); describe('Cairo imported via GI', function () { const giCairo = imports.gi.cairo; it('has the same functionality as imports.cairo', function () { const surface = new giCairo.ImageSurface(Cairo.Format.ARGB32, 1, 1); void new giCairo.Context(surface); }); it('has boxed types from the GIR file', function () { void new giCairo.RectangleInt(); }); }); cjs-5.2.0/installed-tests/js/testImporter.js0000644000175000017500000002021614144444702021232 0ustar jpeisachjpeisachdescribe('GI importer', function () { it('can import GI modules', function () { var GLib = imports.gi.GLib; expect(GLib.MAJOR_VERSION).toEqual(2); }); describe('on failure', function () { // For these tests, we provide special overrides files to sabotage the // import, at the path resource:///org/gjs/jsunit/modules/badOverrides. let oldSearchPath; beforeAll(function () { oldSearchPath = imports.overrides.searchPath.slice(); imports.overrides.searchPath = ['resource:///org/gjs/jsunit/modules/badOverrides']; }); afterAll(function () { imports.overrides.searchPath = oldSearchPath; }); it("throws an exception when the overrides file can't be imported", function () { expect(() => imports.gi.WarnLib).toThrowError(SyntaxError); }); it('throws an exception when the overrides import throws one', function () { expect(() => imports.gi.GIMarshallingTests).toThrow('💩'); }); it('throws an exception when the overrides _init throws one', function () { expect(() => imports.gi.Regress).toThrow('💩'); }); it("throws an exception when the overrides _init isn't a function", function () { expect(() => imports.gi.Gio).toThrowError(/_init/); }); }); }); describe('Importer', function () { let oldSearchPath; let foobar, subA, subB, subFoobar; beforeAll(function () { oldSearchPath = imports.searchPath.slice(); imports.searchPath = ['resource:///org/gjs/jsunit/modules']; foobar = imports.foobar; subA = imports.subA; subB = imports.subA.subB; subFoobar = subB.foobar; }); afterAll(function () { imports.searchPath = oldSearchPath; }); it('exists', function () { expect(imports).toBeDefined(); }); it('has a toString representation', function () { expect(imports.toString()).toEqual('[GjsFileImporter root]'); expect(subA.toString()).toEqual('[GjsFileImporter subA]'); }); it('throws an import error when trying to import a nonexistent module', function () { expect(() => imports.nonexistentModuleName) .toThrow(jasmine.objectContaining({name: 'ImportError'})); }); it('throws an error when evaluating the module file throws an error', function () { expect(() => imports.alwaysThrows).toThrow(); // Try again to make sure that we properly discarded the module object expect(() => imports.alwaysThrows).toThrow(); }); it('can import a module', function () { expect(foobar).toBeDefined(); expect(foobar.foo).toEqual('This is foo'); expect(foobar.bar).toEqual('This is bar'); }); it('can import a module with a toString property', function () { expect(foobar.testToString('foo')).toEqual('foo'); }); it('makes deleting the import a no-op', function () { expect(delete imports.foobar).toBeFalsy(); expect(imports.foobar).toBe(foobar); }); it('gives the same object when importing a second time', function () { foobar.somethingElse = 'Should remain'; const foobar2 = imports.foobar; expect(foobar2.somethingElse).toEqual('Should remain'); }); it('can import a submodule', function () { expect(subB).toBeDefined(); expect(subFoobar).toBeDefined(); expect(subFoobar.foo).toEqual('This is foo'); expect(subFoobar.bar).toEqual('This is bar'); }); it('imports modules with a toString representation', function () { expect(Object.prototype.toString.call(foobar)) .toEqual('[object GjsModule foobar]'); expect(subFoobar.toString()) .toEqual('[object GjsModule subA.subB.foobar]'); }); it('does not share the same object for a module on a different path', function () { foobar.somethingElse = 'Should remain'; expect(subFoobar.somethingElse).not.toBeDefined(); }); it('gives the same object when importing a submodule a second time', function () { subFoobar.someProp = 'Should be here'; const subFoobar2 = imports.subA.subB.foobar; expect(subFoobar2.someProp).toEqual('Should be here'); }); it('has no meta properties on the toplevel importer', function () { expect(imports.__moduleName__).toBeNull(); expect(imports.__parentModule__).toBeNull(); }); it('sets the names of imported modules', function () { expect(subA.__moduleName__).toEqual('subA'); expect(subB.__moduleName__).toEqual('subB'); }); it('gives a module the importer object as parent module', function () { expect(subA.__parentModule__).toBe(imports); }); it('gives a submodule the module as parent module', function () { expect(subB.__parentModule__).toBe(subA); }); // We want to check that the copy of the 'a' module imported directly // is the same as the copy that 'b' imports, and that we don't have two // copies because of the A imports B imports A loop. it('does not make a separate copy of a module imported in two places', function () { let A = imports.mutualImport.a; A.incrementCount(); expect(A.getCount()).toEqual(1); expect(A.getCountViaB()).toEqual(1); }); it('evaluates an __init__.js file in an imported directory', function () { expect(subB.testImporterFunction()).toEqual('__init__ function tested'); }); it('accesses a class defined in an __init__.js file', function () { let o = new subB.ImporterClass(); expect(o).not.toBeNull(); expect(o.testMethod()).toEqual('__init__ class tested'); }); it('can import a file encoded in UTF-8', function () { const ModUnicode = imports.modunicode; expect(ModUnicode.uval).toEqual('const \u2665 utf8'); }); describe("properties defined in the module's lexical scope", function () { let LexicalScope; beforeAll(function () { globalThis.expectMe = true; LexicalScope = imports.lexicalScope; }); it('will log a compatibility warning when accessed', function () { const GLib = imports.gi.GLib; GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, "Some code accessed the property 'b' on the module " + "'lexicalScope'.*"); GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, "Some code accessed the property 'c' on the module " + "'lexicalScope'.*"); void LexicalScope.b; void LexicalScope.c; // g_test_assert_expected_messages() is a macro, not introspectable GLib.test_assert_expected_messages_internal('Cjs', 'testImporter.js', 179, ''); }); it('can be accessed', function () { expect(LexicalScope.a).toEqual(1); expect(LexicalScope.b).toEqual(2); expect(LexicalScope.c).toEqual(3); expect(LexicalScope.d).toEqual(4); }); it('does not leak module properties into the global scope', function () { expect(globalThis.d).not.toBeDefined(); }); }); describe('enumerating modules', function () { let keys; beforeEach(function () { keys = []; for (let key in imports) keys.push(key); }); it('gets all of them', function () { expect(keys).toContain('foobar', 'subA', 'mutualImport', 'modunicode'); }); it('includes modules that throw on import', function () { expect(keys).toContain('alwaysThrows'); }); it('does not include meta properties', function () { expect(keys).not.toContain('__parentModule__'); expect(keys).not.toContain('__moduleName__'); expect(keys).not.toContain('searchPath'); }); }); it("doesn't crash when resolving a non-string property", function () { expect(imports[0]).not.toBeDefined(); expect(imports.foobar[0]).not.toBeDefined(); }); }); cjs-5.2.0/installed-tests/js/testByteArray.js0000644000175000017500000000655414144444702021344 0ustar jpeisachjpeisachconst ByteArray = imports.byteArray; const {GIMarshallingTests, GLib} = imports.gi; describe('Byte array', function () { it('can be created from a string', function () { let a = ByteArray.fromString('abcd'); expect(a.length).toEqual(4); [97, 98, 99, 100].forEach((val, ix) => expect(a[ix]).toEqual(val)); }); it('can be encoded from a string', function () { // Pick a string likely to be stored internally as Latin1 let a = ByteArray.fromString('äbcd', 'LATIN1'); expect(a.length).toEqual(4); [228, 98, 99, 100].forEach((val, ix) => expect(a[ix]).toEqual(val)); // Try again with a string not likely to be Latin1 internally a = ByteArray.fromString('⅜', 'UTF-8'); expect(a.length).toEqual(3); [0xe2, 0x85, 0x9c].forEach((val, ix) => expect(a[ix]).toEqual(val)); }); it('encodes as UTF-8 by default', function () { let a = ByteArray.fromString('⅜'); expect(a.length).toEqual(3); [0xe2, 0x85, 0x9c].forEach((val, ix) => expect(a[ix]).toEqual(val)); }); it('can be converted to a string of ASCII characters', function () { let a = new Uint8Array(4); a[0] = 97; a[1] = 98; a[2] = 99; a[3] = 100; let s = ByteArray.toString(a); expect(s.length).toEqual(4); expect(s).toEqual('abcd'); }); it('can be converted to a string of UTF-8 characters even if it ends with a 0', function () { const a = Uint8Array.of(97, 98, 99, 100, 0); const s = ByteArray.toString(a); expect(s.length).toEqual(4); expect(s).toEqual('abcd'); }); it('can be converted to a string of encoded characters even with a 0 byte', function () { const a = Uint8Array.of(97, 98, 99, 100, 0); const s = ByteArray.toString(a, 'LATIN1'); expect(s.length).toEqual(4); expect(s).toEqual('abcd'); }); it('stops converting to a string at an embedded 0 byte', function () { const a = Uint8Array.of(97, 98, 0, 99, 100); const s = ByteArray.toString(a); expect(s.length).toEqual(2); expect(s).toEqual('ab'); }); it('deals gracefully with a 0-length array', function () { const a = new Uint8Array(0); expect(ByteArray.toString(a)).toEqual(''); expect(ByteArray.toGBytes(a).get_size()).toEqual(0); }); it('deals gracefully with a non Uint8Array', function () { const a = [97, 98, 99, 100, 0]; expect(() => ByteArray.toString(a)).toThrow(); expect(() => ByteArray.toGBytes(a)).toThrow(); }); describe('legacy toString() behavior', function () { beforeEach(function () { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, 'Some code called array.toString()*'); }); it('is preserved when created from a string', function () { let a = ByteArray.fromString('⅜'); expect(a.toString()).toEqual('⅜'); }); it('is preserved when marshalled from GI', function () { let a = GIMarshallingTests.bytearray_full_return(); expect(a.toString()).toEqual(''); }); afterEach(function () { GLib.test_assert_expected_messages_internal('Cjs', 'testByteArray.js', 0, 'testToStringCompatibility'); }); }); }); cjs-5.2.0/installed-tests/js/testLang.js0000644000175000017500000000560214144444702020314 0ustar jpeisachjpeisach/* eslint-disable no-restricted-properties */ // tests for imports.lang module // except for Lang.Class and Lang.Interface, which are tested in testLegacyClass const Lang = imports.lang; describe('Lang module', function () { it('counts properties with Lang.countProperties()', function () { var foo = {'a': 10, 'b': 11}; expect(Lang.countProperties(foo)).toEqual(2); }); it('copies properties from one object to another with Lang.copyProperties()', function () { var foo = {'a': 10, 'b': 11}; var bar = {}; Lang.copyProperties(foo, bar); expect(bar).toEqual(foo); }); it('copies properties without an underscore with Lang.copyPublicProperties()', function () { var foo = {'a': 10, 'b': 11, '_c': 12}; var bar = {}; Lang.copyPublicProperties(foo, bar); expect(bar).toEqual({'a': 10, 'b': 11}); }); it('copies property getters and setters', function () { var foo = { 'a': 10, 'b': 11, get c() { return this.a; }, set c(n) { this.a = n; }, }; var bar = {}; Lang.copyProperties(foo, bar); expect(bar.__lookupGetter__('c')).not.toBeNull(); expect(bar.__lookupSetter__('c')).not.toBeNull(); // this should return the value of 'a' expect(bar.c).toEqual(10); // this should set 'a' value bar.c = 13; expect(bar.a).toEqual(13); }); describe('bind()', function () { let o; beforeEach(function () { o = { callback() { return true; }, }; spyOn(o, 'callback').and.callThrough(); }); it('calls the bound function with the supplied this-object', function () { let callback = Lang.bind(o, o.callback); callback(); expect(o.callback.calls.mostRecent()).toEqual({ object: o, args: [], returnValue: true, }); }); it('throws an error when no function supplied', function () { expect(() => Lang.bind(o, undefined)).toThrow(); }); it('throws an error when this-object undefined', function () { expect(() => Lang.bind(undefined, function () {})).toThrow(); }); it('supplies extra arguments to the function', function () { let callback = Lang.bind(o, o.callback, 42, 1138); callback(); expect(o.callback).toHaveBeenCalledWith(42, 1138); }); it('appends the extra arguments to any arguments passed', function () { let callback = Lang.bind(o, o.callback, 42, 1138); callback(1, 2, 3); expect(o.callback).toHaveBeenCalledWith(1, 2, 3, 42, 1138); }); }); }); cjs-5.2.0/installed-tests/js/testGtk4.js0000644000175000017500000001371514144444702020250 0ustar jpeisachjpeisachimports.gi.versions.Gtk = '4.0'; const ByteArray = imports.byteArray; const {Gio, GObject, Gtk} = imports.gi; // This is ugly here, but usually it would be in a resource function createTemplate(className) { return ` `; } const MyComplexGtkSubclass = GObject.registerClass({ Template: ByteArray.fromString(createTemplate('Gjs_MyComplexGtkSubclass')), Children: ['label-child', 'label-child2'], InternalChildren: ['internal-label-child'], CssName: 'complex-subclass', }, class MyComplexGtkSubclass extends Gtk.Grid { templateCallback(widget) { this.callbackEmittedBy = widget; } boundCallback(widget) { widget.callbackBoundTo = this; } }); // Sadly, putting this in the body of the class will prevent calling // get_template_child, since MyComplexGtkSubclass will be bound to the ES6 // class name without the GObject goodies in it MyComplexGtkSubclass.prototype.testChildrenExist = function () { this._internalLabel = this.get_template_child(MyComplexGtkSubclass, 'label-child'); expect(this._internalLabel).toEqual(jasmine.anything()); expect(this.label_child2).toEqual(jasmine.anything()); expect(this._internal_label_child).toEqual(jasmine.anything()); }; const MyComplexGtkSubclassFromResource = GObject.registerClass({ Template: 'resource:///org/gjs/jsunit/complex4.ui', Children: ['label-child', 'label-child2'], InternalChildren: ['internal-label-child'], }, class MyComplexGtkSubclassFromResource extends Gtk.Grid { testChildrenExist() { expect(this.label_child).toEqual(jasmine.anything()); expect(this.label_child2).toEqual(jasmine.anything()); expect(this._internal_label_child).toEqual(jasmine.anything()); } templateCallback(widget) { this.callbackEmittedBy = widget; } boundCallback(widget) { widget.callbackBoundTo = this; } }); const [templateFile, stream] = Gio.File.new_tmp(null); const baseStream = stream.get_output_stream(); const out = new Gio.DataOutputStream({baseStream}); out.put_string(createTemplate('Gjs_MyComplexGtkSubclassFromFile'), null); out.close(null); const MyComplexGtkSubclassFromFile = GObject.registerClass({ Template: templateFile.get_uri(), Children: ['label-child', 'label-child2'], InternalChildren: ['internal-label-child'], }, class MyComplexGtkSubclassFromFile extends Gtk.Grid { testChildrenExist() { expect(this.label_child).toEqual(jasmine.anything()); expect(this.label_child2).toEqual(jasmine.anything()); expect(this._internal_label_child).toEqual(jasmine.anything()); } templateCallback(widget) { this.callbackEmittedBy = widget; } boundCallback(widget) { widget.callbackBoundTo = this; } }); const SubclassSubclass = GObject.registerClass( class SubclassSubclass extends MyComplexGtkSubclass {}); function validateTemplate(description, ClassName, pending = false) { let suite = pending ? xdescribe : describe; suite(description, function () { let win, content; beforeEach(function () { win = new Gtk.Window(); content = new ClassName(); content.label_child.emit('copy-clipboard'); content.label_child2.emit('copy-clipboard'); win.set_child(content); }); it('sets up internal and public template children', function () { content.testChildrenExist(); }); it('sets up public template children with the correct widgets', function () { expect(content.label_child.get_label()).toEqual('Complex!'); expect(content.label_child2.get_label()).toEqual('Complex as well!'); }); it('sets up internal template children with the correct widgets', function () { expect(content._internal_label_child.get_label()) .toEqual('Complex and internal!'); }); it('connects template callbacks to the correct handler', function () { expect(content.callbackEmittedBy).toBe(content.label_child); }); it('binds template callbacks to the correct object', function () { expect(content.label_child2.callbackBoundTo).toBe(content.label_child); }); afterEach(function () { win.destroy(); }); }); } describe('Gtk overrides', function () { beforeAll(function () { Gtk.init(); }); afterAll(function () { templateFile.delete(null); }); validateTemplate('UI template', MyComplexGtkSubclass); validateTemplate('UI template from resource', MyComplexGtkSubclassFromResource); validateTemplate('UI template from file', MyComplexGtkSubclassFromFile); validateTemplate('Class inheriting from template class', SubclassSubclass, true); it('sets CSS names on classes', function () { expect(Gtk.Widget.get_css_name.call(MyComplexGtkSubclass)).toEqual('complex-subclass'); }); it('can create a Gtk.TreeIter with accessible stamp field', function () { const iter = new Gtk.TreeIter(); iter.stamp = 42; expect(iter.stamp).toEqual(42); }); }); cjs-5.2.0/installed-tests/js/jasmine.js0000644000175000017500000027666514144444702020204 0ustar jpeisachjpeisach/* Copyright (c) 2008-2016 Pivotal Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ var getJasmineRequireObj = (function (jasmineGlobal) { var jasmineRequire; if (typeof module !== 'undefined' && module.exports && typeof exports !== 'undefined') { if (typeof global !== 'undefined') { jasmineGlobal = global; } else { jasmineGlobal = {}; } jasmineRequire = exports; } else { if (typeof window !== 'undefined' && typeof window.toString === 'function' && window.toString() === '[object GjsGlobal]') { jasmineGlobal = window; } jasmineRequire = jasmineGlobal.jasmineRequire = jasmineGlobal.jasmineRequire || {}; } function getJasmineRequire() { return jasmineRequire; } getJasmineRequire().core = function(jRequire) { var j$ = {}; jRequire.base(j$, jasmineGlobal); j$.util = jRequire.util(); j$.errors = jRequire.errors(); j$.formatErrorMsg = jRequire.formatErrorMsg(); j$.Any = jRequire.Any(j$); j$.Anything = jRequire.Anything(j$); j$.CallTracker = jRequire.CallTracker(j$); j$.MockDate = jRequire.MockDate(); j$.Clock = jRequire.Clock(); j$.DelayedFunctionScheduler = jRequire.DelayedFunctionScheduler(); j$.Env = jRequire.Env(j$); j$.ExceptionFormatter = jRequire.ExceptionFormatter(); j$.Expectation = jRequire.Expectation(); j$.buildExpectationResult = jRequire.buildExpectationResult(); j$.JsApiReporter = jRequire.JsApiReporter(); j$.matchersUtil = jRequire.matchersUtil(j$); j$.ObjectContaining = jRequire.ObjectContaining(j$); j$.ArrayContaining = jRequire.ArrayContaining(j$); j$.pp = jRequire.pp(j$); j$.QueueRunner = jRequire.QueueRunner(j$); j$.ReportDispatcher = jRequire.ReportDispatcher(); j$.Spec = jRequire.Spec(j$); j$.SpyRegistry = jRequire.SpyRegistry(j$); j$.SpyStrategy = jRequire.SpyStrategy(j$); j$.StringMatching = jRequire.StringMatching(j$); j$.Suite = jRequire.Suite(j$); j$.Timer = jRequire.Timer(); j$.TreeProcessor = jRequire.TreeProcessor(); j$.version = jRequire.version(); j$.Order = jRequire.Order(); j$.matchers = jRequire.requireMatchers(jRequire, j$); return j$; }; return getJasmineRequire; })(this); getJasmineRequireObj().requireMatchers = function(jRequire, j$) { var availableMatchers = [ 'toBe', 'toBeCloseTo', 'toBeDefined', 'toBeFalsy', 'toBeGreaterThan', 'toBeGreaterThanOrEqual', 'toBeLessThanOrEqual', 'toBeLessThan', 'toBeNaN', 'toBeNull', 'toBeTruthy', 'toBeUndefined', 'toContain', 'toEqual', 'toHaveBeenCalled', 'toHaveBeenCalledWith', 'toHaveBeenCalledTimes', 'toMatch', 'toThrow', 'toThrowError' ], matchers = {}; for (var i = 0; i < availableMatchers.length; i++) { var name = availableMatchers[i]; matchers[name] = jRequire[name](j$); } return matchers; }; getJasmineRequireObj().base = function(j$, jasmineGlobal) { j$.unimplementedMethod_ = function() { throw new Error('unimplemented method'); }; j$.MAX_PRETTY_PRINT_DEPTH = 40; j$.MAX_PRETTY_PRINT_ARRAY_LENGTH = 100; j$.DEFAULT_TIMEOUT_INTERVAL = 5000; j$.getGlobal = function() { return jasmineGlobal; }; j$.getEnv = function(options) { var env = j$.currentEnv_ = j$.currentEnv_ || new j$.Env(options); //jasmine. singletons in here (setTimeout blah blah). return env; }; j$.isArray_ = function(value) { return j$.isA_('Array', value); }; j$.isString_ = function(value) { return j$.isA_('String', value); }; j$.isNumber_ = function(value) { return j$.isA_('Number', value); }; j$.isFunction_ = function(value) { return j$.isA_('Function', value); }; j$.isA_ = function(typeName, value) { return Object.prototype.toString.apply(value) === '[object ' + typeName + ']'; }; j$.isDomNode = function(obj) { return obj.nodeType > 0; }; j$.fnNameFor = function(func) { if (func.name) { return func.name; } var matches = func.toString().match(/^\s*function\s*(\w*)\s*\(/); return matches ? matches[1] : ''; }; j$.any = function(clazz) { return new j$.Any(clazz); }; j$.anything = function() { return new j$.Anything(); }; j$.objectContaining = function(sample) { return new j$.ObjectContaining(sample); }; j$.stringMatching = function(expected) { return new j$.StringMatching(expected); }; j$.arrayContaining = function(sample) { return new j$.ArrayContaining(sample); }; j$.createSpy = function(name, originalFn) { var spyStrategy = new j$.SpyStrategy({ name: name, fn: originalFn, getSpy: function() { return spy; } }), callTracker = new j$.CallTracker(), spy = function() { var callData = { object: this, args: Array.prototype.slice.apply(arguments) }; callTracker.track(callData); var returnValue = spyStrategy.exec.apply(this, arguments); callData.returnValue = returnValue; return returnValue; }; for (var prop in originalFn) { if (prop === 'and' || prop === 'calls') { throw new Error('Jasmine spies would overwrite the \'and\' and \'calls\' properties on the object being spied upon'); } spy[prop] = originalFn[prop]; } spy.and = spyStrategy; spy.calls = callTracker; return spy; }; j$.isSpy = function(putativeSpy) { if (!putativeSpy) { return false; } return putativeSpy.and instanceof j$.SpyStrategy && putativeSpy.calls instanceof j$.CallTracker; }; j$.createSpyObj = function(baseName, methodNames) { if (j$.isArray_(baseName) && j$.util.isUndefined(methodNames)) { methodNames = baseName; baseName = 'unknown'; } if (!j$.isArray_(methodNames) || methodNames.length === 0) { throw 'createSpyObj requires a non-empty array of method names to create spies for'; } var obj = {}; for (var i = 0; i < methodNames.length; i++) { obj[methodNames[i]] = j$.createSpy(baseName + '.' + methodNames[i]); } return obj; }; }; getJasmineRequireObj().util = function() { var util = {}; util.inherit = function(childClass, parentClass) { var Subclass = function() { }; Subclass.prototype = parentClass.prototype; childClass.prototype = new Subclass(); }; util.htmlEscape = function(str) { if (!str) { return str; } return str.replace(/&/g, '&') .replace(//g, '>'); }; util.argsToArray = function(args) { var arrayOfArgs = []; for (var i = 0; i < args.length; i++) { arrayOfArgs.push(args[i]); } return arrayOfArgs; }; util.isUndefined = function(obj) { return obj === void 0; }; util.arrayContains = function(array, search) { var i = array.length; while (i--) { if (array[i] === search) { return true; } } return false; }; util.clone = function(obj) { if (Object.prototype.toString.apply(obj) === '[object Array]') { return obj.slice(); } var cloned = {}; for (var prop in obj) { if (obj.hasOwnProperty(prop)) { cloned[prop] = obj[prop]; } } return cloned; }; return util; }; getJasmineRequireObj().Spec = function(j$) { function Spec(attrs) { this.expectationFactory = attrs.expectationFactory; this.resultCallback = attrs.resultCallback || function() {}; this.id = attrs.id; this.description = attrs.description || ''; this.queueableFn = attrs.queueableFn; this.beforeAndAfterFns = attrs.beforeAndAfterFns || function() { return {befores: [], afters: []}; }; this.userContext = attrs.userContext || function() { return {}; }; this.onStart = attrs.onStart || function() {}; this.getSpecName = attrs.getSpecName || function() { return ''; }; this.expectationResultFactory = attrs.expectationResultFactory || function() { }; this.queueRunnerFactory = attrs.queueRunnerFactory || function() {}; this.catchingExceptions = attrs.catchingExceptions || function() { return true; }; this.throwOnExpectationFailure = !!attrs.throwOnExpectationFailure; if (!this.queueableFn.fn) { this.pend(); } this.result = { id: this.id, description: this.description, fullName: this.getFullName(), failedExpectations: [], passedExpectations: [], pendingReason: '' }; } Spec.prototype.addExpectationResult = function(passed, data, isError) { var expectationResult = this.expectationResultFactory(data); if (passed) { this.result.passedExpectations.push(expectationResult); } else { this.result.failedExpectations.push(expectationResult); if (this.throwOnExpectationFailure && !isError) { throw new j$.errors.ExpectationFailed(); } } }; Spec.prototype.expect = function(actual) { return this.expectationFactory(actual, this); }; Spec.prototype.execute = function(onComplete, enabled) { var self = this; this.onStart(this); if (!this.isExecutable() || this.markedPending || enabled === false) { complete(enabled); return; } var fns = this.beforeAndAfterFns(); var allFns = fns.befores.concat(this.queueableFn).concat(fns.afters); this.queueRunnerFactory({ queueableFns: allFns, onException: function() { self.onException.apply(self, arguments); }, onComplete: complete, userContext: this.userContext() }); function complete(enabledAgain) { self.result.status = self.status(enabledAgain); self.resultCallback(self.result); if (onComplete) { onComplete(); } } }; Spec.prototype.onException = function onException(e) { if (Spec.isPendingSpecException(e)) { this.pend(extractCustomPendingMessage(e)); return; } if (e instanceof j$.errors.ExpectationFailed) { return; } this.addExpectationResult(false, { matcherName: '', passed: false, expected: '', actual: '', error: e }, true); }; Spec.prototype.disable = function() { this.disabled = true; }; Spec.prototype.pend = function(message) { this.markedPending = true; if (message) { this.result.pendingReason = message; } }; Spec.prototype.getResult = function() { this.result.status = this.status(); return this.result; }; Spec.prototype.status = function(enabled) { if (this.disabled || enabled === false) { return 'disabled'; } if (this.markedPending) { return 'pending'; } if (this.result.failedExpectations.length > 0) { return 'failed'; } else { return 'passed'; } }; Spec.prototype.isExecutable = function() { return !this.disabled; }; Spec.prototype.getFullName = function() { return this.getSpecName(this); }; var extractCustomPendingMessage = function(e) { var fullMessage = e.toString(), boilerplateStart = fullMessage.indexOf(Spec.pendingSpecExceptionMessage), boilerplateEnd = boilerplateStart + Spec.pendingSpecExceptionMessage.length; return fullMessage.substr(boilerplateEnd); }; Spec.pendingSpecExceptionMessage = '=> marked Pending'; Spec.isPendingSpecException = function(e) { return !!(e && e.toString && e.toString().indexOf(Spec.pendingSpecExceptionMessage) !== -1); }; return Spec; }; if (typeof window == void 0 && typeof exports == 'object') { exports.Spec = jasmineRequire.Spec; } /*jshint bitwise: false*/ getJasmineRequireObj().Order = function() { function Order(options) { this.random = 'random' in options ? options.random : true; var seed = this.seed = options.seed || generateSeed(); this.sort = this.random ? randomOrder : naturalOrder; function naturalOrder(items) { return items; } function randomOrder(items) { var copy = items.slice(); copy.sort(function(a, b) { return jenkinsHash(seed + a.id) - jenkinsHash(seed + b.id); }); return copy; } function generateSeed() { return String(Math.random()).slice(-5); } // Bob Jenkins One-at-a-Time Hash algorithm is a non-cryptographic hash function // used to get a different output when the key changes slighly. // We use your return to sort the children randomly in a consistent way when // used in conjunction with a seed function jenkinsHash(key) { var hash, i; for(hash = i = 0; i < key.length; ++i) { hash += key.charCodeAt(i); hash += (hash << 10); hash ^= (hash >> 6); } hash += (hash << 3); hash ^= (hash >> 11); hash += (hash << 15); return hash; } } return Order; }; getJasmineRequireObj().Env = function(j$) { function Env(options) { options = options || {}; var self = this; var global = options.global || j$.getGlobal(); var totalSpecsDefined = 0; var catchExceptions = true; var realSetTimeout = j$.getGlobal().setTimeout; var realClearTimeout = j$.getGlobal().clearTimeout; this.clock = new j$.Clock(global, function () { return new j$.DelayedFunctionScheduler(); }, new j$.MockDate(global)); var runnableResources = {}; var currentSpec = null; var currentlyExecutingSuites = []; var currentDeclarationSuite = null; var throwOnExpectationFailure = false; var random = false; var seed = null; var currentSuite = function() { return currentlyExecutingSuites[currentlyExecutingSuites.length - 1]; }; var currentRunnable = function() { return currentSpec || currentSuite(); }; var reporter = new j$.ReportDispatcher([ 'jasmineStarted', 'jasmineDone', 'suiteStarted', 'suiteDone', 'specStarted', 'specDone' ]); this.specFilter = function() { return true; }; this.addCustomEqualityTester = function(tester) { if(!currentRunnable()) { throw new Error('Custom Equalities must be added in a before function or a spec'); } runnableResources[currentRunnable().id].customEqualityTesters.push(tester); }; this.addMatchers = function(matchersToAdd) { if(!currentRunnable()) { throw new Error('Matchers must be added in a before function or a spec'); } var customMatchers = runnableResources[currentRunnable().id].customMatchers; for (var matcherName in matchersToAdd) { customMatchers[matcherName] = matchersToAdd[matcherName]; } }; j$.Expectation.addCoreMatchers(j$.matchers); var nextSpecId = 0; var getNextSpecId = function() { return 'spec' + nextSpecId++; }; var nextSuiteId = 0; var getNextSuiteId = function() { return 'suite' + nextSuiteId++; }; var expectationFactory = function(actual, spec) { return j$.Expectation.Factory({ util: j$.matchersUtil, customEqualityTesters: runnableResources[spec.id].customEqualityTesters, customMatchers: runnableResources[spec.id].customMatchers, actual: actual, addExpectationResult: addExpectationResult }); function addExpectationResult(passed, result) { return spec.addExpectationResult(passed, result); } }; var defaultResourcesForRunnable = function(id, parentRunnableId) { var resources = {spies: [], customEqualityTesters: [], customMatchers: {}}; if(runnableResources[parentRunnableId]){ resources.customEqualityTesters = j$.util.clone(runnableResources[parentRunnableId].customEqualityTesters); resources.customMatchers = j$.util.clone(runnableResources[parentRunnableId].customMatchers); } runnableResources[id] = resources; }; var clearResourcesForRunnable = function(id) { spyRegistry.clearSpies(); delete runnableResources[id]; }; var beforeAndAfterFns = function(suite) { return function() { var befores = [], afters = []; while(suite) { befores = befores.concat(suite.beforeFns); afters = afters.concat(suite.afterFns); suite = suite.parentSuite; } return { befores: befores.reverse(), afters: afters }; }; }; var getSpecName = function(spec, suite) { var fullName = [spec.description], suiteFullName = suite.getFullName(); if (suiteFullName !== '') { fullName.unshift(suiteFullName); } return fullName.join(' '); }; // TODO: we may just be able to pass in the fn instead of wrapping here var buildExpectationResult = j$.buildExpectationResult, exceptionFormatter = new j$.ExceptionFormatter(), expectationResultFactory = function(attrs) { attrs.messageFormatter = exceptionFormatter.message; attrs.stackFormatter = exceptionFormatter.stack; return buildExpectationResult(attrs); }; // TODO: fix this naming, and here's where the value comes in this.catchExceptions = function(value) { catchExceptions = !!value; return catchExceptions; }; this.catchingExceptions = function() { return catchExceptions; }; var maximumSpecCallbackDepth = 20; var currentSpecCallbackDepth = 0; function clearStack(fn) { currentSpecCallbackDepth++; if (currentSpecCallbackDepth >= maximumSpecCallbackDepth) { currentSpecCallbackDepth = 0; realSetTimeout(fn, 0); } else { fn(); } } var catchException = function(e) { return j$.Spec.isPendingSpecException(e) || catchExceptions; }; this.throwOnExpectationFailure = function(value) { throwOnExpectationFailure = !!value; }; this.throwingExpectationFailures = function() { return throwOnExpectationFailure; }; this.randomizeTests = function(value) { random = !!value; }; this.randomTests = function() { return random; }; this.seed = function(value) { if (value) { seed = value; } return seed; }; var queueRunnerFactory = function(options) { options.catchException = catchException; options.clearStack = options.clearStack || clearStack; options.timeout = {setTimeout: realSetTimeout, clearTimeout: realClearTimeout}; options.fail = self.fail; new j$.QueueRunner(options).execute(); }; var topSuite = new j$.Suite({ env: this, id: getNextSuiteId(), description: 'Jasmine__TopLevel__Suite', expectationFactory: expectationFactory, expectationResultFactory: expectationResultFactory }); defaultResourcesForRunnable(topSuite.id); currentDeclarationSuite = topSuite; this.topSuite = function() { return topSuite; }; this.execute = function(runnablesToRun) { if(!runnablesToRun) { if (focusedRunnables.length) { runnablesToRun = focusedRunnables; } else { runnablesToRun = [topSuite.id]; } } var order = new j$.Order({ random: random, seed: seed }); var processor = new j$.TreeProcessor({ tree: topSuite, runnableIds: runnablesToRun, queueRunnerFactory: queueRunnerFactory, nodeStart: function(suite) { currentlyExecutingSuites.push(suite); defaultResourcesForRunnable(suite.id, suite.parentSuite.id); reporter.suiteStarted(suite.result); }, nodeComplete: function(suite, result) { if (!suite.disabled) { clearResourcesForRunnable(suite.id); } currentlyExecutingSuites.pop(); reporter.suiteDone(result); }, orderChildren: function(node) { return order.sort(node.children); } }); if(!processor.processTree().valid) { throw new Error('Invalid order: would cause a beforeAll or afterAll to be run multiple times'); } reporter.jasmineStarted({ totalSpecsDefined: totalSpecsDefined }); currentlyExecutingSuites.push(topSuite); processor.execute(function() { clearResourcesForRunnable(topSuite.id); currentlyExecutingSuites.pop(); reporter.jasmineDone({ order: order, failedExpectations: topSuite.result.failedExpectations }); }); }; this.addReporter = function(reporterToAdd) { reporter.addReporter(reporterToAdd); }; this.provideFallbackReporter = function(reporterToAdd) { reporter.provideFallbackReporter(reporterToAdd); }; this.clearReporters = function() { reporter.clearReporters(); }; var spyRegistry = new j$.SpyRegistry({currentSpies: function() { if(!currentRunnable()) { throw new Error('Spies must be created in a before function or a spec'); } return runnableResources[currentRunnable().id].spies; }}); this.allowRespy = function(allow){ spyRegistry.allowRespy(allow); }; this.spyOn = function() { return spyRegistry.spyOn.apply(spyRegistry, arguments); }; var suiteFactory = function(description) { var suite = new j$.Suite({ env: self, id: getNextSuiteId(), description: description, parentSuite: currentDeclarationSuite, expectationFactory: expectationFactory, expectationResultFactory: expectationResultFactory, throwOnExpectationFailure: throwOnExpectationFailure }); return suite; }; this.describe = function(description, specDefinitions) { var suite = suiteFactory(description); if (specDefinitions.length > 0) { throw new Error('describe does not expect any arguments'); } if (currentDeclarationSuite.markedPending) { suite.pend(); } addSpecsToSuite(suite, specDefinitions); return suite; }; this.xdescribe = function(description, specDefinitions) { var suite = suiteFactory(description); suite.pend(); addSpecsToSuite(suite, specDefinitions); return suite; }; var focusedRunnables = []; this.fdescribe = function(description, specDefinitions) { var suite = suiteFactory(description); suite.isFocused = true; focusedRunnables.push(suite.id); unfocusAncestor(); addSpecsToSuite(suite, specDefinitions); return suite; }; function addSpecsToSuite(suite, specDefinitions) { var parentSuite = currentDeclarationSuite; parentSuite.addChild(suite); currentDeclarationSuite = suite; var declarationError = null; try { specDefinitions.call(suite); } catch (e) { declarationError = e; } if (declarationError) { self.it('encountered a declaration exception', function() { throw declarationError; }); } currentDeclarationSuite = parentSuite; } function findFocusedAncestor(suite) { while (suite) { if (suite.isFocused) { return suite.id; } suite = suite.parentSuite; } return null; } function unfocusAncestor() { var focusedAncestor = findFocusedAncestor(currentDeclarationSuite); if (focusedAncestor) { for (var i = 0; i < focusedRunnables.length; i++) { if (focusedRunnables[i] === focusedAncestor) { focusedRunnables.splice(i, 1); break; } } } } var specFactory = function(description, fn, suite, timeout) { totalSpecsDefined++; var spec = new j$.Spec({ id: getNextSpecId(), beforeAndAfterFns: beforeAndAfterFns(suite), expectationFactory: expectationFactory, resultCallback: specResultCallback, getSpecName: function(spec) { return getSpecName(spec, suite); }, onStart: specStarted, description: description, expectationResultFactory: expectationResultFactory, queueRunnerFactory: queueRunnerFactory, userContext: function() { return suite.clonedSharedUserContext(); }, queueableFn: { fn: fn, timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; } }, throwOnExpectationFailure: throwOnExpectationFailure }); if (!self.specFilter(spec)) { spec.disable(); } return spec; function specResultCallback(result) { clearResourcesForRunnable(spec.id); currentSpec = null; reporter.specDone(result); } function specStarted(spec) { currentSpec = spec; defaultResourcesForRunnable(spec.id, suite.id); reporter.specStarted(spec.result); } }; this.it = function(description, fn, timeout) { var spec = specFactory(description, fn, currentDeclarationSuite, timeout); if (currentDeclarationSuite.markedPending) { spec.pend(); } currentDeclarationSuite.addChild(spec); return spec; }; this.xit = function() { var spec = this.it.apply(this, arguments); spec.pend('Temporarily disabled with xit'); return spec; }; this.fit = function(description, fn, timeout){ var spec = specFactory(description, fn, currentDeclarationSuite, timeout); currentDeclarationSuite.addChild(spec); focusedRunnables.push(spec.id); unfocusAncestor(); return spec; }; this.expect = function(actual) { if (!currentRunnable()) { throw new Error('\'expect\' was used when there was no current spec, this could be because an asynchronous test timed out'); } return currentRunnable().expect(actual); }; this.beforeEach = function(beforeEachFunction, timeout) { currentDeclarationSuite.beforeEach({ fn: beforeEachFunction, timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; } }); }; this.beforeAll = function(beforeAllFunction, timeout) { currentDeclarationSuite.beforeAll({ fn: beforeAllFunction, timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; } }); }; this.afterEach = function(afterEachFunction, timeout) { currentDeclarationSuite.afterEach({ fn: afterEachFunction, timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; } }); }; this.afterAll = function(afterAllFunction, timeout) { currentDeclarationSuite.afterAll({ fn: afterAllFunction, timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; } }); }; this.pending = function(message) { var fullMessage = j$.Spec.pendingSpecExceptionMessage; if(message) { fullMessage += message; } throw fullMessage; }; this.fail = function(error) { var message = 'Failed'; if (error) { message += ': '; message += error.message || error; } currentRunnable().addExpectationResult(false, { matcherName: '', passed: false, expected: '', actual: '', message: message, error: error && error.message ? error : null }); }; } return Env; }; getJasmineRequireObj().JsApiReporter = function() { var noopTimer = { start: function(){}, elapsed: function(){ return 0; } }; function JsApiReporter(options) { var timer = options.timer || noopTimer, status = 'loaded'; this.started = false; this.finished = false; this.runDetails = {}; this.jasmineStarted = function() { this.started = true; status = 'started'; timer.start(); }; var executionTime; this.jasmineDone = function(runDetails) { this.finished = true; this.runDetails = runDetails; executionTime = timer.elapsed(); status = 'done'; }; this.status = function() { return status; }; var suites = [], suites_hash = {}; this.suiteStarted = function(result) { suites_hash[result.id] = result; }; this.suiteDone = function(result) { storeSuite(result); }; this.suiteResults = function(index, length) { return suites.slice(index, index + length); }; function storeSuite(result) { suites.push(result); suites_hash[result.id] = result; } this.suites = function() { return suites_hash; }; var specs = []; this.specDone = function(result) { specs.push(result); }; this.specResults = function(index, length) { return specs.slice(index, index + length); }; this.specs = function() { return specs; }; this.executionTime = function() { return executionTime; }; } return JsApiReporter; }; getJasmineRequireObj().CallTracker = function(j$) { function CallTracker() { var calls = []; var opts = {}; function argCloner(context) { var clonedArgs = []; var argsAsArray = j$.util.argsToArray(context.args); for(var i = 0; i < argsAsArray.length; i++) { if(Object.prototype.toString.apply(argsAsArray[i]).match(/^\[object/)) { clonedArgs.push(j$.util.clone(argsAsArray[i])); } else { clonedArgs.push(argsAsArray[i]); } } context.args = clonedArgs; } this.track = function(context) { if(opts.cloneArgs) { argCloner(context); } calls.push(context); }; this.any = function() { return !!calls.length; }; this.count = function() { return calls.length; }; this.argsFor = function(index) { var call = calls[index]; return call ? call.args : []; }; this.all = function() { return calls; }; this.allArgs = function() { var callArgs = []; for(var i = 0; i < calls.length; i++){ callArgs.push(calls[i].args); } return callArgs; }; this.first = function() { return calls[0]; }; this.mostRecent = function() { return calls[calls.length - 1]; }; this.reset = function() { calls = []; }; this.saveArgumentsByValue = function() { opts.cloneArgs = true; }; } return CallTracker; }; getJasmineRequireObj().Clock = function() { function Clock(global, delayedFunctionSchedulerFactory, mockDate) { var self = this, realTimingFunctions = { setTimeout: global.setTimeout, clearTimeout: global.clearTimeout, setInterval: global.setInterval, clearInterval: global.clearInterval }, fakeTimingFunctions = { setTimeout: setTimeout, clearTimeout: clearTimeout, setInterval: setInterval, clearInterval: clearInterval }, installed = false, delayedFunctionScheduler, timer; self.install = function() { if(!originalTimingFunctionsIntact()) { throw new Error('Jasmine Clock was unable to install over custom global timer functions. Is the clock already installed?'); } replace(global, fakeTimingFunctions); timer = fakeTimingFunctions; delayedFunctionScheduler = delayedFunctionSchedulerFactory(); installed = true; return self; }; self.uninstall = function() { delayedFunctionScheduler = null; mockDate.uninstall(); replace(global, realTimingFunctions); timer = realTimingFunctions; installed = false; }; self.withMock = function(closure) { this.install(); try { closure(); } finally { this.uninstall(); } }; self.mockDate = function(initialDate) { mockDate.install(initialDate); }; self.setTimeout = function(fn, delay, params) { if (legacyIE()) { if (arguments.length > 2) { throw new Error('IE < 9 cannot support extra params to setTimeout without a polyfill'); } return timer.setTimeout(fn, delay); } return Function.prototype.apply.apply(timer.setTimeout, [global, arguments]); }; self.setInterval = function(fn, delay, params) { if (legacyIE()) { if (arguments.length > 2) { throw new Error('IE < 9 cannot support extra params to setInterval without a polyfill'); } return timer.setInterval(fn, delay); } return Function.prototype.apply.apply(timer.setInterval, [global, arguments]); }; self.clearTimeout = function(id) { return Function.prototype.call.apply(timer.clearTimeout, [global, id]); }; self.clearInterval = function(id) { return Function.prototype.call.apply(timer.clearInterval, [global, id]); }; self.tick = function(millis) { if (installed) { delayedFunctionScheduler.tick(millis, function(millis) { mockDate.tick(millis); }); } else { throw new Error('Mock clock is not installed, use jasmine.clock().install()'); } }; return self; function originalTimingFunctionsIntact() { return global.setTimeout === realTimingFunctions.setTimeout && global.clearTimeout === realTimingFunctions.clearTimeout && global.setInterval === realTimingFunctions.setInterval && global.clearInterval === realTimingFunctions.clearInterval; } function legacyIE() { //if these methods are polyfilled, apply will be present return !(realTimingFunctions.setTimeout || realTimingFunctions.setInterval).apply; } function replace(dest, source) { for (var prop in source) { dest[prop] = source[prop]; } } function setTimeout(fn, delay) { return delayedFunctionScheduler.scheduleFunction(fn, delay, argSlice(arguments, 2)); } function clearTimeout(id) { return delayedFunctionScheduler.removeFunctionWithId(id); } function setInterval(fn, interval) { return delayedFunctionScheduler.scheduleFunction(fn, interval, argSlice(arguments, 2), true); } function clearInterval(id) { return delayedFunctionScheduler.removeFunctionWithId(id); } function argSlice(argsObj, n) { return Array.prototype.slice.call(argsObj, n); } } return Clock; }; getJasmineRequireObj().DelayedFunctionScheduler = function() { function DelayedFunctionScheduler() { var self = this; var scheduledLookup = []; var scheduledFunctions = {}; var currentTime = 0; var delayedFnCount = 0; self.tick = function(millis, tickDate) { millis = millis || 0; var endTime = currentTime + millis; runScheduledFunctions(endTime, tickDate); currentTime = endTime; }; self.scheduleFunction = function(funcToCall, millis, params, recurring, timeoutKey, runAtMillis) { var f; if (typeof(funcToCall) === 'string') { /* jshint evil: true */ f = function() { return eval(funcToCall); }; /* jshint evil: false */ } else { f = funcToCall; } millis = millis || 0; timeoutKey = timeoutKey || ++delayedFnCount; runAtMillis = runAtMillis || (currentTime + millis); var funcToSchedule = { runAtMillis: runAtMillis, funcToCall: f, recurring: recurring, params: params, timeoutKey: timeoutKey, millis: millis }; if (runAtMillis in scheduledFunctions) { scheduledFunctions[runAtMillis].push(funcToSchedule); } else { scheduledFunctions[runAtMillis] = [funcToSchedule]; scheduledLookup.push(runAtMillis); scheduledLookup.sort(function (a, b) { return a - b; }); } return timeoutKey; }; self.removeFunctionWithId = function(timeoutKey) { for (var runAtMillis in scheduledFunctions) { var funcs = scheduledFunctions[runAtMillis]; var i = indexOfFirstToPass(funcs, function (func) { return func.timeoutKey === timeoutKey; }); if (i > -1) { if (funcs.length === 1) { delete scheduledFunctions[runAtMillis]; deleteFromLookup(runAtMillis); } else { funcs.splice(i, 1); } // intervals get rescheduled when executed, so there's never more // than a single scheduled function with a given timeoutKey break; } } }; return self; function indexOfFirstToPass(array, testFn) { var index = -1; for (var i = 0; i < array.length; ++i) { if (testFn(array[i])) { index = i; break; } } return index; } function deleteFromLookup(key) { var value = Number(key); var i = indexOfFirstToPass(scheduledLookup, function (millis) { return millis === value; }); if (i > -1) { scheduledLookup.splice(i, 1); } } function reschedule(scheduledFn) { self.scheduleFunction(scheduledFn.funcToCall, scheduledFn.millis, scheduledFn.params, true, scheduledFn.timeoutKey, scheduledFn.runAtMillis + scheduledFn.millis); } function forEachFunction(funcsToRun, callback) { for (var i = 0; i < funcsToRun.length; ++i) { callback(funcsToRun[i]); } } function runScheduledFunctions(endTime, tickDate) { tickDate = tickDate || function() {}; if (scheduledLookup.length === 0 || scheduledLookup[0] > endTime) { tickDate(endTime - currentTime); return; } do { var newCurrentTime = scheduledLookup.shift(); tickDate(newCurrentTime - currentTime); currentTime = newCurrentTime; var funcsToRun = scheduledFunctions[currentTime]; delete scheduledFunctions[currentTime]; forEachFunction(funcsToRun, function(funcToRun) { if (funcToRun.recurring) { reschedule(funcToRun); } }); forEachFunction(funcsToRun, function(funcToRun) { funcToRun.funcToCall.apply(null, funcToRun.params || []); }); } while (scheduledLookup.length > 0 && // checking first if we're out of time prevents setTimeout(0) // scheduled in a funcToRun from forcing an extra iteration currentTime !== endTime && scheduledLookup[0] <= endTime); // ran out of functions to call, but still time left on the clock if (currentTime !== endTime) { tickDate(endTime - currentTime); } } } return DelayedFunctionScheduler; }; getJasmineRequireObj().ExceptionFormatter = function() { function ExceptionFormatter() { this.message = function(error) { var message = ''; if (error.name && error.message) { message += error.name + ': ' + error.message; } else { message += error.toString() + ' thrown'; } if (error.fileName || error.sourceURL) { message += ' in ' + (error.fileName || error.sourceURL); } if (error.line || error.lineNumber) { message += ' (line ' + (error.line || error.lineNumber) + ')'; } return message; }; this.stack = function(error) { return error ? error.stack : null; }; } return ExceptionFormatter; }; getJasmineRequireObj().Expectation = function() { function Expectation(options) { this.util = options.util || { buildFailureMessage: function() {} }; this.customEqualityTesters = options.customEqualityTesters || []; this.actual = options.actual; this.addExpectationResult = options.addExpectationResult || function(){}; this.isNot = options.isNot; var customMatchers = options.customMatchers || {}; for (var matcherName in customMatchers) { this[matcherName] = Expectation.prototype.wrapCompare(matcherName, customMatchers[matcherName]); } } Expectation.prototype.wrapCompare = function(name, matcherFactory) { return function() { var args = Array.prototype.slice.call(arguments, 0), expected = args.slice(0), message = ''; args.unshift(this.actual); var matcher = matcherFactory(this.util, this.customEqualityTesters), matcherCompare = matcher.compare; function defaultNegativeCompare() { var result = matcher.compare.apply(null, args); result.pass = !result.pass; return result; } if (this.isNot) { matcherCompare = matcher.negativeCompare || defaultNegativeCompare; } var result = matcherCompare.apply(null, args); if (!result.pass) { if (!result.message) { args.unshift(this.isNot); args.unshift(name); message = this.util.buildFailureMessage.apply(null, args); } else { if (Object.prototype.toString.apply(result.message) === '[object Function]') { message = result.message(); } else { message = result.message; } } } if (expected.length == 1) { expected = expected[0]; } // TODO: how many of these params are needed? this.addExpectationResult( result.pass, { matcherName: name, passed: result.pass, message: message, actual: this.actual, expected: expected // TODO: this may need to be arrayified/sliced } ); }; }; Expectation.addCoreMatchers = function(matchers) { var prototype = Expectation.prototype; for (var matcherName in matchers) { var matcher = matchers[matcherName]; prototype[matcherName] = prototype.wrapCompare(matcherName, matcher); } }; Expectation.Factory = function(options) { options = options || {}; var expect = new Expectation(options); // TODO: this would be nice as its own Object - NegativeExpectation // TODO: copy instead of mutate options options.isNot = true; expect.not = new Expectation(options); return expect; }; return Expectation; }; //TODO: expectation result may make more sense as a presentation of an expectation. getJasmineRequireObj().buildExpectationResult = function() { function buildExpectationResult(options) { var messageFormatter = options.messageFormatter || function() {}, stackFormatter = options.stackFormatter || function() {}; var result = { matcherName: options.matcherName, message: message(), stack: stack(), passed: options.passed }; if(!result.passed) { result.expected = options.expected; result.actual = options.actual; } return result; function message() { if (options.passed) { return 'Passed.'; } else if (options.message) { return options.message; } else if (options.error) { return messageFormatter(options.error); } return ''; } function stack() { if (options.passed) { return ''; } var error = options.error; if (!error) { try { throw new Error(message()); } catch (e) { error = e; } } return stackFormatter(error); } } return buildExpectationResult; }; getJasmineRequireObj().MockDate = function() { function MockDate(global) { var self = this; var currentTime = 0; if (!global || !global.Date) { self.install = function() {}; self.tick = function() {}; self.uninstall = function() {}; return self; } var GlobalDate = global.Date; self.install = function(mockDate) { if (mockDate instanceof GlobalDate) { currentTime = mockDate.getTime(); } else { currentTime = new GlobalDate().getTime(); } global.Date = FakeDate; }; self.tick = function(millis) { millis = millis || 0; currentTime = currentTime + millis; }; self.uninstall = function() { currentTime = 0; global.Date = GlobalDate; }; createDateProperties(); return self; function FakeDate() { switch(arguments.length) { case 0: return new GlobalDate(currentTime); case 1: return new GlobalDate(arguments[0]); case 2: return new GlobalDate(arguments[0], arguments[1]); case 3: return new GlobalDate(arguments[0], arguments[1], arguments[2]); case 4: return new GlobalDate(arguments[0], arguments[1], arguments[2], arguments[3]); case 5: return new GlobalDate(arguments[0], arguments[1], arguments[2], arguments[3], arguments[4]); case 6: return new GlobalDate(arguments[0], arguments[1], arguments[2], arguments[3], arguments[4], arguments[5]); default: return new GlobalDate(arguments[0], arguments[1], arguments[2], arguments[3], arguments[4], arguments[5], arguments[6]); } } function createDateProperties() { FakeDate.prototype = GlobalDate.prototype; FakeDate.now = function() { if (GlobalDate.now) { return currentTime; } else { throw new Error('Browser does not support Date.now()'); } }; FakeDate.toSource = GlobalDate.toSource; FakeDate.toString = GlobalDate.toString; FakeDate.parse = GlobalDate.parse; FakeDate.UTC = GlobalDate.UTC; } } return MockDate; }; getJasmineRequireObj().pp = function(j$) { function PrettyPrinter() { this.ppNestLevel_ = 0; this.seen = []; } PrettyPrinter.prototype.format = function(value) { this.ppNestLevel_++; try { if (j$.util.isUndefined(value)) { this.emitScalar('undefined'); } else if (value === null) { this.emitScalar('null'); } else if (value === 0 && 1/value === -Infinity) { this.emitScalar('-0'); } else if (value === j$.getGlobal()) { this.emitScalar(''); } else if (value.jasmineToString) { this.emitScalar(value.jasmineToString()); } else if (typeof value === 'string') { this.emitString(value); } else if (j$.isSpy(value)) { this.emitScalar('spy on ' + value.and.identity()); } else if (value instanceof RegExp) { this.emitScalar(value.toString()); } else if (typeof value === 'function') { this.emitScalar('Function'); } else if (typeof value.nodeType === 'number') { this.emitScalar('HTMLNode'); } else if (value instanceof Date) { this.emitScalar('Date(' + value + ')'); } else if (value.toString && typeof value === 'object' && !(value instanceof Array) && value.toString !== Object.prototype.toString) { this.emitScalar(value.toString()); } else if (j$.util.arrayContains(this.seen, value)) { this.emitScalar(''); } else if (j$.isArray_(value) || j$.isA_('Object', value)) { this.seen.push(value); if (j$.isArray_(value)) { this.emitArray(value); } else { this.emitObject(value); } this.seen.pop(); } else { this.emitScalar(value.toString()); } } finally { this.ppNestLevel_--; } }; PrettyPrinter.prototype.iterateObject = function(obj, fn) { for (var property in obj) { if (!Object.prototype.hasOwnProperty.call(obj, property)) { continue; } fn(property, obj.__lookupGetter__ ? (!j$.util.isUndefined(obj.__lookupGetter__(property)) && obj.__lookupGetter__(property) !== null) : false); } }; PrettyPrinter.prototype.emitArray = j$.unimplementedMethod_; PrettyPrinter.prototype.emitObject = j$.unimplementedMethod_; PrettyPrinter.prototype.emitScalar = j$.unimplementedMethod_; PrettyPrinter.prototype.emitString = j$.unimplementedMethod_; function StringPrettyPrinter() { PrettyPrinter.call(this); this.string = ''; } j$.util.inherit(StringPrettyPrinter, PrettyPrinter); StringPrettyPrinter.prototype.emitScalar = function(value) { this.append(value); }; StringPrettyPrinter.prototype.emitString = function(value) { this.append('\'' + value + '\''); }; StringPrettyPrinter.prototype.emitArray = function(array) { if (this.ppNestLevel_ > j$.MAX_PRETTY_PRINT_DEPTH) { this.append('Array'); return; } var length = Math.min(array.length, j$.MAX_PRETTY_PRINT_ARRAY_LENGTH); this.append('[ '); for (var i = 0; i < length; i++) { if (i > 0) { this.append(', '); } this.format(array[i]); } if(array.length > length){ this.append(', ...'); } var self = this; var first = array.length === 0; this.iterateObject(array, function(property, isGetter) { if (property.match(/^\d+$/)) { return; } if (first) { first = false; } else { self.append(', '); } self.formatProperty(array, property, isGetter); }); this.append(' ]'); }; StringPrettyPrinter.prototype.emitObject = function(obj) { var constructorName = obj.constructor ? j$.fnNameFor(obj.constructor) : 'null'; this.append(constructorName); if (this.ppNestLevel_ > j$.MAX_PRETTY_PRINT_DEPTH) { return; } var self = this; this.append('({ '); var first = true; this.iterateObject(obj, function(property, isGetter) { if (first) { first = false; } else { self.append(', '); } self.formatProperty(obj, property, isGetter); }); this.append(' })'); }; StringPrettyPrinter.prototype.formatProperty = function(obj, property, isGetter) { this.append(property); this.append(': '); if (isGetter) { this.append(''); } else { this.format(obj[property]); } }; StringPrettyPrinter.prototype.append = function(value) { this.string += value; }; return function(value) { var stringPrettyPrinter = new StringPrettyPrinter(); stringPrettyPrinter.format(value); return stringPrettyPrinter.string; }; }; getJasmineRequireObj().QueueRunner = function(j$) { function once(fn) { var called = false; return function() { if (!called) { called = true; fn(); } return null; }; } function QueueRunner(attrs) { this.queueableFns = attrs.queueableFns || []; this.onComplete = attrs.onComplete || function() {}; this.clearStack = attrs.clearStack || function(fn) {fn();}; this.onException = attrs.onException || function() {}; this.catchException = attrs.catchException || function() { return true; }; this.userContext = attrs.userContext || {}; this.timeout = attrs.timeout || {setTimeout: setTimeout, clearTimeout: clearTimeout}; this.fail = attrs.fail || function() {}; } QueueRunner.prototype.execute = function() { this.run(this.queueableFns, 0); }; QueueRunner.prototype.run = function(queueableFns, recursiveIndex) { var length = queueableFns.length, self = this, iterativeIndex; for(iterativeIndex = recursiveIndex; iterativeIndex < length; iterativeIndex++) { var queueableFn = queueableFns[iterativeIndex]; if (queueableFn.fn.length > 0) { attemptAsync(queueableFn); return; } else { attemptSync(queueableFn); } } var runnerDone = iterativeIndex >= length; if (runnerDone) { this.clearStack(this.onComplete); } function attemptSync(queueableFn) { try { queueableFn.fn.call(self.userContext); } catch (e) { handleException(e, queueableFn); } } function attemptAsync(queueableFn) { var clearTimeout = function () { Function.prototype.apply.apply(self.timeout.clearTimeout, [j$.getGlobal(), [timeoutId]]); }, next = once(function () { clearTimeout(timeoutId); self.run(queueableFns, iterativeIndex + 1); }), timeoutId; next.fail = function() { self.fail.apply(null, arguments); next(); }; if (queueableFn.timeout) { timeoutId = Function.prototype.apply.apply(self.timeout.setTimeout, [j$.getGlobal(), [function() { var error = new Error('Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.'); onException(error); next(); }, queueableFn.timeout()]]); } try { queueableFn.fn.call(self.userContext, next); } catch (e) { handleException(e, queueableFn); next(); } } function onException(e) { self.onException(e); } function handleException(e, queueableFn) { onException(e); if (!self.catchException(e)) { //TODO: set a var when we catch an exception and //use a finally block to close the loop in a nice way.. throw e; } } }; return QueueRunner; }; getJasmineRequireObj().ReportDispatcher = function() { function ReportDispatcher(methods) { var dispatchedMethods = methods || []; for (var i = 0; i < dispatchedMethods.length; i++) { var method = dispatchedMethods[i]; this[method] = (function(m) { return function() { dispatch(m, arguments); }; }(method)); } var reporters = []; var fallbackReporter = null; this.addReporter = function(reporter) { reporters.push(reporter); }; this.provideFallbackReporter = function(reporter) { fallbackReporter = reporter; }; this.clearReporters = function() { reporters = []; }; return this; function dispatch(method, args) { if (reporters.length === 0 && fallbackReporter !== null) { reporters.push(fallbackReporter); } for (var i = 0; i < reporters.length; i++) { var reporter = reporters[i]; if (reporter[method]) { reporter[method].apply(reporter, args); } } } } return ReportDispatcher; }; getJasmineRequireObj().SpyRegistry = function(j$) { var getErrorMsg = j$.formatErrorMsg('', 'spyOn(, )'); function SpyRegistry(options) { options = options || {}; var currentSpies = options.currentSpies || function() { return []; }; this.allowRespy = function(allow){ this.respy = allow; }; this.spyOn = function(obj, methodName) { if (j$.util.isUndefined(obj)) { throw new Error(getErrorMsg('could not find an object to spy upon for ' + methodName + '()')); } if (j$.util.isUndefined(methodName)) { throw new Error(getErrorMsg('No method name supplied')); } if (j$.util.isUndefined(obj[methodName])) { throw new Error(getErrorMsg(methodName + '() method does not exist')); } if (obj[methodName] && j$.isSpy(obj[methodName]) ) { if ( !!this.respy ){ return obj[methodName]; }else { throw new Error(getErrorMsg(methodName + ' has already been spied upon')); } } var descriptor; try { descriptor = Object.getOwnPropertyDescriptor(obj, methodName); } catch(e) { // IE 8 doesn't support `definePropery` on non-DOM nodes } if (descriptor && !(descriptor.writable || descriptor.set)) { throw new Error(getErrorMsg(methodName + ' is not declared writable or has no setter')); } var originalMethod = obj[methodName], spiedMethod = j$.createSpy(methodName, originalMethod), restoreStrategy; if (Object.prototype.hasOwnProperty.call(obj, methodName)) { restoreStrategy = function() { obj[methodName] = originalMethod; }; } else { restoreStrategy = function() { if (!delete obj[methodName]) { obj[methodName] = originalMethod; } }; } currentSpies().push({ restoreObjectToOriginalState: restoreStrategy }); obj[methodName] = spiedMethod; return spiedMethod; }; this.clearSpies = function() { var spies = currentSpies(); for (var i = spies.length - 1; i >= 0; i--) { var spyEntry = spies[i]; spyEntry.restoreObjectToOriginalState(); } }; } return SpyRegistry; }; getJasmineRequireObj().SpyStrategy = function(j$) { function SpyStrategy(options) { options = options || {}; var identity = options.name || 'unknown', originalFn = options.fn || function() {}, getSpy = options.getSpy || function() {}, plan = function() {}; this.identity = function() { return identity; }; this.exec = function() { return plan.apply(this, arguments); }; this.callThrough = function() { plan = originalFn; return getSpy(); }; this.returnValue = function(value) { plan = function() { return value; }; return getSpy(); }; this.returnValues = function() { var values = Array.prototype.slice.call(arguments); plan = function () { return values.shift(); }; return getSpy(); }; this.throwError = function(something) { var error = (something instanceof Error) ? something : new Error(something); plan = function() { throw error; }; return getSpy(); }; this.callFake = function(fn) { if(!j$.isFunction_(fn)) { throw new Error('Argument passed to callFake should be a function, got ' + fn); } plan = fn; return getSpy(); }; this.stub = function(fn) { plan = function() {}; return getSpy(); }; } return SpyStrategy; }; getJasmineRequireObj().Suite = function(j$) { function Suite(attrs) { this.env = attrs.env; this.id = attrs.id; this.parentSuite = attrs.parentSuite; this.description = attrs.description; this.expectationFactory = attrs.expectationFactory; this.expectationResultFactory = attrs.expectationResultFactory; this.throwOnExpectationFailure = !!attrs.throwOnExpectationFailure; this.beforeFns = []; this.afterFns = []; this.beforeAllFns = []; this.afterAllFns = []; this.disabled = false; this.children = []; this.result = { id: this.id, description: this.description, fullName: this.getFullName(), failedExpectations: [] }; } Suite.prototype.expect = function(actual) { return this.expectationFactory(actual, this); }; Suite.prototype.getFullName = function() { var fullName = []; for (var parentSuite = this; parentSuite; parentSuite = parentSuite.parentSuite) { if (parentSuite.parentSuite) { fullName.unshift(parentSuite.description); } } return fullName.join(' '); }; Suite.prototype.disable = function() { this.disabled = true; }; Suite.prototype.pend = function(message) { this.markedPending = true; }; Suite.prototype.beforeEach = function(fn) { this.beforeFns.unshift(fn); }; Suite.prototype.beforeAll = function(fn) { this.beforeAllFns.push(fn); }; Suite.prototype.afterEach = function(fn) { this.afterFns.unshift(fn); }; Suite.prototype.afterAll = function(fn) { this.afterAllFns.push(fn); }; Suite.prototype.addChild = function(child) { this.children.push(child); }; Suite.prototype.status = function() { if (this.disabled) { return 'disabled'; } if (this.markedPending) { return 'pending'; } if (this.result.failedExpectations.length > 0) { return 'failed'; } else { return 'finished'; } }; Suite.prototype.isExecutable = function() { return !this.disabled; }; Suite.prototype.canBeReentered = function() { return this.beforeAllFns.length === 0 && this.afterAllFns.length === 0; }; Suite.prototype.getResult = function() { this.result.status = this.status(); return this.result; }; Suite.prototype.sharedUserContext = function() { if (!this.sharedContext) { this.sharedContext = this.parentSuite ? clone(this.parentSuite.sharedUserContext()) : {}; } return this.sharedContext; }; Suite.prototype.clonedSharedUserContext = function() { return clone(this.sharedUserContext()); }; Suite.prototype.onException = function() { if (arguments[0] instanceof j$.errors.ExpectationFailed) { return; } if(isAfterAll(this.children)) { var data = { matcherName: '', passed: false, expected: '', actual: '', error: arguments[0] }; this.result.failedExpectations.push(this.expectationResultFactory(data)); } else { for (var i = 0; i < this.children.length; i++) { var child = this.children[i]; child.onException.apply(child, arguments); } } }; Suite.prototype.addExpectationResult = function () { if(isAfterAll(this.children) && isFailure(arguments)){ var data = arguments[1]; this.result.failedExpectations.push(this.expectationResultFactory(data)); if(this.throwOnExpectationFailure) { throw new j$.errors.ExpectationFailed(); } } else { for (var i = 0; i < this.children.length; i++) { var child = this.children[i]; try { child.addExpectationResult.apply(child, arguments); } catch(e) { // keep going } } } }; function isAfterAll(children) { return children && children[0].result.status; } function isFailure(args) { return !args[0]; } function clone(obj) { var clonedObj = {}; for (var prop in obj) { if (obj.hasOwnProperty(prop)) { clonedObj[prop] = obj[prop]; } } return clonedObj; } return Suite; }; if (typeof window == void 0 && typeof exports == 'object') { exports.Suite = jasmineRequire.Suite; } getJasmineRequireObj().Timer = function() { var defaultNow = (function(Date) { return function() { return new Date().getTime(); }; })(Date); function Timer(options) { options = options || {}; var now = options.now || defaultNow, startTime; this.start = function() { startTime = now(); }; this.elapsed = function() { return now() - startTime; }; } return Timer; }; getJasmineRequireObj().TreeProcessor = function() { function TreeProcessor(attrs) { var tree = attrs.tree, runnableIds = attrs.runnableIds, queueRunnerFactory = attrs.queueRunnerFactory, nodeStart = attrs.nodeStart || function() {}, nodeComplete = attrs.nodeComplete || function() {}, orderChildren = attrs.orderChildren || function(node) { return node.children; }, stats = { valid: true }, processed = false, defaultMin = Infinity, defaultMax = 1 - Infinity; this.processTree = function() { processNode(tree, false); processed = true; return stats; }; this.execute = function(done) { if (!processed) { this.processTree(); } if (!stats.valid) { throw 'invalid order'; } var childFns = wrapChildren(tree, 0); queueRunnerFactory({ queueableFns: childFns, userContext: tree.sharedUserContext(), onException: function() { tree.onException.apply(tree, arguments); }, onComplete: done }); }; function runnableIndex(id) { for (var i = 0; i < runnableIds.length; i++) { if (runnableIds[i] === id) { return i; } } } function processNode(node, parentEnabled) { var executableIndex = runnableIndex(node.id); if (executableIndex !== undefined) { parentEnabled = true; } parentEnabled = parentEnabled && node.isExecutable(); if (!node.children) { stats[node.id] = { executable: parentEnabled && node.isExecutable(), segments: [{ index: 0, owner: node, nodes: [node], min: startingMin(executableIndex), max: startingMax(executableIndex) }] }; } else { var hasExecutableChild = false; var orderedChildren = orderChildren(node); for (var i = 0; i < orderedChildren.length; i++) { var child = orderedChildren[i]; processNode(child, parentEnabled); if (!stats.valid) { return; } var childStats = stats[child.id]; hasExecutableChild = hasExecutableChild || childStats.executable; } stats[node.id] = { executable: hasExecutableChild }; segmentChildren(node, orderedChildren, stats[node.id], executableIndex); if (!node.canBeReentered() && stats[node.id].segments.length > 1) { stats = { valid: false }; } } } function startingMin(executableIndex) { return executableIndex === undefined ? defaultMin : executableIndex; } function startingMax(executableIndex) { return executableIndex === undefined ? defaultMax : executableIndex; } function segmentChildren(node, orderedChildren, nodeStats, executableIndex) { var currentSegment = { index: 0, owner: node, nodes: [], min: startingMin(executableIndex), max: startingMax(executableIndex) }, result = [currentSegment], lastMax = defaultMax, orderedChildSegments = orderChildSegments(orderedChildren); function isSegmentBoundary(minIndex) { return lastMax !== defaultMax && minIndex !== defaultMin && lastMax < minIndex - 1; } for (var i = 0; i < orderedChildSegments.length; i++) { var childSegment = orderedChildSegments[i], maxIndex = childSegment.max, minIndex = childSegment.min; if (isSegmentBoundary(minIndex)) { currentSegment = {index: result.length, owner: node, nodes: [], min: defaultMin, max: defaultMax}; result.push(currentSegment); } currentSegment.nodes.push(childSegment); currentSegment.min = Math.min(currentSegment.min, minIndex); currentSegment.max = Math.max(currentSegment.max, maxIndex); lastMax = maxIndex; } nodeStats.segments = result; } function orderChildSegments(children) { var specifiedOrder = [], unspecifiedOrder = []; for (var i = 0; i < children.length; i++) { var child = children[i], segments = stats[child.id].segments; for (var j = 0; j < segments.length; j++) { var seg = segments[j]; if (seg.min === defaultMin) { unspecifiedOrder.push(seg); } else { specifiedOrder.push(seg); } } } specifiedOrder.sort(function(a, b) { return a.min - b.min; }); return specifiedOrder.concat(unspecifiedOrder); } function executeNode(node, segmentNumber) { if (node.children) { return { fn: function(done) { nodeStart(node); queueRunnerFactory({ onComplete: function() { nodeComplete(node, node.getResult()); done(); }, queueableFns: wrapChildren(node, segmentNumber), userContext: node.sharedUserContext(), onException: function() { node.onException.apply(node, arguments); } }); } }; } else { return { fn: function(done) { node.execute(done, stats[node.id].executable); } }; } } function wrapChildren(node, segmentNumber) { var result = [], segmentChildren = stats[node.id].segments[segmentNumber].nodes; for (var i = 0; i < segmentChildren.length; i++) { result.push(executeNode(segmentChildren[i].owner, segmentChildren[i].index)); } if (!stats[node.id].executable) { return result; } return node.beforeAllFns.concat(result).concat(node.afterAllFns); } } return TreeProcessor; }; getJasmineRequireObj().Any = function(j$) { function Any(expectedObject) { if (typeof expectedObject === 'undefined') { throw new TypeError( 'jasmine.any() expects to be passed a constructor function. ' + 'Please pass one or use jasmine.anything() to match any object.' ); } this.expectedObject = expectedObject; } Any.prototype.asymmetricMatch = function(other) { if (this.expectedObject == String) { return typeof other == 'string' || other instanceof String; } if (this.expectedObject == Number) { return typeof other == 'number' || other instanceof Number; } if (this.expectedObject == Function) { return typeof other == 'function' || other instanceof Function; } if (this.expectedObject == Object) { return typeof other == 'object'; } if (this.expectedObject == Boolean) { return typeof other == 'boolean'; } return other instanceof this.expectedObject; }; Any.prototype.jasmineToString = function() { return ''; }; return Any; }; getJasmineRequireObj().Anything = function(j$) { function Anything() {} Anything.prototype.asymmetricMatch = function(other) { return !j$.util.isUndefined(other) && other !== null; }; Anything.prototype.jasmineToString = function() { return ''; }; return Anything; }; getJasmineRequireObj().ArrayContaining = function(j$) { function ArrayContaining(sample) { this.sample = sample; } ArrayContaining.prototype.asymmetricMatch = function(other) { var className = Object.prototype.toString.call(this.sample); if (className !== '[object Array]') { throw new Error('You must provide an array to arrayContaining, not \'' + this.sample + '\'.'); } for (var i = 0; i < this.sample.length; i++) { var item = this.sample[i]; if (!j$.matchersUtil.contains(other, item)) { return false; } } return true; }; ArrayContaining.prototype.jasmineToString = function () { return ''; }; return ArrayContaining; }; getJasmineRequireObj().ObjectContaining = function(j$) { function ObjectContaining(sample) { this.sample = sample; } function getPrototype(obj) { if (Object.getPrototypeOf) { return Object.getPrototypeOf(obj); } if (obj.constructor.prototype == obj) { return null; } return obj.constructor.prototype; } function hasProperty(obj, property) { if (!obj) { return false; } if (Object.prototype.hasOwnProperty.call(obj, property)) { return true; } return hasProperty(getPrototype(obj), property); } ObjectContaining.prototype.asymmetricMatch = function(other) { if (typeof(this.sample) !== 'object') { throw new Error('You must provide an object to objectContaining, not \''+this.sample+'\'.'); } for (var property in this.sample) { if (!hasProperty(other, property) || !j$.matchersUtil.equals(this.sample[property], other[property])) { return false; } } return true; }; ObjectContaining.prototype.jasmineToString = function() { return ''; }; return ObjectContaining; }; getJasmineRequireObj().StringMatching = function(j$) { function StringMatching(expected) { if (!j$.isString_(expected) && !j$.isA_('RegExp', expected)) { throw new Error('Expected is not a String or a RegExp'); } this.regexp = new RegExp(expected); } StringMatching.prototype.asymmetricMatch = function(other) { return this.regexp.test(other); }; StringMatching.prototype.jasmineToString = function() { return ''; }; return StringMatching; }; getJasmineRequireObj().errors = function() { function ExpectationFailed() {} ExpectationFailed.prototype = new Error(); ExpectationFailed.prototype.constructor = ExpectationFailed; return { ExpectationFailed: ExpectationFailed }; }; getJasmineRequireObj().formatErrorMsg = function() { function generateErrorMsg(domain, usage) { var usageDefinition = usage ? '\nUsage: ' + usage : ''; return function errorMsg(msg) { return domain + ' : ' + msg + usageDefinition; }; } return generateErrorMsg; }; getJasmineRequireObj().matchersUtil = function(j$) { // TODO: what to do about jasmine.pp not being inject? move to JSON.stringify? gut PrettyPrinter? return { equals: function(a, b, customTesters) { customTesters = customTesters || []; return eq(a, b, [], [], customTesters); }, contains: function(haystack, needle, customTesters) { customTesters = customTesters || []; if ((Object.prototype.toString.apply(haystack) === '[object Array]') || (!!haystack && !haystack.indexOf)) { for (var i = 0; i < haystack.length; i++) { if (eq(haystack[i], needle, [], [], customTesters)) { return true; } } return false; } return !!haystack && haystack.indexOf(needle) >= 0; }, buildFailureMessage: function() { var args = Array.prototype.slice.call(arguments, 0), matcherName = args[0], isNot = args[1], actual = args[2], expected = args.slice(3), englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); }); var message = 'Expected ' + j$.pp(actual) + (isNot ? ' not ' : ' ') + englishyPredicate; if (expected.length > 0) { for (var i = 0; i < expected.length; i++) { if (i > 0) { message += ','; } message += ' ' + j$.pp(expected[i]); } } return message + '.'; } }; function isAsymmetric(obj) { return obj && j$.isA_('Function', obj.asymmetricMatch); } function asymmetricMatch(a, b) { var asymmetricA = isAsymmetric(a), asymmetricB = isAsymmetric(b); if (asymmetricA && asymmetricB) { return undefined; } if (asymmetricA) { return a.asymmetricMatch(b); } if (asymmetricB) { return b.asymmetricMatch(a); } } // Equality function lovingly adapted from isEqual in // [Underscore](http://underscorejs.org) function eq(a, b, aStack, bStack, customTesters) { var result = true; var asymmetricResult = asymmetricMatch(a, b); if (!j$.util.isUndefined(asymmetricResult)) { return asymmetricResult; } for (var i = 0; i < customTesters.length; i++) { var customTesterResult = customTesters[i](a, b); if (!j$.util.isUndefined(customTesterResult)) { return customTesterResult; } } if (a instanceof Error && b instanceof Error) { return a.message == b.message; } // Identical objects are equal. `0 === -0`, but they aren't identical. // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal). if (a === b) { return a !== 0 || 1 / a == 1 / b; } // A strict comparison is necessary because `null == undefined`. if (a === null || b === null) { return a === b; } var className = Object.prototype.toString.call(a); if (className != Object.prototype.toString.call(b)) { return false; } switch (className) { // Strings, numbers, dates, and booleans are compared by value. case '[object String]': // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is // equivalent to `new String("5")`. return a == String(b); case '[object Number]': // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for // other numeric values. return a != +a ? b != +b : (a === 0 ? 1 / a == 1 / b : a == +b); case '[object Date]': case '[object Boolean]': // Coerce dates and booleans to numeric primitive values. Dates are compared by their // millisecond representations. Note that invalid dates with millisecond representations // of `NaN` are not equivalent. return +a == +b; // RegExps are compared by their source patterns and flags. case '[object RegExp]': return a.source == b.source && a.global == b.global && a.multiline == b.multiline && a.ignoreCase == b.ignoreCase; } if (typeof a != 'object' || typeof b != 'object') { return false; } var aIsDomNode = j$.isDomNode(a); var bIsDomNode = j$.isDomNode(b); if (aIsDomNode && bIsDomNode) { // At first try to use DOM3 method isEqualNode if (a.isEqualNode) { return a.isEqualNode(b); } // IE8 doesn't support isEqualNode, try to use outerHTML && innerText var aIsElement = a instanceof Element; var bIsElement = b instanceof Element; if (aIsElement && bIsElement) { return a.outerHTML == b.outerHTML; } if (aIsElement || bIsElement) { return false; } return a.innerText == b.innerText && a.textContent == b.textContent; } if (aIsDomNode || bIsDomNode) { return false; } // Assume equality for cyclic structures. The algorithm for detecting cyclic // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. var length = aStack.length; while (length--) { // Linear search. Performance is inversely proportional to the number of // unique nested structures. if (aStack[length] == a) { return bStack[length] == b; } } // Add the first object to the stack of traversed objects. aStack.push(a); bStack.push(b); var size = 0; // Recursively compare objects and arrays. // Compare array lengths to determine if a deep comparison is necessary. if (className == '[object Array]') { size = a.length; if (size !== b.length) { return false; } while (size--) { result = eq(a[size], b[size], aStack, bStack, customTesters); if (!result) { return false; } } } else { // Objects with different constructors are not equivalent, but `Object`s // or `Array`s from different frames are. var aCtor = a.constructor, bCtor = b.constructor; if (aCtor !== bCtor && !(isObjectConstructor(aCtor) && isObjectConstructor(bCtor))) { return false; } } // Deep compare objects. var aKeys = keys(a, className == '[object Array]'), key; size = aKeys.length; // Ensure that both objects contain the same number of properties before comparing deep equality. if (keys(b, className == '[object Array]').length !== size) { return false; } while (size--) { key = aKeys[size]; // Deep compare each member result = has(b, key) && eq(a[key], b[key], aStack, bStack, customTesters); if (!result) { return false; } } // Remove the first object from the stack of traversed objects. aStack.pop(); bStack.pop(); return result; function keys(obj, isArray) { var allKeys = Object.keys ? Object.keys(obj) : (function(o) { var keys = []; for (var key in o) { if (has(o, key)) { keys.push(key); } } return keys; })(obj); if (!isArray) { return allKeys; } var extraKeys = []; if (allKeys.length === 0) { return allKeys; } for (var x = 0; x < allKeys.length; x++) { if (!allKeys[x].match(/^[0-9]+$/)) { extraKeys.push(allKeys[x]); } } return extraKeys; } } function has(obj, key) { return Object.prototype.hasOwnProperty.call(obj, key); } function isFunction(obj) { return typeof obj === 'function'; } function isObjectConstructor(ctor) { // aCtor instanceof aCtor is true for the Object and Function // constructors (since a constructor is-a Function and a function is-a // Object). We don't just compare ctor === Object because the constructor // might come from a different frame with different globals. return isFunction(ctor) && ctor instanceof ctor; } }; getJasmineRequireObj().toBe = function() { function toBe() { return { compare: function(actual, expected) { return { pass: actual === expected }; } }; } return toBe; }; getJasmineRequireObj().toBeCloseTo = function() { function toBeCloseTo() { return { compare: function(actual, expected, precision) { if (precision !== 0) { precision = precision || 2; } return { pass: Math.abs(expected - actual) < (Math.pow(10, -precision) / 2) }; } }; } return toBeCloseTo; }; getJasmineRequireObj().toBeDefined = function() { function toBeDefined() { return { compare: function(actual) { return { pass: (void 0 !== actual) }; } }; } return toBeDefined; }; getJasmineRequireObj().toBeFalsy = function() { function toBeFalsy() { return { compare: function(actual) { return { pass: !!!actual }; } }; } return toBeFalsy; }; getJasmineRequireObj().toBeGreaterThan = function() { function toBeGreaterThan() { return { compare: function(actual, expected) { return { pass: actual > expected }; } }; } return toBeGreaterThan; }; getJasmineRequireObj().toBeGreaterThanOrEqual = function() { function toBeGreaterThanOrEqual() { return { compare: function(actual, expected) { return { pass: actual >= expected }; } }; } return toBeGreaterThanOrEqual; }; getJasmineRequireObj().toBeLessThan = function() { function toBeLessThan() { return { compare: function(actual, expected) { return { pass: actual < expected }; } }; } return toBeLessThan; }; getJasmineRequireObj().toBeLessThanOrEqual = function() { function toBeLessThanOrEqual() { return { compare: function(actual, expected) { return { pass: actual <= expected }; } }; } return toBeLessThanOrEqual; }; getJasmineRequireObj().toBeNaN = function(j$) { function toBeNaN() { return { compare: function(actual) { var result = { pass: (actual !== actual) }; if (result.pass) { result.message = 'Expected actual not to be NaN.'; } else { result.message = function() { return 'Expected ' + j$.pp(actual) + ' to be NaN.'; }; } return result; } }; } return toBeNaN; }; getJasmineRequireObj().toBeNull = function() { function toBeNull() { return { compare: function(actual) { return { pass: actual === null }; } }; } return toBeNull; }; getJasmineRequireObj().toBeTruthy = function() { function toBeTruthy() { return { compare: function(actual) { return { pass: !!actual }; } }; } return toBeTruthy; }; getJasmineRequireObj().toBeUndefined = function() { function toBeUndefined() { return { compare: function(actual) { return { pass: void 0 === actual }; } }; } return toBeUndefined; }; getJasmineRequireObj().toContain = function() { function toContain(util, customEqualityTesters) { customEqualityTesters = customEqualityTesters || []; return { compare: function(actual, expected) { return { pass: util.contains(actual, expected, customEqualityTesters) }; } }; } return toContain; }; getJasmineRequireObj().toEqual = function() { function toEqual(util, customEqualityTesters) { customEqualityTesters = customEqualityTesters || []; return { compare: function(actual, expected) { var result = { pass: false }; result.pass = util.equals(actual, expected, customEqualityTesters); return result; } }; } return toEqual; }; getJasmineRequireObj().toHaveBeenCalled = function(j$) { var getErrorMsg = j$.formatErrorMsg('', 'expect().toHaveBeenCalled()'); function toHaveBeenCalled() { return { compare: function(actual) { var result = {}; if (!j$.isSpy(actual)) { throw new Error(getErrorMsg('Expected a spy, but got ' + j$.pp(actual) + '.')); } if (arguments.length > 1) { throw new Error(getErrorMsg('Does not take arguments, use toHaveBeenCalledWith')); } result.pass = actual.calls.any(); result.message = result.pass ? 'Expected spy ' + actual.and.identity() + ' not to have been called.' : 'Expected spy ' + actual.and.identity() + ' to have been called.'; return result; } }; } return toHaveBeenCalled; }; getJasmineRequireObj().toHaveBeenCalledTimes = function(j$) { var getErrorMsg = j$.formatErrorMsg('', 'expect().toHaveBeenCalledTimes()'); function toHaveBeenCalledTimes() { return { compare: function(actual, expected) { if (!j$.isSpy(actual)) { throw new Error(getErrorMsg('Expected a spy, but got ' + j$.pp(actual) + '.')); } var args = Array.prototype.slice.call(arguments, 0), result = { pass: false }; if (!j$.isNumber_(expected)){ throw new Error(getErrorMsg('The expected times failed is a required argument and must be a number.')); } actual = args[0]; var calls = actual.calls.count(); var timesMessage = expected === 1 ? 'once' : expected + ' times'; result.pass = calls === expected; result.message = result.pass ? 'Expected spy ' + actual.and.identity() + ' not to have been called ' + timesMessage + '. It was called ' + calls + ' times.' : 'Expected spy ' + actual.and.identity() + ' to have been called ' + timesMessage + '. It was called ' + calls + ' times.'; return result; } }; } return toHaveBeenCalledTimes; }; getJasmineRequireObj().toHaveBeenCalledWith = function(j$) { var getErrorMsg = j$.formatErrorMsg('', 'expect().toHaveBeenCalledWith(...arguments)'); function toHaveBeenCalledWith(util, customEqualityTesters) { return { compare: function() { var args = Array.prototype.slice.call(arguments, 0), actual = args[0], expectedArgs = args.slice(1), result = { pass: false }; if (!j$.isSpy(actual)) { throw new Error(getErrorMsg('Expected a spy, but got ' + j$.pp(actual) + '.')); } if (!actual.calls.any()) { result.message = function() { return 'Expected spy ' + actual.and.identity() + ' to have been called with ' + j$.pp(expectedArgs) + ' but it was never called.'; }; return result; } if (util.contains(actual.calls.allArgs(), expectedArgs, customEqualityTesters)) { result.pass = true; result.message = function() { return 'Expected spy ' + actual.and.identity() + ' not to have been called with ' + j$.pp(expectedArgs) + ' but it was.'; }; } else { result.message = function() { return 'Expected spy ' + actual.and.identity() + ' to have been called with ' + j$.pp(expectedArgs) + ' but actual calls were ' + j$.pp(actual.calls.allArgs()).replace(/^\[ | \]$/g, '') + '.'; }; } return result; } }; } return toHaveBeenCalledWith; }; getJasmineRequireObj().toMatch = function(j$) { var getErrorMsg = j$.formatErrorMsg('', 'expect().toMatch( || )'); function toMatch() { return { compare: function(actual, expected) { if (!j$.isString_(expected) && !j$.isA_('RegExp', expected)) { throw new Error(getErrorMsg('Expected is not a String or a RegExp')); } var regexp = new RegExp(expected); return { pass: regexp.test(actual) }; } }; } return toMatch; }; getJasmineRequireObj().toThrow = function(j$) { var getErrorMsg = j$.formatErrorMsg('', 'expect(function() {}).toThrow()'); function toThrow(util) { return { compare: function(actual, expected) { var result = { pass: false }, threw = false, thrown; if (typeof actual != 'function') { throw new Error(getErrorMsg('Actual is not a Function')); } try { actual(); } catch (e) { threw = true; thrown = e; } if (!threw) { result.message = 'Expected function to throw an exception.'; return result; } if (arguments.length == 1) { result.pass = true; result.message = function() { return 'Expected function not to throw, but it threw ' + j$.pp(thrown) + '.'; }; return result; } if (util.equals(thrown, expected)) { result.pass = true; result.message = function() { return 'Expected function not to throw ' + j$.pp(expected) + '.'; }; } else { result.message = function() { return 'Expected function to throw ' + j$.pp(expected) + ', but it threw ' + j$.pp(thrown) + '.'; }; } return result; } }; } return toThrow; }; getJasmineRequireObj().toThrowError = function(j$) { var getErrorMsg = j$.formatErrorMsg('', 'expect(function() {}).toThrowError(, )'); function toThrowError () { return { compare: function(actual) { var threw = false, pass = {pass: true}, fail = {pass: false}, thrown; if (typeof actual != 'function') { throw new Error(getErrorMsg('Actual is not a Function')); } var errorMatcher = getMatcher.apply(null, arguments); try { actual(); } catch (e) { threw = true; thrown = e; } if (!threw) { fail.message = 'Expected function to throw an Error.'; return fail; } if (!(thrown instanceof Error)) { fail.message = function() { return 'Expected function to throw an Error, but it threw ' + j$.pp(thrown) + '.'; }; return fail; } if (errorMatcher.hasNoSpecifics()) { pass.message = 'Expected function not to throw an Error, but it threw ' + j$.fnNameFor(thrown) + '.'; return pass; } if (errorMatcher.matches(thrown)) { pass.message = function() { return 'Expected function not to throw ' + errorMatcher.errorTypeDescription + errorMatcher.messageDescription() + '.'; }; return pass; } else { fail.message = function() { return 'Expected function to throw ' + errorMatcher.errorTypeDescription + errorMatcher.messageDescription() + ', but it threw ' + errorMatcher.thrownDescription(thrown) + '.'; }; return fail; } } }; function getMatcher() { var expected = null, errorType = null; if (arguments.length == 2) { expected = arguments[1]; if (isAnErrorType(expected)) { errorType = expected; expected = null; } } else if (arguments.length > 2) { errorType = arguments[1]; expected = arguments[2]; if (!isAnErrorType(errorType)) { throw new Error(getErrorMsg('Expected error type is not an Error.')); } } if (expected && !isStringOrRegExp(expected)) { if (errorType) { throw new Error(getErrorMsg('Expected error message is not a string or RegExp.')); } else { throw new Error(getErrorMsg('Expected is not an Error, string, or RegExp.')); } } function messageMatch(message) { if (typeof expected == 'string') { return expected == message; } else { return expected.test(message); } } return { errorTypeDescription: errorType ? j$.fnNameFor(errorType) : 'an exception', thrownDescription: function(thrown) { var thrownName = errorType ? j$.fnNameFor(thrown.constructor) : 'an exception', thrownMessage = ''; if (expected) { thrownMessage = ' with message ' + j$.pp(thrown.message); } return thrownName + thrownMessage; }, messageDescription: function() { if (expected === null) { return ''; } else if (expected instanceof RegExp) { return ' with a message matching ' + j$.pp(expected); } else { return ' with message ' + j$.pp(expected); } }, hasNoSpecifics: function() { return expected === null && errorType === null; }, matches: function(error) { return (errorType === null || error instanceof errorType) && (expected === null || messageMatch(error.message)); } }; } function isStringOrRegExp(potential) { return potential instanceof RegExp || (typeof potential == 'string'); } function isAnErrorType(type) { if (typeof type !== 'function') { return false; } var Surrogate = function() {}; Surrogate.prototype = type.prototype; return (new Surrogate()) instanceof Error; } } return toThrowError; }; getJasmineRequireObj().interface = function(jasmine, env) { var jasmineInterface = { describe: function(description, specDefinitions) { return env.describe(description, specDefinitions); }, xdescribe: function(description, specDefinitions) { return env.xdescribe(description, specDefinitions); }, fdescribe: function(description, specDefinitions) { return env.fdescribe(description, specDefinitions); }, it: function() { return env.it.apply(env, arguments); }, xit: function() { return env.xit.apply(env, arguments); }, fit: function() { return env.fit.apply(env, arguments); }, beforeEach: function() { return env.beforeEach.apply(env, arguments); }, afterEach: function() { return env.afterEach.apply(env, arguments); }, beforeAll: function() { return env.beforeAll.apply(env, arguments); }, afterAll: function() { return env.afterAll.apply(env, arguments); }, expect: function(actual) { return env.expect(actual); }, pending: function() { return env.pending.apply(env, arguments); }, fail: function() { return env.fail.apply(env, arguments); }, spyOn: function(obj, methodName) { return env.spyOn(obj, methodName); }, jsApiReporter: new jasmine.JsApiReporter({ timer: new jasmine.Timer() }), jasmine: jasmine }; jasmine.addCustomEqualityTester = function(tester) { env.addCustomEqualityTester(tester); }; jasmine.addMatchers = function(matchers) { return env.addMatchers(matchers); }; jasmine.clock = function() { return env.clock; }; return jasmineInterface; }; getJasmineRequireObj().version = function() { return '2.5.2'; }; cjs-5.2.0/installed-tests/js/testGtk3.js0000644000175000017500000002102614144444702020241 0ustar jpeisachjpeisachimports.gi.versions.Gtk = '3.0'; const ByteArray = imports.byteArray; const {GLib, Gio, GObject, Gtk} = imports.gi; const System = imports.system; // This is ugly here, but usually it would be in a resource function createTemplate(className) { return ` `; } const MyComplexGtkSubclass = GObject.registerClass({ Template: ByteArray.fromString(createTemplate('Gjs_MyComplexGtkSubclass')), Children: ['label-child', 'label-child2'], InternalChildren: ['internal-label-child'], CssName: 'complex-subclass', }, class MyComplexGtkSubclass extends Gtk.Grid { templateCallback(widget) { this.callbackEmittedBy = widget; } boundCallback(widget) { widget.callbackBoundTo = this; } }); // Sadly, putting this in the body of the class will prevent calling // get_template_child, since MyComplexGtkSubclass will be bound to the ES6 // class name without the GObject goodies in it MyComplexGtkSubclass.prototype.testChildrenExist = function () { this._internalLabel = this.get_template_child(MyComplexGtkSubclass, 'label-child'); expect(this._internalLabel).toEqual(jasmine.anything()); expect(this.label_child2).toEqual(jasmine.anything()); expect(this._internal_label_child).toEqual(jasmine.anything()); }; const MyComplexGtkSubclassFromResource = GObject.registerClass({ Template: 'resource:///org/gjs/jsunit/complex3.ui', Children: ['label-child', 'label-child2'], InternalChildren: ['internal-label-child'], }, class MyComplexGtkSubclassFromResource extends Gtk.Grid { testChildrenExist() { expect(this.label_child).toEqual(jasmine.anything()); expect(this.label_child2).toEqual(jasmine.anything()); expect(this._internal_label_child).toEqual(jasmine.anything()); } templateCallback(widget) { this.callbackEmittedBy = widget; } boundCallback(widget) { widget.callbackBoundTo = this; } }); const [templateFile, stream] = Gio.File.new_tmp(null); const baseStream = stream.get_output_stream(); const out = new Gio.DataOutputStream({baseStream}); out.put_string(createTemplate('Gjs_MyComplexGtkSubclassFromFile'), null); out.close(null); const MyComplexGtkSubclassFromFile = GObject.registerClass({ Template: templateFile.get_uri(), Children: ['label-child', 'label-child2'], InternalChildren: ['internal-label-child'], }, class MyComplexGtkSubclassFromFile extends Gtk.Grid { testChildrenExist() { expect(this.label_child).toEqual(jasmine.anything()); expect(this.label_child2).toEqual(jasmine.anything()); expect(this._internal_label_child).toEqual(jasmine.anything()); } templateCallback(widget) { this.callbackEmittedBy = widget; } boundCallback(widget) { widget.callbackBoundTo = this; } }); const SubclassSubclass = GObject.registerClass( class SubclassSubclass extends MyComplexGtkSubclass {}); function validateTemplate(description, ClassName, pending = false) { let suite = pending ? xdescribe : describe; suite(description, function () { let win, content; beforeEach(function () { win = new Gtk.Window({type: Gtk.WindowType.TOPLEVEL}); content = new ClassName(); content.label_child.emit('grab-focus'); content.label_child2.emit('grab-focus'); win.add(content); }); it('sets up internal and public template children', function () { content.testChildrenExist(); }); it('sets up public template children with the correct widgets', function () { expect(content.label_child.get_label()).toEqual('Complex!'); expect(content.label_child2.get_label()).toEqual('Complex as well!'); }); it('sets up internal template children with the correct widgets', function () { expect(content._internal_label_child.get_label()) .toEqual('Complex and internal!'); }); it('connects template callbacks to the correct handler', function () { expect(content.callbackEmittedBy).toBe(content.label_child); }); it('binds template callbacks to the correct object', function () { expect(content.label_child2.callbackBoundTo).toBe(content.label_child); }); afterEach(function () { win.destroy(); }); }); } describe('Gtk overrides', function () { beforeAll(function () { Gtk.init(null); }); afterAll(function () { templateFile.delete(null); }); validateTemplate('UI template', MyComplexGtkSubclass); validateTemplate('UI template from resource', MyComplexGtkSubclassFromResource); validateTemplate('UI template from file', MyComplexGtkSubclassFromFile); validateTemplate('Class inheriting from template class', SubclassSubclass, true); it('sets CSS names on classes', function () { expect(Gtk.Widget.get_css_name.call(MyComplexGtkSubclass)).toEqual('complex-subclass'); }); // it('avoid crashing when GTK vfuncs are called in garbage collection', function () { // GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, // '*during garbage collection*'); // GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, // '*destroy*'); // let BadLabel = GObject.registerClass(class BadLabel extends Gtk.Label { // vfunc_destroy() {} // }); // let w = new Gtk.Window(); // w.add(new BadLabel()); // w.destroy(); // System.gc(); // GLib.test_assert_expected_messages_internal('Cjs', 'testGtk3.js', 0, // 'Gtk overrides avoid crashing and print a stack trace'); // }); it('accepts string in place of GdkAtom', function () { expect(() => Gtk.Clipboard.get(1)).toThrow(); expect(() => Gtk.Clipboard.get(true)).toThrow(); expect(() => Gtk.Clipboard.get(() => undefined)).toThrow(); const clipboard = Gtk.Clipboard.get('CLIPBOARD'); const primary = Gtk.Clipboard.get('PRIMARY'); const anotherClipboard = Gtk.Clipboard.get('CLIPBOARD'); expect(clipboard).toBeTruthy(); expect(primary).toBeTruthy(); expect(clipboard).not.toBe(primary); expect(clipboard).toBe(anotherClipboard); }); it('accepts null in place of GdkAtom as GDK_NONE', function () { /** * When you pass GDK_NONE (an atom, interned from the 'NONE' string) * to Gtk.Clipboard.get(), it throws an error, mentioning null in * its message. */ expect(() => Gtk.Clipboard.get('NONE')).toThrowError(/null/); /** * Null is converted to GDK_NONE, so you get the same message. If you * know an API function that accepts GDK_NONE without throwing, and * returns something different when passed another atom, consider * adding a less confusing example here. */ expect(() => Gtk.Clipboard.get(null)).toThrowError(/null/); }); it('uses the correct GType for null child properties', function () { let s = new Gtk.Stack(); let p = new Gtk.Box(); s.add_named(p, 'foo'); expect(s.get_child_by_name('foo')).toBe(p); s.child_set_property(p, 'name', null); expect(s.get_child_by_name('foo')).toBeNull(); }); it('can create a Gtk.TreeIter with accessible stamp field', function () { const iter = new Gtk.TreeIter(); iter.stamp = 42; expect(iter.stamp).toEqual(42); }); }); cjs-5.2.0/installed-tests/js/testFundamental.js0000644000175000017500000000051414144444702021666 0ustar jpeisachjpeisachconst {GObject, Regress} = imports.gi; describe('Fundamental type support', function () { it('can marshal a subtype of a custom fundamental type into a GValue', function () { const fund = new Regress.TestFundamentalSubObject('plop'); expect(() => GObject.strdup_value_contents(fund)).not.toThrow(); }); }); cjs-5.2.0/installed-tests/js/testNamespace.js0000644000175000017500000000030014144444702021315 0ustar jpeisachjpeisachconst Regress = imports.gi.Regress; describe('GI repository namespace', function () { it('supplies a name', function () { expect(Regress.__name__).toEqual('Regress'); }); }); cjs-5.2.0/installed-tests/js/testFormat.js0000644000175000017500000000374214144444702020666 0ustar jpeisachjpeisachconst Format = imports.format; String.prototype.format = Format.format; describe('imports.format', function () { it('escapes % with another % character', function () { expect('%d%%'.format(10)).toEqual('10%'); }); it('formats a single string argument', function () { expect('%s'.format('Foo')).toEqual('Foo'); }); it('formats two string arguments', function () { expect('%s %s'.format('Foo', 'Bar')).toEqual('Foo Bar'); }); it('formats two swapped string arguments', function () { expect('%2$s %1$s'.format('Foo', 'Bar')).toEqual('Bar Foo'); }); it('formats a number in base 10', function () { expect('%d'.format(42)).toEqual('42'); }); it('formats a number in base 16', function () { expect('%x'.format(42)).toEqual('2a'); }); it('formats a floating point number with no precision', function () { expect('%f'.format(0.125)).toEqual('0.125'); }); it('formats a floating point number with precision 2', function () { expect('%.2f'.format(0.125)).toEqual('0.13'); }); it('pads with zeroes', function () { let zeroFormat = '%04d'; expect(zeroFormat.format(1)).toEqual('0001'); expect(zeroFormat.format(10)).toEqual('0010'); expect(zeroFormat.format(100)).toEqual('0100'); }); it('pads with spaces', function () { let spaceFormat = '%4d'; expect(spaceFormat.format(1)).toEqual(' 1'); expect(spaceFormat.format(10)).toEqual(' 10'); expect(spaceFormat.format(100)).toEqual(' 100'); }); it('throws an error when given incorrect modifiers for the conversion type', function () { expect(() => '%z'.format(42)).toThrow(); expect(() => '%.2d'.format(42)).toThrow(); expect(() => '%Ix'.format(42)).toThrow(); }); it('throws an error when incorrectly instructed to swap arguments', function () { expect(() => '%2$d %d %1$d'.format(1, 2, 3)).toThrow(); }); }); cjs-5.2.0/installed-tests/js/testLegacyGtk.js0000644000175000017500000000723414144444702021310 0ustar jpeisachjpeisach// -*- mode: js; indent-tabs-mode: nil -*- /* eslint-disable no-restricted-properties */ imports.gi.versions.Gtk = '3.0'; const ByteArray = imports.byteArray; const Gtk = imports.gi.Gtk; const Lang = imports.lang; const template = ` `; const MyComplexGtkSubclass = new Lang.Class({ Name: 'MyComplexGtkSubclass', Extends: Gtk.Grid, Template: ByteArray.fromString(template), Children: ['label-child', 'label-child2'], InternalChildren: ['internal-label-child'], CssName: 'complex-subclass', testChildrenExist() { this._internalLabel = this.get_template_child(MyComplexGtkSubclass, 'label-child'); expect(this._internalLabel).toEqual(jasmine.anything()); expect(this.label_child2).toEqual(jasmine.anything()); expect(this._internal_label_child).toEqual(jasmine.anything()); }, }); const MyComplexGtkSubclassFromResource = new Lang.Class({ Name: 'MyComplexGtkSubclassFromResource', Extends: Gtk.Grid, Template: 'resource:///org/gjs/jsunit/complex3.ui', Children: ['label-child', 'label-child2'], InternalChildren: ['internal-label-child'], testChildrenExist() { expect(this.label_child).toEqual(jasmine.anything()); expect(this.label_child2).toEqual(jasmine.anything()); expect(this._internal_label_child).toEqual(jasmine.anything()); }, templateCallback() {}, boundCallback() {}, }); function validateTemplate(description, ClassName) { describe(description, function () { let win, content; beforeEach(function () { win = new Gtk.Window({type: Gtk.WindowType.TOPLEVEL}); content = new ClassName(); win.add(content); }); it('sets up internal and public template children', function () { content.testChildrenExist(); }); it('sets up public template children with the correct widgets', function () { expect(content.label_child.get_label()).toEqual('Complex!'); expect(content.label_child2.get_label()).toEqual('Complex as well!'); }); it('sets up internal template children with the correct widgets', function () { expect(content._internal_label_child.get_label()) .toEqual('Complex and internal!'); }); afterEach(function () { win.destroy(); }); }); } describe('Legacy Gtk overrides', function () { beforeAll(function () { Gtk.init(null); }); validateTemplate('UI template', MyComplexGtkSubclass); validateTemplate('UI template from resource', MyComplexGtkSubclassFromResource); it('sets CSS names on classes', function () { expect(Gtk.Widget.get_css_name.call(MyComplexGtkSubclass)).toEqual('complex-subclass'); }); }); cjs-5.2.0/installed-tests/js/testGLib.js0000644000175000017500000002161514144444702020252 0ustar jpeisachjpeisachconst ByteArray = imports.byteArray; const GLib = imports.gi.GLib; describe('GVariant constructor', function () { it('constructs a string variant', function () { let strVariant = new GLib.Variant('s', 'mystring'); expect(strVariant.get_string()[0]).toEqual('mystring'); expect(strVariant.deepUnpack()).toEqual('mystring'); }); it('constructs a string variant (backwards compatible API)', function () { let strVariant = new GLib.Variant('s', 'mystring'); let strVariantOld = GLib.Variant.new('s', 'mystring'); expect(strVariant.equal(strVariantOld)).toBeTruthy(); }); it('constructs a struct variant', function () { let structVariant = new GLib.Variant('(sogvau)', [ 'a string', '/a/object/path', 'asig', // nature new GLib.Variant('s', 'variant'), [7, 3], ]); expect(structVariant.n_children()).toEqual(5); let unpacked = structVariant.deepUnpack(); expect(unpacked[0]).toEqual('a string'); expect(unpacked[1]).toEqual('/a/object/path'); expect(unpacked[2]).toEqual('asig'); expect(unpacked[3] instanceof GLib.Variant).toBeTruthy(); expect(unpacked[3].deepUnpack()).toEqual('variant'); expect(unpacked[4] instanceof Array).toBeTruthy(); expect(unpacked[4].length).toEqual(2); }); it('constructs a maybe variant', function () { let maybeVariant = new GLib.Variant('ms', null); expect(maybeVariant.deepUnpack()).toBeNull(); maybeVariant = new GLib.Variant('ms', 'string'); expect(maybeVariant.deepUnpack()).toEqual('string'); }); it('constructs a byte array variant', function () { const byteArray = Uint8Array.from('pizza', c => c.charCodeAt(0)); const byteArrayVariant = new GLib.Variant('ay', byteArray); expect(ByteArray.toString(byteArrayVariant.deepUnpack())) .toEqual('pizza'); }); it('constructs a byte array variant from a string', function () { const byteArrayVariant = new GLib.Variant('ay', 'pizza'); expect(ByteArray.toString(byteArrayVariant.deepUnpack())) .toEqual('pizza'); }); it('0-terminates a byte array variant constructed from a string', function () { const byteArrayVariant = new GLib.Variant('ay', 'pizza'); const a = byteArrayVariant.deepUnpack(); [112, 105, 122, 122, 97, 0].forEach((val, ix) => expect(a[ix]).toEqual(val)); }); it('does not 0-terminate a byte array variant constructed from a Uint8Array', function () { const byteArray = Uint8Array.from('pizza', c => c.charCodeAt(0)); const byteArrayVariant = new GLib.Variant('ay', byteArray); const a = byteArrayVariant.deepUnpack(); [112, 105, 122, 122, 97].forEach((val, ix) => expect(a[ix]).toEqual(val)); }); }); describe('GVariant unpack', function () { let v; beforeEach(function () { v = new GLib.Variant('a{sv}', {foo: new GLib.Variant('s', 'bar')}); }); it('preserves type information if the unpacked object contains variants', function () { expect(v.deepUnpack().foo instanceof GLib.Variant).toBeTruthy(); expect(v.deep_unpack().foo instanceof GLib.Variant).toBeTruthy(); }); it('recursive leaves no variants in the unpacked object', function () { expect(v.recursiveUnpack().foo instanceof GLib.Variant).toBeFalsy(); expect(v.recursiveUnpack().foo).toEqual('bar'); }); }); describe('GVariantDict lookup', function () { let variantDict; beforeEach(function () { variantDict = new GLib.VariantDict(null); variantDict.insert_value('foo', GLib.Variant.new_string('bar')); }); it('returns the unpacked variant', function () { expect(variantDict.lookup('foo')).toEqual('bar'); expect(variantDict.lookup('foo', null)).toEqual('bar'); expect(variantDict.lookup('foo', 's')).toEqual('bar'); expect(variantDict.lookup('foo', new GLib.VariantType('s'))).toEqual('bar'); }); it("returns null if the key isn't present", function () { expect(variantDict.lookup('bar')).toBeNull(); expect(variantDict.lookup('bar', null)).toBeNull(); expect(variantDict.lookup('bar', 's')).toBeNull(); expect(variantDict.lookup('bar', new GLib.VariantType('s'))).toBeNull(); }); }); describe('GLib string function overrides', function () { let numExpectedWarnings; function expectWarnings(count) { numExpectedWarnings = count; for (let c = 0; c < count; c++) { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, '*not introspectable*'); } } function assertWarnings(testName) { for (let c = 0; c < numExpectedWarnings; c++) { GLib.test_assert_expected_messages_internal('Cjs', 'testGLib.js', 0, `test GLib.${testName}`); } numExpectedWarnings = 0; } beforeEach(function () { numExpectedWarnings = 0; }); // TODO: Add Regress.func_not_nullable_untyped_gpointer_in and move to testRegress.js it('GLib.str_hash errors when marshalling null to a not-nullable parameter', function () { // This tests that we don't marshal null to a not-nullable untyped gpointer. expect(() => GLib.str_hash(null)).toThrowError( /Argument [a-z]+ may not be null/ ); }); it('GLib.stpcpy', function () { expect(() => GLib.stpcpy('dest', 'src')).toThrowError(/not introspectable/); }); it('GLib.strstr_len', function () { expectWarnings(4); expect(GLib.strstr_len('haystack', -1, 'needle')).toBeNull(); expect(GLib.strstr_len('haystacks', -1, 'stack')).toEqual('stacks'); expect(GLib.strstr_len('haystacks', 4, 'stack')).toBeNull(); expect(GLib.strstr_len('haystack', 4, 'ays')).toEqual('aystack'); assertWarnings('strstr_len'); }); it('GLib.strrstr', function () { expectWarnings(2); expect(GLib.strrstr('haystack', 'needle')).toBeNull(); expect(GLib.strrstr('hackstacks', 'ack')).toEqual('acks'); assertWarnings('strrstr'); }); it('GLib.strrstr_len', function () { expectWarnings(3); expect(GLib.strrstr_len('haystack', -1, 'needle')).toBeNull(); expect(GLib.strrstr_len('hackstacks', -1, 'ack')).toEqual('acks'); expect(GLib.strrstr_len('hackstacks', 4, 'ack')).toEqual('ackstacks'); assertWarnings('strrstr_len'); }); it('GLib.strup', function () { expectWarnings(1); expect(GLib.strup('string')).toEqual('STRING'); assertWarnings('strup'); }); it('GLib.strdown', function () { expectWarnings(1); expect(GLib.strdown('STRING')).toEqual('string'); assertWarnings('strdown'); }); it('GLib.strreverse', function () { expectWarnings(1); expect(GLib.strreverse('abcdef')).toEqual('fedcba'); assertWarnings('strreverse'); }); it('GLib.ascii_dtostr', function () { expectWarnings(2); expect(GLib.ascii_dtostr('', GLib.ASCII_DTOSTR_BUF_SIZE, Math.PI)) .toEqual('3.141592653589793'); expect(GLib.ascii_dtostr('', 4, Math.PI)).toEqual('3.14'); assertWarnings('ascii_dtostr'); }); it('GLib.ascii_formatd', function () { expect(() => GLib.ascii_formatd('', 8, '%e', Math.PI)).toThrowError(/not introspectable/); }); it('GLib.strchug', function () { expectWarnings(2); expect(GLib.strchug('text')).toEqual('text'); expect(GLib.strchug(' text')).toEqual('text'); assertWarnings('strchug'); }); it('GLib.strchomp', function () { expectWarnings(2); expect(GLib.strchomp('text')).toEqual('text'); expect(GLib.strchomp('text ')).toEqual('text'); assertWarnings('strchomp'); }); it('GLib.strstrip', function () { expectWarnings(4); expect(GLib.strstrip('text')).toEqual('text'); expect(GLib.strstrip(' text')).toEqual('text'); expect(GLib.strstrip('text ')).toEqual('text'); expect(GLib.strstrip(' text ')).toEqual('text'); assertWarnings('strstrip'); }); it('GLib.strdelimit', function () { expectWarnings(4); expect(GLib.strdelimit('1a2b3c4', 'abc', '_'.charCodeAt())).toEqual('1_2_3_4'); expect(GLib.strdelimit('1-2_3<4', null, '|'.charCodeAt())).toEqual('1|2|3|4'); expect(GLib.strdelimit('1a2b3c4', 'abc', '_')).toEqual('1_2_3_4'); expect(GLib.strdelimit('1-2_3<4', null, '|')).toEqual('1|2|3|4'); assertWarnings('strdelimit'); }); it('GLib.strcanon', function () { expectWarnings(2); expect(GLib.strcanon('1a2b3c4', 'abc', '?'.charCodeAt())).toEqual('?a?b?c?'); expect(GLib.strcanon('1a2b3c4', 'abc', '?')).toEqual('?a?b?c?'); assertWarnings('strcanon'); }); }); cjs-5.2.0/installed-tests/js/testPackage.js0000644000175000017500000000570514144444702020772 0ustar jpeisachjpeisachconst Pkg = imports.package; describe('Package module', function () { it('finds an existing library', function () { expect(Pkg.checkSymbol('Regress', '1.0')).toEqual(true); }); it('doesn\'t find a non-existent library', function () { expect(Pkg.checkSymbol('Rägräss', '1.0')).toEqual(false); }); it('finds a function', function () { expect(Pkg.checkSymbol('Regress', '1.0', 'get_variant')).toEqual(true); }); it('doesn\'t find a non-existent function', function () { expect(Pkg.checkSymbol('Regress', '1.0', 'get_väriänt')).toEqual(false); }); it('finds a class', function () { expect(Pkg.checkSymbol('Regress', '1.0', 'TestObj')).toEqual(true); }); it('doesn\'t find a non-existent class', function () { expect(Pkg.checkSymbol('Regress', '1.0', 'TestNoObj')).toEqual(false); }); it('finds a property', function () { expect(Pkg.checkSymbol('Regress', '1.0', 'TestObj.bare')).toEqual(true); }); it('doesn\'t find a non-existent property', function () { expect(Pkg.checkSymbol('Regress', '1.0', 'TestObj.bäre')).toEqual(false); }); it('finds a static function', function () { expect(Pkg.checkSymbol('Regress', '1.0', 'TestObj.static_method')).toEqual(true); }); it('doesn\'t find a non-existent static function', function () { expect(Pkg.checkSymbol('Regress', '1.0', 'TestObj.stätic_methöd')).toEqual(false); }); it('finds a method', function () { expect(Pkg.checkSymbol('Regress', '1.0', 'TestObj.null_out')).toEqual(true); }); it('doesn\'t find a non-existent method', function () { expect(Pkg.checkSymbol('Regress', '1.0', 'TestObj.nüll_out')).toEqual(false); }); it('finds an interface', function () { expect(Pkg.checkSymbol('GIMarshallingTests', '1.0', 'Interface')).toEqual(true); }); it('doesn\'t find a non-existent interface', function () { expect(Pkg.checkSymbol('GIMarshallingTests', '1.0', 'Interfäce')).toEqual(false); }); it('finds an interface method', function () { expect(Pkg.checkSymbol('GIMarshallingTests', '1.0', 'Interface.test_int8_in')).toEqual(true); }); it('doesn\'t find a non-existent interface method', function () { expect(Pkg.checkSymbol('GIMarshallingTests', '1.0', 'Interface.test_int42_in')).toEqual(false); }); it('finds an enum value', function () { expect(Pkg.checkSymbol('Regress', '1.0', 'TestEnum.VALUE1')).toEqual(true); }); it('doesn\'t find a non-existent enum value', function () { expect(Pkg.checkSymbol('Regress', '1.0', 'TestEnum.value1')).toEqual(false); }); it('finds a constant', function () { expect(Pkg.checkSymbol('Regress', '1.0', 'BOOL_CONSTANT')).toEqual(true); }); it('doesn\'t find a non-existent constant', function () { expect(Pkg.checkSymbol('Regress', '1.0', 'BööL_CONSTANT')).toEqual(false); }); }); cjs-5.2.0/installed-tests/js/testGTypeClass.js0000644000175000017500000000430114144444702021444 0ustar jpeisachjpeisach// We use Gio to have some objects that we know exist const Gio = imports.gi.Gio; const GObject = imports.gi.GObject; describe('Looking up param specs', function () { let p1, p2; beforeEach(function () { let findProperty = GObject.Object.find_property; p1 = findProperty.call(Gio.ThemedIcon, 'name'); p2 = findProperty.call(Gio.SimpleAction, 'enabled'); }); it('works', function () { expect(p1 instanceof GObject.ParamSpec).toBeTruthy(); expect(p2 instanceof GObject.ParamSpec).toBeTruthy(); }); it('gives the correct name', function () { expect(p1.name).toEqual('name'); expect(p2.name).toEqual('enabled'); }); it('gives the default value if present', function () { expect(p2.default_value).toBeTruthy(); }); }); describe('GType object', function () { it('has a name', function () { expect(GObject.TYPE_NONE.name).toEqual('void'); expect(GObject.TYPE_STRING.name).toEqual('gchararray'); }); it('has a read-only name', function () { try { GObject.TYPE_STRING.name = 'foo'; } catch (e) { } expect(GObject.TYPE_STRING.name).toEqual('gchararray'); }); it('has an undeletable name', function () { try { delete GObject.TYPE_STRING.name; } catch (e) { } expect(GObject.TYPE_STRING.name).toEqual('gchararray'); }); it('has a string representation', function () { expect(GObject.TYPE_NONE.toString()).toEqual("[object GType for 'void']"); expect(GObject.TYPE_STRING.toString()).toEqual("[object GType for 'gchararray']"); }); }); describe('GType marshalling', function () { it('marshals the invalid GType object into JS null', function () { expect(GObject.type_from_name('NonexistentType')).toBeNull(); expect(GObject.type_parent(GObject.TYPE_STRING)).toBeNull(); }); }); describe('GType prototype object', function () { it('has no name', function () { expect(GIRepositoryGType.name).toBeNull(); }); it('has a string representation', function () { expect(GIRepositoryGType.toString()).toEqual('[object GType prototype]'); }); }); cjs-5.2.0/installed-tests/js/testExceptions.js0000644000175000017500000001543014144444702021554 0ustar jpeisachjpeisachconst {GIMarshallingTests, Gio, GLib, GObject} = imports.gi; const Foo = GObject.registerClass({ Properties: { 'prop': GObject.ParamSpec.string('prop', '', '', GObject.ParamFlags.READWRITE, ''), }, }, class Foo extends GObject.Object { set prop(v) { throw new Error('set'); } get prop() { throw new Error('get'); } }); const Bar = GObject.registerClass({ Properties: { 'prop': GObject.ParamSpec.string('prop', '', '', GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT, ''), }, }, class Bar extends GObject.Object {}); describe('Exceptions', function () { it('are thrown from property setter', function () { let foo = new Foo(); expect(() => (foo.prop = 'bar')).toThrowError(/set/); }); it('are thrown from property getter', function () { let foo = new Foo(); expect(() => foo.prop).toThrowError(/get/); }); // FIXME: In the next cases the errors aren't thrown but logged it('are logged from constructor', function () { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'JS ERROR: Error: set*'); new Foo({prop: 'bar'}); GLib.test_assert_expected_messages_internal('Cjs', 'testExceptions.js', 0, 'testExceptionInPropertySetterFromConstructor'); }); it('are logged from property setter with binding', function () { let foo = new Foo(); let bar = new Bar(); bar.bind_property('prop', foo, 'prop', GObject.BindingFlags.DEFAULT); GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'JS ERROR: Error: set*'); // wake up the binding so that g_object_set() is called on foo bar.notify('prop'); GLib.test_assert_expected_messages_internal('Cjs', 'testExceptions.js', 0, 'testExceptionInPropertySetterWithBinding'); }); it('are logged from property getter with binding', function () { let foo = new Foo(); let bar = new Bar(); foo.bind_property('prop', bar, 'prop', GObject.BindingFlags.DEFAULT); GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'JS ERROR: Error: get*'); // wake up the binding so that g_object_get() is called on foo foo.notify('prop'); GLib.test_assert_expected_messages_internal('Cjs', 'testExceptions.js', 0, 'testExceptionInPropertyGetterWithBinding'); }); }); describe('logError', function () { afterEach(function () { GLib.test_assert_expected_messages_internal('Cjs', 'testExceptions.js', 0, 'testGErrorMessages'); }); it('logs a warning for a GError', function () { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: Gio.IOErrorEnum: *'); try { let file = Gio.file_new_for_path("\\/,.^!@&$_don't exist"); file.read(null); } catch (e) { logError(e); } }); it('logs a warning with a message if given', function marker() { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: Gio.IOErrorEnum: a message\nmarker@*'); try { throw new Gio.IOErrorEnum({message: 'a message', code: 0}); } catch (e) { logError(e); } }); it('also logs an error for a created GError that is not thrown', function marker() { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: Gio.IOErrorEnum: a message\nmarker@*'); logError(new Gio.IOErrorEnum({message: 'a message', code: 0})); }); it('logs an error created with the GLib.Error constructor', function marker() { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: Gio.IOErrorEnum: a message\nmarker@*'); logError(new GLib.Error(Gio.IOErrorEnum, 0, 'a message')); }); it('logs the quark for a JS-created GError type', function marker() { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: GLib.Error my-error: a message\nmarker@*'); logError(new GLib.Error(GLib.quark_from_string('my-error'), 0, 'a message')); }); it('logs with stack for a GError created from a C struct', function marker() { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: GLib.Error gi-marshalling-tests-gerror-domain: gi-marshalling-tests-gerror-message\nmarker@*'); logError(GIMarshallingTests.gerror_return()); }); // Now with prefix it('logs an error with a prefix if given', function () { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: prefix: Gio.IOErrorEnum: *'); try { let file = Gio.file_new_for_path("\\/,.^!@&$_don't exist"); file.read(null); } catch (e) { logError(e, 'prefix'); } }); it('logs an error with prefix and message', function marker() { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: prefix: Gio.IOErrorEnum: a message\nmarker@*'); try { throw new Gio.IOErrorEnum({message: 'a message', code: 0}); } catch (e) { logError(e, 'prefix'); } }); it('logs a SyntaxError', function () { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: SyntaxError:*'); try { Reflect.parse('!@#$%^&'); } catch (e) { logError(e); } }); }); describe('Exception from function with too few arguments', function () { it('contains the full function name', function () { expect(() => GLib.get_locale_variants()) .toThrowError(/GLib\.get_locale_variants/); }); it('contains the full method name', function () { let file = Gio.File.new_for_path('foo'); expect(() => file.read()).toThrowError(/Gio\.File\.read/); }); }); describe('thrown GError', function () { let err; beforeEach(function () { try { let file = Gio.file_new_for_path("\\/,.^!@&$_don't exist"); file.read(null); } catch (x) { err = x; } }); it('is an instance of error enum type', function () { expect(err).toEqual(jasmine.any(Gio.IOErrorEnum)); }); it('matches error domain and code', function () { expect(err.matches(Gio.io_error_quark(), Gio.IOErrorEnum.NOT_FOUND)) .toBeTruthy(); }); it('has properties for domain and code', function () { expect(err.domain).toEqual(Gio.io_error_quark()); expect(err.code).toEqual(Gio.IOErrorEnum.NOT_FOUND); }); }); cjs-5.2.0/installed-tests/js/testTweener.js0000644000175000017500000002752314144444702021052 0ustar jpeisachjpeisachconst Tweener = imports.tweener.tweener; function installFrameTicker() { // Set up Tweener to have a "frame pulse" that the Jasmine clock functions // can influence let ticker = { FRAME_RATE: 50, _init() { }, start() { this._currentTime = 0; this._timeoutID = setInterval(() => { this._currentTime += 1000 / this.FRAME_RATE; this.emit('prepare-frame'); }, Math.floor(1000 / this.FRAME_RATE)); }, stop() { if ('_timeoutID' in this) { clearInterval(this._timeoutID); delete this._timeoutID; } this._currentTime = 0; }, getTime() { return this._currentTime; }, }; imports.signals.addSignalMethods(ticker); Tweener.setFrameTicker(ticker); } describe('Tweener', function () { beforeAll(function () { jasmine.clock().install(); installFrameTicker(); }); afterAll(function () { jasmine.clock().uninstall(); }); let start, update, overwrite, complete; beforeEach(function () { start = jasmine.createSpy('start'); update = jasmine.createSpy('update'); overwrite = jasmine.createSpy('overwrite'); complete = jasmine.createSpy('complete'); }); it('runs a simple tween', function () { var objectA = { x: 0, y: 0, }; var objectB = { x: 0, y: 0, }; Tweener.addTween(objectA, {x: 10, y: 10, time: 1, transition: 'linear'}); Tweener.addTween(objectB, {x: 10, y: 10, time: 1, delay: 0.5, transition: 'linear'}); jasmine.clock().tick(1001); expect(objectA.x).toEqual(10); expect(objectA.y).toEqual(10); expect(objectB.x).toEqual(5); expect(objectB.y).toEqual(5); }); it('calls callbacks during the tween', function () { Tweener.addTween({}, { time: 0.1, onStart: start, onUpdate: update, onComplete: complete, }); jasmine.clock().tick(101); expect(start).toHaveBeenCalled(); expect(update).toHaveBeenCalled(); expect(complete).toHaveBeenCalled(); }); it('can pause tweens', function () { var objectA = { foo: 0, }; var objectB = { bar: 0, }; var objectC = { baaz: 0, }; Tweener.addTween(objectA, {foo: 100, time: 0.1}); Tweener.addTween(objectC, {baaz: 100, time: 0.1}); Tweener.addTween(objectB, {bar: 100, time: 0.1}); Tweener.pauseTweens(objectA); // This should do nothing expect(Tweener.pauseTweens(objectB, 'quux')).toBeFalsy(); /* Pause and resume should be equal to doing nothing */ Tweener.pauseTweens(objectC, 'baaz'); Tweener.resumeTweens(objectC, 'baaz'); jasmine.clock().tick(101); expect(objectA.foo).toEqual(0); expect(objectB.bar).toEqual(100); expect(objectC.baaz).toEqual(100); }); it('can remove tweens', function () { var object = { foo: 0, bar: 0, baaz: 0, }; Tweener.addTween(object, {foo: 50, time: 0.1}); Tweener.addTween(object, {bar: 50, time: 0.1}); Tweener.addTween(object, {baaz: 50, time: 0.1}); /* The Tween on property foo should still be run after removing the other two */ Tweener.removeTweens(object, 'bar', 'baaz'); jasmine.clock().tick(101); expect(object.foo).toEqual(50); expect(object.bar).toEqual(0); expect(object.baaz).toEqual(0); }); it('overrides a tween with another one acting on the same object and property at the same time', function () { var objectA = { foo: 0, }; Tweener.addTween(objectA, {foo: 100, time: 0.1}); Tweener.addTween(objectA, {foo: 0, time: 0.1}); jasmine.clock().tick(101); expect(objectA.foo).toEqual(0); }); it('does not override a tween with another one acting not at the same time', function () { var objectB = { bar: 0, }; /* In this case both tweens should be executed, as they don't * act on the object at the same time (the second one has a * delay equal to the running time of the first one) */ Tweener.addTween(objectB, {bar: 100, time: 0.1}); Tweener.addTween(objectB, {bar: 150, time: 0.1, delay: 0.1}); jasmine.clock(0).tick(201); expect(objectB.bar).toEqual(150); }); it('can pause and resume all tweens', function () { var objectA = { foo: 0, }; var objectB = { bar: 0, }; Tweener.addTween(objectA, {foo: 100, time: 0.1}); Tweener.addTween(objectB, {bar: 100, time: 0.1}); Tweener.pauseAllTweens(); jasmine.clock().tick(10); Tweener.resumeAllTweens(); jasmine.clock().tick(101); expect(objectA.foo).toEqual(100); expect(objectB.bar).toEqual(100); }); it('can remove all tweens', function () { var objectA = { foo: 0, }; var objectB = { bar: 0, }; Tweener.addTween(objectA, {foo: 100, time: 0.1}); Tweener.addTween(objectB, {bar: 100, time: 0.1}); Tweener.removeAllTweens(); jasmine.clock().tick(200); expect(objectA.foo).toEqual(0); expect(objectB.bar).toEqual(0); }); it('runs a tween with a time of 0 immediately', function () { var object = { foo: 100, }; Tweener.addTween(object, {foo: 50, time: 0, delay: 0}); Tweener.addTween(object, { foo: 200, time: 0.1, onStart: () => { /* The immediate tween should set it to 50 before we run */ expect(object.foo).toEqual(50); }, }); jasmine.clock().tick(101); expect(object.foo).toEqual(200); }); it('can call a callback a certain number of times', function () { var object = { foo: 0, }; Tweener.addCaller(object, { onUpdate: () => { object.foo += 1; }, count: 10, time: 0.1, }); jasmine.clock().tick(101); expect(object.foo).toEqual(10); }); it('can count the number of tweens on an object', function () { var object = { foo: 0, bar: 0, baaz: 0, quux: 0, }; expect(Tweener.getTweenCount(object)).toEqual(0); Tweener.addTween(object, {foo: 100, time: 0.1}); expect(Tweener.getTweenCount(object)).toEqual(1); Tweener.addTween(object, {bar: 100, time: 0.1}); expect(Tweener.getTweenCount(object)).toEqual(2); Tweener.addTween(object, {baaz: 100, time: 0.1}); expect(Tweener.getTweenCount(object)).toEqual(3); Tweener.addTween(object, {quux: 100, time: 0.1}); expect(Tweener.getTweenCount(object)).toEqual(4); Tweener.removeTweens(object, 'bar', 'baaz'); expect(Tweener.getTweenCount(object)).toEqual(2); }); it('can register special properties', function () { Tweener.registerSpecialProperty( 'negative_x', function (obj) { return -obj.x; }, function (obj, val) { obj.x = -val; } ); var objectA = { x: 0, y: 0, }; Tweener.addTween(objectA, {negative_x: 10, y: 10, time: 1, transition: 'linear'}); jasmine.clock().tick(1001); expect(objectA.x).toEqual(-10); expect(objectA.y).toEqual(10); }); it('can register special modifiers for properties', function () { Tweener.registerSpecialPropertyModifier('discrete', discreteModifier, discreteGet); function discreteModifier(props) { return props.map(function (prop) { return {name: prop, parameters: null}; }); } function discreteGet(begin, end, time) { return Math.floor(begin + time * (end - begin)); } var objectA = { x: 0, y: 0, xFraction: false, yFraction: false, }; Tweener.addTween(objectA, { x: 10, y: 10, time: 1, discrete: ['x'], transition: 'linear', onUpdate() { if (objectA.x !== Math.floor(objectA.x)) objectA.xFraction = true; if (objectA.y !== Math.floor(objectA.y)) objectA.yFraction = true; }, }); jasmine.clock().tick(1001); expect(objectA.x).toEqual(10); expect(objectA.y).toEqual(10); expect(objectA.xFraction).toBeFalsy(); expect(objectA.yFraction).toBeTruthy(); }); it('can split properties into more than one special property', function () { Tweener.registerSpecialPropertySplitter( 'xnegy', function (val) { return [{name: 'x', value: val}, {name: 'y', value: -val}]; } ); var objectA = { x: 0, y: 0, }; Tweener.addTween(objectA, {xnegy: 10, time: 1, transition: 'linear'}); jasmine.clock().tick(1001); expect(objectA.x).toEqual(10); expect(objectA.y).toEqual(-10); }); it('calls an overwrite callback when a tween is replaced', function () { var object = { a: 0, b: 0, c: 0, d: 0, }; var tweenA = { a: 10, b: 10, c: 10, d: 10, time: 0.1, onStart: start, onOverwrite: overwrite, onComplete: complete, }; var tweenB = { a: 20, b: 20, c: 20, d: 20, time: 0.1, onStart: start, onOverwrite: overwrite, onComplete: complete, }; Tweener.addTween(object, tweenA); Tweener.addTween(object, tweenB); jasmine.clock().tick(101); expect(start).toHaveBeenCalledTimes(1); expect(overwrite).toHaveBeenCalledTimes(1); expect(complete).toHaveBeenCalledTimes(1); }); it('can still overwrite a tween after it has started', function () { var object = { a: 0, b: 0, c: 0, d: 0, }; var tweenA = { a: 10, b: 10, c: 10, d: 10, time: 0.1, onStart: () => { start(); Tweener.addTween(object, tweenB); }, onOverwrite: overwrite, onComplete: complete, }; var tweenB = { a: 20, b: 20, c: 20, d: 20, time: 0.1, onStart: start, onOverwrite: overwrite, onComplete: complete, }; Tweener.addTween(object, tweenA); jasmine.clock().tick(121); expect(start).toHaveBeenCalledTimes(2); expect(overwrite).toHaveBeenCalledTimes(1); expect(complete).toHaveBeenCalledTimes(1); }); it('stays within min and max values', function () { var objectA = { x: 0, y: 0, }; var objectB = { x: 0, y: 0, }; Tweener.addTween(objectA, {x: 300, y: 300, time: 1, max: 255, transition: 'linear'}); Tweener.addTween(objectB, {x: -200, y: -200, time: 1, delay: 0.5, min: 0, transition: 'linear'}); jasmine.clock().tick(1001); expect(objectA.x).toEqual(255); expect(objectA.y).toEqual(255); expect(objectB.x).toEqual(0); expect(objectB.y).toEqual(0); }); }); cjs-5.2.0/installed-tests/js/complex3.ui0000644000175000017500000000220614144444702020263 0ustar jpeisachjpeisach cjs-5.2.0/installed-tests/js/testParamSpec.js0000644000175000017500000000333414144444702021306 0ustar jpeisachjpeisachconst Regress = imports.gi.Regress; const GObject = imports.gi.GObject; let name = 'foo-property'; let nick = 'Foo property'; let blurb = 'This is the foo property'; let flags = GObject.ParamFlags.READABLE; function testParamSpec(type, params, defaultValue) { describe(`GObject.ParamSpec.${type}`, function () { let paramSpec; beforeEach(function () { paramSpec = GObject.ParamSpec[type](name, nick, blurb, flags, ...params); }); it('has the correct name strings', function () { expect(paramSpec.name).toEqual(name); expect(paramSpec._nick).toEqual(nick); expect(paramSpec._blurb).toEqual(blurb); }); it('has the correct flags', function () { expect(paramSpec.flags).toEqual(flags); }); it('has the correct default value', function () { expect(paramSpec.default_value).toEqual(defaultValue); }); }); } testParamSpec('string', ['Default Value'], 'Default Value'); testParamSpec('int', [-100, 100, -42], -42); testParamSpec('uint', [20, 100, 42], 42); testParamSpec('int64', [0x4000, 0xffffffff, 0x2266bbff], 0x2266bbff); testParamSpec('uint64', [0, 0xffffffff, 0x2266bbff], 0x2266bbff); testParamSpec('enum', [Regress.TestEnum, Regress.TestEnum.VALUE2], Regress.TestEnum.VALUE2); testParamSpec('flags', [Regress.TestFlags, Regress.TestFlags.FLAG2], Regress.TestFlags.FLAG2); testParamSpec('object', [GObject.Object], null); describe('GObject.ParamSpec object', function () { it("doesn't crash when resolving a non-string property", function () { let paramSpec = GObject.ParamSpec.string(name, nick, blurb, flags, ''); expect(paramSpec[0]).not.toBeDefined(); }); }); cjs-5.2.0/installed-tests/js/.eslintrc.yml0000644000175000017500000000253214144444702020620 0ustar jpeisachjpeisach--- env: jasmine: true rules: no-restricted-globals: - error - name: fdescribe message: Do not commit fdescribe(). Use describe() instead. - name: fit message: Do not commit fit(). Use it() instead. no-restricted-syntax: - error - selector: CallExpression[callee.name="it"] > ArrowFunctionExpression message: Arrow functions can mess up some Jasmine APIs. Use function () instead - selector: CallExpression[callee.name="describe"] > ArrowFunctionExpression message: Arrow functions can mess up some Jasmine APIs. Use function () instead - selector: CallExpression[callee.name="beforeEach"] > ArrowFunctionExpression message: Arrow functions can mess up some Jasmine APIs. Use function () instead - selector: CallExpression[callee.name="afterEach"] > ArrowFunctionExpression message: Arrow functions can mess up some Jasmine APIs. Use function () instead - selector: CallExpression[callee.name="beforeAll"] > ArrowFunctionExpression message: Arrow functions can mess up some Jasmine APIs. Use function () instead - selector: CallExpression[callee.name="afterAll"] > ArrowFunctionExpression message: Arrow functions can mess up some Jasmine APIs. Use function () instead globals: clearInterval: writable clearTimeout: writable setInterval: writable setTimeout: writable cjs-5.2.0/installed-tests/js/testGettext.js0000644000175000017500000000106714144444702021060 0ustar jpeisachjpeisach// -*- mode: js; indent-tabs-mode: nil -*- const Gettext = imports.gettext; describe('Gettext module', function () { // We don't actually want to mess with the locale, so just use setlocale's // query mode. We also don't want to make this test locale-dependent, so // just assert that it returns a string with at least length 1 (the shortest // locale is "C".) it('setlocale returns a locale', function () { let locale = Gettext.setlocale(Gettext.LocaleCategory.ALL, null); expect(locale.length).not.toBeLessThan(1); }); }); cjs-5.2.0/installed-tests/js/org.cinnamon.CjsTest.gschema.xml0000644000175000017500000000113414144444702024267 0ustar jpeisachjpeisach (-1, -1) false false 10 cjs-5.2.0/installed-tests/js/testGObject.js0000644000175000017500000000406514144444702020752 0ustar jpeisachjpeisach// This is where overrides in modules/core/overrides/GObject.js are tested, // except for the class machinery, interface machinery, and GObject.ParamSpec, // which are big enough to get their own files. const {GLib, GObject} = imports.gi; describe('GObject overrides', function () { const TestObj = GObject.registerClass({ Properties: { int: GObject.ParamSpec.int('int', '', '', GObject.ParamFlags.READWRITE, 0, GLib.MAXINT32, 0), string: GObject.ParamSpec.string('string', '', '', GObject.ParamFlags.READWRITE, ''), }, Signals: { test: {}, }, }, class TestObj extends GObject.Object {}); it('GObject.set()', function () { const o = new TestObj(); o.set({string: 'Answer', int: 42}); expect(o.string).toBe('Answer'); expect(o.int).toBe(42); }); describe('Signal alternative syntax', function () { let o, handler; beforeEach(function () { handler = jasmine.createSpy('handler'); o = new TestObj(); const handlerId = GObject.signal_connect(o, 'test', handler); handler.and.callFake(() => GObject.signal_handler_disconnect(o, handlerId)); GObject.signal_emit_by_name(o, 'test'); }); it('handler is called with the right object', function () { expect(handler).toHaveBeenCalledTimes(1); expect(handler).toHaveBeenCalledWith(o); }); it('disconnected handler is not called', function () { handler.calls.reset(); GObject.signal_emit_by_name(o, 'test'); expect(handler).not.toHaveBeenCalled(); }); }); }); describe('GObject should', function () { const types = ['gpointer', 'GBoxed', 'GParam', 'GInterface', 'GObject', 'GVariant']; types.forEach(type => { it(`be able to create a GType object for ${type}`, function () { const gtype = GObject.Type(type); expect(gtype.name).toEqual(type); }); }); }); cjs-5.2.0/installed-tests/js/testSystem.js0000644000175000017500000000316514144444702020721 0ustar jpeisachjpeisachconst System = imports.system; const GObject = imports.gi.GObject; describe('System.addressOf()', function () { it('gives different results for different objects', function () { let a = {some: 'object'}; let b = {different: 'object'}; expect(System.addressOf(a)).not.toEqual(System.addressOf(b)); }); }); describe('System.version', function () { it('gives a plausible number', function () { expect(System.version).not.toBeLessThan(40802); expect(System.version).toBeLessThan(60000); }); }); describe('System.refcount()', function () { it('gives the correct number', function () { let o = new GObject.Object({}); expect(System.refcount(o)).toEqual(1); }); }); describe('System.addressOfGObject()', function () { it('gives different results for different objects', function () { let a = new GObject.Object({}); let b = new GObject.Object({}); expect(System.addressOfGObject(a)).toEqual(System.addressOfGObject(a)); expect(System.addressOfGObject(a)).not.toEqual(System.addressOfGObject(b)); }); it('throws for non GObject objects', function () { expect(() => System.addressOfGObject({})) .toThrowError(/Object 0x[a-f0-9]+ is not a GObject/); }); }); describe('System.gc()', function () { it('does not crash the application', function () { expect(System.gc).not.toThrow(); }); }); describe('System.dumpHeap()', function () { it('throws but does not crash when given a nonexistent path', function () { expect(() => System.dumpHeap('/does/not/exist')).toThrow(); }); }); cjs-5.2.0/installed-tests/js/testLegacyByteArray.js0000644000175000017500000000612614144444702022464 0ustar jpeisachjpeisachconst ByteArray = imports.byteArray; const GIMarshallingTests = imports.gi.GIMarshallingTests; describe('Legacy byte array', function () { it('has length 0 for empty array', function () { let a = new ByteArray.ByteArray(); expect(a.length).toEqual(0); }); describe('initially sized to 10', function () { let a; beforeEach(function () { a = new ByteArray.ByteArray(10); }); it('has length 10', function () { expect(a.length).toEqual(10); }); it('is initialized to zeroes', function () { for (let i = 0; i < a.length; ++i) expect(a[i]).toEqual(0); }); }); it('assigns values correctly', function () { let a = new ByteArray.ByteArray(256); for (let i = 0; i < a.length; ++i) a[i] = 255 - i; for (let i = 0; i < a.length; ++i) expect(a[i]).toEqual(255 - i); }); describe('assignment past end', function () { let a; beforeEach(function () { a = new ByteArray.ByteArray(); a[2] = 5; }); it('implicitly lengthens the array', function () { expect(a.length).toEqual(3); expect(a[2]).toEqual(5); }); it('implicitly creates zero bytes', function () { expect(a[0]).toEqual(0); expect(a[1]).toEqual(0); }); }); it('changes the length when assigning to length property', function () { let a = new ByteArray.ByteArray(20); expect(a.length).toEqual(20); a.length = 5; expect(a.length).toEqual(5); }); describe('conversions', function () { let a; beforeEach(function () { a = new ByteArray.ByteArray(); a[0] = 255; }); it('gives a byte 5 when assigning 5', function () { a[0] = 5; expect(a[0]).toEqual(5); }); it('gives a byte 0 when assigning null', function () { a[0] = null; expect(a[0]).toEqual(0); }); it('gives a byte 0 when assigning undefined', function () { a[0] = undefined; expect(a[0]).toEqual(0); }); it('rounds off when assigning a double', function () { a[0] = 3.14; expect(a[0]).toEqual(3); }); }); it('can be created from an array', function () { let a = ByteArray.fromArray([1, 2, 3, 4]); expect(a.length).toEqual(4); [1, 2, 3, 4].forEach((val, ix) => expect(a[ix]).toEqual(val)); }); it('can be converted to a string of ASCII characters', function () { let a = new ByteArray.ByteArray(4); a[0] = 97; a[1] = 98; a[2] = 99; a[3] = 100; let s = a.toString(); expect(s.length).toEqual(4); expect(s).toEqual('abcd'); }); it('can be passed in with transfer none', function () { const refByteArray = ByteArray.fromArray([0, 49, 0xFF, 51]); expect(() => GIMarshallingTests.bytearray_none_in(refByteArray)).not.toThrow(); }); }); cjs-5.2.0/installed-tests/js/testMainloop.js0000644000175000017500000000625214144444702021213 0ustar jpeisachjpeisach/* eslint-disable no-restricted-properties */ const Mainloop = imports.mainloop; describe('Mainloop.timeout_add()', function () { let runTenTimes, runOnlyOnce, neverRun, neverRunSource; beforeAll(function (done) { let count = 0; runTenTimes = jasmine.createSpy('runTenTimes').and.callFake(() => { if (count === 10) { done(); return false; } count += 1; return true; }); runOnlyOnce = jasmine.createSpy('runOnlyOnce').and.returnValue(false); neverRun = jasmine.createSpy('neverRun').and.throwError(); Mainloop.timeout_add(10, runTenTimes); Mainloop.timeout_add(10, runOnlyOnce); neverRunSource = Mainloop.timeout_add(15000, neverRun); }); it('runs a timeout function', function () { expect(runOnlyOnce).toHaveBeenCalledTimes(1); }); it('runs a timeout function until it returns false', function () { expect(runTenTimes).toHaveBeenCalledTimes(11); }); it('runs a timeout function after an initial timeout', function () { expect(neverRun).not.toHaveBeenCalled(); }); afterAll(function () { Mainloop.source_remove(neverRunSource); }); }); describe('Mainloop.idle_add()', function () { let runOnce, runTwice, neverRuns, quitAfterManyRuns; beforeAll(function (done) { runOnce = jasmine.createSpy('runOnce').and.returnValue(false); runTwice = jasmine.createSpy('runTwice').and.returnValues([true, false]); neverRuns = jasmine.createSpy('neverRuns').and.throwError(); let count = 0; quitAfterManyRuns = jasmine.createSpy('quitAfterManyRuns').and.callFake(() => { count += 1; if (count > 10) { done(); return false; } return true; }); Mainloop.idle_add(runOnce); Mainloop.idle_add(runTwice); let neverRunsId = Mainloop.idle_add(neverRuns); Mainloop.idle_add(quitAfterManyRuns); Mainloop.source_remove(neverRunsId); }); it('runs an idle function', function () { expect(runOnce).toHaveBeenCalledTimes(1); }); it('continues to run idle functions that return true', function () { expect(runTwice).toHaveBeenCalledTimes(2); expect(quitAfterManyRuns).toHaveBeenCalledTimes(11); }); it('does not run idle functions if removed', function () { expect(neverRuns).not.toHaveBeenCalled(); }); it('can remove idle functions while they are being invoked', function (done) { let removeId = Mainloop.idle_add(() => { Mainloop.source_remove(removeId); done(); return false; }); }); // Add an idle before exit, then never run main loop again. // This is to test that we remove idle callbacks when the associated // JSContext is blown away. The leak check in minijasmine will // fail if the idle function is not garbage collected. it('does not leak idle callbacks', function () { Mainloop.idle_add(() => { fail('This should never have been called'); return true; }); }); }); cjs-5.2.0/installed-tests/js/modules/0000755000175000017500000000000014144444702017642 5ustar jpeisachjpeisachcjs-5.2.0/installed-tests/js/modules/modunicode.js0000644000175000017500000000012414144444702022323 0ustar jpeisachjpeisach/* exported uval */ // This file is written in UTF-8. var uval = 'const ♥ utf8'; cjs-5.2.0/installed-tests/js/modules/lexicalScope.js0000644000175000017500000000152314144444702022614 0ustar jpeisachjpeisach/* exported a, b, c */ // Tests bindings in the global scope (var) and lexical environment (let, const) // This should be exported as a property when importing this module: var a = 1; // These should not be exported, but for compatibility we will pretend they are // for the time being: let b = 2; const c = 3; // It's not clear whether this should be exported in ES6, but for compatibility // it should be: this.d = 4; // Modules should have access to standard properties on the global object. if (typeof imports === 'undefined') throw new Error('fail the import'); // This should probably not be supported in the future, but I'm not sure how // we can phase it out compatibly. The module should also have access to // properties that the importing code defines. if (typeof expectMe === 'undefined') throw new Error('fail the import'); cjs-5.2.0/installed-tests/js/modules/mutualImport/0000755000175000017500000000000014144444702022344 5ustar jpeisachjpeisachcjs-5.2.0/installed-tests/js/modules/mutualImport/b.js0000644000175000017500000000015614144444702023125 0ustar jpeisachjpeisach/* exported getCount */ const A = imports.mutualImport.a; function getCount() { return A.getCount(); } cjs-5.2.0/installed-tests/js/modules/mutualImport/a.js0000644000175000017500000000036714144444702023130 0ustar jpeisachjpeisach/* exported getCount, getCountViaB, incrementCount */ const B = imports.mutualImport.b; let count = 0; function incrementCount() { count++; } function getCount() { return count; } function getCountViaB() { return B.getCount(); } cjs-5.2.0/installed-tests/js/modules/foobar.js0000644000175000017500000000046414144444702021454 0ustar jpeisachjpeisach// simple test module (used by testImporter.js) /* eslint no-redeclare: ["error", { "builtinGlobals": false }] */ // for toString /* exported bar, foo, testToString, toString */ var foo = 'This is foo'; var bar = 'This is bar'; var toString = x => x; function testToString(x) { return toString(x); } cjs-5.2.0/installed-tests/js/modules/subA/0000755000175000017500000000000014144444702020534 5ustar jpeisachjpeisachcjs-5.2.0/installed-tests/js/modules/subA/subB/0000755000175000017500000000000014144444702021427 5ustar jpeisachjpeisachcjs-5.2.0/installed-tests/js/modules/subA/subB/__init__.js0000644000175000017500000000050214144444702023521 0ustar jpeisachjpeisach/* exported ImporterClass, testImporterFunction */ function testImporterFunction() { return '__init__ function tested'; } function ImporterClass() { this._init(); } ImporterClass.prototype = { _init() { this._a = '__init__ class tested'; }, testMethod() { return this._a; }, }; cjs-5.2.0/installed-tests/js/modules/subA/subB/baz.js0000644000175000017500000000000014144444702022527 0ustar jpeisachjpeisachcjs-5.2.0/installed-tests/js/modules/subA/subB/foobar.js0000644000175000017500000000017414144444702023237 0ustar jpeisachjpeisach// simple test module (used by testImporter.js) /* exported bar, foo */ var foo = 'This is foo'; var bar = 'This is bar'; cjs-5.2.0/installed-tests/js/modules/overrides/0000755000175000017500000000000014144444702021644 5ustar jpeisachjpeisachcjs-5.2.0/installed-tests/js/modules/overrides/GIMarshallingTests.js0000644000175000017500000000153714144444702025714 0ustar jpeisachjpeisachfunction _init() { const GIMarshallingTests = this; GIMarshallingTests.OVERRIDES_CONSTANT = 7; GIMarshallingTests.OverridesStruct.prototype._real_method = GIMarshallingTests.OverridesStruct.prototype.method; GIMarshallingTests.OverridesStruct.prototype.method = function () { return this._real_method() / 7; }; GIMarshallingTests.OverridesObject.prototype._realInit = GIMarshallingTests.OverridesObject.prototype._init; GIMarshallingTests.OverridesObject.prototype._init = function (num, ...args) { this._realInit(...args); this.num = num; }; GIMarshallingTests.OverridesObject.prototype._realMethod = GIMarshallingTests.OverridesObject.prototype.method; GIMarshallingTests.OverridesObject.prototype.method = function () { return this._realMethod() / 7; }; } cjs-5.2.0/installed-tests/js/modules/overrides/.eslintrc.yml0000644000175000017500000000011214144444702024262 0ustar jpeisachjpeisach--- rules: no-unused-vars: - error - varsIgnorePattern: ^_init$ cjs-5.2.0/installed-tests/js/modules/alwaysThrows.js0000644000175000017500000000014114144444702022703 0ustar jpeisachjpeisach// line 0 // line 1 // line 2 throw new Error('This is an error that always happens on line 3'); cjs-5.2.0/installed-tests/js/modules/badOverrides/0000755000175000017500000000000014144444702022253 5ustar jpeisachjpeisachcjs-5.2.0/installed-tests/js/modules/badOverrides/GIMarshallingTests.js0000644000175000017500000000011014144444702026305 0ustar jpeisachjpeisach// Sabotage the import of imports.gi.GIMarshallingTests! throw '💩'; cjs-5.2.0/installed-tests/js/modules/badOverrides/WarnLib.js0000644000175000017500000000011014144444702024137 0ustar jpeisachjpeisach// Sabotage the import of imports.gi.WarnLib! k$^s^%$#^*($%jdghdsfjkgd cjs-5.2.0/installed-tests/js/modules/badOverrides/Gio.js0000644000175000017500000000007714144444702023333 0ustar jpeisachjpeisach// Sabotage the import of imports.gi.Gio! var _init = '💩'; cjs-5.2.0/installed-tests/js/modules/badOverrides/Regress.js0000644000175000017500000000012614144444702024222 0ustar jpeisachjpeisach// Sabotage the import of imports.gi.Regress! function _init() { throw '💩'; } cjs-5.2.0/installed-tests/js/modules/badOverrides/.eslintrc.yml0000644000175000017500000000021114144444702024671 0ustar jpeisachjpeisach--- rules: no-throw-literal: 'off' # these are intended to be bad code no-unused-vars: - error - varsIgnorePattern: ^_init$ cjs-5.2.0/installed-tests/js/meson.build0000644000175000017500000001424514144444702020342 0ustar jpeisachjpeisach### Jasmine tests ############################################################## jsunit_resources_files = gnome.compile_resources('jsunit-resources', 'jsunit.gresources.xml', c_name: 'jsunit_resources') minijasmine = executable('minijasmine', '../minijasmine.cpp', jsunit_resources_files, dependencies: libgjs_dep, cpp_args: [ '-DINSTTESTDIR="@0@"'.format(installed_tests_execdir), ], include_directories: top_include, install: get_option('installed_tests'), install_dir: installed_tests_execdir) gidatadir = gi.get_pkgconfig_variable('gidatadir') gi_tests = gidatadir / 'tests' test_gir_extra_c_args = [] test_gir_warning_c_args = [] if cc.get_argument_syntax() == 'msvc' # We need to ensure the symbols in the test DLLs export in clang-cl builds test_gir_extra_c_args += ['-D_GI_EXTERN=__declspec(dllexport)extern'] else # These consist of external code (from gobject-introspection) so they should not # error out even when building with -Werror test_gir_warning_c_args += ['-Wno-error'] endif regress_dependencies = [glib, gobject, gio] regress_gir_includes = ['Gio-2.0'] regress_gir_c_args = test_gir_extra_c_args if build_cairo regress_gir_includes += 'cairo-1.0' regress_dependencies += [cairo, cairo_gobject] else regress_gir_c_args += ['-D_GI_DISABLE_CAIRO'] endif regress_sources = [ gi_tests / 'regress.c', gi_tests / 'regress.h', ] libregress = library('regress', regress_sources, c_args: regress_gir_c_args + test_gir_warning_c_args, dependencies: regress_dependencies, install: get_option('installed_tests'), install_dir: installed_tests_execdir) regress_gir = gnome.generate_gir(libregress, includes: regress_gir_includes, sources: regress_sources, namespace: 'Regress', nsversion: '1.0', identifier_prefix: 'Regress', symbol_prefix: 'regress_', extra_args: ['--warn-all', '--warn-error'] + regress_gir_c_args, install: get_option('installed_tests'), install_dir_gir: false, install_dir_typelib: installed_tests_execdir) regress_typelib = regress_gir[1] warnlib_sources = [ gi_tests / 'warnlib.c', gi_tests / 'warnlib.h', ] libwarnlib = library('warnlib', warnlib_sources, c_args: test_gir_warning_c_args + test_gir_extra_c_args, dependencies: [glib, gobject, gio], install: get_option('installed_tests'), include_directories: top_include, install_dir: installed_tests_execdir) # This should have --warn-all turned off, but there is currently no way to do so # in gnome.generate_gir(). See https://github.com/mesonbuild/meson/issues/5876 warnlib_gir = gnome.generate_gir(libwarnlib, includes: ['Gio-2.0'], sources: warnlib_sources, namespace: 'WarnLib', nsversion: '1.0', symbol_prefix: 'warnlib_', header: 'warnlib.h', install: get_option('installed_tests'), install_dir_gir: false, install_dir_typelib: installed_tests_execdir) warnlib_typelib = warnlib_gir[1] gimarshallingtests_sources = [ gi_tests / 'gimarshallingtests.c', gi_tests / 'gimarshallingtests.h', ] libgimarshallingtests = library('gimarshallingtests', gimarshallingtests_sources, dependencies: [glib, gobject, gio], c_args: test_gir_extra_c_args + test_gir_warning_c_args, install: get_option('installed_tests'), install_dir: installed_tests_execdir) gimarshallingtests_gir = gnome.generate_gir(libgimarshallingtests, includes: ['Gio-2.0'], sources: gimarshallingtests_sources, namespace: 'GIMarshallingTests', nsversion: '1.0', symbol_prefix: 'gi_marshalling_tests_', extra_args: '--warn-error', install: get_option('installed_tests'), install_dir_gir: false, install_dir_typelib: installed_tests_execdir) gimarshallingtests_typelib = gimarshallingtests_gir[1] jasmine_tests = [ 'self', 'ByteArray', 'Exceptions', 'Format', 'Fundamental', 'Gettext', 'GIMarshalling', 'Gio', 'GLib', 'GObject', 'GObjectClass', 'GObjectInterface', 'GTypeClass', 'Importer', 'Introspection', 'Lang', 'LegacyByteArray', 'LegacyClass', 'LegacyGObject', 'Mainloop', 'Namespace', 'Package', 'ParamSpec', 'Print', 'Regress', 'Signals', 'System', 'Tweener', 'WarnLib', ] if build_cairo jasmine_tests += 'Cairo' endif if not get_option('skip_gtk_tests') jasmine_tests += [ 'Gtk3', 'GObjectDestructionAccess', 'LegacyGtk', ] if have_gtk4 jasmine_tests += 'Gtk4' endif endif installed_js_tests_dir = installed_tests_execdir / 'js' gschemas_compiled = gnome.compile_schemas( depend_files: 'org.cinnamon.CjsTest.gschema.xml') foreach test : jasmine_tests test_file = files('test@0@.js'.format(test)) test(test, minijasmine, args: test_file, depends: [ gschemas_compiled, gimarshallingtests_typelib, regress_typelib, warnlib_typelib, ], env: tests_environment, protocol: 'tap', suite: 'JS') test_description_subst = { 'name': 'test@0@.js'.format(test), 'installed_tests_execdir': installed_tests_execdir, } test_description = configure_file(configuration: test_description_subst, input: '../minijasmine.test.in', output: 'test@0@.test'.format(test), install_dir: installed_tests_metadir) if get_option('installed_tests') install_data(test_file, install_dir: installed_js_tests_dir) endif endforeach # testGDBus.js is separate, because it can be skipped, and during build should # be run using dbus-run-session if not get_option('skip_dbus_tests') test_file = files('testGDBus.js') bus_config = files('../../test/test-bus.conf') test('GDBus', dbus_run_session, args: ['--config-file', bus_config, '--', minijasmine, test_file], env: tests_environment, protocol: 'tap', suite: 'dbus') endif gdbus_test_description_subst = { 'name': 'testGDBus.js', 'installed_tests_execdir': installed_tests_execdir, } gdbus_test_description = configure_file( configuration: gdbus_test_description_subst, input: '../minijasmine.test.in', output: 'testGDBus.test', install_dir: installed_tests_metadir) if get_option('installed_tests') install_data('testGDBus.js', install_dir: installed_js_tests_dir) endif cjs-5.2.0/installed-tests/js/testIntrospection.js0000644000175000017500000001216314144444702022273 0ustar jpeisachjpeisach// Various tests having to do with how introspection is implemented in GJS imports.gi.versions.Gdk = '3.0'; imports.gi.versions.Gtk = '3.0'; const {Gdk, Gio, GLib, GObject, Gtk} = imports.gi; const System = imports.system; describe('GLib.DestroyNotify parameter', function () { it('throws when encountering a GDestroyNotify not associated with a callback', function () { // the 'destroy' argument applies to the data, which is not supported in // gobject-introspection expect(() => Gio.MemoryInputStream.new_from_data('foobar')) .toThrowError(/destroy/); }); }); describe('Unsafe integer marshalling', function () { it('warns when conversion is lossy', function () { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, '*cannot be safely stored*'); GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, '*cannot be safely stored*'); GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, '*cannot be safely stored*'); void GLib.MININT64; void GLib.MAXINT64; void GLib.MAXUINT64; GLib.test_assert_expected_messages_internal('Cjs', 'testEverythingBasic.js', 0, 'Limits warns when conversion is lossy'); }); }); describe('Marshalling empty flat arrays of structs', function () { let widget; beforeAll(function () { if (GLib.getenv('ENABLE_GTK') !== 'yes') { pending('GTK disabled'); return; } Gtk.init(null); }); beforeEach(function () { widget = new Gtk.Label(); }); it('accepts null', function () { widget.drag_dest_set(0, null, Gdk.DragAction.COPY); }); it('accepts an empty array', function () { widget.drag_dest_set(0, [], Gdk.DragAction.COPY); }); }); describe('Constructor', function () { it('throws when constructor called without new', function () { expect(() => Gio.AppLaunchContext()) .toThrowError(/Constructor called as normal method/); }); }); describe('Enum classes', function () { it('enum has a $gtype property', function () { expect(Gio.BusType.$gtype).toBeDefined(); }); it('enum $gtype property is enumerable', function () { expect('$gtype' in Gio.BusType).toBeTruthy(); }); }); describe('GError domains', function () { it('Number converts error to quark', function () { expect(Gio.ResolverError.quark()).toEqual(Number(Gio.ResolverError)); }); }); describe('Object properties on GtkBuilder-constructed objects', function () { let o1; beforeAll(function () { if (GLib.getenv('ENABLE_GTK') !== 'yes') { pending('GTK disabled'); return; } Gtk.init(null); }); beforeEach(function () { const ui = ` Click me `; let builder = Gtk.Builder.new_from_string(ui, -1); o1 = builder.get_object('button'); }); it('are found on the GObject itself', function () { expect(o1.label).toBe('Click me'); }); it('are found on the GObject\'s parents', function () { expect(o1.visible).toBeFalsy(); }); it('are found on the GObject\'s interfaces', function () { expect(o1.action_name).toBeNull(); }); }); describe('Garbage collection of introspected objects', function () { // This tests a regression that would very rarely crash, but // when run under valgrind this code would show use-after-free. it('collects objects properly with signals connected', function (done) { function orphanObject() { let obj = new GObject.Object(); obj.connect('notify', () => {}); } orphanObject(); System.gc(); GLib.idle_add(GLib.PRIORITY_LOW, () => done()); }); }); describe('Gdk.Atom', function () { it('is presented as string', function () { expect(Gdk.Atom.intern('CLIPBOARD', false)).toBe('CLIPBOARD'); expect(Gdk.Atom.intern('NONE', false)).toBe(null); }); }); describe('Complete enumeration (boxed types)', function () { it('enumerates all properties', function () { // Note: this test breaks down if other code access all the methods of Rectangle const rect = new Gdk.Rectangle(); const names = Object.getOwnPropertyNames(Object.getPrototypeOf(rect)); const expectAtLeast = ['equal', 'intersect', 'union', 'x', 'y', 'width', 'height']; expect(names).toEqual(jasmine.arrayContaining(expectAtLeast)); }); }); describe('Complete enumeration of GIRepositoryNamespace (new_enumerate)', function () { it('enumerates all properties (sampled)', function () { const names = Object.getOwnPropertyNames(Gdk); // Note: properties which has been accessed are listed without new_enumerate hook const expectAtLeast = ['KEY_ybelowdot', 'EventSequence', 'ByteOrder', 'Window']; expect(names).toEqual(jasmine.arrayContaining(expectAtLeast)); }); }); cjs-5.2.0/installed-tests/js/testGDBus.js0000644000175000017500000005131314144444702020377 0ustar jpeisachjpeisachconst ByteArray = imports.byteArray; const {Gio, CjsPrivate, GLib} = imports.gi; /* The methods list with their signatures. * * *** NOTE: If you add stuff here, you need to update the Test class below. */ var TestIface = ` `; const PROP_READ_ONLY_INITIAL_VALUE = Math.random(); const PROP_READ_WRITE_INITIAL_VALUE = 58; const PROP_WRITE_ONLY_INITIAL_VALUE = 'Initial value'; /* Test is the actual object exporting the dbus methods */ class Test { constructor() { this._propReadOnly = PROP_READ_ONLY_INITIAL_VALUE; this._propWriteOnly = PROP_WRITE_ONLY_INITIAL_VALUE; this._propReadWrite = PROP_READ_WRITE_INITIAL_VALUE; this._impl = Gio.DBusExportedObject.wrapJSObject(TestIface, this); this._impl.export(Gio.DBus.session, '/org/gnome/gjs/Test'); } frobateStuff() { return {hello: new GLib.Variant('s', 'world')}; } nonJsonFrobateStuff(i) { if (i === 42) return '42 it is!'; else return 'Oops'; } alwaysThrowException() { throw Error('Exception!'); } thisDoesNotExist() { /* We'll remove this later! */ } noInParameter() { return 'Yes!'; } multipleInArgs(a, b, c, d, e) { return `${a} ${b} ${c} ${d} ${e}`; } emitSignal() { this._impl.emit_signal('signalFoo', GLib.Variant.new('(s)', ['foobar'])); } noReturnValue() { /* Empty! */ } /* The following two functions have identical return values * in JS, but the bus message will be different. * multipleOutValues is "sss", while oneArrayOut is "as" */ multipleOutValues() { return ['Hello', 'World', '!']; } oneArrayOut() { return ['Hello', 'World', '!']; } /* Same thing again. In this case multipleArrayOut is "asas", * while arrayOfArrayOut is "aas". */ multipleArrayOut() { return [['Hello', 'World'], ['World', 'Hello']]; } arrayOfArrayOut() { return [['Hello', 'World'], ['World', 'Hello']]; } arrayOutBadSig() { return Symbol('Hello World!'); } byteArrayEcho(binaryString) { return binaryString; } byteEcho(aByte) { return aByte; } dictEcho(dict) { return dict; } /* This one is implemented asynchronously. Returns * the input arguments */ echoAsync(parameters, invocation) { var [someString, someInt] = parameters; GLib.idle_add(GLib.PRIORITY_DEFAULT, function () { invocation.return_value(new GLib.Variant('(si)', [someString, someInt])); return false; }); } // double get PropReadOnly() { return this._propReadOnly; } // string set PropWriteOnly(value) { this._propWriteOnly = value; } // variant get PropReadWrite() { return new GLib.Variant('s', this._propReadWrite.toString()); } set PropReadWrite(value) { this._propReadWrite = value.deepUnpack(); } structArray() { return [[128, 123456], [42, 654321]]; } fdIn(fdIndex, fdList) { const fd = fdList.get(fdIndex); const stream = new Gio.UnixInputStream({fd, closeFd: true}); const bytes = stream.read_bytes(4096, null); return bytes; } // Same as fdIn(), but implemented asynchronously fdIn2Async([fdIndex], invocation, fdList) { const fd = fdList.get(fdIndex); const stream = new Gio.UnixInputStream({fd, closeFd: true}); stream.read_bytes_async(4096, GLib.PRIORITY_DEFAULT, null, (obj, res) => { const bytes = obj.read_bytes_finish(res); invocation.return_value(new GLib.Variant('(ay)', [bytes])); }); } fdOut(bytes) { const fd = CjsPrivate.open_bytes(bytes); const fdList = Gio.UnixFDList.new_from_array([fd]); return [0, fdList]; } fdOut2Async([bytes], invocation) { GLib.idle_add(GLib.PRIORITY_DEFAULT, function () { const fd = CjsPrivate.open_bytes(bytes); const fdList = Gio.UnixFDList.new_from_array([fd]); invocation.return_value_with_unix_fd_list(new GLib.Variant('(h)', [0]), fdList); return GLib.SOURCE_REMOVE; }); } } const ProxyClass = Gio.DBusProxy.makeProxyWrapper(TestIface); describe('Exported DBus object', function () { let ownNameID; var test; var proxy; let loop; function waitForServerProperty(property, value = undefined, timeout = 500) { let waitId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, timeout, () => { waitId = 0; throw new Error(`Timeout waiting for property ${property} expired`); }); while (waitId && (!test[property] || value !== undefined && test[property] !== value)) loop.get_context().iteration(true); if (waitId) GLib.source_remove(waitId); expect(waitId).not.toBe(0); return test[property]; } beforeAll(function () { loop = new GLib.MainLoop(null, false); test = new Test(); ownNameID = Gio.DBus.session.own_name('org.gnome.gjs.Test', Gio.BusNameOwnerFlags.NONE, name => { log(`Acquired name ${name}`); loop.quit(); }, name => { log(`Lost name ${name}`); }); loop.run(); new ProxyClass(Gio.DBus.session, 'org.gnome.gjs.Test', '/org/gnome/gjs/Test', (obj, error) => { expect(error).toBeNull(); proxy = obj; expect(proxy).not.toBeNull(); loop.quit(); }, Gio.DBusProxyFlags.NONE); loop.run(); }); afterAll(function () { // Not really needed, but if we don't cleanup // memory checking will complain Gio.DBus.session.unown_name(ownNameID); }); beforeEach(function () { loop = new GLib.MainLoop(null, false); }); it('can call a remote method', function () { proxy.frobateStuffRemote({}, ([result], excp) => { expect(excp).toBeNull(); expect(result.hello.deepUnpack()).toEqual('world'); loop.quit(); }); loop.run(); }); it('can call a remote method when not using makeProxyWrapper', function () { let info = Gio.DBusNodeInfo.new_for_xml(TestIface); let iface = info.interfaces[0]; let otherProxy = null; Gio.DBusProxy.new_for_bus(Gio.BusType.SESSION, Gio.DBusProxyFlags.DO_NOT_AUTO_START, iface, 'org.gnome.gjs.Test', '/org/gnome/gjs/Test', iface.name, null, (o, res) => { otherProxy = Gio.DBusProxy.new_for_bus_finish(res); loop.quit(); }); loop.run(); otherProxy.frobateStuffRemote({}, ([result], excp) => { expect(excp).toBeNull(); expect(result.hello.deepUnpack()).toEqual('world'); loop.quit(); }); loop.run(); }); /* excp must be exactly the exception thrown by the remote method (more or less) */ it('can handle an exception thrown by a remote method', function () { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: Exception in method call: alwaysThrowException: *'); proxy.alwaysThrowExceptionRemote({}, function (result, excp) { expect(excp).not.toBeNull(); loop.quit(); }); loop.run(); }); it('can still destructure the return value when an exception is thrown', function () { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: Exception in method call: alwaysThrowException: *'); // This test will not fail, but instead if the functionality is not // implemented correctly it will hang. The exception in the function // argument destructuring will not propagate across the FFI boundary // and the main loop will never quit. // https://bugzilla.gnome.org/show_bug.cgi?id=729015 proxy.alwaysThrowExceptionRemote({}, function ([a, b, c], excp) { expect(a).not.toBeDefined(); expect(b).not.toBeDefined(); expect(c).not.toBeDefined(); void excp; loop.quit(); }); loop.run(); }); it('throws an exception when trying to call a method that does not exist', function () { /* First remove the method from the object! */ delete Test.prototype.thisDoesNotExist; proxy.thisDoesNotExistRemote(function (result, excp) { expect(excp).not.toBeNull(); loop.quit(); }); loop.run(); }); it('can pass a parameter to a remote method that is not a JSON object', function () { proxy.nonJsonFrobateStuffRemote(42, ([result], excp) => { expect(result).toEqual('42 it is!'); expect(excp).toBeNull(); loop.quit(); }); loop.run(); }); it('can call a remote method with no in parameter', function () { proxy.noInParameterRemote(([result], excp) => { expect(result).toEqual('Yes!'); expect(excp).toBeNull(); loop.quit(); }); loop.run(); }); it('can call a remote method with multiple in parameters', function () { proxy.multipleInArgsRemote(1, 2, 3, 4, 5, ([result], excp) => { expect(result).toEqual('1 2 3 4 5'); expect(excp).toBeNull(); loop.quit(); }); loop.run(); }); it('can call a remote method with no return value', function () { proxy.noReturnValueRemote(([result], excp) => { expect(result).not.toBeDefined(); expect(excp).toBeNull(); loop.quit(); }); loop.run(); }); it('can emit a DBus signal', function () { let handler = jasmine.createSpy('signalFoo'); let id = proxy.connectSignal('signalFoo', handler); handler.and.callFake(() => proxy.disconnectSignal(id)); proxy.emitSignalRemote(([result], excp) => { expect(result).not.toBeDefined(); expect(excp).toBeNull(); expect(handler).toHaveBeenCalledTimes(1); expect(handler).toHaveBeenCalledWith(jasmine.anything(), jasmine.anything(), ['foobar']); loop.quit(); }); loop.run(); }); it('can call a remote method with multiple return values', function () { proxy.multipleOutValuesRemote(function (result, excp) { expect(result).toEqual(['Hello', 'World', '!']); expect(excp).toBeNull(); loop.quit(); }); loop.run(); }); it('does not coalesce one array into the array of return values', function () { proxy.oneArrayOutRemote(([result], excp) => { expect(result).toEqual(['Hello', 'World', '!']); expect(excp).toBeNull(); loop.quit(); }); loop.run(); }); it('does not coalesce an array of arrays into the array of return values', function () { proxy.arrayOfArrayOutRemote(([[a1, a2]], excp) => { expect(a1).toEqual(['Hello', 'World']); expect(a2).toEqual(['World', 'Hello']); expect(excp).toBeNull(); loop.quit(); }); loop.run(); }); it('can return multiple arrays from a remote method', function () { proxy.multipleArrayOutRemote(([a1, a2], excp) => { expect(a1).toEqual(['Hello', 'World']); expect(a2).toEqual(['World', 'Hello']); expect(excp).toBeNull(); loop.quit(); }); loop.run(); }); it('handles a bad signature by throwing an exception', function () { proxy.arrayOutBadSigRemote(function (result, excp) { expect(excp).not.toBeNull(); loop.quit(); }); loop.run(); }); it('can call a remote method that is implemented asynchronously', function () { let someString = 'Hello world!'; let someInt = 42; proxy.echoRemote(someString, someInt, function (result, excp) { expect(excp).toBeNull(); expect(result).toEqual([someString, someInt]); loop.quit(); }); loop.run(); }); it('can send and receive bytes from a remote method', function () { let someBytes = [0, 63, 234]; someBytes.forEach(b => { proxy.byteEchoRemote(b, ([result], excp) => { expect(excp).toBeNull(); expect(result).toEqual(b); loop.quit(); }); loop.run(); }); }); it('can call a remote method that returns an array of structs', function () { proxy.structArrayRemote(([result], excp) => { expect(excp).toBeNull(); expect(result).toEqual([[128, 123456], [42, 654321]]); loop.quit(); }); loop.run(); }); it('can send and receive dicts from a remote method', function () { let someDict = { aDouble: new GLib.Variant('d', 10), // should be an integer after round trip anInteger: new GLib.Variant('i', 10.5), // should remain a double aDoubleBeforeAndAfter: new GLib.Variant('d', 10.5), }; proxy.dictEchoRemote(someDict, ([result], excp) => { expect(excp).toBeNull(); expect(result).not.toBeNull(); // verify the fractional part was dropped off int expect(result['anInteger'].deepUnpack()).toEqual(10); // and not dropped off a double expect(result['aDoubleBeforeAndAfter'].deepUnpack()).toEqual(10.5); // check without type conversion expect(result['aDouble'].deepUnpack()).toBe(10.0); loop.quit(); }); loop.run(); }); it('can call a remote method with a Unix FD', function (done) { const expectedBytes = ByteArray.fromString('some bytes'); const fd = CjsPrivate.open_bytes(expectedBytes); const fdList = Gio.UnixFDList.new_from_array([fd]); proxy.fdInRemote(0, fdList, ([bytes], exc, outFdList) => { expect(exc).toBeNull(); expect(outFdList).toBeNull(); expect(bytes).toEqual(expectedBytes); done(); }); }); it('can call an asynchronously implemented remote method with a Unix FD', function (done) { const expectedBytes = ByteArray.fromString('some bytes'); const fd = CjsPrivate.open_bytes(expectedBytes); const fdList = Gio.UnixFDList.new_from_array([fd]); proxy.fdIn2Remote(0, fdList, ([bytes], exc, outFdList) => { expect(exc).toBeNull(); expect(outFdList).toBeNull(); expect(bytes).toEqual(expectedBytes); done(); }); }); function readBytesFromFdSync(fd) { const stream = new Gio.UnixInputStream({fd, closeFd: true}); const bytes = stream.read_bytes(4096, null); return ByteArray.fromGBytes(bytes); } it('can call a remote method that returns a Unix FD', function (done) { const expectedBytes = ByteArray.fromString('some bytes'); proxy.fdOutRemote(expectedBytes, ([fdIndex], exc, outFdList) => { expect(exc).toBeNull(); const bytes = readBytesFromFdSync(outFdList.get(fdIndex)); expect(bytes).toEqual(expectedBytes); done(); }); }); it('can call an asynchronously implemented remote method that returns a Unix FD', function (done) { const expectedBytes = ByteArray.fromString('some bytes'); proxy.fdOut2Remote(expectedBytes, ([fdIndex], exc, outFdList) => { expect(exc).toBeNull(); const bytes = readBytesFromFdSync(outFdList.get(fdIndex)); expect(bytes).toEqual(expectedBytes); done(); }); }); it('throws an exception when not passing a Gio.UnixFDList to a method that requires one', function () { expect(() => proxy.fdInRemote(0, () => {})).toThrow(); }); it('throws an exception when passing a handle out of range of a Gio.UnixFDList', function () { const fdList = new Gio.UnixFDList(); expect(() => proxy.fdInRemote(0, fdList, () => {})).toThrow(); }); it('Has defined properties', function () { expect(proxy.hasOwnProperty('PropReadWrite')).toBeTruthy(); expect(proxy.hasOwnProperty('PropReadOnly')).toBeTruthy(); expect(proxy.hasOwnProperty('PropWriteOnly')).toBeTruthy(); }); it('reading readonly property works', function () { expect(proxy.PropReadOnly).toEqual(PROP_READ_ONLY_INITIAL_VALUE); }); it('reading readwrite property works', function () { expect(proxy.PropReadWrite).toEqual( GLib.Variant.new_string(PROP_READ_WRITE_INITIAL_VALUE.toString())); }); it('reading writeonly throws an error', function () { expect(() => proxy.PropWriteOnly).toThrowError('Property PropWriteOnly is not readable'); }); it('Setting a readwrite property works', function () { let testStr = 'GjsVariantValue'; expect(() => { proxy.PropReadWrite = GLib.Variant.new_string(testStr); }).not.toThrow(); expect(proxy.PropReadWrite.deepUnpack()).toEqual(testStr); expect(waitForServerProperty('_propReadWrite', testStr)).toEqual(testStr); }); it('Setting a writeonly property works', function () { let testValue = Math.random().toString(); expect(() => { proxy.PropWriteOnly = testValue; }).not.toThrow(); expect(() => proxy.PropWriteOnly).toThrow(); expect(waitForServerProperty('_propWriteOnly', testValue)).toEqual(testValue); }); it('Setting a readonly property throws an error', function () { let testValue = Math.random().toString(); expect(() => { proxy.PropReadOnly = testValue; }).toThrowError('Property PropReadOnly is not writable'); expect(proxy.PropReadOnly).toBe(PROP_READ_ONLY_INITIAL_VALUE); }); }); cjs-5.2.0/installed-tests/js/testGObjectClass.js0000644000175000017500000007763314144444702021753 0ustar jpeisachjpeisach// -*- mode: js; indent-tabs-mode: nil -*- imports.gi.versions.Gtk = '3.0'; const Gio = imports.gi.Gio; const GLib = imports.gi.GLib; const GObject = imports.gi.GObject; const Gtk = imports.gi.Gtk; const MyObject = GObject.registerClass({ Properties: { 'readwrite': GObject.ParamSpec.string('readwrite', 'ParamReadwrite', 'A read write parameter', GObject.ParamFlags.READWRITE, ''), 'readonly': GObject.ParamSpec.string('readonly', 'ParamReadonly', 'A readonly parameter', GObject.ParamFlags.READABLE, ''), 'construct': GObject.ParamSpec.string('construct', 'ParamConstructOnly', 'A readwrite construct-only parameter', GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, 'default'), }, Signals: { 'empty': {}, 'minimal': {param_types: [GObject.TYPE_INT, GObject.TYPE_INT]}, 'full': { flags: GObject.SignalFlags.RUN_LAST, accumulator: GObject.AccumulatorType.FIRST_WINS, return_type: GObject.TYPE_INT, param_types: [], }, 'run-last': {flags: GObject.SignalFlags.RUN_LAST}, 'detailed': { flags: GObject.SignalFlags.RUN_FIRST | GObject.SignalFlags.DETAILED, param_types: [GObject.TYPE_STRING], }, }, }, class MyObject extends GObject.Object { get readwrite() { if (typeof this._readwrite === 'undefined') return 'foo'; return this._readwrite; } set readwrite(val) { if (val === 'ignore') return; this._readwrite = val; } get readonly() { if (typeof this._readonly === 'undefined') return 'bar'; return this._readonly; } set readonly(val) { // this should never be called void val; this._readonly = 'bogus'; } get construct() { if (typeof this._constructProp === 'undefined') return null; return this._constructProp; } set construct(val) { // this should be called at most once if (this._constructCalled) throw Error('Construct-Only property set more than once'); this._constructProp = val; this._constructCalled = true; } notifyProp() { this._readonly = 'changed'; this.notify('readonly'); } emitEmpty() { this.emit('empty'); } emitMinimal(one, two) { this.emit('minimal', one, two); } emitFull() { return this.emit('full'); } emitDetailed() { this.emit('detailed::one'); this.emit('detailed::two'); } emitRunLast(callback) { this._run_last_callback = callback; this.emit('run-last'); } on_run_last() { this._run_last_callback(); } on_empty() { this.empty_called = true; } on_full() { this.full_default_handler_called = true; return 79; } }); const MyAbstractObject = GObject.registerClass({ GTypeFlags: GObject.TypeFlags.ABSTRACT, }, class MyAbstractObject extends GObject.Object { }); const MyApplication = GObject.registerClass({ Signals: {'custom': {param_types: [GObject.TYPE_INT]}}, }, class MyApplication extends Gio.Application { emitCustom(n) { this.emit('custom', n); } }); const MyInitable = GObject.registerClass({ Implements: [Gio.Initable], }, class MyInitable extends GObject.Object { vfunc_init(cancellable) { if (!(cancellable instanceof Gio.Cancellable)) throw new Error('Bad argument'); this.inited = true; } }); const Derived = GObject.registerClass(class Derived extends MyObject { _init() { super._init({readwrite: 'yes'}); } }); const Cla$$ = GObject.registerClass(class Cla$$ extends MyObject {}); const MyCustomInit = GObject.registerClass(class MyCustomInit extends GObject.Object { _instance_init() { this.foo = true; } }); describe('GObject class with decorator', function () { let myInstance; beforeEach(function () { myInstance = new MyObject(); }); it('throws an error when not used with a GObject-derived class', function () { class Foo {} expect(() => GObject.registerClass(class Bar extends Foo {})).toThrow(); }); it('throws an error when used with an abstract class', function () { expect(() => new MyAbstractObject()).toThrow(); }); it('constructs with default values for properties', function () { expect(myInstance.readwrite).toEqual('foo'); expect(myInstance.readonly).toEqual('bar'); expect(myInstance.construct).toEqual('default'); }); it('constructs with a hash of property values', function () { let myInstance2 = new MyObject({readwrite: 'baz', construct: 'asdf'}); expect(myInstance2.readwrite).toEqual('baz'); expect(myInstance2.readonly).toEqual('bar'); expect(myInstance2.construct).toEqual('asdf'); }); it('warns if more than one argument passed to the default constructor', function () { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_MESSAGE, '*Too many arguments*'); new MyObject({readwrite: 'baz'}, 'this is ignored', 123); GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectClass.js', 0, 'testGObjectClassTooManyArguments'); }); it('throws an error if the first argument to the default constructor is not a property hash', function () { expect(() => new MyObject('this is wrong')).toThrow(); }); it('accepts a property hash that is not a plain object', function () { expect(() => new MyObject(new GObject.Object())).not.toThrow(); }); const ui = ` baz quz `; it('constructs with property values from Gtk.Builder', function () { let builder = Gtk.Builder.new_from_string(ui, -1); let myInstance3 = builder.get_object('MyObject'); expect(myInstance3.readwrite).toEqual('baz'); expect(myInstance3.readonly).toEqual('bar'); expect(myInstance3.construct).toEqual('quz'); }); it('has a name', function () { expect(MyObject.name).toEqual('MyObject'); }); // the following would (should) cause a CRITICAL: // myInstance.readonly = 'val'; // myInstance.construct = 'val'; it('has a notify signal', function () { let notifySpy = jasmine.createSpy('notifySpy'); myInstance.connect('notify::readonly', notifySpy); myInstance.notifyProp(); myInstance.notifyProp(); expect(notifySpy).toHaveBeenCalledTimes(2); }); it('can define its own signals', function () { let emptySpy = jasmine.createSpy('emptySpy'); myInstance.connect('empty', emptySpy); myInstance.emitEmpty(); expect(emptySpy).toHaveBeenCalled(); expect(myInstance.empty_called).toBeTruthy(); }); it('passes emitted arguments to signal handlers', function () { let minimalSpy = jasmine.createSpy('minimalSpy'); myInstance.connect('minimal', minimalSpy); myInstance.emitMinimal(7, 5); expect(minimalSpy).toHaveBeenCalledWith(myInstance, 7, 5); }); it('can return values from signals', function () { let fullSpy = jasmine.createSpy('fullSpy').and.returnValue(42); myInstance.connect('full', fullSpy); let result = myInstance.emitFull(); expect(fullSpy).toHaveBeenCalled(); expect(result).toEqual(42); }); it('does not call first-wins signal handlers after one returns a value', function () { let neverCalledSpy = jasmine.createSpy('neverCalledSpy'); myInstance.connect('full', () => 42); myInstance.connect('full', neverCalledSpy); myInstance.emitFull(); expect(neverCalledSpy).not.toHaveBeenCalled(); expect(myInstance.full_default_handler_called).toBeFalsy(); }); it('gets the return value of the default handler', function () { let result = myInstance.emitFull(); expect(myInstance.full_default_handler_called).toBeTruthy(); expect(result).toEqual(79); }); it('calls run-last default handler last', function () { let stack = []; let runLastSpy = jasmine.createSpy('runLastSpy') .and.callFake(() => { stack.push(1); }); myInstance.connect('run-last', runLastSpy); myInstance.emitRunLast(() => { stack.push(2); }); expect(stack).toEqual([1, 2]); }); it("can inherit from something that's not GObject.Object", function () { // ...and still get all the goodies of GObject.Class let instance = new MyApplication({application_id: 'org.gjs.Application'}); let customSpy = jasmine.createSpy('customSpy'); instance.connect('custom', customSpy); instance.emitCustom(73); expect(customSpy).toHaveBeenCalledWith(instance, 73); }); it('can implement an interface', function () { let instance = new MyInitable(); expect(instance instanceof Gio.Initable).toBeTruthy(); expect(instance instanceof Gio.AsyncInitable).toBeFalsy(); // Old syntax, backwards compatible expect(instance.constructor.implements(Gio.Initable)).toBeTruthy(); expect(instance.constructor.implements(Gio.AsyncInitable)).toBeFalsy(); }); it('can implement interface vfuncs', function () { let instance = new MyInitable(); expect(instance.inited).toBeFalsy(); instance.init(new Gio.Cancellable()); expect(instance.inited).toBeTruthy(); }); it('can be a subclass', function () { let derived = new Derived(); expect(derived instanceof Derived).toBeTruthy(); expect(derived instanceof MyObject).toBeTruthy(); expect(derived.readwrite).toEqual('yes'); }); it('can have any valid class name', function () { let obj = new Cla$$(); expect(obj instanceof Cla$$).toBeTruthy(); expect(obj instanceof MyObject).toBeTruthy(); }); it('calls its _instance_init() function while chaining up in constructor', function () { let instance = new MyCustomInit(); expect(instance.foo).toBeTruthy(); }); it('can have an interface-valued property', function () { const InterfacePropObject = GObject.registerClass({ Properties: { 'file': GObject.ParamSpec.object('file', 'File', 'File', GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, Gio.File.$gtype), }, }, class InterfacePropObject extends GObject.Object {}); let file = Gio.File.new_for_path('dummy'); expect(() => new InterfacePropObject({file})).not.toThrow(); }); it('can override a property from the parent class', function () { const OverrideObject = GObject.registerClass({ Properties: { 'readwrite': GObject.ParamSpec.override('readwrite', MyObject), }, }, class OverrideObject extends MyObject { get readwrite() { return this._subclass_readwrite; } set readwrite(val) { this._subclass_readwrite = `subclass${val}`; } }); let obj = new OverrideObject(); obj.readwrite = 'foo'; expect(obj.readwrite).toEqual('subclassfoo'); }); it('cannot override a non-existent property', function () { expect(() => GObject.registerClass({ Properties: { 'nonexistent': GObject.ParamSpec.override('nonexistent', GObject.Object), }, }, class BadOverride extends GObject.Object {})).toThrow(); }); it('handles gracefully forgetting to override a C property', function () { GLib.test_expect_message('GLib-GObject', GLib.LogLevelFlags.LEVEL_CRITICAL, "*Object class Gjs_ForgottenOverride doesn't implement property " + "'anchors' from interface 'GTlsFileDatabase'*"); // This is a random interface in Gio with a read-write property const ForgottenOverride = GObject.registerClass({ Implements: [Gio.TlsFileDatabase], }, class ForgottenOverride extends Gio.TlsDatabase {}); const obj = new ForgottenOverride(); expect(obj.anchors).not.toBeDefined(); expect(() => (obj.anchors = 'foo')).not.toThrow(); expect(obj.anchors).toEqual('foo'); GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectClass.js', 0, 'testGObjectClassForgottenOverride'); }); it('handles gracefully overriding a C property but forgetting the accessors', function () { // This is a random interface in Gio with a read-write property const ForgottenAccessors = GObject.registerClass({ Implements: [Gio.TlsFileDatabase], Properties: { 'anchors': GObject.ParamSpec.override('anchors', Gio.TlsFileDatabase), }, }, class ForgottenAccessors extends Gio.TlsDatabase {}); const obj = new ForgottenAccessors(); expect(obj.anchors).toBeNull(); // the property's default value obj.anchors = 'foo'; expect(obj.anchors).toEqual('foo'); const ForgottenAccessors2 = GObject.registerClass(class ForgottenAccessors2 extends ForgottenAccessors {}); const obj2 = new ForgottenAccessors2(); expect(obj2.anchors).toBeNull(); obj2.anchors = 'foo'; expect(obj2.anchors).toEqual('foo'); }); it('does not pollute the wrong prototype with GObject properties', function () { const MyCustomCharset = GObject.registerClass(class MyCustomCharset extends Gio.CharsetConverter { _init() { super._init(); void this.from_charset; } }); const MySecondCustomCharset = GObject.registerClass(class MySecondCustomCharset extends GObject.Object { _init() { super._init(); this.from_charset = 'another value'; } }); expect(() => new MyCustomCharset() && new MySecondCustomCharset()).not.toThrow(); }); it('resolves properties from interfaces', function () { const mon = Gio.NetworkMonitor.get_default(); expect(mon.network_available).toBeDefined(); expect(mon.networkAvailable).toBeDefined(); expect(mon['network-available']).toBeDefined(); }); it('has a toString() defintion', function () { expect(myInstance.toString()).toMatch( /\[object instance wrapper GType:Gjs_MyObject jsobj@0x[a-f0-9]+ native@0x[a-f0-9]+\]/); expect(new Derived().toString()).toMatch( /\[object instance wrapper GType:Gjs_Derived jsobj@0x[a-f0-9]+ native@0x[a-f0-9]+\]/); }); }); describe('GObject virtual function', function () { it('can have its property read', function () { expect(GObject.Object.prototype.vfunc_constructed).toBeTruthy(); }); it('can have its property overridden with an anonymous function', function () { let callback; let key = 'vfunc_constructed'; class _SimpleTestClass1 extends GObject.Object {} if (GObject.Object.prototype.vfunc_constructed) { let parentFunc = GObject.Object.prototype.vfunc_constructed; _SimpleTestClass1.prototype[key] = function (...args) { parentFunc.call(this, ...args); callback('123'); }; } else { _SimpleTestClass1.prototype[key] = function () { callback('abc'); }; } callback = jasmine.createSpy('callback'); const SimpleTestClass1 = GObject.registerClass({GTypeName: 'SimpleTestClass1'}, _SimpleTestClass1); new SimpleTestClass1(); expect(callback).toHaveBeenCalledWith('123'); }); it('can access the parent prototype with super()', function () { let callback; class _SimpleTestClass2 extends GObject.Object { vfunc_constructed() { super.vfunc_constructed(); callback('vfunc_constructed'); } } callback = jasmine.createSpy('callback'); const SimpleTestClass2 = GObject.registerClass({GTypeName: 'SimpleTestClass2'}, _SimpleTestClass2); new SimpleTestClass2(); expect(callback).toHaveBeenCalledWith('vfunc_constructed'); }); it('handles non-existing properties', function () { const _SimpleTestClass3 = class extends GObject.Object {}; _SimpleTestClass3.prototype.vfunc_doesnt_exist = function () {}; if (GObject.Object.prototype.vfunc_doesnt_exist) fail('Virtual function should not exist'); expect(() => GObject.registerClass({GTypeName: 'SimpleTestClass3'}, _SimpleTestClass3)).toThrow(); }); it('gracefully bails out when overriding an unsupported vfunc type', function () { expect(() => GObject.registerClass({ Implements: [Gio.AsyncInitable], }, class Foo extends GObject.Object { vfunc_init_async() {} })).toThrow(); }); }); describe('GObject creation using base classes without registered GType', function () { it('fails when trying to instantiate a class that inherits from a GObject type', function () { const BadInheritance = class extends GObject.Object {}; const BadDerivedInheritance = class extends Derived {}; expect(() => new BadInheritance()).toThrowError(/Tried to construct an object without a GType/); expect(() => new BadDerivedInheritance()).toThrowError(/Tried to construct an object without a GType/); }); it('fails when trying to register a GObject class that inherits from a non-GObject type', function () { const BadInheritance = class extends GObject.Object {}; expect(() => GObject.registerClass(class BadInheritanceDerived extends BadInheritance {})) .toThrowError(/Object 0x[a-f0-9]+ is not a subclass of GObject_Object, it's a Object/); }); }); describe('Register GType name', function () { beforeAll(function () { expect(GObject.gtypeNameBasedOnJSPath).toBeFalsy(); }); afterEach(function () { GObject.gtypeNameBasedOnJSPath = false; }); it('uses the class name', function () { const GTypeTestAutoName = GObject.registerClass( class GTypeTestAutoName extends GObject.Object { }); expect(GTypeTestAutoName.$gtype.name).toEqual( 'Gjs_GTypeTestAutoName'); }); it('uses the sanitized class name', function () { const GTypeTestAutoName = GObject.registerClass( class GTypeTestAutoCla$$Name extends GObject.Object { }); expect(GTypeTestAutoName.$gtype.name).toEqual( 'Gjs_GTypeTestAutoCla__Name'); }); it('use the file path and class name', function () { GObject.gtypeNameBasedOnJSPath = true; const GTypeTestAutoName = GObject.registerClass( class GTypeTestAutoName extends GObject.Object {}); /* Update this test if the file is moved */ expect(GTypeTestAutoName.$gtype.name).toEqual( 'Gjs_js_testGObjectClass_GTypeTestAutoName'); }); it('use the file path and sanitized class name', function () { GObject.gtypeNameBasedOnJSPath = true; const GTypeTestAutoName = GObject.registerClass( class GTypeTestAutoCla$$Name extends GObject.Object { }); /* Update this test if the file is moved */ expect(GTypeTestAutoName.$gtype.name).toEqual( 'Gjs_js_testGObjectClass_GTypeTestAutoCla__Name'); }); it('use provided class name', function () { const GtypeClass = GObject.registerClass({ GTypeName: 'GTypeTestManualName', }, class extends GObject.Object {}); expect(GtypeClass.$gtype.name).toEqual('GTypeTestManualName'); }); it('sanitizes user provided class name', function () { let gtypeName = 'GType$Test/WithLòt\'s of*bad§chars!'; let expectedSanitized = 'GType_Test_WithL_t_s_of_bad_chars_'; GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, `*RangeError: Provided GType name '${gtypeName}' is not valid; ` + `automatically sanitized to '${expectedSanitized}'*`); const GtypeClass = GObject.registerClass({ GTypeName: gtypeName, }, class extends GObject.Object {}); GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectClass.js', 0, 'testGObjectRegisterClassSanitize'); expect(GtypeClass.$gtype.name).toEqual(expectedSanitized); }); }); describe('Signal handler matching', function () { let o, handleEmpty, emptyId, handleDetailed, detailedId, handleDetailedOne, detailedOneId, handleDetailedTwo, detailedTwoId, handleNotifyTwo, notifyTwoId, handleMinimalOrFull, minimalId, fullId; beforeEach(function () { o = new MyObject(); handleEmpty = jasmine.createSpy('handleEmpty'); emptyId = o.connect('empty', handleEmpty); handleDetailed = jasmine.createSpy('handleDetailed'); detailedId = o.connect('detailed', handleDetailed); handleDetailedOne = jasmine.createSpy('handleDetailedOne'); detailedOneId = o.connect('detailed::one', handleDetailedOne); handleDetailedTwo = jasmine.createSpy('handleDetailedTwo'); detailedTwoId = o.connect('detailed::two', handleDetailedTwo); handleNotifyTwo = jasmine.createSpy('handleNotifyTwo'); notifyTwoId = o.connect('notify::two', handleNotifyTwo); handleMinimalOrFull = jasmine.createSpy('handleMinimalOrFull'); minimalId = o.connect('minimal', handleMinimalOrFull); fullId = o.connect('full', handleMinimalOrFull); }); it('finds handlers by signal ID', function () { expect(GObject.signal_handler_find(o, {signalId: 'empty'})).toEqual(emptyId); // when more than one are connected, returns an arbitrary one expect([detailedId, detailedOneId, detailedTwoId]) .toContain(GObject.signal_handler_find(o, {signalId: 'detailed'})); }); it('finds handlers by signal detail', function () { expect(GObject.signal_handler_find(o, {detail: 'one'})).toEqual(detailedOneId); // when more than one are connected, returns an arbitrary one expect([detailedTwoId, notifyTwoId]) .toContain(GObject.signal_handler_find(o, {detail: 'two'})); }); it('finds handlers by callback', function () { expect(GObject.signal_handler_find(o, {func: handleEmpty})).toEqual(emptyId); expect(GObject.signal_handler_find(o, {func: handleDetailed})).toEqual(detailedId); expect(GObject.signal_handler_find(o, {func: handleDetailedOne})).toEqual(detailedOneId); expect(GObject.signal_handler_find(o, {func: handleDetailedTwo})).toEqual(detailedTwoId); expect(GObject.signal_handler_find(o, {func: handleNotifyTwo})).toEqual(notifyTwoId); // when more than one are connected, returns an arbitrary one expect([minimalId, fullId]) .toContain(GObject.signal_handler_find(o, {func: handleMinimalOrFull})); }); it('finds handlers by a combination of parameters', function () { expect(GObject.signal_handler_find(o, {signalId: 'detailed', detail: 'two'})) .toEqual(detailedTwoId); expect(GObject.signal_handler_find(o, {signalId: 'detailed', func: handleDetailed})) .toEqual(detailedId); }); it('blocks a handler by callback', function () { expect(GObject.signal_handlers_block_matched(o, {func: handleEmpty})).toEqual(1); o.emitEmpty(); expect(handleEmpty).not.toHaveBeenCalled(); expect(GObject.signal_handlers_unblock_matched(o, {func: handleEmpty})).toEqual(1); o.emitEmpty(); expect(handleEmpty).toHaveBeenCalled(); }); it('blocks multiple handlers by callback', function () { expect(GObject.signal_handlers_block_matched(o, {func: handleMinimalOrFull})).toEqual(2); o.emitMinimal(); o.emitFull(); expect(handleMinimalOrFull).not.toHaveBeenCalled(); expect(GObject.signal_handlers_unblock_matched(o, {func: handleMinimalOrFull})).toEqual(2); o.emitMinimal(); o.emitFull(); expect(handleMinimalOrFull).toHaveBeenCalledTimes(2); }); it('blocks handlers by a combination of parameters', function () { expect(GObject.signal_handlers_block_matched(o, {signalId: 'detailed', func: handleDetailed})) .toEqual(1); o.emit('detailed', ''); o.emit('detailed::one', ''); expect(handleDetailed).not.toHaveBeenCalled(); expect(handleDetailedOne).toHaveBeenCalled(); expect(GObject.signal_handlers_unblock_matched(o, {signalId: 'detailed', func: handleDetailed})) .toEqual(1); o.emit('detailed', ''); o.emit('detailed::one', ''); expect(handleDetailed).toHaveBeenCalled(); }); it('disconnects a handler by callback', function () { expect(GObject.signal_handlers_disconnect_matched(o, {func: handleEmpty})).toEqual(1); o.emitEmpty(); expect(handleEmpty).not.toHaveBeenCalled(); }); it('blocks multiple handlers by callback', function () { expect(GObject.signal_handlers_disconnect_matched(o, {func: handleMinimalOrFull})).toEqual(2); o.emitMinimal(); o.emitFull(); expect(handleMinimalOrFull).not.toHaveBeenCalled(); }); it('blocks handlers by a combination of parameters', function () { expect(GObject.signal_handlers_disconnect_matched(o, {signalId: 'detailed', func: handleDetailed})) .toEqual(1); o.emit('detailed', ''); o.emit('detailed::one', ''); expect(handleDetailed).not.toHaveBeenCalled(); expect(handleDetailedOne).toHaveBeenCalled(); }); it('blocks a handler by callback, convenience method', function () { expect(GObject.signal_handlers_block_by_func(o, handleEmpty)).toEqual(1); o.emitEmpty(); expect(handleEmpty).not.toHaveBeenCalled(); expect(GObject.signal_handlers_unblock_by_func(o, handleEmpty)).toEqual(1); o.emitEmpty(); expect(handleEmpty).toHaveBeenCalled(); }); it('disconnects a handler by callback, convenience method', function () { expect(GObject.signal_handlers_disconnect_by_func(o, handleEmpty)).toEqual(1); o.emitEmpty(); expect(handleEmpty).not.toHaveBeenCalled(); }); it('does not support disconnecting a handler by callback data', function () { expect(() => GObject.signal_handlers_disconnect_by_data(o, null)).toThrow(); }); }); describe('Auto accessor generation', function () { const AutoAccessors = GObject.registerClass({ Properties: { 'simple': GObject.ParamSpec.int('simple', 'Simple', 'Short-named property', GObject.ParamFlags.READWRITE, 0, 100, 24), 'long-long-name': GObject.ParamSpec.int('long-long-name', 'Long long name', 'Long-named property', GObject.ParamFlags.READWRITE, 0, 100, 48), 'construct': GObject.ParamSpec.int('construct', 'Construct', 'Construct', GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT, 0, 100, 96), 'snake-name': GObject.ParamSpec.int('snake-name', 'Snake name', 'Snake-cased property', GObject.ParamFlags.READWRITE, 0, 100, 36), 'camel-name': GObject.ParamSpec.int('camel-name', 'Camel name', 'Camel-cased property', GObject.ParamFlags.READWRITE, 0, 100, 72), 'kebab-name': GObject.ParamSpec.int('kebab-name', 'Kebab name', 'Kebab-cased property', GObject.ParamFlags.READWRITE, 0, 100, 12), 'readonly': GObject.ParamSpec.int('readonly', 'Readonly', 'Readonly property', GObject.ParamFlags.READABLE, 0, 100, 54), 'writeonly': GObject.ParamSpec.int('writeonly', 'Writeonly', 'Writeonly property', GObject.ParamFlags.WRITABLE, 0, 100, 60), 'missing-getter': GObject.ParamSpec.int('missing-getter', 'Missing getter', 'Missing a getter', GObject.ParamFlags.READWRITE, 0, 100, 18), 'missing-setter': GObject.ParamSpec.int('missing-setter', 'Missing setter', 'Missing a setter', GObject.ParamFlags.READWRITE, 0, 100, 42), }, }, class AutoAccessors extends GObject.Object { _init(props = {}) { super._init(props); this._snakeNameGetterCalled = 0; this._snakeNameSetterCalled = 0; this._camelNameGetterCalled = 0; this._camelNameSetterCalled = 0; this._kebabNameGetterCalled = 0; this._kebabNameSetterCalled = 0; } get snake_name() { this._snakeNameGetterCalled++; return 42; } set snake_name(value) { this._snakeNameSetterCalled++; } get camelName() { this._camelNameGetterCalled++; return 42; } set camelName(value) { this._camelNameSetterCalled++; } get ['kebab-name']() { this._kebabNameGetterCalled++; return 42; } set ['kebab-name'](value) { this._kebabNameSetterCalled++; } set missing_getter(value) { this._missingGetter = value; } get missing_setter() { return 42; } }); let a; beforeEach(function () { a = new AutoAccessors(); }); it('get and set the property', function () { a.simple = 1; expect(a.simple).toEqual(1); a['long-long-name'] = 1; expect(a['long-long-name']).toEqual(1); a.construct = 1; expect(a.construct).toEqual(1); }); it("initial value is the param spec's default value", function () { expect(a.simple).toEqual(24); expect(a['long-long-name']).toEqual(48); expect(a.construct).toEqual(96); }); it('notify when the property changes', function () { const notify = jasmine.createSpy('notify'); a.connect('notify::simple', notify); a.simple = 1; expect(notify).toHaveBeenCalledTimes(1); notify.calls.reset(); a.simple = 1; expect(notify).not.toHaveBeenCalled(); }); it('copies accessors for camel and kebab if snake accessors given', function () { a.snakeName = 42; expect(a.snakeName).toEqual(42); a['snake-name'] = 42; expect(a['snake-name']).toEqual(42); expect(a._snakeNameGetterCalled).toEqual(2); expect(a._snakeNameSetterCalled).toEqual(2); }); it('copies accessors for snake and kebab if camel accessors given', function () { a.camel_name = 42; expect(a.camel_name).toEqual(42); a['camel-name'] = 42; expect(a['camel-name']).toEqual(42); expect(a._camelNameGetterCalled).toEqual(2); expect(a._camelNameSetterCalled).toEqual(2); }); it('copies accessors for snake and camel if kebab accessors given', function () { a.kebabName = 42; expect(a.kebabName).toEqual(42); a.kebab_name = 42; expect(a.kebab_name).toEqual(42); expect(a._kebabNameGetterCalled).toEqual(2); expect(a._kebabNameSetterCalled).toEqual(2); }); it('readonly getter throws', function () { expect(() => a.readonly).toThrowError(/getter/); }); it('writeonly setter throws', function () { expect(() => (a.writeonly = 1)).toThrowError(/setter/); }); it('getter throws when setter defined', function () { expect(() => a.missingGetter).toThrowError(/getter/); }); it('setter throws when getter defined', function () { expect(() => (a.missingSetter = 1)).toThrowError(/setter/); }); }); cjs-5.2.0/installed-tests/js/testGObjectInterface.js0000644000175000017500000002676714144444702022610 0ustar jpeisachjpeisachconst Gio = imports.gi.Gio; const GLib = imports.gi.GLib; const GObject = imports.gi.GObject; const AGObjectInterface = GObject.registerClass({ GTypeName: 'ArbitraryGTypeName', Requires: [GObject.Object], Properties: { 'interface-prop': GObject.ParamSpec.string('interface-prop', 'Interface property', 'Must be overridden in implementation', GObject.ParamFlags.READABLE, 'foobar'), }, Signals: { 'interface-signal': {}, }, }, class AGObjectInterface extends GObject.Interface { requiredG() { throw new GObject.NotImplementedError(); } optionalG() { return 'AGObjectInterface.optionalG()'; } }); const InterfaceRequiringGObjectInterface = GObject.registerClass({ Requires: [AGObjectInterface], }, class InterfaceRequiringGObjectInterface extends GObject.Interface { optionalG() { return `InterfaceRequiringGObjectInterface.optionalG()\n${ AGObjectInterface.optionalG(this)}`; } }); const GObjectImplementingGObjectInterface = GObject.registerClass({ Implements: [AGObjectInterface], Properties: { 'interface-prop': GObject.ParamSpec.override('interface-prop', AGObjectInterface), 'class-prop': GObject.ParamSpec.string('class-prop', 'Class property', 'A property that is not on the interface', GObject.ParamFlags.READABLE, 'meh'), }, Signals: { 'class-signal': {}, }, }, class GObjectImplementingGObjectInterface extends GObject.Object { get interface_prop() { return 'foobar'; } get class_prop() { return 'meh'; } requiredG() {} optionalG() { return AGObjectInterface.optionalG(this); } }); const MinimalImplementationOfAGObjectInterface = GObject.registerClass({ Implements: [AGObjectInterface], Properties: { 'interface-prop': GObject.ParamSpec.override('interface-prop', AGObjectInterface), }, }, class MinimalImplementationOfAGObjectInterface extends GObject.Object { requiredG() {} }); const ImplementationOfTwoInterfaces = GObject.registerClass({ Implements: [AGObjectInterface, InterfaceRequiringGObjectInterface], Properties: { 'interface-prop': GObject.ParamSpec.override('interface-prop', AGObjectInterface), }, }, class ImplementationOfTwoInterfaces extends GObject.Object { requiredG() {} optionalG() { return InterfaceRequiringGObjectInterface.optionalG(this); } }); const ImplementationOfIntrospectedInterface = GObject.registerClass({ Implements: [Gio.Action], Properties: { 'enabled': GObject.ParamSpec.override('enabled', Gio.Action), 'name': GObject.ParamSpec.override('name', Gio.Action), 'state': GObject.ParamSpec.override('state', Gio.Action), 'state-type': GObject.ParamSpec.override('state-type', Gio.Action), 'parameter-type': GObject.ParamSpec.override('parameter-type', Gio.Action), }, }, class ImplementationOfIntrospectedInterface extends GObject.Object { get name() { return 'inaction'; } }); describe('GObject interface', function () { it('cannot be instantiated', function () { expect(() => new AGObjectInterface()).toThrow(); }); it('has a name', function () { expect(AGObjectInterface.name).toEqual('AGObjectInterface'); }); it('reports its type name', function () { expect(AGObjectInterface.$gtype.name).toEqual('ArbitraryGTypeName'); }); it('can be implemented by a GObject class', function () { let obj; expect(() => { obj = new GObjectImplementingGObjectInterface(); }).not.toThrow(); expect(obj instanceof AGObjectInterface).toBeTruthy(); }); it('is implemented by a GObject class with the correct class object', function () { let obj = new GObjectImplementingGObjectInterface(); expect(obj.constructor).toBe(GObjectImplementingGObjectInterface); expect(obj.constructor.name) .toEqual('GObjectImplementingGObjectInterface'); }); it('can have its required function implemented', function () { expect(() => { let obj = new GObjectImplementingGObjectInterface(); obj.requiredG(); }).not.toThrow(); }); it('must have its required function implemented', function () { const BadObject = GObject.registerClass({ Implements: [AGObjectInterface], Properties: { 'interface-prop': GObject.ParamSpec.override('interface-prop', AGObjectInterface), }, }, class BadObject extends GObject.Object {}); expect(() => new BadObject().requiredG()) .toThrowError(GObject.NotImplementedError); }); it("doesn't have to have its optional function implemented", function () { let obj; expect(() => { obj = new MinimalImplementationOfAGObjectInterface(); }).not.toThrow(); expect(obj instanceof AGObjectInterface).toBeTruthy(); }); it('can have its optional function deferred to by the implementation', function () { let obj = new MinimalImplementationOfAGObjectInterface(); expect(obj.optionalG()).toEqual('AGObjectInterface.optionalG()'); }); it('can have its function chained up to', function () { let obj = new GObjectImplementingGObjectInterface(); expect(obj.optionalG()).toEqual('AGObjectInterface.optionalG()'); }); it('can require another interface', function () { let obj; expect(() => { obj = new ImplementationOfTwoInterfaces(); }).not.toThrow(); expect(obj instanceof AGObjectInterface).toBeTruthy(); expect(obj instanceof InterfaceRequiringGObjectInterface).toBeTruthy(); }); it('can chain up to another interface', function () { let obj = new ImplementationOfTwoInterfaces(); expect(obj.optionalG()) .toEqual('InterfaceRequiringGObjectInterface.optionalG()\nAGObjectInterface.optionalG()'); }); it("defers to the last interface's optional function", function () { const MinimalImplementationOfTwoInterfaces = GObject.registerClass({ Implements: [AGObjectInterface, InterfaceRequiringGObjectInterface], Properties: { 'interface-prop': GObject.ParamSpec.override('interface-prop', AGObjectInterface), }, }, class MinimalImplementationOfTwoInterfaces extends GObject.Object { requiredG() {} }); let obj = new MinimalImplementationOfTwoInterfaces(); expect(obj.optionalG()) .toEqual('InterfaceRequiringGObjectInterface.optionalG()\nAGObjectInterface.optionalG()'); }); it('must be implemented by a class that implements all required interfaces', function () { expect(() => GObject.registerClass({ Implements: [InterfaceRequiringGObjectInterface], }, class BadObject { required() {} })).toThrow(); }); it('must be implemented by a class that implements required interfaces in correct order', function () { expect(() => GObject.registerClass({ Implements: [InterfaceRequiringGObjectInterface, AGObjectInterface], }, class BadObject { required() {} })).toThrow(); }); it('can require an interface from C', function () { const InitableInterface = GObject.registerClass({ Requires: [GObject.Object, Gio.Initable], }, class InitableInterface extends GObject.Interface {}); expect(() => GObject.registerClass({ Implements: [InitableInterface], }, class BadObject {})).toThrow(); }); it('can connect class signals on the implementing class', function (done) { function quitLoop() { expect(classSignalSpy).toHaveBeenCalled(); done(); } let obj = new GObjectImplementingGObjectInterface(); let classSignalSpy = jasmine.createSpy('classSignalSpy') .and.callFake(quitLoop); obj.connect('class-signal', classSignalSpy); GLib.idle_add(GLib.PRIORITY_DEFAULT, () => { obj.emit('class-signal'); return GLib.SOURCE_REMOVE; }); }); it('can connect interface signals on the implementing class', function (done) { function quitLoop() { expect(interfaceSignalSpy).toHaveBeenCalled(); done(); } let obj = new GObjectImplementingGObjectInterface(); let interfaceSignalSpy = jasmine.createSpy('interfaceSignalSpy') .and.callFake(quitLoop); obj.connect('interface-signal', interfaceSignalSpy); GLib.idle_add(GLib.PRIORITY_DEFAULT, () => { obj.emit('interface-signal'); return GLib.SOURCE_REMOVE; }); }); it('can define properties on the implementing class', function () { let obj = new GObjectImplementingGObjectInterface(); expect(obj.interface_prop).toEqual('foobar'); expect(obj.class_prop).toEqual('meh'); }); it('must have its properties overridden', function () { // Failing to override an interface property doesn't raise an error but // instead logs a critical warning. GLib.test_expect_message('GLib-GObject', GLib.LogLevelFlags.LEVEL_CRITICAL, "Object class * doesn't implement property 'interface-prop' from " + "interface 'ArbitraryGTypeName'"); GObject.registerClass({ Implements: [AGObjectInterface], }, class MyNaughtyObject extends GObject.Object { requiredG() {} }); // g_test_assert_expected_messages() is a macro, not introspectable GLib.test_assert_expected_messages_internal('Gjs', 'testGObjectInterface.js', 253, 'testGObjectMustOverrideInterfaceProperties'); }); it('can have introspected properties overriden', function () { let obj = new ImplementationOfIntrospectedInterface(); expect(obj.name).toEqual('inaction'); }); it('can be implemented by a class as well as its parent class', function () { const SubObject = GObject.registerClass( class SubObject extends GObjectImplementingGObjectInterface {}); let obj = new SubObject(); expect(obj instanceof AGObjectInterface).toBeTruthy(); expect(obj.interface_prop).toEqual('foobar'); // override not needed }); it('can be reimplemented by a subclass of a class that already implements it', function () { const SubImplementer = GObject.registerClass({ Implements: [AGObjectInterface], }, class SubImplementer extends GObjectImplementingGObjectInterface {}); let obj = new SubImplementer(); expect(obj instanceof AGObjectInterface).toBeTruthy(); expect(obj.interface_prop).toEqual('foobar'); // override not needed }); it('has a toString() defintion', function () { expect(new GObjectImplementingGObjectInterface().toString()).toMatch( /\[object instance wrapper GType:Gjs_GObjectImplementingGObjectInterface jsobj@0x[a-f0-9]+ native@0x[a-f0-9]+\]/); }); }); describe('Specific class and interface checks', function () { it('Gio.AsyncInitable must implement vfunc_async_init', function () { expect(() => GObject.registerClass({ Implements: [Gio.Initable, Gio.AsyncInitable], }, class BadAsyncInitable extends GObject.Object { vfunc_init() {} })).toThrow(); }); }); cjs-5.2.0/installed-tests/js/testWarnLib.js0000644000175000017500000000217014144444702020766 0ustar jpeisachjpeisach// File with tests from the WarnLib-1.0.gir test suite from GI const {Gio, GObject, WarnLib} = imports.gi; describe('WarnLib', function () { // Calling matches() on an unpaired error used to JSUnit.assert: // https://bugzilla.gnome.org/show_bug.cgi?id=689482 it('bug 689482', function () { try { WarnLib.throw_unpaired(); fail(); } catch (e) { expect(e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_FOUND)).toBeFalsy(); } }); const WhateverImpl = GObject.registerClass({ Implements: [WarnLib.Whatever], }, class WhateverImpl extends GObject.Object { vfunc_do_moo(x) { expect(x).toEqual(5); this.mooCalled = true; } vfunc_do_boo(x) { expect(x).toEqual(6); this.booCalled = true; } }); it('calls vfuncs with unnamed parameters', function () { const o = new WhateverImpl(); o.do_moo(5, null); o.do_boo(6, null); expect(o.mooCalled).toBeTruthy(); // spies don't work on vfuncs expect(o.booCalled).toBeTruthy(); }); }); cjs-5.2.0/installed-tests/js/testLegacyGObject.js0000644000175000017500000007312214144444702022077 0ustar jpeisachjpeisach// -*- mode: js; indent-tabs-mode: nil -*- /* eslint-disable no-restricted-properties */ imports.gi.versions.Gtk = '3.0'; const Gio = imports.gi.Gio; const GLib = imports.gi.GLib; const GObject = imports.gi.GObject; const Gtk = imports.gi.Gtk; const Lang = imports.lang; const Mainloop = imports.mainloop; const MyObject = new GObject.Class({ Name: 'MyObject', Properties: { 'readwrite': GObject.ParamSpec.string('readwrite', 'ParamReadwrite', 'A read write parameter', GObject.ParamFlags.READWRITE, ''), 'readonly': GObject.ParamSpec.string('readonly', 'ParamReadonly', 'A readonly parameter', GObject.ParamFlags.READABLE, ''), 'construct': GObject.ParamSpec.string('construct', 'ParamConstructOnly', 'A readwrite construct-only parameter', GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, 'default'), }, Signals: { 'empty': { }, 'minimal': {param_types: [GObject.TYPE_INT, GObject.TYPE_INT]}, 'full': { flags: GObject.SignalFlags.RUN_LAST, accumulator: GObject.AccumulatorType.FIRST_WINS, return_type: GObject.TYPE_INT, param_types: [], }, 'run-last': {flags: GObject.SignalFlags.RUN_LAST}, 'detailed': { flags: GObject.SignalFlags.RUN_FIRST | GObject.SignalFlags.DETAILED, param_types: [GObject.TYPE_STRING], }, }, _init(props) { // check that it's safe to set properties before // chaining up (priv is NULL at this point, remember) this._readwrite = 'foo'; this._readonly = 'bar'; this._constructProp = null; this._constructCalled = false; this.parent(props); }, get readwrite() { return this._readwrite; }, set readwrite(val) { if (val === 'ignore') return; this._readwrite = val; }, get readonly() { return this._readonly; }, set readonly(val) { // this should never be called this._readonly = 'bogus'; }, get construct() { return this._constructProp; }, set construct(val) { // this should be called at most once if (this._constructCalled) throw Error('Construct-Only property set more than once'); this._constructProp = val; this._constructCalled = true; }, notify_prop() { this._readonly = 'changed'; this.notify('readonly'); }, emit_empty() { this.emit('empty'); }, emit_minimal(one, two) { this.emit('minimal', one, two); }, emit_full() { return this.emit('full'); }, emit_detailed() { this.emit('detailed::one'); this.emit('detailed::two'); }, emit_run_last(callback) { this._run_last_callback = callback; this.emit('run-last'); }, on_run_last() { this._run_last_callback(); }, on_empty() { this.empty_called = true; }, on_full() { this.full_default_handler_called = true; return 79; }, }); const MyApplication = new Lang.Class({ Name: 'MyApplication', Extends: Gio.Application, Signals: {'custom': {param_types: [GObject.TYPE_INT]}}, _init(params) { this.parent(params); }, emit_custom(n) { this.emit('custom', n); }, }); const MyInitable = new Lang.Class({ Name: 'MyInitable', Extends: GObject.Object, Implements: [Gio.Initable], _init(params) { this.parent(params); this.inited = false; }, vfunc_init(cancellable) { // error? if (!(cancellable instanceof Gio.Cancellable)) throw new Error('Bad argument'); this.inited = true; }, }); const Derived = new Lang.Class({ Name: 'Derived', Extends: MyObject, _init() { this.parent({readwrite: 'yes'}); }, }); const OddlyNamed = new Lang.Class({ Name: 'Legacy.OddlyNamed', Extends: MyObject, }); const MyCustomInit = new Lang.Class({ Name: 'MyCustomInit', Extends: GObject.Object, _init() { this.foo = false; this.parent(); }, _instance_init() { this.foo = true; }, }); describe('GObject class', function () { let myInstance; beforeEach(function () { myInstance = new MyObject(); }); it('constructs with default values for properties', function () { expect(myInstance.readwrite).toEqual('foo'); expect(myInstance.readonly).toEqual('bar'); expect(myInstance.construct).toEqual('default'); }); it('constructs with a hash of property values', function () { let myInstance2 = new MyObject({readwrite: 'baz', construct: 'asdf'}); expect(myInstance2.readwrite).toEqual('baz'); expect(myInstance2.readonly).toEqual('bar'); expect(myInstance2.construct).toEqual('asdf'); }); const ui = ` baz quz `; it('constructs with property values from Gtk.Builder', function () { let builder = Gtk.Builder.new_from_string(ui, -1); let myInstance3 = builder.get_object('MyObject'); expect(myInstance3.readwrite).toEqual('baz'); expect(myInstance3.readonly).toEqual('bar'); expect(myInstance3.construct).toEqual('quz'); }); it('has a name', function () { expect(MyObject.name).toEqual('MyObject'); }); // the following would (should) cause a CRITICAL: // myInstance.readonly = 'val'; // myInstance.construct = 'val'; it('has a notify signal', function () { let notifySpy = jasmine.createSpy('notifySpy'); myInstance.connect('notify::readonly', notifySpy); myInstance.notify_prop(); myInstance.notify_prop(); expect(notifySpy).toHaveBeenCalledTimes(2); }); it('can define its own signals', function () { let emptySpy = jasmine.createSpy('emptySpy'); myInstance.connect('empty', emptySpy); myInstance.emit_empty(); expect(emptySpy).toHaveBeenCalled(); expect(myInstance.empty_called).toBeTruthy(); }); it('passes emitted arguments to signal handlers', function () { let minimalSpy = jasmine.createSpy('minimalSpy'); myInstance.connect('minimal', minimalSpy); myInstance.emit_minimal(7, 5); expect(minimalSpy).toHaveBeenCalledWith(myInstance, 7, 5); }); it('can return values from signals', function () { let fullSpy = jasmine.createSpy('fullSpy').and.returnValue(42); myInstance.connect('full', fullSpy); let result = myInstance.emit_full(); expect(fullSpy).toHaveBeenCalled(); expect(result).toEqual(42); }); it('does not call first-wins signal handlers after one returns a value', function () { let neverCalledSpy = jasmine.createSpy('neverCalledSpy'); myInstance.connect('full', () => 42); myInstance.connect('full', neverCalledSpy); myInstance.emit_full(); expect(neverCalledSpy).not.toHaveBeenCalled(); expect(myInstance.full_default_handler_called).toBeFalsy(); }); it('gets the return value of the default handler', function () { let result = myInstance.emit_full(); expect(myInstance.full_default_handler_called).toBeTruthy(); expect(result).toEqual(79); }); it('calls run-last default handler last', function () { let stack = []; let runLastSpy = jasmine.createSpy('runLastSpy') .and.callFake(() => { stack.push(1); }); myInstance.connect('run-last', runLastSpy); myInstance.emit_run_last(() => { stack.push(2); }); expect(stack).toEqual([1, 2]); }); it("can inherit from something that's not GObject.Object", function () { // ...and still get all the goodies of GObject.Class let instance = new MyApplication({application_id: 'org.gjs.Application'}); let customSpy = jasmine.createSpy('customSpy'); instance.connect('custom', customSpy); instance.emit_custom(73); expect(customSpy).toHaveBeenCalledWith(instance, 73); }); it('can implement an interface', function () { let instance = new MyInitable(); expect(instance.constructor.implements(Gio.Initable)).toBeTruthy(); }); it('can implement interface vfuncs', function () { let instance = new MyInitable(); expect(instance.inited).toBeFalsy(); instance.init(new Gio.Cancellable()); expect(instance.inited).toBeTruthy(); }); it('can be a subclass', function () { let derived = new Derived(); expect(derived instanceof Derived).toBeTruthy(); expect(derived instanceof MyObject).toBeTruthy(); expect(derived.readwrite).toEqual('yes'); }); it('can have any valid Lang.Class name', function () { let obj = new OddlyNamed(); expect(obj instanceof OddlyNamed).toBeTruthy(); expect(obj instanceof MyObject).toBeTruthy(); }); it('calls its _instance_init() function while chaining up in constructor', function () { let instance = new MyCustomInit(); expect(instance.foo).toBeTruthy(); }); it('can have an interface-valued property', function () { const InterfacePropObject = new Lang.Class({ Name: 'InterfacePropObject', Extends: GObject.Object, Properties: { 'file': GObject.ParamSpec.object('file', 'File', 'File', GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, Gio.File.$gtype), }, }); let file = Gio.File.new_for_path('dummy'); expect(() => new InterfacePropObject({file})).not.toThrow(); }); it('can override a property from the parent class', function () { const OverrideObject = new Lang.Class({ Name: 'OverrideObject', Extends: MyObject, Properties: { 'readwrite': GObject.ParamSpec.override('readwrite', MyObject), }, get readwrite() { return this._subclass_readwrite; }, set readwrite(val) { this._subclass_readwrite = `subclass${val}`; }, }); let obj = new OverrideObject(); obj.readwrite = 'foo'; expect(obj.readwrite).toEqual('subclassfoo'); }); it('cannot override a non-existent property', function () { expect(() => new Lang.Class({ Name: 'BadOverride', Extends: GObject.Object, Properties: { 'nonexistent': GObject.ParamSpec.override('nonexistent', GObject.Object), }, })).toThrow(); }); it('handles gracefully forgetting to override a C property', function () { GLib.test_expect_message('GLib-GObject', GLib.LogLevelFlags.LEVEL_CRITICAL, "*Object class Gjs_ForgottenOverride doesn't implement property " + "'anchors' from interface 'GTlsFileDatabase'*"); // This is a random interface in Gio with a read-write property const ForgottenOverride = new Lang.Class({ Name: 'ForgottenOverride', Extends: Gio.TlsDatabase, Implements: [Gio.TlsFileDatabase], }); const obj = new ForgottenOverride(); expect(obj.anchors).not.toBeDefined(); expect(() => (obj.anchors = 'foo')).not.toThrow(); expect(obj.anchors).toEqual('foo'); GLib.test_assert_expected_messages_internal('Gjs', 'testGObjectClass.js', 0, 'testGObjectClassForgottenOverride'); }); it('handles gracefully overriding a C property but forgetting the accessors', function () { // This is a random interface in Gio with a read-write property const ForgottenAccessors = new Lang.Class({ Name: 'ForgottenAccessors', Extends: Gio.TlsDatabase, Implements: [Gio.TlsFileDatabase], Properties: { 'anchors': GObject.ParamSpec.override('anchors', Gio.TlsFileDatabase), }, }); const obj = new ForgottenAccessors(); expect(obj.anchors).toBeNull(); obj.anchors = 'foo'; const ForgottenAccessors2 = new Lang.Class({ Name: 'ForgottenAccessors2', Extends: ForgottenAccessors, }); const obj2 = new ForgottenAccessors2(); expect(obj2.anchors).toBeNull(); obj2.anchors = 'foo'; }); }); const AnInterface = new Lang.Interface({ Name: 'AnInterface', }); const GObjectImplementingLangInterface = new Lang.Class({ Name: 'GObjectImplementingLangInterface', Extends: GObject.Object, Implements: [AnInterface], }); const AGObjectInterface = new Lang.Interface({ Name: 'AGObjectInterface', GTypeName: 'ArbitraryGTypeName', Requires: [GObject.Object], Properties: { 'interface-prop': GObject.ParamSpec.string('interface-prop', 'Interface property', 'Must be overridden in implementation', GObject.ParamFlags.READABLE, 'foobar'), }, Signals: { 'interface-signal': {}, }, requiredG: Lang.Interface.UNIMPLEMENTED, optionalG() { return 'AGObjectInterface.optionalG()'; }, }); const InterfaceRequiringGObjectInterface = new Lang.Interface({ Name: 'InterfaceRequiringGObjectInterface', Requires: [AGObjectInterface], optionalG() { return `InterfaceRequiringGObjectInterface.optionalG()\n${ AGObjectInterface.optionalG(this)}`; }, }); const GObjectImplementingGObjectInterface = new Lang.Class({ Name: 'GObjectImplementingGObjectInterface', Extends: GObject.Object, Implements: [AGObjectInterface], Properties: { 'interface-prop': GObject.ParamSpec.override('interface-prop', AGObjectInterface), 'class-prop': GObject.ParamSpec.string('class-prop', 'Class property', 'A property that is not on the interface', GObject.ParamFlags.READABLE, 'meh'), }, Signals: { 'class-signal': {}, }, get interface_prop() { return 'foobar'; }, get class_prop() { return 'meh'; }, requiredG() {}, optionalG() { return AGObjectInterface.optionalG(this); }, }); const MinimalImplementationOfAGObjectInterface = new Lang.Class({ Name: 'MinimalImplementationOfAGObjectInterface', Extends: GObject.Object, Implements: [AGObjectInterface], Properties: { 'interface-prop': GObject.ParamSpec.override('interface-prop', AGObjectInterface), }, requiredG() {}, }); const ImplementationOfTwoInterfaces = new Lang.Class({ Name: 'ImplementationOfTwoInterfaces', Extends: GObject.Object, Implements: [AGObjectInterface, InterfaceRequiringGObjectInterface], Properties: { 'interface-prop': GObject.ParamSpec.override('interface-prop', AGObjectInterface), }, requiredG() {}, optionalG() { return InterfaceRequiringGObjectInterface.optionalG(this); }, }); describe('GObject interface', function () { it('class can implement a Lang.Interface', function () { let obj; expect(() => { obj = new GObjectImplementingLangInterface(); }).not.toThrow(); expect(obj.constructor.implements(AnInterface)).toBeTruthy(); }); it('throws when an interface requires a GObject interface but not GObject.Object', function () { expect(() => new Lang.Interface({ Name: 'GObjectInterfaceNotRequiringGObject', GTypeName: 'GTypeNameNotRequiringGObject', Requires: [Gio.Initable], })).toThrow(); }); it('can be implemented by a GObject class along with a JS interface', function () { const ObjectImplementingLangInterfaceAndCInterface = new Lang.Class({ Name: 'ObjectImplementingLangInterfaceAndCInterface', Extends: GObject.Object, Implements: [AnInterface, Gio.Initable], }); let obj; expect(() => { obj = new ObjectImplementingLangInterfaceAndCInterface(); }).not.toThrow(); expect(obj.constructor.implements(AnInterface)).toBeTruthy(); expect(obj.constructor.implements(Gio.Initable)).toBeTruthy(); }); it('is an instance of the interface classes', function () { expect(AGObjectInterface instanceof Lang.Interface).toBeTruthy(); expect(AGObjectInterface instanceof GObject.Interface).toBeTruthy(); }); it('cannot be instantiated', function () { expect(() => new AGObjectInterface()).toThrow(); }); it('has a name', function () { expect(AGObjectInterface.name).toEqual('AGObjectInterface'); }); it('reports its type name', function () { expect(AGObjectInterface.$gtype.name).toEqual('ArbitraryGTypeName'); }); it('can be implemented by a GObject class', function () { let obj; expect(() => { obj = new GObjectImplementingGObjectInterface(); }).not.toThrow(); expect(obj.constructor.implements(AGObjectInterface)).toBeTruthy(); }); it('is implemented by a GObject class with the correct class object', function () { let obj = new GObjectImplementingGObjectInterface(); expect(obj.constructor).toEqual(GObjectImplementingGObjectInterface); expect(obj.constructor.name) .toEqual('GObjectImplementingGObjectInterface'); }); it('can be implemented by a class also implementing a Lang.Interface', function () { const GObjectImplementingBothKindsOfInterface = new Lang.Class({ Name: 'GObjectImplementingBothKindsOfInterface', Extends: GObject.Object, Implements: [AnInterface, AGObjectInterface], Properties: { 'interface-prop': GObject.ParamSpec.override('interface-prop', AGObjectInterface), }, required() {}, requiredG() {}, }); let obj; expect(() => { obj = new GObjectImplementingBothKindsOfInterface(); }).not.toThrow(); expect(obj.constructor.implements(AnInterface)).toBeTruthy(); expect(obj.constructor.implements(AGObjectInterface)).toBeTruthy(); }); it('can have its required function implemented', function () { expect(() => { let obj = new GObjectImplementingGObjectInterface(); obj.requiredG(); }).not.toThrow(); }); it('must have its required function implemented', function () { expect(() => new Lang.Class({ Name: 'BadObject', Extends: GObject.Object, Implements: [AGObjectInterface], Properties: { 'interface-prop': GObject.ParamSpec.override('interface-prop', AGObjectInterface), }, })).toThrow(); }); it("doesn't have to have its optional function implemented", function () { let obj; expect(() => { obj = new MinimalImplementationOfAGObjectInterface(); }).not.toThrow(); expect(obj.constructor.implements(AGObjectInterface)).toBeTruthy(); }); it('can have its optional function deferred to by the implementation', function () { let obj = new MinimalImplementationOfAGObjectInterface(); expect(obj.optionalG()).toEqual('AGObjectInterface.optionalG()'); }); it('can have its function chained up to', function () { let obj = new GObjectImplementingGObjectInterface(); expect(obj.optionalG()).toEqual('AGObjectInterface.optionalG()'); }); it('can require another interface', function () { let obj; expect(() => { obj = new ImplementationOfTwoInterfaces(); }).not.toThrow(); expect(obj.constructor.implements(AGObjectInterface)).toBeTruthy(); expect(obj.constructor.implements(InterfaceRequiringGObjectInterface)) .toBeTruthy(); }); it('can chain up to another interface', function () { let obj = new ImplementationOfTwoInterfaces(); expect(obj.optionalG()) .toEqual('InterfaceRequiringGObjectInterface.optionalG()\nAGObjectInterface.optionalG()'); }); it("defers to the last interface's optional function", function () { const MinimalImplementationOfTwoInterfaces = new Lang.Class({ Name: 'MinimalImplementationOfTwoInterfaces', Extends: GObject.Object, Implements: [AGObjectInterface, InterfaceRequiringGObjectInterface], Properties: { 'interface-prop': GObject.ParamSpec.override('interface-prop', AGObjectInterface), }, requiredG() {}, }); let obj = new MinimalImplementationOfTwoInterfaces(); expect(obj.optionalG()) .toEqual('InterfaceRequiringGObjectInterface.optionalG()\nAGObjectInterface.optionalG()'); }); it('must be implemented by a class that implements all required interfaces', function () { expect(() => new Lang.Class({ Name: 'BadObject', Implements: [InterfaceRequiringGObjectInterface], required() {}, })).toThrow(); }); it('must be implemented by a class that implements required interfaces in correct order', function () { expect(() => new Lang.Class({ Name: 'BadObject', Implements: [InterfaceRequiringGObjectInterface, AGObjectInterface], required() {}, })).toThrow(); }); it('can require an interface from C', function () { const InitableInterface = new Lang.Interface({ Name: 'InitableInterface', Requires: [GObject.Object, Gio.Initable], }); expect(() => new Lang.Class({ Name: 'BadObject', Implements: [InitableInterface], })).toThrow(); }); it('can define signals on the implementing class', function () { function quitLoop() { Mainloop.quit('signal'); } let obj = new GObjectImplementingGObjectInterface(); let interfaceSignalSpy = jasmine.createSpy('interfaceSignalSpy') .and.callFake(quitLoop); let classSignalSpy = jasmine.createSpy('classSignalSpy') .and.callFake(quitLoop); obj.connect('interface-signal', interfaceSignalSpy); obj.connect('class-signal', classSignalSpy); GLib.idle_add(GLib.PRIORITY_DEFAULT, () => { obj.emit('interface-signal'); return GLib.SOURCE_REMOVE; }); Mainloop.run('signal'); GLib.idle_add(GLib.PRIORITY_DEFAULT, () => { obj.emit('class-signal'); return GLib.SOURCE_REMOVE; }); Mainloop.run('signal'); expect(interfaceSignalSpy).toHaveBeenCalled(); expect(classSignalSpy).toHaveBeenCalled(); }); it('can define properties on the implementing class', function () { let obj = new GObjectImplementingGObjectInterface(); expect(obj.interface_prop).toEqual('foobar'); expect(obj.class_prop).toEqual('meh'); }); it('must have its properties overridden', function () { // Failing to override an interface property doesn't raise an error but // instead logs a critical warning. GLib.test_expect_message('GLib-GObject', GLib.LogLevelFlags.LEVEL_CRITICAL, "Object class * doesn't implement property 'interface-prop' from " + "interface 'ArbitraryGTypeName'"); new Lang.Class({ Name: 'MyNaughtyObject', Extends: GObject.Object, Implements: [AGObjectInterface], requiredG() {}, }); // g_test_assert_expected_messages() is a macro, not introspectable GLib.test_assert_expected_messages_internal('Gjs', 'testGObjectInterface.js', 416, 'testGObjectMustOverrideInterfaceProperties'); }); // This makes sure that we catch the case where the metaclass (e.g. // GtkWidgetClass) doesn't specify a meta-interface. In that case we get the // meta-interface from the metaclass's parent. it('gets the correct type for its metaclass', function () { const MyMeta = new Lang.Class({ Name: 'MyMeta', Extends: GObject.Class, }); const MyMetaObject = new MyMeta({ Name: 'MyMetaObject', }); const MyMetaInterface = new Lang.Interface({ Name: 'MyMetaInterface', Requires: [MyMetaObject], }); expect(MyMetaInterface instanceof GObject.Interface).toBeTruthy(); }); it('can be implemented by a class as well as its parent class', function () { const SubObject = new Lang.Class({ Name: 'SubObject', Extends: GObjectImplementingGObjectInterface, }); let obj = new SubObject(); expect(obj.constructor.implements(AGObjectInterface)).toBeTruthy(); expect(obj.interface_prop).toEqual('foobar'); // override not needed }); it('can be reimplemented by a subclass of a class that already implements it', function () { const SubImplementer = new Lang.Class({ Name: 'SubImplementer', Extends: GObjectImplementingGObjectInterface, Implements: [AGObjectInterface], }); let obj = new SubImplementer(); expect(obj.constructor.implements(AGObjectInterface)).toBeTruthy(); expect(obj.interface_prop).toEqual('foobar'); // override not needed }); }); const LegacyInterface1 = new Lang.Interface({ Name: 'LegacyInterface1', Requires: [GObject.Object], Signals: {'legacy-iface1-signal': {}}, }); const LegacyInterface2 = new Lang.Interface({ Name: 'LegacyInterface2', Requires: [GObject.Object], Signals: {'legacy-iface2-signal': {}}, }); const Legacy = new Lang.Class({ Name: 'Legacy', Extends: GObject.Object, Implements: [LegacyInterface1], Properties: { 'property': GObject.ParamSpec.int('property', 'Property', 'A magic property', GObject.ParamFlags.READWRITE, 0, 100, 0), 'override-property': GObject.ParamSpec.int('override-property', 'Override property', 'Another magic property', GObject.ParamFlags.READWRITE, 0, 100, 0), }, Signals: { 'signal': {}, }, _init(someval) { this.constructorCalledWith = someval; this.parent(); }, instanceMethod() {}, chainUpToMe() {}, overrideMe() {}, get property() { return this._property + 1; }, set property(value) { this._property = value - 2; }, get overrideProperty() { return this._overrideProperty + 1; }, set overrideProperty(value) { this._overrideProperty = value - 2; }, }); Legacy.staticMethod = function () {}; const Shiny = GObject.registerClass({ Implements: [LegacyInterface2], Properties: { 'override-property': GObject.ParamSpec.override('override-property', Legacy), }, }, class Shiny extends Legacy { chainUpToMe() { super.chainUpToMe(); } overrideMe() {} get overrideProperty() { return this._overrideProperty + 2; } set overrideProperty(value) { this._overrideProperty = value - 1; } }); describe('ES6 GObject class inheriting from GObject.Class', function () { let instance; beforeEach(function () { spyOn(Legacy, 'staticMethod'); spyOn(Legacy.prototype, 'instanceMethod'); spyOn(Legacy.prototype, 'chainUpToMe'); spyOn(Legacy.prototype, 'overrideMe'); instance = new Shiny(); }); it('calls a static method on the parent class', function () { Shiny.staticMethod(); expect(Legacy.staticMethod).toHaveBeenCalled(); }); it('calls a method on the parent class', function () { instance.instanceMethod(); expect(Legacy.prototype.instanceMethod).toHaveBeenCalled(); }); it("passes arguments to the parent class's constructor", function () { instance = new Shiny(42); expect(instance.constructorCalledWith).toEqual(42); }); it('chains up to a method on the parent class', function () { instance.chainUpToMe(); expect(Legacy.prototype.chainUpToMe).toHaveBeenCalled(); }); it('overrides a method on the parent class', function () { instance.overrideMe(); expect(Legacy.prototype.overrideMe).not.toHaveBeenCalled(); }); it('sets and gets a property from the parent class', function () { instance.property = 42; expect(instance.property).toEqual(41); }); it('overrides a property from the parent class', function () { instance.overrideProperty = 42; expect(instance.overrideProperty).toEqual(43); }); it('inherits a signal from the parent class', function () { let signalSpy = jasmine.createSpy('signalSpy'); expect(() => { instance.connect('signal', signalSpy); instance.emit('signal'); }).not.toThrow(); expect(signalSpy).toHaveBeenCalled(); }); it('inherits legacy interfaces from the parent', function () { expect(() => instance.emit('legacy-iface1-signal')).not.toThrow(); expect(instance instanceof LegacyInterface1).toBeTruthy(); }); it('can implement a legacy interface itself', function () { expect(() => instance.emit('legacy-iface2-signal')).not.toThrow(); expect(instance instanceof LegacyInterface2).toBeTruthy(); }); }); cjs-5.2.0/installed-tests/js/testGio.js0000644000175000017500000001322414144444702020150 0ustar jpeisachjpeisachconst {GLib, Gio, GObject} = imports.gi; const Foo = GObject.registerClass({ Properties: { boolval: GObject.ParamSpec.boolean('boolval', '', '', GObject.ParamFlags.READWRITE, false), }, }, class Foo extends GObject.Object { _init(value) { super._init(); this.value = value; } }); describe('ListStore iterator', function () { let list; beforeEach(function () { list = new Gio.ListStore({item_type: Foo}); for (let i = 0; i < 100; i++) list.append(new Foo(i)); }); it('ListStore iterates', function () { let i = 0; for (let f of list) expect(f.value).toBe(i++); }); }); describe('Gio.Settings overrides', function () { it("doesn't crash when forgetting to specify a schema ID", function () { expect(() => new Gio.Settings()).toThrowError(/schema/); }); it("doesn't crash when specifying a schema ID that isn't installed", function () { expect(() => new Gio.Settings({schema: 'com.example.ThisDoesntExist'})) .toThrowError(/schema/); }); it("doesn't crash when forgetting to specify a schema path", function () { expect(() => new Gio.Settings({schema: 'org.cinnamon.CjsTest.Sub'})) .toThrowError(/schema/); }); it("doesn't crash when specifying conflicting schema paths", function () { expect(() => new Gio.Settings({ schema: 'org.cinnamon.CjsTest', path: '/conflicting/path/', })).toThrowError(/schema/); }); describe('with existing schema', function () { const KINDS = ['boolean', 'double', 'enum', 'flags', 'int', 'int64', 'string', 'strv', 'uint', 'uint64', 'value']; let settings; beforeEach(function () { settings = new Gio.Settings({schema: 'org.cinnamon.CjsTest'}); }); it("doesn't crash when resetting a nonexistent key", function () { expect(() => settings.reset('foobar')).toThrowError(/key/); }); it("doesn't crash when checking a nonexistent key", function () { KINDS.forEach(kind => { expect(() => settings[`get_${kind}`]('foobar')).toThrowError(/key/); }); }); it("doesn't crash when setting a nonexistent key", function () { KINDS.forEach(kind => { expect(() => settings[`set_${kind}`]('foobar', null)).toThrowError(/key/); }); }); it("doesn't crash when checking writable for a nonexistent key", function () { expect(() => settings.is_writable('foobar')).toThrowError(/key/); }); it("doesn't crash when getting the user value for a nonexistent key", function () { expect(() => settings.get_user_value('foobar')).toThrowError(/key/); }); it("doesn't crash when getting the default value for a nonexistent key", function () { expect(() => settings.get_default_value('foobar')).toThrowError(/key/); }); it("doesn't crash when binding a nonexistent key", function () { const foo = new Foo(); expect(() => settings.bind('foobar', foo, 'boolval', Gio.SettingsBindFlags.GET)) .toThrowError(/key/); expect(() => settings.bind_writable('foobar', foo, 'boolval', false)) .toThrowError(/key/); }); it("doesn't crash when creating actions for a nonexistent key", function () { expect(() => settings.create_action('foobar')).toThrowError(/key/); }); it("doesn't crash when checking info about a nonexistent key", function () { expect(() => settings.settings_schema.get_key('foobar')).toThrowError(/key/); }); it("doesn't crash when getting a nonexistent sub-schema", function () { expect(() => settings.get_child('foobar')).toThrowError(/foobar/); }); it('still works with correct keys', function () { const KEYS = ['window-size', 'maximized', 'fullscreen']; KEYS.forEach(key => expect(settings.is_writable(key)).toBeTruthy()); expect(() => { settings.set_value('window-size', new GLib.Variant('(ii)', [100, 100])); settings.set_boolean('maximized', true); settings.set_boolean('fullscreen', true); }).not.toThrow(); expect(settings.get_value('window-size').deepUnpack()).toEqual([100, 100]); expect(settings.get_boolean('maximized')).toEqual(true); expect(settings.get_boolean('fullscreen')).toEqual(true); expect(() => { KEYS.forEach(key => settings.reset(key)); }).not.toThrow(); KEYS.forEach(key => expect(settings.get_user_value(key)).toBeNull()); expect(settings.get_default_value('window-size').deepUnpack()).toEqual([-1, -1]); expect(settings.get_default_value('maximized').deepUnpack()).toEqual(false); expect(settings.get_default_value('fullscreen').deepUnpack()).toEqual(false); const foo = new Foo({boolval: true}); settings.bind('maximized', foo, 'boolval', Gio.SettingsBindFlags.GET); expect(foo.boolval).toBeFalsy(); Gio.Settings.unbind(foo, 'boolval'); settings.bind_writable('maximized', foo, 'boolval', false); expect(foo.boolval).toBeTruthy(); expect(settings.create_action('maximized')).not.toBeNull(); expect(settings.settings_schema.get_key('fullscreen')).not.toBeNull(); const sub = settings.get_child('sub'); expect(sub.get_uint('marine')).toEqual(10); }); }); }); cjs-5.2.0/installed-tests/debugger/0000755000175000017500000000000014144444702017342 5ustar jpeisachjpeisachcjs-5.2.0/installed-tests/debugger/continue.debugger0000644000175000017500000000002014144444702022664 0ustar jpeisachjpeisachcontinue cont c cjs-5.2.0/installed-tests/debugger/step.debugger0000644000175000017500000000003014144444702022014 0ustar jpeisachjpeisachs s s s s s s s s s s s cjs-5.2.0/installed-tests/debugger/next.debugger.js0000644000175000017500000000016414144444702022442 0ustar jpeisachjpeisachfunction a() { debugger; b(); print('A line in a'); } function b() { print('A line in b'); } a(); cjs-5.2.0/installed-tests/debugger/step.debugger.output0000644000175000017500000000132314144444702023361 0ustar jpeisachjpeisachGJS debugger. Type "help" for help db> s toplevel at step.debugger.js:10:0 entered frame: a() at step.debugger.js:2:4 db> s a() at step.debugger.js:2:4 entered frame: b() at step.debugger.js:7:4 db> s b() at step.debugger.js:7:4 A line in b db> s b() at step.debugger.js:8:0 No value returned. db> s b() at step.debugger.js:8:0 a() at step.debugger.js:2:4 db> s a() at step.debugger.js:2:4 db> s a() at step.debugger.js:3:4 A line in a db> s a() at step.debugger.js:4:0 No value returned. db> s a() at step.debugger.js:4:0 toplevel at step.debugger.js:10:0 db> s toplevel at step.debugger.js:10:0 db> s toplevel at step.debugger.js:11:0 No value returned. db> s toplevel at step.debugger.js:11:0 Program exited with code 0 cjs-5.2.0/installed-tests/debugger/frame.debugger.js0000644000175000017500000000010014144444702022544 0ustar jpeisachjpeisachfunction a() { b(); } function b() { debugger; } a(); cjs-5.2.0/installed-tests/debugger/print.debugger.output0000644000175000017500000000231114144444702023540 0ustar jpeisachjpeisachGJS debugger. Type "help" for help db> c Debugger statement, toplevel at print.debugger.js:15:0 db> # Simple types db> print a $1 = undefined db> p b $2 = null db> p c $3 = 42 db> p d $4 = "some string" db> p e $5 = false db> p f $6 = true db> p g $7 = Symbol("foobar") db> # Objects db> print h $8 = [object Array] [ 1, "money", 2, "show", { "three": "to", "get ready": "go cat go" } ] db> print/b h $9 = [object Array] { "0": 1, "1": "money", "2": 2, "3": "show", "4": "(...)", "length": 5 } db> print/p h $10 = [object Array] [ 1, "money", 2, "show", { "three": "to", "get ready": "go cat go" } ] db> p i $11 = [object Object] { "some": "plain object", "that": "has keys" } db> p/b i $12 = [object Object] { "some": "plain object", "that": "has keys" } db> p j $13 = [object Set] {} db> p k $14 = [object Function] db> p/b k $15 = [object Function] { "prototype": "(...)", "length": 0, "name": "J" } db> p l $16 = [object GObject_Object] [object instance wrapper GIName:GObject.Object jsobj@0xADDR native@0xADDR] db> p m $17 = [object Error] Error: message db> c Program exited with code 0 cjs-5.2.0/installed-tests/debugger/until.debugger0000644000175000017500000000002514144444702022200 0ustar jpeisachjpeisachuntil 3 upto 5 u 7 c cjs-5.2.0/installed-tests/debugger/set.debugger0000644000175000017500000000042414144444702021643 0ustar jpeisachjpeisach# Currently the only option is "pretty" for pretty-printing. Set doesn't yet # allow setting variables in the program. c p a set pretty 0 p a set pretty 1 p a set pretty off p a set pretty on p a set pretty false p a set pretty true p a set pretty no p a set pretty yes p a q cjs-5.2.0/installed-tests/debugger/continue.debugger.output0000644000175000017500000000031414144444702024231 0ustar jpeisachjpeisachGJS debugger. Type "help" for help db> continue Debugger statement, toplevel at continue.debugger.js:1:0 db> cont Debugger statement, toplevel at continue.debugger.js:2:0 db> c Program exited with code 0 cjs-5.2.0/installed-tests/debugger/down-up.debugger.output0000644000175000017500000000112014144444702023772 0ustar jpeisachjpeisachGJS debugger. Type "help" for help db> c Debugger statement, d() at down-up.debugger.js:14:4 db> down Youngest frame selected; you cannot go down. db> up #1 c() at down-up.debugger.js:10:4 db> up #2 b() at down-up.debugger.js:6:4 db> up #3 a() at down-up.debugger.js:2:4 db> up #4 toplevel at down-up.debugger.js:17:0 db> up Initial frame selected; you cannot go up. db> down #3 a() at down-up.debugger.js:2:4 db> dn #2 b() at down-up.debugger.js:6:4 db> dn #1 c() at down-up.debugger.js:10:4 db> dn #0 d() at down-up.debugger.js:14:4 db> c Program exited with code 0 cjs-5.2.0/installed-tests/debugger/set.debugger.js0000644000175000017500000000004014144444702022250 0ustar jpeisachjpeisachconst a = {}; debugger; void a; cjs-5.2.0/installed-tests/debugger/breakpoint.debugger0000644000175000017500000000004114144444702023201 0ustar jpeisachjpeisachbreakpoint 2 break 4 b 6 c c c c cjs-5.2.0/installed-tests/debugger/breakpoint.debugger.output0000644000175000017500000000065314144444702024551 0ustar jpeisachjpeisachGJS debugger. Type "help" for help db> breakpoint 2 Breakpoint 1 at breakpoint.debugger.js:2:0 db> break 4 Breakpoint 2 at breakpoint.debugger.js:4:4 db> b 6 Breakpoint 3 at breakpoint.debugger.js:6:0 db> c 1 Breakpoint 1, toplevel at breakpoint.debugger.js:2:0 db> c 2 Breakpoint 3, toplevel at breakpoint.debugger.js:6:0 db> c 3 Breakpoint 2, foo() at breakpoint.debugger.js:4:4 db> c Function foo Program exited with code 0 cjs-5.2.0/installed-tests/debugger/return.debugger0000644000175000017500000000010014144444702022356 0ustar jpeisachjpeisachb 2 b 6 b 10 c return ret 5 ret `${4 * 10 + 2} is the answer` c cjs-5.2.0/installed-tests/debugger/breakpoint.debugger.js0000644000175000017500000000013114144444702023614 0ustar jpeisachjpeisachprint('1'); print('2'); function foo() { print('Function foo'); } print('3'); foo(); cjs-5.2.0/installed-tests/debugger/throw.debugger0000644000175000017500000000004314144444702022210 0ustar jpeisachjpeisachc throw 'foobar' + 3.14; fin throw cjs-5.2.0/installed-tests/debugger/continue.debugger.js0000644000175000017500000000002414144444702023303 0ustar jpeisachjpeisachdebugger; debugger; cjs-5.2.0/installed-tests/debugger/down-up.debugger0000644000175000017500000000004614144444702022441 0ustar jpeisachjpeisachc down up up up up up down dn dn dn c cjs-5.2.0/installed-tests/debugger/throw.debugger.js0000644000175000017500000000015314144444702022625 0ustar jpeisachjpeisachfunction a() { debugger; return 5; } try { a(); } catch (e) { print(`Exception: ${e}`); } cjs-5.2.0/installed-tests/debugger/until.debugger.js0000644000175000017500000000012514144444702022614 0ustar jpeisachjpeisachprint('1'); print('2'); print('3'); (function () { print('4'); })(); print('5'); cjs-5.2.0/installed-tests/debugger/keys.debugger.output0000644000175000017500000000040414144444702023360 0ustar jpeisachjpeisachGJS debugger. Type "help" for help db> c Debugger statement, toplevel at keys.debugger.js:6:0 db> keys a $1 = [object Array] [ "foo", "bar", "tres" ] db> k a $2 = [object Array] [ "foo", "bar", "tres" ] db> c Program exited with code 0 cjs-5.2.0/installed-tests/debugger/print.debugger.js0000644000175000017500000000072314144444702022621 0ustar jpeisachjpeisachconst {GObject} = imports.gi; const a = undefined; const b = null; const c = 42; const d = 'some string'; const e = false; const f = true; const g = Symbol('foobar'); const h = [1, 'money', 2, 'show', {three: 'to', 'get ready': 'go cat go'}]; const i = {some: 'plain object', that: 'has keys'}; const j = new Set([5, 6, 7]); const k = class J {}; const l = new GObject.Object(); const m = new Error('message'); debugger; void (a, b, c, d, e, f, g, h, i, j, k, l, m); cjs-5.2.0/installed-tests/debugger/backtrace.debugger.js0000644000175000017500000000023414144444702023401 0ustar jpeisachjpeisachdebugger; [[1, 2, 3, 4, 5], [6, 7, 8, 9, 10]].forEach(array => { debugger; array.forEach(num => { debugger; print(num); }); }); cjs-5.2.0/installed-tests/debugger/quit.debugger.js0000644000175000017500000000001514144444702022441 0ustar jpeisachjpeisachprint('hi'); cjs-5.2.0/installed-tests/debugger/print.debugger0000644000175000017500000000017114144444702022203 0ustar jpeisachjpeisachc # Simple types print a p b p c p d p e p f p g # Objects print h print/b h print/p h p i p/b i p j p k p/b k p l p m c cjs-5.2.0/installed-tests/debugger/frame.debugger.output0000644000175000017500000000033214144444702023477 0ustar jpeisachjpeisachGJS debugger. Type "help" for help db> c Debugger statement, b() at frame.debugger.js:6:4 db> frame 2 #2 toplevel at frame.debugger.js:9:0 db> f 1 #1 a() at frame.debugger.js:2:4 db> c Program exited with code 0 cjs-5.2.0/installed-tests/debugger/detach.debugger.output0000644000175000017500000000011414144444702023633 0ustar jpeisachjpeisachGJS debugger. Type "help" for help db> detach hi Program exited with code 0 cjs-5.2.0/installed-tests/debugger/quit.debugger.output0000644000175000017500000000010414144444702023364 0ustar jpeisachjpeisachGJS debugger. Type "help" for help db> q Program exited with code 0 cjs-5.2.0/installed-tests/debugger/step.debugger.js0000644000175000017500000000014614144444702022437 0ustar jpeisachjpeisachfunction a() { b(); print('A line in a'); } function b() { print('A line in b'); } a(); cjs-5.2.0/installed-tests/debugger/finish.debugger0000644000175000017500000000002114144444702022321 0ustar jpeisachjpeisachc finish c fin c cjs-5.2.0/installed-tests/debugger/keys.debugger0000644000175000017500000000001714144444702022021 0ustar jpeisachjpeisachc keys a k a c cjs-5.2.0/installed-tests/debugger/finish.debugger.js0000644000175000017500000000034414144444702022744 0ustar jpeisachjpeisachfunction foo() { print('Print me'); debugger; print('Print me also'); } function bar() { print('Print me'); debugger; print('Print me also'); return 5; } foo(); bar(); print('Print me at the end'); cjs-5.2.0/installed-tests/debugger/return.debugger.output0000644000175000017500000000067414144444702023735 0ustar jpeisachjpeisachGJS debugger. Type "help" for help db> b 2 Breakpoint 1 at return.debugger.js:2:4 db> b 6 Breakpoint 2 at return.debugger.js:6:4 db> b 10 Breakpoint 3 at return.debugger.js:10:4 db> c Breakpoint 1, func1() at return.debugger.js:2:4 db> return undefined Breakpoint 2, func2() at return.debugger.js:6:4 db> ret 5 5 Breakpoint 3, func3() at return.debugger.js:10:4 db> ret `${4 * 10 + 2} is the answer` 42 is the answer Program exited with code 0 cjs-5.2.0/installed-tests/debugger/down-up.debugger.js0000644000175000017500000000016614144444702023057 0ustar jpeisachjpeisachfunction a() { b(); } function b() { c(); } function c() { d(); } function d() { debugger; } a(); cjs-5.2.0/installed-tests/debugger/.eslintrc.yml0000644000175000017500000000003414144444702021763 0ustar jpeisachjpeisachrules: no-debugger: 'off' cjs-5.2.0/installed-tests/debugger/next.debugger0000644000175000017500000000002514144444702022023 0ustar jpeisachjpeisachc next n n n n n n n cjs-5.2.0/installed-tests/debugger/backtrace.debugger0000644000175000017500000000003114144444702022761 0ustar jpeisachjpeisachbacktrace c bt c where q cjs-5.2.0/installed-tests/debugger/delete.debugger.output0000644000175000017500000000105414144444702023651 0ustar jpeisachjpeisachGJS debugger. Type "help" for help db> b 2 Breakpoint 1 at delete.debugger.js:2:0 db> b 3 Breakpoint 2 at delete.debugger.js:3:0 db> b 4 Breakpoint 3 at delete.debugger.js:4:0 db> b 5 Breakpoint 4 at delete.debugger.js:5:0 db> # Check that breakpoint 4 still remains after deleting 1-3 db> delete 1 Breakpoint 1 at delete.debugger.js:2:0 deleted db> del 2 Breakpoint 2 at delete.debugger.js:3:0 deleted db> d 3 Breakpoint 3 at delete.debugger.js:4:0 deleted db> c 1 2 3 4 Breakpoint 4, toplevel at delete.debugger.js:5:0 db> c 5 Program exited with code 0 cjs-5.2.0/installed-tests/debugger/frame.debugger0000644000175000017500000000002014144444702022132 0ustar jpeisachjpeisachc frame 2 f 1 c cjs-5.2.0/installed-tests/debugger/throw.debugger.output0000644000175000017500000000111314144444702023546 0ustar jpeisachjpeisachGJS debugger. Type "help" for help db> c Debugger statement, a() at throw.debugger.js:2:4 db> throw 'foobar' + 3.14; Unwinding due to exception. (Type 'c' to continue unwinding.) #0 a() at throw.debugger.js:2:4 Exception value is: $1 = "foobar3.14" db> fin Run till exit from a() at throw.debugger.js:2:4 Frame terminated by exception: $2 = "foobar3.14" (To rethrow it, type 'throw'.) Unwinding due to exception. (Type 'c' to continue unwinding.) #0 toplevel at throw.debugger.js:7:4 Exception value is: $3 = "foobar3.14" db> throw Exception: foobar3.14 Program exited with code 0 cjs-5.2.0/installed-tests/debugger/finish.debugger.output0000644000175000017500000000075014144444702023671 0ustar jpeisachjpeisachGJS debugger. Type "help" for help db> c Print me Debugger statement, foo() at finish.debugger.js:3:4 db> finish Run till exit from foo() at finish.debugger.js:3:4 Print me also No value returned. toplevel at finish.debugger.js:14:0 db> c Print me Debugger statement, bar() at finish.debugger.js:9:4 db> fin Run till exit from bar() at finish.debugger.js:9:4 Print me also Value returned is: $1 = 5 toplevel at finish.debugger.js:15:0 db> c Print me at the end Program exited with code 0 cjs-5.2.0/installed-tests/debugger/delete.debugger0000644000175000017500000000014214144444702022307 0ustar jpeisachjpeisachb 2 b 3 b 4 b 5 # Check that breakpoint 4 still remains after deleting 1-3 delete 1 del 2 d 3 c c cjs-5.2.0/installed-tests/debugger/return.debugger.js0000644000175000017500000000023414144444702023001 0ustar jpeisachjpeisachfunction func1() { return 1; } function func2() { return 2; } function func3() { return 3; } print(func1()); print(func2()); print(func3()); cjs-5.2.0/installed-tests/debugger/keys.debugger.js0000644000175000017500000000012114144444702022430 0ustar jpeisachjpeisachconst a = { foo: 1, bar: null, tres: undefined, }; debugger; void a; cjs-5.2.0/installed-tests/debugger/set.debugger.output0000644000175000017500000000124614144444702023205 0ustar jpeisachjpeisachGJS debugger. Type "help" for help db> # Currently the only option is "pretty" for pretty-printing. Set doesn't yet db> # allow setting variables in the program. db> c Debugger statement, toplevel at set.debugger.js:2:0 db> p a $1 = [object Object] {} db> set pretty 0 db> p a $2 = [object Object] db> set pretty 1 db> p a $3 = [object Object] {} db> set pretty off db> p a $4 = [object Object] db> set pretty on db> p a $5 = [object Object] {} db> set pretty false db> p a $6 = [object Object] db> set pretty true db> p a $7 = [object Object] {} db> set pretty no db> p a $8 = [object Object] db> set pretty yes db> p a $9 = [object Object] {} db> q Program exited with code 0 cjs-5.2.0/installed-tests/debugger/next.debugger.output0000644000175000017500000000077114144444702023372 0ustar jpeisachjpeisachGJS debugger. Type "help" for help db> c Debugger statement, a() at next.debugger.js:2:4 db> next a() at next.debugger.js:2:4 db> n a() at next.debugger.js:3:4 A line in b db> n a() at next.debugger.js:4:4 A line in a db> n a() at next.debugger.js:5:0 No value returned. db> n a() at next.debugger.js:5:0 toplevel at next.debugger.js:11:0 db> n toplevel at next.debugger.js:11:0 db> n toplevel at next.debugger.js:12:0 No value returned. db> n toplevel at next.debugger.js:12:0 Program exited with code 0 cjs-5.2.0/installed-tests/debugger/until.debugger.output0000644000175000017500000000050314144444702023540 0ustar jpeisachjpeisachGJS debugger. Type "help" for help db> until 3 toplevel at until.debugger.js:1:0 1 2 db> upto 5 toplevel at until.debugger.js:3:0 3 entered frame: () at until.debugger.js:5:4 db> u 7 () at until.debugger.js:5:4 4 No value returned. toplevel at until.debugger.js:7:0 db> c 5 Program exited with code 0 cjs-5.2.0/installed-tests/debugger/delete.debugger.js0000644000175000017500000000007414144444702022726 0ustar jpeisachjpeisachprint('1'); print('2'); print('3'); print('4'); print('5'); cjs-5.2.0/installed-tests/debugger/quit.debugger0000644000175000017500000000000214144444702022022 0ustar jpeisachjpeisachq cjs-5.2.0/installed-tests/debugger/backtrace.debugger.output0000644000175000017500000000074014144444702024327 0ustar jpeisachjpeisachGJS debugger. Type "help" for help db> backtrace #0 toplevel at backtrace.debugger.js:1:0 db> c Debugger statement, toplevel at backtrace.debugger.js:1:0 db> bt #0 toplevel at backtrace.debugger.js:1:0 db> c Debugger statement, ([object Array], 0, [object Array]) at backtrace.debugger.js:3:4 db> where #0 ([object Array], 0, [object Array]) at backtrace.debugger.js:3:4 #1 toplevel at backtrace.debugger.js:2:36 db> q Program exited with code 0 cjs-5.2.0/installed-tests/debugger/detach.debugger0000644000175000017500000000000714144444702022275 0ustar jpeisachjpeisachdetach cjs-5.2.0/installed-tests/debugger/detach.debugger.js0000644000175000017500000000001514144444702022707 0ustar jpeisachjpeisachprint('hi'); cjs-5.2.0/installed-tests/script.test.in0000644000175000017500000000012014144444702020361 0ustar jpeisachjpeisach[Test] Type=session Exec=sh @installed_tests_execdir@/scripts/@name@ Output=TAP cjs-5.2.0/installed-tests/meson.build0000644000175000017500000000524014144444702017721 0ustar jpeisachjpeisach### Installed tests ############################################################ installed_tests_execdir = get_option('prefix') / get_option('libexecdir') / 'installed-tests' / meson.project_name() if get_option('installed_tests') installed_tests_metadir = abs_datadir / 'installed-tests' / meson.project_name() else installed_tests_metadir = '' endif # Simple shell script tests # simple_tests = [] # The test scripts need to be ported from shell scripts # for clang-cl builds, which do not use BASH-style shells if cxx.get_argument_syntax() != 'msvc' simple_tests += [ 'CommandLine', 'Warnings', ] endif foreach test : simple_tests test_file = files('scripts' / 'test@0@.sh'.format(test)) test(test, test_file, env: tests_environment, protocol: 'tap', suite: 'Scripts') test_description_subst = { 'name': 'test@0@.sh'.format(test), 'installed_tests_execdir': installed_tests_execdir, } test_description = configure_file(configuration: test_description_subst, input: 'script.test.in', output: 'test@0@.sh.test'.format(test), install_dir: installed_tests_metadir) if get_option('installed_tests') install_data(test_file, install_dir: installed_tests_execdir / 'scripts') endif endforeach # Jasmine tests # subdir('js') # Debugger script tests # debugger_tests = [ 'backtrace', 'breakpoint', 'continue', 'delete', 'detach', 'down-up', 'finish', 'frame', 'keys', 'next', 'print', 'quit', 'return', 'set', 'step', 'throw', 'until', ] debugger_test_driver = find_program(files('debugger-test.sh')) if get_option('installed_tests') install_data('debugger-test.sh', install_dir: installed_tests_execdir) endif foreach test : debugger_tests test_file = files('debugger' / '@0@.debugger'.format(test)) test('@0@ command'.format(test), debugger_test_driver, args: test_file, env: tests_environment, protocol: 'tap', suite: 'Debugger') test_description_subst = { 'name': '@0@.debugger'.format(test), 'installed_tests_execdir': installed_tests_execdir, } test_description = configure_file(configuration: test_description_subst, input: 'debugger.test.in', output: '@0@.test'.format(test), install_dir: installed_tests_metadir) if get_option('installed_tests') install_data(test_file, install_dir: installed_tests_execdir / 'debugger') install_data('debugger' / '@0@.debugger.js'.format(test), 'debugger' / '@0@.debugger.output'.format(test), install_dir: installed_tests_execdir / 'debugger') endif endforeach cjs-5.2.0/libgjs.map0000644000175000017500000000004614144444702014410 0ustar jpeisachjpeisach{ global: gjs_*; local: *; }; cjs-5.2.0/CPPLINT.cfg0000644000175000017500000000104614144444702014232 0ustar jpeisachjpeisach# This is the toplevel CPPLINT.cfg file set noparent # We give a limit to clang-format of 80, but we allow 100 here for cases where # it really is more readable to have a longer line linelength=100 # Exceptions to Google style # - build/include_order: We have a special order for include files, see "Header # inclusion order" in CPP_Style_Guide.md. # - build/c++11: This rule bans certain C++ standard library features, which # have their own alternatives in the Chromium codebase, doesn't apply to us. filter=-build/include_order,-build/c++11 cjs-5.2.0/util/0000755000175000017500000000000014144444702013414 5ustar jpeisachjpeisachcjs-5.2.0/util/log.h0000644000175000017500000001321114144444702014344 0ustar jpeisachjpeisach/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* * Copyright (c) 2008 litl, LLC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #ifndef UTIL_LOG_H_ #define UTIL_LOG_H_ #include /* The idea of this is to be able to have one big log file for the entire * environment, and grep out what you care about. So each module or app * should have its own entry in the enum. Be sure to add new enum entries * to the switch in log.c */ typedef enum { GJS_DEBUG_GI_USAGE, GJS_DEBUG_MEMORY, GJS_DEBUG_CONTEXT, GJS_DEBUG_IMPORTER, GJS_DEBUG_NATIVE, GJS_DEBUG_KEEP_ALIVE, GJS_DEBUG_GREPO, GJS_DEBUG_GNAMESPACE, GJS_DEBUG_GOBJECT, GJS_DEBUG_GFUNCTION, GJS_DEBUG_GCLOSURE, GJS_DEBUG_GBOXED, GJS_DEBUG_GENUM, GJS_DEBUG_GPARAM, GJS_DEBUG_GERROR, GJS_DEBUG_GFUNDAMENTAL, GJS_DEBUG_GINTERFACE, } GjsDebugTopic; /* These defines are because we have some pretty expensive and * extremely verbose debug output in certain areas, that's useful * sometimes, but just too much to compile in by default. The areas * tend to be broader and less focused than the ones represented by * GjsDebugTopic. * * Don't use these special "disabled by default" log macros to print * anything that's an abnormal or error situation. * * Don't use them for one-time events, either. They are for routine * stuff that happens over and over and would deluge the logs, so * should be off by default. */ /* Whether to be verbose about JavaScript property access and resolution */ #ifndef GJS_VERBOSE_ENABLE_PROPS #define GJS_VERBOSE_ENABLE_PROPS 0 #endif /* Whether to be verbose about JavaScript function arg and closure marshaling */ #ifndef GJS_VERBOSE_ENABLE_MARSHAL #define GJS_VERBOSE_ENABLE_MARSHAL 0 #endif /* Whether to be verbose about constructing, destroying, and gc-rooting * various kinds of JavaScript thingy */ #ifndef GJS_VERBOSE_ENABLE_LIFECYCLE #define GJS_VERBOSE_ENABLE_LIFECYCLE 0 #endif /* Whether to log all gobject-introspection types and methods we use */ #ifndef GJS_VERBOSE_ENABLE_GI_USAGE #define GJS_VERBOSE_ENABLE_GI_USAGE 0 #endif /* Whether to log all callback GClosure debugging (finalizing, invalidating etc) */ #ifndef GJS_VERBOSE_ENABLE_GCLOSURE #define GJS_VERBOSE_ENABLE_GCLOSURE 0 #endif /* Whether to log all GObject signal debugging */ #ifndef GJS_VERBOSE_ENABLE_GSIGNAL #define GJS_VERBOSE_ENABLE_GSIGNAL 0 #endif #if GJS_VERBOSE_ENABLE_PROPS # define GJS_USED_VERBOSE_PROPS # define gjs_debug_jsprop(topic, ...) \ do { \ gjs_debug(topic, __VA_ARGS__); \ } while (0) #else # define GJS_USED_VERBOSE_PROPS [[maybe_unused]] # define gjs_debug_jsprop(topic, ...) ((void)0) #endif #if GJS_VERBOSE_ENABLE_MARSHAL # define GJS_USED_VERBOSE_MARSHAL # define gjs_debug_marshal(topic, ...) \ do { \ gjs_debug(topic, __VA_ARGS__); \ } while (0) #else # define GJS_USED_VERBOSE_MARSHAL [[maybe_unused]] # define gjs_debug_marshal(topic, ...) ((void)0) #endif #if GJS_VERBOSE_ENABLE_LIFECYCLE # define GJS_USED_VERBOSE_LIFECYCLE # define gjs_debug_lifecycle(topic, ...) \ do { \ gjs_debug(topic, __VA_ARGS__); \ } while (0) #else # define GJS_USED_VERBOSE_LIFECYCLE [[maybe_unused]] # define gjs_debug_lifecycle(topic, ...) ((void)0) #endif #if GJS_VERBOSE_ENABLE_GI_USAGE # define GJS_USED_VERBOSE_GI_USAGE # define gjs_debug_gi_usage(...) \ do { \ gjs_debug(GJS_DEBUG_GI_USAGE, __VA_ARGS__); \ } while (0) #else # define GJS_USED_VERBOSE_GI_USAGE [[maybe_unused]] # define gjs_debug_gi_usage(...) ((void)0) #endif #if GJS_VERBOSE_ENABLE_GCLOSURE # define GJS_USED_VERBOSE_GCLOSURE # define gjs_debug_closure(...) \ do { \ gjs_debug(GJS_DEBUG_GCLOSURE, __VA_ARGS__); \ } while (0) #else # define GJS_USED_VERBOSE_GCLOSURE [[maybe_unused]] # define gjs_debug_closure(...) ((void)0) #endif #if GJS_VERBOSE_ENABLE_GSIGNAL # define GJS_USED_VERBOSE_GSIGNAL # define gjs_debug_gsignal(...) \ do { \ gjs_debug(GJS_DEBUG_GOBJECT, __VA_ARGS__); \ } while (0) #else # define GJS_USED_VERBOSE_GSIGNAL [[maybe_unused]] # define gjs_debug_gsignal(...) ((void)0) #endif void gjs_debug(GjsDebugTopic topic, const char *format, ...) G_GNUC_PRINTF (2, 3); #endif // UTIL_LOG_H_ cjs-5.2.0/util/log.cpp0000644000175000017500000001646614144444702014716 0ustar jpeisachjpeisach/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* * Copyright (c) 2008 litl, LLC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #include #include #include // for FILE, fprintf, fflush, fopen, fputs, fseek #include // for strchr, strcmp #ifdef _WIN32 # include # include # ifndef F_OK # define F_OK 0 # endif #else # include // for getpid #endif #include "util/log.h" #include "util/misc.h" /* prefix is allowed if it's in the ;-delimited environment variable * GJS_DEBUG_TOPICS or if that variable is not set. */ static bool is_allowed_prefix (const char *prefix) { static const char *topics = NULL; static char **prefixes = NULL; bool found = false; int i; if (topics == NULL) { topics = g_getenv("GJS_DEBUG_TOPICS"); if (!topics) return true; /* We never really free this, should be gone when the process exits */ prefixes = g_strsplit(topics, ";", -1); } if (!prefixes) return true; for (i = 0; prefixes[i] != NULL; i++) { if (!strcmp(prefixes[i], prefix)) { found = true; break; } } return found; } #define PREFIX_LENGTH 12 static void write_to_stream(FILE *logfp, const char *prefix, const char *s) { /* seek to end to avoid truncating in case we're using shared logfile */ (void)fseek(logfp, 0, SEEK_END); fprintf(logfp, "%*s: %s", PREFIX_LENGTH, prefix, s); if (!g_str_has_suffix(s, "\n")) fputs("\n", logfp); fflush(logfp); } void gjs_debug(GjsDebugTopic topic, const char *format, ...) { static FILE *logfp = NULL; static bool debug_log_enabled = false; static bool checked_for_timestamp = false; static bool print_timestamp = false; static bool checked_for_thread = false; static bool print_thread = false; static GTimer *timer = NULL; const char *prefix; va_list args; char *s; if (!checked_for_timestamp) { print_timestamp = gjs_environment_variable_is_set("GJS_DEBUG_TIMESTAMP"); checked_for_timestamp = true; } if (!checked_for_thread) { print_thread = gjs_environment_variable_is_set("GJS_DEBUG_THREAD"); checked_for_thread = true; } if (print_timestamp && !timer) { timer = g_timer_new(); } if (logfp == NULL) { const char *debug_output = g_getenv("GJS_DEBUG_OUTPUT"); if (debug_output != NULL && strcmp(debug_output, "stderr") == 0) { debug_log_enabled = true; } else if (debug_output != NULL) { const char *log_file; char *free_me; char *c; /* Allow debug-%u.log for per-pid logfiles as otherwise log * messages from multiple processes can overwrite each other. * * (printf below should be safe as we check '%u' is the only format * string) */ c = strchr((char *) debug_output, '%'); if (c && c[1] == 'u' && !strchr(c+1, '%')) { #if defined(__clang__) || __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6) _Pragma("GCC diagnostic push") _Pragma("GCC diagnostic ignored \"-Wformat-nonliteral\"") #endif free_me = g_strdup_printf(debug_output, (guint)getpid()); #if defined(__clang__) || __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6) _Pragma("GCC diagnostic pop") #endif log_file = free_me; } else { log_file = debug_output; free_me = NULL; } /* avoid truncating in case we're using shared logfile */ logfp = fopen(log_file, "a"); if (!logfp) fprintf(stderr, "Failed to open log file `%s': %s\n", log_file, g_strerror(errno)); g_free(free_me); debug_log_enabled = true; } if (logfp == NULL) logfp = stderr; } if (!debug_log_enabled) return; switch (topic) { case GJS_DEBUG_GI_USAGE: prefix = "JS GI USE"; break; case GJS_DEBUG_MEMORY: prefix = "JS MEMORY"; break; case GJS_DEBUG_CONTEXT: prefix = "JS CTX"; break; case GJS_DEBUG_IMPORTER: prefix = "JS IMPORT"; break; case GJS_DEBUG_NATIVE: prefix = "JS NATIVE"; break; case GJS_DEBUG_KEEP_ALIVE: prefix = "JS KP ALV"; break; case GJS_DEBUG_GREPO: prefix = "JS G REPO"; break; case GJS_DEBUG_GNAMESPACE: prefix = "JS G NS"; break; case GJS_DEBUG_GOBJECT: prefix = "JS G OBJ"; break; case GJS_DEBUG_GFUNCTION: prefix = "JS G FUNC"; break; case GJS_DEBUG_GFUNDAMENTAL: prefix = "JS G FNDMTL"; break; case GJS_DEBUG_GCLOSURE: prefix = "JS G CLSR"; break; case GJS_DEBUG_GBOXED: prefix = "JS G BXD"; break; case GJS_DEBUG_GENUM: prefix = "JS G ENUM"; break; case GJS_DEBUG_GPARAM: prefix = "JS G PRM"; break; case GJS_DEBUG_GERROR: prefix = "JS G ERR"; break; case GJS_DEBUG_GINTERFACE: prefix = "JS G IFACE"; break; default: prefix = "???"; break; } if (!is_allowed_prefix(prefix)) return; va_start (args, format); s = g_strdup_vprintf (format, args); va_end (args); if (print_timestamp) { static gdouble previous = 0.0; gdouble total = g_timer_elapsed(timer, NULL) * 1000.0; gdouble since = total - previous; const char *ts_suffix; char *s2; if (since > 50.0) { ts_suffix = "!! "; } else if (since > 100.0) { ts_suffix = "!!! "; } else if (since > 200.0) { ts_suffix = "!!!!"; } else { ts_suffix = " "; } s2 = g_strdup_printf("%g %s%s", total, ts_suffix, s); g_free(s); s = s2; previous = total; } if (print_thread) { char *s2 = g_strdup_printf("(thread %p) %s", g_thread_self(), s); g_free(s); s = s2; } write_to_stream(logfp, prefix, s); g_free(s); } cjs-5.2.0/util/misc.cpp0000644000175000017500000000430514144444702015055 0ustar jpeisachjpeisach/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* * Copyright (c) 2008 litl, LLC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #include #include "util/misc.h" bool gjs_environment_variable_is_set(const char *env_variable_name) { const char *s; s = g_getenv(env_variable_name); if (!s) return false; if (*s == '\0') return false; return true; } /** gjs_g_strv_concat: * * Concate an array of string arrays to one string array. The strings in each * array is copied to the resulting array. * * @strv_array: array of 0-terminated arrays of strings. Null elements are * allowed. * @len: number of arrays in @strv_array * * Returns: (transfer full): a newly allocated 0-terminated array of strings. */ char** gjs_g_strv_concat(char*** strv_array, int len) { GPtrArray* array = g_ptr_array_sized_new(16); for (int i = 0; i < len; i++) { char** strv = strv_array[i]; if (!strv) continue; for (int j = 0; strv[j]; ++j) g_ptr_array_add(array, g_strdup(strv[j])); } g_ptr_array_add(array, nullptr); return reinterpret_cast(g_ptr_array_free(array, false)); } cjs-5.2.0/util/misc.h0000644000175000017500000000254614144444702014527 0ustar jpeisachjpeisach/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* * Copyright (c) 2008 litl, LLC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #ifndef UTIL_MISC_H_ #define UTIL_MISC_H_ bool gjs_environment_variable_is_set (const char *env_variable_name); char** gjs_g_strv_concat(char*** strv_array, int len); #endif // UTIL_MISC_H_ cjs-5.2.0/gjs.doap0000644000175000017500000000334314144444702014072 0ustar jpeisachjpeisach gjs gjs GNOME JavaScript bindings GNOME JavaScript bindings C++ Philip Chimento ptomato pchimento Cosimo Cecchi cosimoc Giovanni Campagna gcampagna cjs-5.2.0/gi/0000755000175000017500000000000014144444702013036 5ustar jpeisachjpeisachcjs-5.2.0/gi/interface.cpp0000644000175000017500000001343014144444702015503 0ustar jpeisachjpeisach/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* * Copyright (c) 2008 litl, LLC * Copyright (c) 2012 Red Hat, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #include #include #include #include #include "gi/function.h" #include "gi/interface.h" #include "gi/object.h" #include "gi/repo.h" #include "cjs/atoms.h" #include "cjs/context-private.h" #include "cjs/mem-private.h" InterfacePrototype::InterfacePrototype(GIInterfaceInfo* info, GType gtype) : GIWrapperPrototype(info, gtype), m_vtable( static_cast(g_type_default_interface_ref(gtype))) { GJS_INC_COUNTER(interface); } InterfacePrototype::~InterfacePrototype(void) { g_clear_pointer(&m_vtable, g_type_default_interface_unref); GJS_DEC_COUNTER(interface); } // See GIWrapperBase::resolve(). bool InterfacePrototype::resolve_impl(JSContext* context, JS::HandleObject obj, JS::HandleId id, bool* resolved) { /* If we have no GIRepository information then this interface was defined * from within GJS. In that case, it has no properties that need to be * resolved from within C code, as interfaces cannot inherit. */ if (!info()) { *resolved = false; return true; } JS::UniqueChars prop_name; if (!gjs_get_string_id(context, id, &prop_name)) return false; if (!prop_name) { *resolved = false; return true; // not resolved, but no error } GjsAutoFunctionInfo method_info = g_interface_info_find_method(m_info, prop_name.get()); if (method_info) { if (g_function_info_get_flags (method_info) & GI_FUNCTION_IS_METHOD) { if (!gjs_define_function(context, obj, m_gtype, method_info)) return false; *resolved = true; } else { *resolved = false; } } else { *resolved = false; } return true; } /* * InterfaceBase::has_instance: * * JSNative implementation of `[Symbol.hasInstance]()`. This method is never * called directly, but instead is called indirectly by the JS engine as part of * an `instanceof` expression. */ bool InterfaceBase::has_instance(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_GET_THIS(cx, argc, vp, args, interface_constructor); JS::RootedObject interface_proto(cx); const GjsAtoms& atoms = GjsContextPrivate::atoms(cx); if (!gjs_object_require_property(cx, interface_constructor, "interface constructor", atoms.prototype(), &interface_proto)) return false; InterfaceBase* priv = InterfaceBase::for_js_typecheck(cx, interface_proto); if (!priv) return false; return priv->to_prototype()->has_instance_impl(cx, args); } // See InterfaceBase::has_instance(). bool InterfacePrototype::has_instance_impl(JSContext* cx, const JS::CallArgs& args) { // This method is never called directly, so no need for error messages. g_assert(args.length() == 1); g_assert(args[0].isObject()); JS::RootedObject instance(cx, &args[0].toObject()); bool isinstance = ObjectBase::typecheck(cx, instance, nullptr, m_gtype, GjsTypecheckNoThrow()); args.rval().setBoolean(isinstance); return true; } // clang-format off const struct JSClassOps InterfaceBase::class_ops = { nullptr, // addProperty nullptr, // deleteProperty nullptr, // enumerate nullptr, // newEnumerate &InterfaceBase::resolve, nullptr, // mayResolve &InterfaceBase::finalize, }; const struct JSClass InterfaceBase::klass = { "GObject_Interface", JSCLASS_HAS_PRIVATE | JSCLASS_BACKGROUND_FINALIZE, &InterfaceBase::class_ops }; JSFunctionSpec InterfaceBase::static_methods[] = { JS_SYM_FN(hasInstance, &InterfaceBase::has_instance, 1, 0), JS_FS_END }; // clang-format on bool gjs_lookup_interface_constructor(JSContext *context, GType gtype, JS::MutableHandleValue value_p) { JSObject *constructor; GIBaseInfo *interface_info; interface_info = g_irepository_find_by_gtype(nullptr, gtype); if (!interface_info) { gjs_throw(context, "Cannot expose non introspectable interface %s", g_type_name(gtype)); return false; } g_assert(g_base_info_get_type(interface_info) == GI_INFO_TYPE_INTERFACE); constructor = gjs_lookup_generic_constructor(context, interface_info); if (G_UNLIKELY(!constructor)) return false; g_base_info_unref(interface_info); value_p.setObject(*constructor); return true; } cjs-5.2.0/gi/gtype.h0000644000175000017500000000344514144444702014345 0ustar jpeisachjpeisach/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* * Copyright (c) 2008 litl, LLC * Copyright (c) 2012 Red Hat, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #ifndef GI_GTYPE_H_ #define GI_GTYPE_H_ #include #include #include #include "cjs/macros.h" GJS_JSAPI_RETURN_CONVENTION JSObject * gjs_gtype_create_gtype_wrapper (JSContext *context, GType gtype); GJS_JSAPI_RETURN_CONVENTION bool gjs_gtype_get_actual_gtype(JSContext* context, JS::HandleObject object, GType* gtype_out); [[nodiscard]] bool gjs_typecheck_gtype(JSContext* cx, JS::HandleObject obj, bool throw_error); #endif // GI_GTYPE_H_ cjs-5.2.0/gi/gerror.h0000644000175000017500000001436214144444702014515 0ustar jpeisachjpeisach/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* * Copyright (c) 2008 litl, LLC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #ifndef GI_GERROR_H_ #define GI_GERROR_H_ #include #include #include #include #include #include #include "gi/wrapperutils.h" #include "cjs/macros.h" #include "util/log.h" class ErrorPrototype; class ErrorInstance; namespace JS { class CallArgs; } /* To conserve memory, we have two different kinds of private data for GError * JS wrappers: ErrorInstance, and ErrorPrototype. Both inherit from ErrorBase * for their common functionality. For more information, see the notes in * wrapperutils.h. * * ErrorPrototype, unlike the other GIWrapperPrototype subclasses, represents a * single error domain instead of a single GType. All Errors have a GType of * G_TYPE_ERROR. * * Note that in some situations GError structs can show up as BoxedInstance * instead of ErrorInstance. We have some special cases in this code to deal * with that. */ class ErrorBase : public GIWrapperBase { friend class GIWrapperBase; protected: explicit ErrorBase(ErrorPrototype* proto = nullptr) : GIWrapperBase(proto) {} ~ErrorBase(void) {} static const GjsDebugTopic debug_topic = GJS_DEBUG_GERROR; static constexpr const char* debug_tag = "gerror"; static const struct JSClassOps class_ops; static const struct JSClass klass; static JSPropertySpec proto_properties[]; static JSFunctionSpec static_methods[]; // Accessors public: [[nodiscard]] GQuark domain(void) const; // Property getters protected: GJS_JSAPI_RETURN_CONVENTION static bool get_domain(JSContext* cx, unsigned argc, JS::Value* vp); GJS_JSAPI_RETURN_CONVENTION static bool get_message(JSContext* cx, unsigned argc, JS::Value* vp); GJS_JSAPI_RETURN_CONVENTION static bool get_code(JSContext* cx, unsigned argc, JS::Value* vp); // JS methods GJS_JSAPI_RETURN_CONVENTION static bool value_of(JSContext* cx, unsigned argc, JS::Value* vp); public: GJS_JSAPI_RETURN_CONVENTION static bool to_string(JSContext* cx, unsigned argc, JS::Value* vp); // Helper methods GJS_JSAPI_RETURN_CONVENTION static GError* to_c_ptr(JSContext* cx, JS::HandleObject obj); GJS_JSAPI_RETURN_CONVENTION static bool transfer_to_gi_argument(JSContext* cx, JS::HandleObject obj, GIArgument* arg, GIDirection transfer_direction, GITransfer transfer_ownership); GJS_JSAPI_RETURN_CONVENTION static bool typecheck(JSContext* cx, JS::HandleObject obj); [[nodiscard]] static bool typecheck(JSContext* cx, JS::HandleObject obj, GjsTypecheckNoThrow); }; class ErrorPrototype : public GIWrapperPrototype { friend class GIWrapperPrototype; friend class GIWrapperBase; GQuark m_domain; static constexpr InfoType::Tag info_type_tag = InfoType::Enum; explicit ErrorPrototype(GIEnumInfo* info, GType gtype); ~ErrorPrototype(void); GJS_JSAPI_RETURN_CONVENTION bool get_parent_proto(JSContext* cx, JS::MutableHandleObject proto) const; public: [[nodiscard]] GQuark domain(void) const { return m_domain; } GJS_JSAPI_RETURN_CONVENTION static bool define_class(JSContext* cx, JS::HandleObject in_object, GIEnumInfo* info); }; class ErrorInstance : public GIWrapperInstance { friend class GIWrapperInstance; friend class GIWrapperBase; explicit ErrorInstance(JSContext* cx, JS::HandleObject obj); ~ErrorInstance(void); public: void copy_gerror(GError* other) { m_ptr = g_error_copy(other); } GJS_JSAPI_RETURN_CONVENTION static GError* copy_ptr(JSContext*, GType, void* ptr) { return g_error_copy(static_cast(ptr)); } // Accessors [[nodiscard]] const char* message(void) const { return m_ptr->message; } [[nodiscard]] int code(void) const { return m_ptr->code; } // JS constructor private: GJS_JSAPI_RETURN_CONVENTION bool constructor_impl(JSContext* cx, JS::HandleObject obj, const JS::CallArgs& args); // Public API public: GJS_JSAPI_RETURN_CONVENTION static JSObject* object_for_c_ptr(JSContext* cx, GError* gerror); }; GJS_JSAPI_RETURN_CONVENTION GError *gjs_gerror_make_from_error(JSContext *cx, JS::HandleObject obj); GJS_JSAPI_RETURN_CONVENTION bool gjs_define_error_properties(JSContext* cx, JS::HandleObject obj); bool gjs_throw_gerror(JSContext* cx, GError* error); #endif // GI_GERROR_H_ cjs-5.2.0/gi/value.h0000644000175000017500000000471114144444702014326 0ustar jpeisachjpeisach/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* * Copyright (c) 2008 litl, LLC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #ifndef GI_VALUE_H_ #define GI_VALUE_H_ #include #include #include #include "cjs/macros.h" GJS_JSAPI_RETURN_CONVENTION bool gjs_value_to_g_value (JSContext *context, JS::HandleValue value, GValue *gvalue); GJS_JSAPI_RETURN_CONVENTION bool gjs_value_to_g_value_no_copy (JSContext *context, JS::HandleValue value, GValue *gvalue); GJS_JSAPI_RETURN_CONVENTION bool gjs_value_from_g_value(JSContext *context, JS::MutableHandleValue value_p, const GValue *gvalue); [[nodiscard]] GClosure* gjs_closure_new_marshaled(JSContext* cx, JSFunction* callable, const char* description); [[nodiscard]] GClosure* gjs_closure_new_for_signal(JSContext* cx, JSFunction* callable, const char* description, unsigned signal_id); #endif // GI_VALUE_H_ cjs-5.2.0/gi/gobject.h0000644000175000017500000000316614144444702014632 0ustar jpeisachjpeisach/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* * Copyright (c) 2018 Philip Chimento * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #ifndef GI_GOBJECT_H_ #define GI_GOBJECT_H_ #include #include #include "cjs/jsapi-util.h" using AutoParamArray = std::vector; extern const GTypeInfo gjs_gobject_class_info; extern const GTypeInfo gjs_gobject_interface_info; void push_class_init_properties(GType gtype, AutoParamArray* params); bool pop_class_init_properties(GType gtype, AutoParamArray* params_out); #endif // GI_GOBJECT_H_ cjs-5.2.0/gi/union.cpp0000644000175000017500000001701614144444702014677 0ustar jpeisachjpeisach/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* * Copyright (c) 2008 litl, LLC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #include #include #include #include #include #include #include #include "gi/arg-inl.h" #include "gi/function.h" #include "gi/repo.h" #include "gi/union.h" #include "cjs/jsapi-util.h" #include "cjs/mem-private.h" #include "util/log.h" UnionPrototype::UnionPrototype(GIUnionInfo* info, GType gtype) : GIWrapperPrototype(info, gtype) { GJS_INC_COUNTER(union_prototype); } UnionPrototype::~UnionPrototype(void) { GJS_DEC_COUNTER(union_prototype); } UnionInstance::UnionInstance(JSContext* cx, JS::HandleObject obj) : GIWrapperInstance(cx, obj) { GJS_INC_COUNTER(union_instance); } UnionInstance::~UnionInstance(void) { if (m_ptr) { g_boxed_free(gtype(), m_ptr); m_ptr = nullptr; } GJS_DEC_COUNTER(union_instance); } // See GIWrapperBase::resolve(). bool UnionPrototype::resolve_impl(JSContext* context, JS::HandleObject obj, JS::HandleId id, bool* resolved) { JS::UniqueChars prop_name; if (!gjs_get_string_id(context, id, &prop_name)) return false; if (!prop_name) { *resolved = false; return true; // not resolved, but no error } // Look for methods and other class properties GjsAutoFunctionInfo method_info = g_union_info_find_method(info(), prop_name.get()); if (method_info) { #if GJS_VERBOSE_ENABLE_GI_USAGE _gjs_log_info_usage(method_info); #endif if (g_function_info_get_flags (method_info) & GI_FUNCTION_IS_METHOD) { gjs_debug(GJS_DEBUG_GBOXED, "Defining method %s in prototype for %s.%s", method_info.name(), ns(), name()); /* obj is union proto */ if (!gjs_define_function(context, obj, gtype(), method_info)) return false; *resolved = true; /* we defined the prop in object_proto */ } else { *resolved = false; } } else { *resolved = false; } return true; } GJS_JSAPI_RETURN_CONVENTION static void* union_new(JSContext* context, JS::HandleObject this_obj, const JS::CallArgs& args, GIUnionInfo* info) { int n_methods; int i; /* Find a zero-args constructor and call it */ n_methods = g_union_info_get_n_methods(info); for (i = 0; i < n_methods; ++i) { GIFunctionInfoFlags flags; GjsAutoFunctionInfo func_info = g_union_info_get_method(info, i); flags = g_function_info_get_flags(func_info); if ((flags & GI_FUNCTION_IS_CONSTRUCTOR) != 0 && g_callable_info_get_n_args((GICallableInfo*) func_info) == 0) { GIArgument rval; if (!gjs_invoke_constructor_from_c(context, func_info, this_obj, args, &rval)) return nullptr; if (!gjs_arg_get(&rval)) { gjs_throw(context, "Unable to construct union type %s as its" "constructor function returned null", g_base_info_get_name(info)); return nullptr; } return gjs_arg_get(&rval); } } gjs_throw(context, "Unable to construct union type %s since it has no zero-args , can only wrap an existing one", g_base_info_get_name((GIBaseInfo*) info)); return nullptr; } // See GIWrapperBase::constructor(). bool UnionInstance::constructor_impl(JSContext* context, JS::HandleObject object, const JS::CallArgs& args) { if (args.length() > 0 && !JS::WarnUTF8(context, "Arguments to constructor of %s ignored", name())) return false; m_ptr = union_new(context, object, args, info()); return !!m_ptr; } // clang-format off const struct JSClassOps UnionBase::class_ops = { nullptr, // addProperty nullptr, // deleteProperty nullptr, // enumerate nullptr, // newEnumerate &UnionBase::resolve, nullptr, // mayResolve &UnionBase::finalize, }; const struct JSClass UnionBase::klass = { "GObject_Union", JSCLASS_HAS_PRIVATE | JSCLASS_FOREGROUND_FINALIZE, &UnionBase::class_ops }; // clang-format on bool gjs_define_union_class(JSContext *context, JS::HandleObject in_object, GIUnionInfo *info) { GType gtype; JS::RootedObject prototype(context), constructor(context); /* For certain unions, we may be able to relax this in the future by * directly allocating union memory, as we do for structures in boxed.c */ gtype = g_registered_type_info_get_g_type( (GIRegisteredTypeInfo*) info); if (gtype == G_TYPE_NONE) { gjs_throw(context, "Unions must currently be registered as boxed types"); return false; } return !!UnionPrototype::create_class(context, in_object, info, gtype, &constructor, &prototype); } JSObject* gjs_union_from_c_union(JSContext *context, GIUnionInfo *info, void *gboxed) { GType gtype; if (!gboxed) return nullptr; /* For certain unions, we may be able to relax this in the future by * directly allocating union memory, as we do for structures in boxed.c */ gtype = g_registered_type_info_get_g_type( (GIRegisteredTypeInfo*) info); if (gtype == G_TYPE_NONE) { gjs_throw(context, "Unions must currently be registered as boxed types"); return nullptr; } gjs_debug_marshal(GJS_DEBUG_GBOXED, "Wrapping union %s %p with JSObject", g_base_info_get_name((GIBaseInfo *)info), gboxed); JS::RootedObject obj(context, gjs_new_object_with_generic_prototype(context, info)); if (!obj) return nullptr; UnionInstance* priv = UnionInstance::new_for_js_object(context, obj); priv->copy_union(gboxed); return obj; } void* UnionInstance::copy_ptr(JSContext* cx, GType gtype, void* ptr) { if (g_type_is_a(gtype, G_TYPE_BOXED)) return g_boxed_copy(gtype, ptr); gjs_throw(cx, "Can't transfer ownership of a union type not registered as " "boxed"); return nullptr; } cjs-5.2.0/gi/boxed.h0000644000175000017500000002175114144444702014316 0ustar jpeisachjpeisach/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* * Copyright (c) 2008 litl, LLC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #ifndef GI_BOXED_H_ #define GI_BOXED_H_ #include #include #include #include #include #include // for GCHashMap #include #include #include #include // for DefaultHasher #include "gi/wrapperutils.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" #include "util/log.h" class BoxedPrototype; class BoxedInstance; class JSTracer; namespace JS { class CallArgs; } namespace js { class SystemAllocPolicy; } /* To conserve memory, we have two different kinds of private data for GBoxed * JS wrappers: BoxedInstance, and BoxedPrototype. Both inherit from BoxedBase * for their common functionality. For more information, see the notes in * wrapperutils.h. */ class BoxedBase : public GIWrapperBase { friend class GIWrapperBase; protected: explicit BoxedBase(BoxedPrototype* proto = nullptr) : GIWrapperBase(proto) {} ~BoxedBase(void) {} static const GjsDebugTopic debug_topic = GJS_DEBUG_GBOXED; static constexpr const char* debug_tag = "GBoxed"; static const struct JSClassOps class_ops; static const struct JSClass klass; // JS property accessors GJS_JSAPI_RETURN_CONVENTION static bool field_getter(JSContext* cx, unsigned argc, JS::Value* vp); GJS_JSAPI_RETURN_CONVENTION static bool field_setter(JSContext* cx, unsigned argc, JS::Value* vp); // Helper methods that work on either instances or prototypes [[nodiscard]] const char* to_string_kind() const { return "boxed"; } GJS_JSAPI_RETURN_CONVENTION GIFieldInfo* get_field_info(JSContext* cx, uint32_t id) const; public: [[nodiscard]] BoxedBase* get_copy_source(JSContext* cx, JS::Value value) const; }; class BoxedPrototype : public GIWrapperPrototype { friend class GIWrapperPrototype; friend class GIWrapperBase; using FieldMap = JS::GCHashMap, GjsAutoFieldInfo, js::DefaultHasher, js::SystemAllocPolicy>; int m_zero_args_constructor; // -1 if none int m_default_constructor; // -1 if none JS::Heap m_default_constructor_name; FieldMap* m_field_map; bool m_can_allocate_directly : 1; explicit BoxedPrototype(GIStructInfo* info, GType gtype); ~BoxedPrototype(void); GJS_JSAPI_RETURN_CONVENTION bool init(JSContext* cx); static constexpr InfoType::Tag info_type_tag = InfoType::Struct; // Accessors public: [[nodiscard]] bool can_allocate_directly() const { return m_can_allocate_directly; } [[nodiscard]] bool has_zero_args_constructor() const { return m_zero_args_constructor >= 0; } [[nodiscard]] bool has_default_constructor() const { return m_default_constructor >= 0; } [[nodiscard]] GIFunctionInfo* zero_args_constructor_info() const { return g_struct_info_get_method(info(), m_zero_args_constructor); } // The ID is traced from the object, so it's OK to create a handle from it. [[nodiscard]] JS::HandleId default_constructor_name() const { return JS::HandleId::fromMarkedLocation( m_default_constructor_name.address()); } // JSClass operations private: GJS_JSAPI_RETURN_CONVENTION bool resolve_impl(JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool* resolved); GJS_JSAPI_RETURN_CONVENTION bool new_enumerate_impl(JSContext* cx, JS::HandleObject obj, JS::MutableHandleIdVector properties, bool only_enumerable); void trace_impl(JSTracer* trc); // Helper methods GJS_JSAPI_RETURN_CONVENTION static FieldMap* create_field_map(JSContext* cx, GIStructInfo* struct_info); GJS_JSAPI_RETURN_CONVENTION bool ensure_field_map(JSContext* cx); GJS_JSAPI_RETURN_CONVENTION bool define_boxed_class_fields(JSContext* cx, JS::HandleObject proto); public: GJS_JSAPI_RETURN_CONVENTION static bool define_class(JSContext* cx, JS::HandleObject in_object, GIStructInfo* info); GJS_JSAPI_RETURN_CONVENTION GIFieldInfo* lookup_field(JSContext* cx, JSString* prop_name); }; class BoxedInstance : public GIWrapperInstance { friend class GIWrapperInstance; friend class GIWrapperBase; friend class BoxedBase; // for field_getter, etc. bool m_allocated_directly : 1; bool m_owning_ptr : 1; // if set, the JS wrapper owns the C memory referred // to by m_ptr. explicit BoxedInstance(JSContext* cx, JS::HandleObject obj); ~BoxedInstance(void); // Don't set GIWrapperBase::m_ptr directly. Instead, use one of these // setters to express your intention to own the pointer or not. void own_ptr(void* boxed_ptr) { g_assert(!m_ptr); m_ptr = boxed_ptr; m_owning_ptr = true; } void share_ptr(void* unowned_boxed_ptr) { g_assert(!m_ptr); m_ptr = unowned_boxed_ptr; m_owning_ptr = false; } // Methods for different ways to allocate the GBoxed pointer void allocate_directly(void); void copy_boxed(void* boxed_ptr); void copy_boxed(BoxedInstance* source); void copy_memory(void* boxed_ptr); void copy_memory(BoxedInstance* source); // Helper methods GJS_JSAPI_RETURN_CONVENTION bool init_from_props(JSContext* cx, JS::Value props_value); GJS_JSAPI_RETURN_CONVENTION bool get_nested_interface_object(JSContext* cx, JSObject* parent_obj, GIFieldInfo* field_info, GIBaseInfo* interface_info, JS::MutableHandleValue value) const; GJS_JSAPI_RETURN_CONVENTION bool set_nested_interface_object(JSContext* cx, GIFieldInfo* field_info, GIBaseInfo* interface_info, JS::HandleValue value); GJS_JSAPI_RETURN_CONVENTION static void* copy_ptr(JSContext* cx, GType gtype, void* ptr); // JS property accessors GJS_JSAPI_RETURN_CONVENTION bool field_getter_impl(JSContext* cx, JSObject* obj, GIFieldInfo* info, JS::MutableHandleValue rval) const; GJS_JSAPI_RETURN_CONVENTION bool field_setter_impl(JSContext* cx, GIFieldInfo* info, JS::HandleValue value); // JS constructor GJS_JSAPI_RETURN_CONVENTION bool constructor_impl(JSContext* cx, JS::HandleObject obj, const JS::CallArgs& args); // Public API for initializing BoxedInstance JS object from C struct public: struct NoCopy {}; private: GJS_JSAPI_RETURN_CONVENTION bool init_from_c_struct(JSContext* cx, void* gboxed); GJS_JSAPI_RETURN_CONVENTION bool init_from_c_struct(JSContext* cx, void* gboxed, NoCopy); template GJS_JSAPI_RETURN_CONVENTION static JSObject* new_for_c_struct_impl( JSContext* cx, GIStructInfo* info, void* gboxed, Args&&... args); public: GJS_JSAPI_RETURN_CONVENTION static JSObject* new_for_c_struct(JSContext* cx, GIStructInfo* info, void* gboxed); GJS_JSAPI_RETURN_CONVENTION static JSObject* new_for_c_struct(JSContext* cx, GIStructInfo* info, void* gboxed, NoCopy); }; #endif // GI_BOXED_H_ cjs-5.2.0/gi/wrapperutils.h0000644000175000017500000012613314144444702015756 0ustar jpeisachjpeisach/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* * Copyright (c) 2008 litl, LLC * Copyright (c) 2018 Philip Chimento * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #ifndef GI_WRAPPERUTILS_H_ #define GI_WRAPPERUTILS_H_ #include #include #include #include #include #include #include #include #include #include #include #include #include #include // for UniqueChars #include // for JS_GetPrivate, JS_SetPrivate, JS_Ge... #include // for JSProto_TypeError #include "gi/arg-inl.h" #include "cjs/atoms.h" #include "cjs/context-private.h" #include "cjs/jsapi-class.h" // IWYU pragma: keep #include "cjs/jsapi-util.h" #include "cjs/macros.h" #include "util/log.h" struct JSFunctionSpec; struct JSPropertySpec; class JSTracer; GJS_JSAPI_RETURN_CONVENTION bool gjs_wrapper_to_string_func(JSContext* cx, JSObject* this_obj, const char* objtype, GIBaseInfo* info, GType gtype, const void* native_address, JS::MutableHandleValue ret); bool gjs_wrapper_throw_nonexistent_field(JSContext* cx, GType gtype, const char* field_name); bool gjs_wrapper_throw_readonly_field(JSContext* cx, GType gtype, const char* field_name); GJS_JSAPI_RETURN_CONVENTION bool gjs_wrapper_define_gtype_prop(JSContext* cx, JS::HandleObject constructor, GType gtype); namespace InfoType { enum Tag { Enum, Interface, Object, Struct, Union }; } namespace MemoryUse { constexpr JS::MemoryUse GObjectInstanceStruct = JS::MemoryUse::Embedding1; } struct GjsTypecheckNoThrow {}; /* * gjs_define_static_methods: * * Defines all static methods from @info on @constructor. Also includes class * methods for GIObjectInfo, and interface methods for GIInterfaceInfo. */ template GJS_JSAPI_RETURN_CONVENTION bool gjs_define_static_methods( JSContext* cx, JS::HandleObject constructor, GType gtype, GIBaseInfo* info); /* * GJS_GET_WRAPPER_PRIV: * @cx: JSContext pointer passed into JSNative function * @argc: Number of arguments passed into JSNative function * @vp: Argument value array passed into JSNative function * @args: Name for JS::CallArgs variable defined by this code snippet * @thisobj: Name for JS::RootedObject variable referring to function's this * @type: Type of private data * @priv: Name for private data variable defined by this code snippet * * A convenience macro for getting the private data from GJS classes using * GIWrapper. * Throws an error and returns false if the 'this' object is not the right type. * Use in any JSNative function. */ #define GJS_GET_WRAPPER_PRIV(cx, argc, vp, args, thisobj, type, priv) \ GJS_GET_THIS(cx, argc, vp, args, thisobj); \ type* priv = type::for_js_typecheck(cx, thisobj, args); \ if (!priv) \ return false; /* * GIWrapperBase: * * In most different kinds of C pointer that we expose to JS through GObject * Introspection (boxed, fundamental, gerror, interface, object, union), we want * to have different private structures for the prototype JS object and the JS * objects representing instances. Both should inherit from a base structure for * their common functionality. * * This is mainly for memory reasons. We need to keep track of the GIBaseInfo* * and GType for each dynamically created class, but we don't need to duplicate * that information (16 bytes on x64 systems) for every instance. In some cases * there can also be other information that's only used on the prototype. * * So, to conserve memory, we split the private structures in FooInstance and * FooPrototype, which both inherit from FooBase. All the repeated code in these * structures lives in GIWrapperBase, GIWrapperPrototype, and GIWrapperInstance. * * The m_proto member needs a bit of explanation, as this is used to implement * an unusual form of polymorphism. Sadly, we cannot have virtual methods in * FooBase, because SpiderMonkey can be compiled with or without RTTI, so we * cannot count on being able to cast FooBase to FooInstance or FooPrototype * with dynamic_cast<>, and the vtable would take up just as much space anyway. * Instead, we use the CRTP technique, and distinguish between FooInstance and * FooPrototype using the m_proto member, which will be null for FooPrototype. * Instead of casting, we have the to_prototype() and to_instance() methods * which will give you a pointer if the FooBase is of the correct type (and * assert if not.) * * The CRTP requires inheriting classes to declare themselves friends of the * parent class, so that the parent class can call their private methods. * * For more information about the CRTP, the Wikipedia article is informative: * https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern */ template class GIWrapperBase { protected: // nullptr if this Base is a Prototype; points to the corresponding // Prototype if this Base is an Instance. Prototype* m_proto; explicit GIWrapperBase(Prototype* proto = nullptr) : m_proto(proto) {} ~GIWrapperBase(void) {} // These three can be overridden in subclasses. See define_jsclass(). static constexpr JSPropertySpec* proto_properties = nullptr; static constexpr JSPropertySpec* static_properties = nullptr; static constexpr JSFunctionSpec* proto_methods = nullptr; static constexpr JSFunctionSpec* static_methods = nullptr; // Methods to get an existing Base public: /* * GIWrapperBase::for_js: * * Gets the Base belonging to a particular JS object wrapper. Checks that * the wrapper object has the right JSClass (Base::klass) and returns null * if not. */ [[nodiscard]] static Base* for_js(JSContext* cx, JS::HandleObject wrapper) { return static_cast( JS_GetInstancePrivate(cx, wrapper, &Base::klass, nullptr)); } /* * GIWrapperBase::check_jsclass: * * Checks if the given wrapper object has the right JSClass (Base::klass). */ [[nodiscard]] static bool check_jsclass(JSContext* cx, JS::HandleObject wrapper) { return !!for_js(cx, wrapper); } /* * GIWrapperBase::for_js_typecheck: * * Like for_js(), only throws a JS exception if the wrapper object has the * wrong class. Use in JSNative functions, where you have access to a * JS::CallArgs. The exception message will mention args.callee. * * The second overload can be used when you don't have access to an * instance of JS::CallArgs. The exception message will be generic. */ GJS_JSAPI_RETURN_CONVENTION static Base* for_js_typecheck( JSContext* cx, JS::HandleObject wrapper, JS::CallArgs& args) { // NOLINT(runtime/references) return static_cast( JS_GetInstancePrivate(cx, wrapper, &Base::klass, &args)); } GJS_JSAPI_RETURN_CONVENTION static Base* for_js_typecheck(JSContext* cx, JS::HandleObject wrapper) { if (!gjs_typecheck_instance(cx, wrapper, &Base::klass, true)) return nullptr; return for_js(cx, wrapper); } /* * GIWrapperBase::for_js_nocheck: * * Use when you don't have a JSContext* available. This method is infallible * and cannot trigger a GC, so it's safe to use from finalize() and trace(). * (It can return null if no private data has been set yet on the wrapper.) */ [[nodiscard]] static Base* for_js_nocheck(JSObject* wrapper) { return static_cast(JS_GetPrivate(wrapper)); } // Methods implementing our CRTP polymorphism scheme follow below. We don't // use standard C++ polymorphism because that would occupy another 8 bytes // for a vtable. /* * GIWrapperBase::is_prototype: * * Returns whether this Base is actually a Prototype (true) or an Instance * (false). */ [[nodiscard]] bool is_prototype() const { return !m_proto; } /* * GIWrapperBase::to_prototype: * GIWrapperBase::to_instance: * * These methods assert that this Base is of the correct subclass. If you * don't want to assert, then either check beforehand with is_prototype(), * or use get_prototype(). */ [[nodiscard]] Prototype* to_prototype() { g_assert(is_prototype()); return reinterpret_cast(this); } [[nodiscard]] const Prototype* to_prototype() const { g_assert(is_prototype()); return reinterpret_cast(this); } [[nodiscard]] Instance* to_instance() { g_assert(!is_prototype()); return reinterpret_cast(this); } [[nodiscard]] const Instance* to_instance() const { g_assert(!is_prototype()); return reinterpret_cast(this); } /* * GIWrapperBase::get_prototype: * * get_prototype() doesn't assert. If you call it on a Prototype, it returns * you the same object cast to the correct type; if you call it on an * Instance, it returns you the Prototype belonging to the corresponding JS * prototype. */ [[nodiscard]] Prototype* get_prototype() { return is_prototype() ? to_prototype() : m_proto; } [[nodiscard]] const Prototype* get_prototype() const { return is_prototype() ? to_prototype() : m_proto; } // Accessors for Prototype members follow below. Both Instance and Prototype // should be able to access the GIFooInfo and the GType, but for space // reasons we store them only on Prototype. [[nodiscard]] GIBaseInfo* info() const { return get_prototype()->info(); } [[nodiscard]] GType gtype() const { return get_prototype()->gtype(); } // The next three methods are operations derived from the GIFooInfo. [[nodiscard]] const char* type_name() const { return g_type_name(gtype()); } [[nodiscard]] const char* ns() const { return info() ? g_base_info_get_namespace(info()) : ""; } [[nodiscard]] const char* name() const { return info() ? g_base_info_get_name(info()) : type_name(); } private: // Accessor for Instance member. Used only in debug methods and toString(). [[nodiscard]] const void* ptr_addr() const { return is_prototype() ? nullptr : to_instance()->ptr(); } // Debug methods protected: void debug_lifecycle(const char* message GJS_USED_VERBOSE_LIFECYCLE) const { gjs_debug_lifecycle( Base::debug_topic, "[%p: %s pointer %p - %s.%s (%s)] %s", this, Base::debug_tag, ptr_addr(), ns(), name(), type_name(), message); } void debug_lifecycle(const void* obj GJS_USED_VERBOSE_LIFECYCLE, const char* message GJS_USED_VERBOSE_LIFECYCLE) const { gjs_debug_lifecycle( Base::debug_topic, "[%p: %s pointer %p - JS wrapper %p - %s.%s (%s)] %s", this, Base::debug_tag, ptr_addr(), obj, ns(), name(), type_name(), message); } void debug_jsprop(const char* message GJS_USED_VERBOSE_PROPS, const char* id GJS_USED_VERBOSE_PROPS, const void* obj GJS_USED_VERBOSE_PROPS) const { gjs_debug_jsprop( Base::debug_topic, "[%p: %s pointer %p - JS wrapper %p - %s.%s (%s)] %s '%s'", this, Base::debug_tag, ptr_addr(), obj, ns(), name(), type_name(), message, id); } void debug_jsprop(const char* message, jsid id, const void* obj) const { debug_jsprop(message, gjs_debug_id(id).c_str(), obj); } void debug_jsprop(const char* message, JSString* id, const void* obj) const { debug_jsprop(message, gjs_debug_string(id).c_str(), obj); } static void debug_jsprop_static(const char* message GJS_USED_VERBOSE_PROPS, jsid id GJS_USED_VERBOSE_PROPS, const void* obj GJS_USED_VERBOSE_PROPS) { gjs_debug_jsprop(Base::debug_topic, "[%s JS wrapper %p] %s '%s', no instance associated", Base::debug_tag, obj, message, gjs_debug_id(id).c_str()); } // JS class operations, used only in the JSClassOps struct /* * GIWrapperBase::new_enumerate: * * Include this in the Base::klass vtable if the class should support * lazy enumeration (listing all of the lazy properties that can be defined * in resolve().) If it is included, then there must be a corresponding * Prototype::new_enumerate_impl() method. */ GJS_JSAPI_RETURN_CONVENTION static bool new_enumerate(JSContext* cx, JS::HandleObject obj, JS::MutableHandleIdVector properties, bool only_enumerable) { Base* priv = Base::for_js(cx, obj); priv->debug_jsprop("Enumerate hook", "(all)", obj); if (!priv->is_prototype()) { // Instances don't have any methods or properties. // Spidermonkey will call new_enumerate on the prototype next. return true; } return priv->to_prototype()->new_enumerate_impl(cx, obj, properties, only_enumerable); } private: /* * GIWrapperBase::id_is_never_lazy: * * Returns true if @id should never be treated as a lazy property. The * JSResolveOp for an instance is called for every property not defined, * even if it's one of the functions or properties we're adding to the * prototype manually, such as toString(). * * Override this and chain up if you have Base::resolve in your JSClassOps * vtable, and have overridden Base::proto_properties or * Base::proto_methods. You should add any identifiers in the override that * you have added to the prototype object. */ [[nodiscard]] static bool id_is_never_lazy(jsid id, const GjsAtoms& atoms) { // toString() is always defined somewhere on the prototype chain, so it // is never a lazy property. return id == atoms.to_string(); } protected: /* * GIWrapperBase::resolve: * * Include this in the Base::klass vtable if the class should support lazy * properties. If it is included, then there must be a corresponding * Prototype::resolve_impl() method. * * The *resolved out parameter, on success, should be false to indicate that * id was not resolved; and true if id was resolved. */ GJS_JSAPI_RETURN_CONVENTION static bool resolve(JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool* resolved) { Base* priv = Base::for_js(cx, obj); if (!priv) { // This catches a case in Object where the private struct isn't set // until the initializer is called, so just defer to prototype // chains in this case. // // This isn't too bad: either you get undefined if the field doesn't // exist on any of the prototype chains, or whatever code will run // afterwards will fail because of the "!priv" check there. debug_jsprop_static("Resolve hook", id, obj); *resolved = false; return true; } priv->debug_jsprop("Resolve hook", id, obj); if (!priv->is_prototype()) { // We are an instance, not a prototype, so look for per-instance // props that we want to define on the JSObject. Generally we do not // want to cache these in JS, we want to always pull them from the C // object, or JS would not see any changes made from C. So we use // the property accessors, not this resolve hook. *resolved = false; return true; } const GjsAtoms& atoms = GjsContextPrivate::atoms(cx); if (id_is_never_lazy(id, atoms)) { *resolved = false; return true; } return priv->to_prototype()->resolve_impl(cx, obj, id, resolved); } /* * GIWrapperBase::finalize: * * This should always be included in the Base::klass vtable. The destructors * of Prototype and Instance will be called in the finalize hook. It is not * necessary to include a finalize_impl() function in Prototype or Instance. * Any needed finalization should be done in ~Prototype() and ~Instance(). */ static void finalize(JSFreeOp* fop, JSObject* obj) { Base* priv = Base::for_js_nocheck(obj); if (!priv) return; // construction didn't finish // Call only GIWrapperBase's original method here, not any overrides; // e.g., we don't want to deal with a read barrier in ObjectInstance. static_cast(priv)->debug_lifecycle(obj, "Finalize"); if (priv->is_prototype()) priv->to_prototype()->finalize_impl(fop, obj); else priv->to_instance()->finalize_impl(fop, obj); // Remove the pointer from the JSObject JS_SetPrivate(obj, nullptr); } /* * GIWrapperBase::trace: * * This should be included in the Base::klass vtable if any of the Base, * Prototype or Instance structures contain any members that the JS garbage * collector must trace. Each struct containing such members must override * GIWrapperBase::trace_impl(), GIWrapperPrototype::trace_impl(), and/or * GIWrapperInstance::trace_impl() in order to perform the trace. */ static void trace(JSTracer* trc, JSObject* obj) { Base* priv = Base::for_js_nocheck(obj); if (!priv) return; // Don't log in trace(). That would overrun even the most verbose logs. if (priv->is_prototype()) priv->to_prototype()->trace_impl(trc); else priv->to_instance()->trace_impl(trc); priv->trace_impl(trc); } /* * GIWrapperBase::trace_impl: * Override if necessary. See trace(). */ void trace_impl(JSTracer*) {} // JSNative methods /* * GIWrapperBase::constructor: * * C++ implementation of the JS constructor passed to JS_InitClass(). Only * called on instances, never on prototypes. This method contains the * functionality common to all GI wrapper classes. There must be a * corresponding Instance::constructor_impl method containing the rest of * the functionality. */ GJS_JSAPI_RETURN_CONVENTION static bool constructor(JSContext* cx, unsigned argc, JS::Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); if (!args.isConstructing()) { gjs_throw_constructor_error(cx); return false; } JS::RootedObject obj( cx, JS_NewObjectForConstructor(cx, &Base::klass, args)); if (!obj) return false; JS::RootedObject proto(cx); if (!JS_GetPrototype(cx, obj, &proto)) return false; if (JS_GetClass(proto) != &Base::klass) { gjs_throw(cx, "Tried to construct an object without a GType"); return false; } args.rval().setUndefined(); Instance* priv = Instance::new_for_js_object(cx, obj); if (!priv->constructor_impl(cx, obj, args)) return false; static_cast(priv)->debug_lifecycle(obj, "JSObject created"); gjs_debug_lifecycle(Base::debug_topic, "m_proto is %p", priv->get_prototype()); // We may need to return a value different from obj (for example because // we delegate to another constructor) if (args.rval().isUndefined()) args.rval().setObject(*obj); return true; } /* * GIWrapperBase::to_string: * * JSNative method connected to the toString() method in JS. */ GJS_JSAPI_RETURN_CONVENTION static bool to_string(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_GET_WRAPPER_PRIV(cx, argc, vp, args, obj, Base, priv); return gjs_wrapper_to_string_func( cx, obj, static_cast(priv)->to_string_kind(), priv->info(), priv->gtype(), priv->ptr_addr(), args.rval()); } // Helper methods public: /* * GIWrapperBase::check_is_instance: * @for_what: string used in the exception message if an exception is thrown * * Used in JSNative methods to ensure the passed-in JS object is an instance * and not the prototype. Throws a JS exception if the prototype is passed * in. */ GJS_JSAPI_RETURN_CONVENTION bool check_is_instance(JSContext* cx, const char* for_what) const { if (!is_prototype()) return true; gjs_throw(cx, "Can't %s on %s.%s.prototype; only on instances", for_what, ns(), name()); return false; } /* * GIWrapperBase::to_c_ptr: * * Returns the underlying C pointer of the wrapped object, or throws a JS * exception if that is not possible (for example, the passed-in JS object * is the prototype.) * * Includes a JS typecheck (but without any extra typecheck of the GType or * introspection info that you would get from GIWrapperBase::typecheck(), so * if you want that you still have to do the typecheck before calling this * method.) */ template GJS_JSAPI_RETURN_CONVENTION static T* to_c_ptr(JSContext* cx, JS::HandleObject obj) { Base* priv = Base::for_js_typecheck(cx, obj); if (!priv || !priv->check_is_instance(cx, "get a C pointer")) return nullptr; return static_cast(priv->to_instance()->ptr()); } /* * GIWrapperBase::transfer_to_gi_argument: * @arg: #GIArgument to fill with the value from @obj * @transfer_direction: Either %GI_DIRECTION_IN or %GI_DIRECTION_OUT * @transfer_ownership: #GITransfer value specifying whether @arg should * copy or acquire a reference to the value or not * @expected_gtype: #GType to perform a typecheck with * @expected_info: Introspection info to perform a typecheck with * * Prepares @arg for passing the value from @obj into C code. It will get a * C pointer from @obj and assign it to @arg's pointer field, taking a * reference with GIWrapperInstance::copy_ptr() if @transfer_direction and * @transfer_ownership indicate that it should. * * Includes a typecheck using GIWrapperBase::typecheck(), to which * @expected_gtype and @expected_info are passed. * * If returning false, then @arg's pointer field is null. */ GJS_JSAPI_RETURN_CONVENTION static bool transfer_to_gi_argument(JSContext* cx, JS::HandleObject obj, GIArgument* arg, GIDirection transfer_direction, GITransfer transfer_ownership, GType expected_gtype, GIBaseInfo* expected_info = nullptr) { g_assert(transfer_direction != GI_DIRECTION_INOUT && "transfer_to_gi_argument() must choose between in or out"); if (!Base::typecheck(cx, obj, expected_info, expected_gtype)) { gjs_arg_unset(arg); return false; } gjs_arg_set(arg, Base::to_c_ptr(cx, obj)); if (!gjs_arg_get(arg)) return false; if ((transfer_direction == GI_DIRECTION_IN && transfer_ownership != GI_TRANSFER_NOTHING) || (transfer_direction == GI_DIRECTION_OUT && transfer_ownership == GI_TRANSFER_EVERYTHING)) { gjs_arg_set(arg, Instance::copy_ptr(cx, expected_gtype, gjs_arg_get(arg))); if (!gjs_arg_get(arg)) return false; } return true; } // Public typecheck API /* * GIWrapperBase::typecheck: * @expected_info: (nullable): GI info to check * @expected_type: (nullable): GType to check * * Checks not only that the JS object is of the correct JSClass (like * for_js_typecheck() does); but also that the object is an instance, not * the protptype; and that the instance's wrapped pointer is of the correct * GType or GI info. * * The overload with a GjsTypecheckNoThrow parameter will not throw a JS * exception if the prototype is passed in or the typecheck fails. */ GJS_JSAPI_RETURN_CONVENTION static bool typecheck(JSContext* cx, JS::HandleObject object, GIBaseInfo* expected_info, GType expected_gtype) { Base* priv = Base::for_js_typecheck(cx, object); if (!priv || !priv->check_is_instance(cx, "convert to pointer")) return false; if (priv->to_instance()->typecheck_impl(cx, expected_info, expected_gtype)) return true; if (expected_info) { gjs_throw_custom( cx, JSProto_TypeError, nullptr, "Object is of type %s.%s - cannot convert to %s.%s", priv->ns(), priv->name(), g_base_info_get_namespace(expected_info), g_base_info_get_name(expected_info)); } else { gjs_throw_custom(cx, JSProto_TypeError, nullptr, "Object is of type %s.%s - cannot convert to %s", priv->ns(), priv->name(), g_type_name(expected_gtype)); } return false; } [[nodiscard]] static bool typecheck(JSContext* cx, JS::HandleObject object, GIBaseInfo* expected_info, GType expected_gtype, GjsTypecheckNoThrow) { Base* priv = Base::for_js(cx, object); if (!priv || priv->is_prototype()) return false; return priv->to_instance()->typecheck_impl(cx, expected_info, expected_gtype); } // Deleting these constructors and assignment operators will also delete // them from derived classes. GIWrapperBase(const GIWrapperBase& other) = delete; GIWrapperBase(GIWrapperBase&& other) = delete; GIWrapperBase& operator=(const GIWrapperBase& other) = delete; GIWrapperBase& operator=(GIWrapperBase&& other) = delete; }; /* * GIWrapperPrototype: * * The specialization of GIWrapperBase which becomes the private data of JS * prototype objects. For example, it is the parent class of BoxedPrototype. * * Classes inheriting from GIWrapperPrototype must declare "friend class * GIWrapperBase" as well as the normal CRTP requirement of "friend class * GIWrapperPrototype", because of the unusual polymorphism scheme, in order for * Base to call methods such as trace_impl(). */ template class GIWrapperPrototype : public Base { protected: // m_info may be null in the case of JS-defined types, or internal types // not exposed through introspection, such as GLocalFile. Not all subclasses // of GIWrapperPrototype support this. Object and Interface support it in // any case. Info* m_info; GType m_gtype; explicit GIWrapperPrototype(Info* info, GType gtype) : Base(), m_info(info ? g_base_info_ref(info) : nullptr), m_gtype(gtype) { Base::debug_lifecycle("Prototype constructor"); } ~GIWrapperPrototype(void) { g_clear_pointer(&m_info, g_base_info_unref); } /* * GIWrapperPrototype::init: * * Performs any initialization that cannot be done in the constructor of * GIWrapperPrototype, either because it can fail, or because it can cause a * garbage collection. * * This default implementation does nothing. Override in a subclass if * necessary. */ GJS_JSAPI_RETURN_CONVENTION bool init(JSContext*) { return true; } // The following four methods are private because they are used only in // create_class(). private: /* * GIWrapperPrototype::parent_proto: * * Returns in @proto the parent class's prototype object, or nullptr if * there is none. * * This default implementation is for GObject introspection types that can't * inherit in JS, like Boxed and Union. Override this if the type can * inherit in JS. */ GJS_JSAPI_RETURN_CONVENTION bool get_parent_proto(JSContext*, JS::MutableHandleObject proto) const { proto.set(nullptr); return true; } /* * GIWrapperPrototype::constructor_nargs: * * Override this if the type's constructor takes other than 1 argument. */ [[nodiscard]] unsigned constructor_nargs() const { return 1; } /* * GIWrapperPrototype::define_jsclass: * @in_object: JSObject on which to define the class constructor as a * property * @parent_proto: (nullable): prototype of the prototype * @constructor: return location for the constructor function object * @prototype: return location for the prototype object * * Defines a JS class with constructor and prototype, and optionally defines * properties and methods on the prototype object, and methods on the * constructor object. * * By default no properties or methods are defined, but derived classes can * override the GIWrapperBase::proto_properties, * GIWrapperBase::proto_methods, and GIWrapperBase::static_methods members. * Static properties would also be possible but are not used anywhere in GJS * so are not implemented yet. * * Note: no prototype methods are defined if @parent_proto is null. * * Here is a refresher comment on the difference between __proto__ and * prototype that has been in the GJS codebase since forever: * * https://web.archive.org/web/20100716231157/http://egachine.berlios.de/embedding-sm-best-practice/apa.html * https://www.sitepoint.com/javascript-inheritance/ * http://www.cs.rit.edu/~atk/JavaScript/manuals/jsobj/ * * What we want is: repoobj.Gtk.Window is constructor for a GtkWindow * wrapper JSObject (gjs_define_object_class() is supposed to define Window * in Gtk.) * * Window.prototype contains the methods on Window, e.g. set_default_size() * mywindow.__proto__ is Window.prototype * mywindow.__proto__.__proto__ is Bin.prototype * mywindow.__proto__.__proto__.__proto__ is Container.prototype * * Because Window.prototype is an instance of Window in a sense, * Window.prototype.__proto__ is Window.prototype, just as * mywindow.__proto__ is Window.prototype * * If we do "mywindow = new Window()" then we should get: * mywindow.__proto__ == Window.prototype * which means "mywindow instanceof Window" is true. * * Remember "Window.prototype" is "the __proto__ of stuff constructed with * new Window()" * * __proto__ is used to search for properties if you do "this.foo", while * .prototype is only relevant for constructors and is used to set __proto__ * on new'd objects. So .prototype only makes sense on constructors. * * JS_SetPrototype() and JS_GetPrototype() are for __proto__. To set/get * .prototype, just use the normal property accessors, or JS_InitClass() * sets it up automatically. */ GJS_JSAPI_RETURN_CONVENTION bool define_jsclass(JSContext* cx, JS::HandleObject in_object, JS::HandleObject parent_proto, JS::MutableHandleObject constructor, JS::MutableHandleObject prototype) { // The GI namespace is only used to set the JSClass->name field (exposed // by Object.prototype.toString, for example). We can safely set // "unknown" if this is a custom or internal JS class with no GI // namespace, as in that case the name is already globally unique (it's // a GType name). const char* gi_namespace = Base::info() ? Base::ns() : "unknown"; unsigned nargs = static_cast(this)->constructor_nargs(); if (!gjs_init_class_dynamic( cx, in_object, parent_proto, gi_namespace, Base::name(), &Base::klass, &Base::constructor, nargs, Base::proto_properties, parent_proto ? nullptr : Base::proto_methods, Base::static_properties, Base::static_methods, prototype, constructor)) return false; gjs_debug(Base::debug_topic, "Defined class for %s (%s), prototype %p, " "JSClass %p, in object %p", Base::name(), Base::type_name(), prototype.get(), JS_GetClass(prototype), in_object.get()); return true; } /* * GIWrapperPrototype::define_static_methods: * * Defines all introspectable static methods on @constructor, including * class methods for objects, and interface methods for interfaces. See * gjs_define_static_methods() for details. * * It requires Prototype to have an info_type_tag member to indicate * the correct template specialization of gjs_define_static_methods(). */ GJS_JSAPI_RETURN_CONVENTION bool define_static_methods(JSContext* cx, JS::HandleObject constructor) { if (!info()) return true; // no introspection means no methods to define return gjs_define_static_methods( cx, constructor, m_gtype, m_info); } public: /** * GIWrapperPrototype::create_class: * @in_object: JSObject on which to define the class constructor as a * property * @info: (nullable): Introspection info for the class, or null if the class * has been defined in JS * @gtype: GType for the class * @constructor: return location for the constructor function object * @prototype: return location for the prototype object * * Creates a JS class that wraps a GI pointer, by defining its constructor * function and prototype object. The prototype object is given an instance * of GIWrapperPrototype as its private data, which is also returned. * Basically treat this method as the public constructor. * * Also defines all the requested methods and properties on the prototype * and constructor objects (see define_jsclass()), as well as a `$gtype` * property and a toString() method. * * This method can be overridden and chained up to if the derived class * needs to define more properties on the constructor or prototype objects, * e.g. eager GI properties. */ GJS_JSAPI_RETURN_CONVENTION static Prototype* create_class(JSContext* cx, JS::HandleObject in_object, Info* info, GType gtype, JS::MutableHandleObject constructor, JS::MutableHandleObject prototype) { g_assert(in_object); g_assert(gtype != G_TYPE_INVALID); // We have to keep the Prototype in an arcbox because some of its // members are needed in some Instance destructors, e.g. m_gtype to // figure out how to free the Instance's m_ptr, and m_info to figure out // how many bytes to free if it is allocated directly. Storing a // refcount on the prototype is cheaper than storing pointers to m_info // and m_gtype on each instance. auto* priv = g_atomic_rc_box_new0(Prototype); new (priv) Prototype(info, gtype); if (!priv->init(cx)) return nullptr; JS::RootedObject parent_proto(cx); if (!priv->get_parent_proto(cx, &parent_proto) || !priv->define_jsclass(cx, in_object, parent_proto, constructor, prototype)) return nullptr; // Init the private variable of @private before we do anything else. If // a garbage collection or error happens subsequently, then this object // might be traced and we would end up dereferencing a null pointer. JS_SetPrivate(prototype, priv); if (!gjs_wrapper_define_gtype_prop(cx, constructor, gtype)) return nullptr; // Every class has a toString() with C++ implementation, so define that // without requiring it to be listed in Base::proto_methods if (!parent_proto) { const GjsAtoms& atoms = GjsContextPrivate::atoms(cx); if (!JS_DefineFunctionById(cx, prototype, atoms.to_string(), &Base::to_string, 0, GJS_MODULE_PROP_FLAGS)) return nullptr; } if (!priv->define_static_methods(cx, constructor)) return nullptr; return priv; } // Methods to get an existing Prototype /* * GIWrapperPrototype::for_js: * * Like Base::for_js(), but asserts that the returned private struct is a * Prototype and not an Instance. */ [[nodiscard]] static Prototype* for_js(JSContext* cx, JS::HandleObject wrapper) { return Base::for_js(cx, wrapper)->to_prototype(); } /* * GIWrapperPrototype::for_js_prototype: * * Gets the Prototype private data from to @wrapper.prototype. Cannot return * null, and asserts so. */ [[nodiscard]] static Prototype* for_js_prototype(JSContext* cx, JS::HandleObject wrapper) { JS::RootedObject proto(cx); JS_GetPrototype(cx, wrapper, &proto); Base* retval = Base::for_js(cx, proto); g_assert(retval); return retval->to_prototype(); } // Accessors [[nodiscard]] Info* info() const { return m_info; } [[nodiscard]] GType gtype() const { return m_gtype; } // Helper methods private: static void destroy_notify(void* ptr) { static_cast(ptr)->~Prototype(); } public: Prototype* acquire(void) { g_atomic_rc_box_acquire(this); return static_cast(this); } void release(void) { g_atomic_rc_box_release_full(this, &destroy_notify); } // JSClass operations protected: void finalize_impl(JSFreeOp*, JSObject*) { release(); } // Override if necessary void trace_impl(JSTracer*) {} }; /* * GIWrapperInstance: * * The specialization of GIWrapperBase which becomes the private data of JS * instance objects. For example, it is the parent class of BoxedInstance. * * Classes inheriting from GIWrapperInstance must declare "friend class * GIWrapperBase" as well as the normal CRTP requirement of "friend class * GIWrapperInstance", because of the unusual polymorphism scheme, in order for * Base to call methods such as trace_impl(). */ template class GIWrapperInstance : public Base { protected: Wrapped* m_ptr; explicit GIWrapperInstance(JSContext* cx, JS::HandleObject obj) : Base(Prototype::for_js_prototype(cx, obj)) { Base::m_proto->acquire(); Base::GIWrapperBase::debug_lifecycle(obj, "Instance constructor"); } ~GIWrapperInstance(void) { Base::m_proto->release(); } public: /* * GIWrapperInstance::new_for_js_object: * * Creates a GIWrapperInstance and associates it with @obj as its private * data. This is called by the JS constructor. Uses the slice allocator. */ [[nodiscard]] static Instance* new_for_js_object(JSContext* cx, JS::HandleObject obj) { g_assert(!JS_GetPrivate(obj)); auto* priv = g_slice_new0(Instance); new (priv) Instance(cx, obj); // Init the private variable before we do anything else. If a garbage // collection happens when calling the constructor, then this object // might be traced and we would end up dereferencing a null pointer. JS_SetPrivate(obj, priv); return priv; } // Method to get an existing Instance /* * GIWrapperInstance::for_js: * * Like Base::for_js(), but asserts that the returned private struct is an * Instance and not a Prototype. */ [[nodiscard]] static Instance* for_js(JSContext* cx, JS::HandleObject wrapper) { return Base::for_js(cx, wrapper)->to_instance(); } // Accessors [[nodiscard]] Wrapped* ptr() const { return m_ptr; } /* * GIWrapperInstance::raw_ptr: * * Like ptr(), but returns a byte pointer for use in byte arithmetic. */ [[nodiscard]] uint8_t* raw_ptr() const { return reinterpret_cast(m_ptr); } // JSClass operations protected: void finalize_impl(JSFreeOp*, JSObject*) { static_cast(this)->~Instance(); g_slice_free(Instance, this); } // Override if necessary void trace_impl(JSTracer*) {} // Helper methods /* * GIWrapperInstance::typecheck_impl: * * See GIWrapperBase::typecheck(). Checks that the instance's wrapped * pointer is of the correct GType or GI info. Does not throw a JS * exception. * * It's possible to override typecheck_impl() if you need an extra step in * the check. */ [[nodiscard]] bool typecheck_impl(JSContext*, GIBaseInfo* expected_info, GType expected_gtype) const { if (expected_gtype != G_TYPE_NONE) return g_type_is_a(Base::gtype(), expected_gtype); else if (expected_info) return g_base_info_equal(Base::info(), expected_info); return true; } }; #endif // GI_WRAPPERUTILS_H_ cjs-5.2.0/gi/arg.h0000644000175000017500000001224614144444702013765 0ustar jpeisachjpeisach/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* * Copyright (c) 2008 litl, LLC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #ifndef GI_ARG_H_ #define GI_ARG_H_ #include #include // for size_t #include #include #include #include #include "cjs/macros.h" // Different roles for a GIArgument; currently used only in exception and debug // messages. typedef enum { GJS_ARGUMENT_ARGUMENT, GJS_ARGUMENT_RETURN_VALUE, GJS_ARGUMENT_FIELD, GJS_ARGUMENT_LIST_ELEMENT, GJS_ARGUMENT_HASH_ELEMENT, GJS_ARGUMENT_ARRAY_ELEMENT } GjsArgumentType; [[nodiscard]] char* gjs_argument_display_name(const char* arg_name, GjsArgumentType arg_type); GJS_JSAPI_RETURN_CONVENTION bool gjs_value_to_arg(JSContext *context, JS::HandleValue value, GIArgInfo *arg_info, GIArgument *arg); GJS_JSAPI_RETURN_CONVENTION bool gjs_array_to_explicit_array(JSContext* cx, JS::HandleValue value, GITypeInfo* type_info, const char* arg_name, GjsArgumentType arg_type, GITransfer transfer, bool may_be_null, void** contents, size_t* length_p); void gjs_gi_argument_init_default(GITypeInfo* type_info, GIArgument* arg); GJS_JSAPI_RETURN_CONVENTION bool gjs_value_to_g_argument (JSContext *context, JS::HandleValue value, GITypeInfo *type_info, const char *arg_name, GjsArgumentType argument_type, GITransfer transfer, bool may_be_null, GArgument *arg); GJS_JSAPI_RETURN_CONVENTION bool gjs_value_from_g_argument(JSContext *context, JS::MutableHandleValue value_p, GITypeInfo *type_info, GIArgument *arg, bool copy_structs); GJS_JSAPI_RETURN_CONVENTION bool gjs_value_from_explicit_array(JSContext *context, JS::MutableHandleValue value_p, GITypeInfo *type_info, GIArgument *arg, int length); GJS_JSAPI_RETURN_CONVENTION bool gjs_g_argument_release (JSContext *context, GITransfer transfer, GITypeInfo *type_info, GArgument *arg); GJS_JSAPI_RETURN_CONVENTION bool gjs_g_argument_release_out_array(JSContext* cx, GITransfer transfer, GITypeInfo* type_info, unsigned length, GIArgument* arg); GJS_JSAPI_RETURN_CONVENTION bool gjs_g_argument_release_in_array(JSContext* cx, GITransfer transfer, GITypeInfo* type_info, unsigned length, GIArgument* arg); GJS_JSAPI_RETURN_CONVENTION bool gjs_g_argument_release_in_arg (JSContext *context, GITransfer transfer, GITypeInfo *type_info, GArgument *arg); GJS_JSAPI_RETURN_CONVENTION bool _gjs_flags_value_is_valid(JSContext* cx, GType gtype, int64_t value); [[nodiscard]] int64_t _gjs_enum_from_int(GIEnumInfo* enum_info, int int_value); GJS_JSAPI_RETURN_CONVENTION bool gjs_array_from_strv(JSContext *context, JS::MutableHandleValue value_p, const char **strv); GJS_JSAPI_RETURN_CONVENTION bool gjs_array_to_strv (JSContext *context, JS::Value array_value, unsigned int length, void **arr_p); #endif // GI_ARG_H_ cjs-5.2.0/gi/gjs_gi_trace.h0000644000175000017500000000307214144444702015631 0ustar jpeisachjpeisach/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* * Copyright (c) 2010 Red Hat, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. * * Author: Colin Walters */ #ifndef GI_GJS_GI_TRACE_H_ #define GI_GJS_GI_TRACE_H_ #include #ifdef HAVE_DTRACE /* include the generated probes header and put markers in code */ #include "gjs_gi_probes.h" #define TRACE(probe) probe #else /* Wrap the probe to allow it to be removed when no systemtap available */ #define TRACE(probe) #endif #endif // GI_GJS_GI_TRACE_H_ cjs-5.2.0/gi/foreign.h0000644000175000017500000000770514144444702014651 0ustar jpeisachjpeisach/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* * Copyright (c) 2010 litl, LLC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #ifndef GI_FOREIGN_H_ #define GI_FOREIGN_H_ #include #include #include #include #include #include "gi/arg.h" #include "cjs/macros.h" typedef bool (*GjsArgOverrideToGArgumentFunc) (JSContext *context, JS::Value value, const char *arg_name, GjsArgumentType argument_type, GITransfer transfer, bool may_be_null, GArgument *arg); typedef bool (*GjsArgOverrideFromGArgumentFunc) (JSContext *context, JS::MutableHandleValue value_p, GIArgument *arg); typedef bool (*GjsArgOverrideReleaseGArgumentFunc) (JSContext *context, GITransfer transfer, GArgument *arg); typedef struct { GjsArgOverrideToGArgumentFunc to_func; GjsArgOverrideFromGArgumentFunc from_func; GjsArgOverrideReleaseGArgumentFunc release_func; } GjsForeignInfo; void gjs_struct_foreign_register(const char* gi_namespace, const char* type_name, GjsForeignInfo* info); GJS_JSAPI_RETURN_CONVENTION bool gjs_struct_foreign_convert_to_g_argument (JSContext *context, JS::Value value, GIBaseInfo *interface_info, const char *arg_name, GjsArgumentType argument_type, GITransfer transfer, bool may_be_null, GArgument *arg); GJS_JSAPI_RETURN_CONVENTION bool gjs_struct_foreign_convert_from_g_argument(JSContext *context, JS::MutableHandleValue value_p, GIBaseInfo *interface_info, GIArgument *arg); GJS_JSAPI_RETURN_CONVENTION bool gjs_struct_foreign_release_g_argument (JSContext *context, GITransfer transfer, GIBaseInfo *interface_info, GArgument *arg); #endif // GI_FOREIGN_H_ cjs-5.2.0/gi/ns.cpp0000644000175000017500000001602214144444702014163 0ustar jpeisachjpeisach/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* * Copyright (c) 2008 litl, LLC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #include #include #include #include #include #include // for JSID_IS_STRING #include #include #include #include // for UniqueChars #include // for JS_GetPrivate, JS_NewObjectWithGivenProto #include "gi/ns.h" #include "gi/repo.h" #include "cjs/atoms.h" #include "cjs/context-private.h" #include "cjs/jsapi-class.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" #include "cjs/mem-private.h" #include "util/log.h" typedef struct { char *gi_namespace; } Ns; extern struct JSClass gjs_ns_class; GJS_DEFINE_PRIV_FROM_JS(Ns, gjs_ns_class) /* The *resolved out parameter, on success, should be false to indicate that id * was not resolved; and true if id was resolved. */ GJS_JSAPI_RETURN_CONVENTION static bool ns_resolve(JSContext *context, JS::HandleObject obj, JS::HandleId id, bool *resolved) { Ns *priv; bool defined; if (!JSID_IS_STRING(id)) { *resolved = false; return true; /* not resolved, but no error */ } /* let Object.prototype resolve these */ const GjsAtoms& atoms = GjsContextPrivate::atoms(context); if (id == atoms.to_string() || id == atoms.value_of()) { *resolved = false; return true; } priv = priv_from_js(context, obj); gjs_debug_jsprop(GJS_DEBUG_GNAMESPACE, "Resolve prop '%s' hook, obj %s, priv %p", gjs_debug_id(id).c_str(), gjs_debug_object(obj).c_str(), priv); if (!priv) { *resolved = false; /* we are the prototype, or have the wrong class */ return true; } JS::UniqueChars name; if (!gjs_get_string_id(context, id, &name)) return false; if (!name) { *resolved = false; return true; /* not resolved, but no error */ } GjsAutoBaseInfo info = g_irepository_find_by_name(nullptr, priv->gi_namespace, name.get()); if (!info) { *resolved = false; /* No property defined, but no error either */ return true; } gjs_debug(GJS_DEBUG_GNAMESPACE, "Found info type %s for '%s' in namespace '%s'", gjs_info_type_name(info.type()), info.name(), info.ns()); if (!gjs_define_info(context, obj, info, &defined)) { gjs_debug(GJS_DEBUG_GNAMESPACE, "Failed to define info '%s'", info.name()); return false; } /* we defined the property in this object? */ *resolved = defined; return true; } GJS_JSAPI_RETURN_CONVENTION static bool ns_new_enumerate(JSContext* cx, JS::HandleObject obj, JS::MutableHandleIdVector properties, bool only_enumerable [[maybe_unused]]) { Ns* priv = priv_from_js(cx, obj); if (!priv) { return true; } int n = g_irepository_get_n_infos(nullptr, priv->gi_namespace); if (!properties.reserve(properties.length() + n)) { JS_ReportOutOfMemory(cx); return false; } for (int k = 0; k < n; k++) { GjsAutoBaseInfo info = g_irepository_get_info(nullptr, priv->gi_namespace, k); const char* name = info.name(); jsid id = gjs_intern_string_to_id(cx, name); if (id == JSID_VOID) return false; properties.infallibleAppend(id); } return true; } GJS_JSAPI_RETURN_CONVENTION static bool get_name (JSContext *context, unsigned argc, JS::Value *vp) { GJS_GET_PRIV(context, argc, vp, args, obj, Ns, priv); if (!priv) return false; return gjs_string_from_utf8(context, priv->gi_namespace, args.rval()); } GJS_NATIVE_CONSTRUCTOR_DEFINE_ABSTRACT(ns) static void ns_finalize(JSFreeOp*, JSObject* obj) { Ns *priv; priv = (Ns *)JS_GetPrivate(obj); gjs_debug_lifecycle(GJS_DEBUG_GNAMESPACE, "finalize, obj %p priv %p", obj, priv); if (!priv) return; /* we are the prototype, not a real instance */ if (priv->gi_namespace) g_free(priv->gi_namespace); GJS_DEC_COUNTER(ns); g_slice_free(Ns, priv); } /* The bizarre thing about this vtable is that it applies to both * instances of the object, and to the prototype that instances of the * class have. */ // clang-format off static const struct JSClassOps gjs_ns_class_ops = { nullptr, // addProperty nullptr, // deleteProperty nullptr, // enumerate ns_new_enumerate, ns_resolve, nullptr, // mayResolve ns_finalize}; struct JSClass gjs_ns_class = { "GIRepositoryNamespace", JSCLASS_HAS_PRIVATE | JSCLASS_FOREGROUND_FINALIZE, &gjs_ns_class_ops }; static JSPropertySpec gjs_ns_proto_props[] = { JS_STRING_SYM_PS(toStringTag, "GIRepositoryNamespace", JSPROP_READONLY), JS_PSG("__name__", get_name, GJS_MODULE_PROP_FLAGS), JS_PS_END }; // clang-format on static JSFunctionSpec *gjs_ns_proto_funcs = nullptr; static JSFunctionSpec *gjs_ns_static_funcs = nullptr; GJS_DEFINE_PROTO_FUNCS(ns) GJS_JSAPI_RETURN_CONVENTION static JSObject* ns_new(JSContext *context, const char *ns_name) { Ns *priv; JS::RootedObject proto(context); if (!gjs_ns_define_proto(context, nullptr, &proto)) return nullptr; JS::RootedObject ns(context, JS_NewObjectWithGivenProto(context, &gjs_ns_class, proto)); if (!ns) return nullptr; priv = g_slice_new0(Ns); GJS_INC_COUNTER(ns); g_assert(!priv_from_js(context, ns)); JS_SetPrivate(ns, priv); gjs_debug_lifecycle(GJS_DEBUG_GNAMESPACE, "ns constructor, obj %p priv %p", ns.get(), priv); priv = priv_from_js(context, ns); priv->gi_namespace = g_strdup(ns_name); return ns; } JSObject* gjs_create_ns(JSContext *context, const char *ns_name) { return ns_new(context, ns_name); } cjs-5.2.0/gi/gobject.cpp0000644000175000017500000002173414144444702015166 0ustar jpeisachjpeisach/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* * Copyright (c) 2008 litl, LLC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #include #include #include // for move, pair #include #include #include #include #include #include // for JS_New, JSAutoRealm, JS_GetProperty #include "gi/gobject.h" #include "gi/object.h" #include "gi/value.h" #include "cjs/context-private.h" #include "cjs/context.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" static std::unordered_map class_init_properties; [[nodiscard]] static JSContext* current_context() { GjsContext* gjs = gjs_context_get_current(); return static_cast(gjs_context_get_native_context(gjs)); } void push_class_init_properties(GType gtype, AutoParamArray* params) { class_init_properties[gtype] = std::move(*params); } bool pop_class_init_properties(GType gtype, AutoParamArray* params_out) { auto found = class_init_properties.find(gtype); if (found == class_init_properties.end()) return false; *params_out = std::move(found->second); class_init_properties.erase(found); return true; } GJS_JSAPI_RETURN_CONVENTION static bool jsobj_set_gproperty(JSContext* cx, JS::HandleObject object, const GValue* value, GParamSpec* pspec) { JS::RootedValue jsvalue(cx); if (!gjs_value_from_g_value(cx, &jsvalue, value)) return false; GjsAutoChar underscore_name = gjs_hyphen_to_underscore(pspec->name); return JS_SetProperty(cx, object, underscore_name, jsvalue); } static void gjs_object_base_init(void* klass) { auto* priv = ObjectPrototype::for_gtype(G_OBJECT_CLASS_TYPE(klass)); if (priv) priv->ref_vfuncs(); } static void gjs_object_base_finalize(void* klass) { auto* priv = ObjectPrototype::for_gtype(G_OBJECT_CLASS_TYPE(klass)); if (priv) priv->unref_vfuncs(); } static GObject* gjs_object_constructor( GType type, unsigned n_construct_properties, GObjectConstructParam* construct_properties) { JSContext* cx = current_context(); GjsContextPrivate* gjs = GjsContextPrivate::from_cx(cx); if (!gjs->object_init_list().empty()) { GType parent_type = g_type_parent(type); /* The object is being constructed from JS: * Simply chain up to the first non-gjs constructor */ while (G_OBJECT_CLASS(g_type_class_peek(parent_type))->constructor == gjs_object_constructor) parent_type = g_type_parent(parent_type); return G_OBJECT_CLASS(g_type_class_peek(parent_type)) ->constructor(type, n_construct_properties, construct_properties); } /* The object is being constructed from native code (e.g. GtkBuilder): * Construct the JS object from the constructor, then use the GObject * that was associated in gjs_object_custom_init() */ JSAutoRealm ar(cx, gjs_get_import_global(cx)); JS::RootedObject constructor( cx, gjs_lookup_object_constructor_from_info(cx, nullptr, type)); if (!constructor) return nullptr; JSObject* object; if (n_construct_properties) { JS::RootedObject props_hash(cx, JS_NewPlainObject(cx)); for (unsigned i = 0; i < n_construct_properties; i++) if (!jsobj_set_gproperty(cx, props_hash, construct_properties[i].value, construct_properties[i].pspec)) return nullptr; JS::RootedValueArray<1> args(cx); args[0].set(JS::ObjectValue(*props_hash)); object = JS_New(cx, constructor, args); } else { object = JS_New(cx, constructor, JS::HandleValueArray::empty()); } if (!object) return nullptr; auto* priv = ObjectBase::for_js_nocheck(object); /* Should have been set in init_impl() and pushed into object_init_list, * then popped from object_init_list in gjs_object_custom_init() */ g_assert(priv); /* We only hold a toggle ref at this point, add back a ref that the * native code can own. */ return G_OBJECT(g_object_ref(priv->to_instance()->ptr())); } static void gjs_object_set_gproperty(GObject* object, unsigned property_id [[maybe_unused]], const GValue* value, GParamSpec* pspec) { auto* priv = ObjectInstance::for_gobject(object); JSContext *cx = current_context(); JS::RootedObject js_obj(cx, priv->wrapper()); JSAutoRealm ar(cx, js_obj); if (!jsobj_set_gproperty(cx, js_obj, value, pspec)) gjs_log_exception_uncaught(cx); } static void gjs_object_get_gproperty(GObject* object, unsigned property_id [[maybe_unused]], GValue* value, GParamSpec* pspec) { auto* priv = ObjectInstance::for_gobject(object); JSContext *cx = current_context(); JS::RootedObject js_obj(cx, priv->wrapper()); JS::RootedValue jsvalue(cx); JSAutoRealm ar(cx, js_obj); GjsAutoChar underscore_name = gjs_hyphen_to_underscore(pspec->name); if (!JS_GetProperty(cx, js_obj, underscore_name, &jsvalue)) { gjs_log_exception_uncaught(cx); return; } if (!gjs_value_to_g_value(cx, jsvalue, value)) gjs_log_exception(cx); } static void gjs_object_class_init(void* class_pointer, void*) { GObjectClass* klass = G_OBJECT_CLASS(class_pointer); GType gtype = G_OBJECT_CLASS_TYPE(klass); klass->constructor = gjs_object_constructor; klass->set_property = gjs_object_set_gproperty; klass->get_property = gjs_object_get_gproperty; AutoParamArray properties; if (!pop_class_init_properties(gtype, &properties)) return; unsigned i = 0; for (GjsAutoParam& pspec : properties) { g_param_spec_set_qdata(pspec, ObjectBase::custom_property_quark(), GINT_TO_POINTER(1)); g_object_class_install_property(klass, ++i, pspec); } } static void gjs_object_custom_init(GTypeInstance* instance, void* g_class [[maybe_unused]]) { JSContext *cx = current_context(); GjsContextPrivate* gjs = GjsContextPrivate::from_cx(cx); if (gjs->object_init_list().empty()) return; JS::RootedObject object(cx, gjs->object_init_list().back()); auto* priv_base = ObjectBase::for_js_nocheck(object); g_assert(priv_base); // Should have been set in init_impl() ObjectInstance* priv = priv_base->to_instance(); if (priv_base->gtype() != G_TYPE_FROM_INSTANCE(instance)) { /* This is not the most derived instance_init function, do nothing. */ return; } gjs->object_init_list().popBack(); if (!priv->init_custom_class_from_gobject(cx, object, G_OBJECT(instance))) gjs_log_exception_uncaught(cx); } static void gjs_interface_init(void* g_iface, void*) { GType gtype = G_TYPE_FROM_INTERFACE(g_iface); AutoParamArray properties; if (!pop_class_init_properties(gtype, &properties)) return; for (GjsAutoParam& pspec : properties) { g_param_spec_set_qdata(pspec, ObjectBase::custom_property_quark(), GINT_TO_POINTER(1)); g_object_interface_install_property(g_iface, pspec); } } constexpr GTypeInfo gjs_gobject_class_info = { 0, // class_size gjs_object_base_init, gjs_object_base_finalize, gjs_object_class_init, GClassFinalizeFunc(nullptr), nullptr, // class_data 0, // instance_size 0, // n_preallocs gjs_object_custom_init, }; constexpr GTypeInfo gjs_gobject_interface_info = { sizeof(GTypeInterface), // class_size GBaseInitFunc(nullptr), GBaseFinalizeFunc(nullptr), gjs_interface_init, GClassFinalizeFunc(nullptr), nullptr, // class_data 0, // instance_size 0, // n_preallocs nullptr, // instance_init }; cjs-5.2.0/gi/arg.cpp0000644000175000017500000040761714144444702014332 0ustar jpeisachjpeisach/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* * Copyright (c) 2008 litl, LLC * Copyright (c) 2020 Canonical, Ltd. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #include #include // for strcmp, strlen, memcpy #include // for numeric_limits #include #include #include #include #include #include #include #include #include // for RootedVector, MutableWrappedPtrOp... #include // for JSPROP_ENUMERATE #include #include #include // for UniqueChars #include #include #include // for JS_ReportOutOfMemory, JS_GetElement #include // for JS_IsUint8Array, JS_GetObjectFunc... #include "gi/arg-inl.h" #include "gi/arg.h" #include "gi/boxed.h" #include "gi/foreign.h" #include "gi/fundamental.h" #include "gi/gerror.h" #include "gi/gtype.h" #include "gi/interface.h" #include "gi/object.h" #include "gi/param.h" #include "gi/union.h" #include "gi/value.h" #include "gi/wrapperutils.h" #include "cjs/atoms.h" #include "cjs/byteArray.h" #include "cjs/context-private.h" #include "cjs/jsapi-util.h" #include "util/log.h" bool _gjs_flags_value_is_valid(JSContext* context, GType gtype, int64_t value) { GFlagsValue *v; guint32 tmpval; /* FIXME: Do proper value check for flags with GType's */ if (gtype == G_TYPE_NONE) return true; GjsAutoTypeClass klass(gtype); /* check all bits are defined for flags.. not necessarily desired */ tmpval = (guint32)value; if (tmpval != value) { /* Not a guint32 */ gjs_throw(context, "0x%" G_GINT64_MODIFIER "x is not a valid value for flags %s", value, g_type_name(G_TYPE_FROM_CLASS(klass))); return false; } while (tmpval) { v = g_flags_get_first_value(klass.as(), tmpval); if (!v) { gjs_throw(context, "0x%x is not a valid value for flags %s", (guint32)value, g_type_name(G_TYPE_FROM_CLASS(klass))); return false; } tmpval &= ~v->value; } return true; } GJS_JSAPI_RETURN_CONVENTION static bool _gjs_enum_value_is_valid(JSContext* context, GIEnumInfo* enum_info, int64_t value) { bool found; int n_values; int i; n_values = g_enum_info_get_n_values(enum_info); found = false; for (i = 0; i < n_values; ++i) { GIValueInfo *value_info; value_info = g_enum_info_get_value(enum_info, i); int64_t enum_value = g_value_info_get_value(value_info); g_base_info_unref((GIBaseInfo *)value_info); if (enum_value == value) { found = true; break; } } if (!found) { gjs_throw(context, "%" G_GINT64_MODIFIER "d is not a valid value for enumeration %s", value, g_base_info_get_name((GIBaseInfo *)enum_info)); } return found; } [[nodiscard]] static bool _gjs_enum_uses_signed_type(GIEnumInfo* enum_info) { GITypeTag storage = g_enum_info_get_storage_type(enum_info); return (storage == GI_TYPE_TAG_INT8 || storage == GI_TYPE_TAG_INT16 || storage == GI_TYPE_TAG_INT32 || storage == GI_TYPE_TAG_INT64); } // This is hacky - g_function_info_invoke() and g_field_info_get/set_field() // expect the enum value in gjs_arg_member(arg) and depend on all flags and // enumerations being passed on the stack in a 32-bit field. See FIXME comment // in g_field_info_get_field(). The same assumption of enums cast to 32-bit // signed integers is found in g_value_set_enum()/g_value_set_flags(). [[nodiscard]] int64_t _gjs_enum_from_int(GIEnumInfo* enum_info, int int_value) { if (_gjs_enum_uses_signed_type (enum_info)) return int64_t(int_value); else return int64_t(uint32_t(int_value)); } /* Here for symmetry, but result is the same for the two cases */ [[nodiscard]] static int _gjs_enum_to_int(int64_t value) { return static_cast(value); } /* Check if an argument of the given needs to be released if we created it * from a JS value to pass it into a function and aren't transfering ownership. */ [[nodiscard]] static bool type_needs_release(GITypeInfo* type_info, GITypeTag type_tag) { if (type_tag == GI_TYPE_TAG_UTF8 || type_tag == GI_TYPE_TAG_FILENAME || type_tag == GI_TYPE_TAG_ARRAY || type_tag == GI_TYPE_TAG_GLIST || type_tag == GI_TYPE_TAG_GSLIST || type_tag == GI_TYPE_TAG_GHASH || type_tag == GI_TYPE_TAG_ERROR) return true; if (type_tag == GI_TYPE_TAG_INTERFACE) { GIBaseInfo* interface_info; GIInfoType interface_type; GType gtype; bool needs_release; interface_info = g_type_info_get_interface(type_info); g_assert(interface_info != NULL); interface_type = g_base_info_get_type(interface_info); if (interface_type == GI_INFO_TYPE_STRUCT || interface_type == GI_INFO_TYPE_ENUM || interface_type == GI_INFO_TYPE_FLAGS || interface_type == GI_INFO_TYPE_OBJECT || interface_type == GI_INFO_TYPE_INTERFACE || interface_type == GI_INFO_TYPE_UNION || interface_type == GI_INFO_TYPE_BOXED) { /* These are subtypes of GIRegisteredTypeInfo for which the * cast is safe */ gtype = g_registered_type_info_get_g_type ((GIRegisteredTypeInfo*)interface_info); } else if (interface_type == GI_INFO_TYPE_VALUE) { /* Special case for GValues */ gtype = G_TYPE_VALUE; } else { /* Everything else */ gtype = G_TYPE_NONE; } if (g_type_is_a(gtype, G_TYPE_CLOSURE)) needs_release = true; else if (g_type_is_a(gtype, G_TYPE_VALUE)) needs_release = g_type_info_is_pointer(type_info); else needs_release = false; g_base_info_unref(interface_info); return needs_release; } return false; } /* Check if an argument of the given needs to be released if we obtained it * from out argument (or the return value), and we're transferring ownership */ [[nodiscard]] static bool type_needs_out_release(GITypeInfo* type_info, GITypeTag type_tag) { if (type_tag == GI_TYPE_TAG_UTF8 || type_tag == GI_TYPE_TAG_FILENAME || type_tag == GI_TYPE_TAG_ARRAY || type_tag == GI_TYPE_TAG_GLIST || type_tag == GI_TYPE_TAG_GSLIST || type_tag == GI_TYPE_TAG_GHASH || type_tag == GI_TYPE_TAG_ERROR) return true; if (type_tag == GI_TYPE_TAG_INTERFACE) { GIBaseInfo* interface_info; GIInfoType interface_type; bool needs_release = true; interface_info = g_type_info_get_interface(type_info); g_assert(interface_info != NULL); interface_type = g_base_info_get_type(interface_info); if (interface_type == GI_INFO_TYPE_ENUM || interface_type == GI_INFO_TYPE_FLAGS) needs_release = false; else if (interface_type == GI_INFO_TYPE_STRUCT || interface_type == GI_INFO_TYPE_UNION) needs_release = g_type_info_is_pointer(type_info); g_base_info_unref(interface_info); return needs_release; } return false; } /* FIXME: This should be added to gobject-introspection */ [[nodiscard]] static GITypeTag _g_type_info_get_storage_type(GITypeInfo* info) { GITypeTag type_tag = g_type_info_get_tag(info); if (type_tag == GI_TYPE_TAG_INTERFACE) { GjsAutoBaseInfo interface = g_type_info_get_interface(info); GIInfoType info_type = g_base_info_get_type(interface); if (info_type == GI_INFO_TYPE_ENUM || info_type == GI_INFO_TYPE_FLAGS) return g_enum_info_get_storage_type(interface); } return type_tag; } /* FIXME: This should be added to gobject-introspection */ static void _g_type_info_argument_from_hash_pointer(GITypeInfo* info, void* hash_pointer, GIArgument* arg) { GITypeTag type_tag = _g_type_info_get_storage_type(info); switch (type_tag) { case GI_TYPE_TAG_BOOLEAN: gjs_arg_set(arg, hash_pointer); break; case GI_TYPE_TAG_INT8: gjs_arg_set(arg, hash_pointer); break; case GI_TYPE_TAG_UINT8: gjs_arg_set(arg, hash_pointer); break; case GI_TYPE_TAG_INT16: gjs_arg_set(arg, hash_pointer); break; case GI_TYPE_TAG_UINT16: gjs_arg_set(arg, hash_pointer); break; case GI_TYPE_TAG_INT32: gjs_arg_set(arg, hash_pointer); break; case GI_TYPE_TAG_UINT32: gjs_arg_set(arg, hash_pointer); break; case GI_TYPE_TAG_UNICHAR: gjs_arg_set(arg, hash_pointer); break; case GI_TYPE_TAG_GTYPE: gjs_arg_set(arg, hash_pointer); break; case GI_TYPE_TAG_UTF8: case GI_TYPE_TAG_FILENAME: case GI_TYPE_TAG_INTERFACE: case GI_TYPE_TAG_ARRAY: case GI_TYPE_TAG_GLIST: case GI_TYPE_TAG_GSLIST: case GI_TYPE_TAG_GHASH: case GI_TYPE_TAG_ERROR: gjs_arg_set(arg, hash_pointer); break; case GI_TYPE_TAG_INT64: case GI_TYPE_TAG_UINT64: case GI_TYPE_TAG_FLOAT: case GI_TYPE_TAG_DOUBLE: default: g_critical("Unsupported type for pointer-stuffing: %s", g_type_tag_to_string(type_tag)); gjs_arg_set(arg, hash_pointer); } } /* FIXME: This should be added to gobject-introspection */ [[nodiscard]] static void* _g_type_info_hash_pointer_from_argument( GITypeInfo* info, GIArgument* arg) { GITypeTag type_tag = _g_type_info_get_storage_type(info); switch (type_tag) { case GI_TYPE_TAG_BOOLEAN: return gjs_arg_get_as_pointer(arg); case GI_TYPE_TAG_INT8: return gjs_arg_get_as_pointer(arg); case GI_TYPE_TAG_UINT8: return gjs_arg_get_as_pointer(arg); case GI_TYPE_TAG_INT16: return gjs_arg_get_as_pointer(arg); case GI_TYPE_TAG_UINT16: return gjs_arg_get_as_pointer(arg); case GI_TYPE_TAG_INT32: return gjs_arg_get_as_pointer(arg); case GI_TYPE_TAG_UINT32: return gjs_arg_get_as_pointer(arg); case GI_TYPE_TAG_UNICHAR: return gjs_arg_get_as_pointer(arg); case GI_TYPE_TAG_GTYPE: return gjs_arg_get_as_pointer(arg); case GI_TYPE_TAG_UTF8: case GI_TYPE_TAG_FILENAME: case GI_TYPE_TAG_INTERFACE: case GI_TYPE_TAG_ARRAY: case GI_TYPE_TAG_GLIST: case GI_TYPE_TAG_GSLIST: case GI_TYPE_TAG_GHASH: case GI_TYPE_TAG_ERROR: return gjs_arg_get(arg); case GI_TYPE_TAG_INT64: case GI_TYPE_TAG_UINT64: case GI_TYPE_TAG_FLOAT: case GI_TYPE_TAG_DOUBLE: default: g_critical("Unsupported type for pointer-stuffing: %s", g_type_tag_to_string(type_tag)); return gjs_arg_get(arg); } } GJS_JSAPI_RETURN_CONVENTION static bool gjs_array_to_g_list(JSContext *context, JS::Value array_value, unsigned int length, GITypeInfo *param_info, GITransfer transfer, GITypeTag list_type, GList **list_p, GSList **slist_p) { guint32 i; GList *list; GSList *slist; list = NULL; slist = NULL; if (transfer == GI_TRANSFER_CONTAINER) { if (type_needs_release (param_info, g_type_info_get_tag(param_info))) { /* FIXME: to make this work, we'd have to keep a list of temporary * GArguments for the function call so we could free them after * the surrounding container had been freed by the callee. */ gjs_throw(context, "Container transfer for in parameters not supported"); return false; } transfer = GI_TRANSFER_NOTHING; } JS::RootedObject array(context, array_value.toObjectOrNull()); JS::RootedValue elem(context); for (i = 0; i < length; ++i) { GArgument elem_arg = { 0 }; elem = JS::UndefinedValue(); if (!JS_GetElement(context, array, i, &elem)) { gjs_throw(context, "Missing array element %u", i); return false; } /* FIXME we don't know if the list elements can be NULL. * gobject-introspection needs to tell us this. * Always say they can't for now. */ if (!gjs_value_to_g_argument(context, elem, param_info, NULL, GJS_ARGUMENT_LIST_ELEMENT, transfer, false, &elem_arg)) { return false; } void* hash_pointer = _g_type_info_hash_pointer_from_argument(param_info, &elem_arg); if (list_type == GI_TYPE_TAG_GLIST) { /* GList */ list = g_list_prepend(list, hash_pointer); } else { /* GSList */ slist = g_slist_prepend(slist, hash_pointer); } } list = g_list_reverse(list); slist = g_slist_reverse(slist); *list_p = list; *slist_p = slist; return true; } [[nodiscard]] static GHashTable* create_hash_table_for_key_type( GITypeInfo* key_param_info) { /* Don't use key/value destructor functions here, because we can't * construct correct ones in general if the value type is complex. * Rely on the type-aware g_argument_release functions. */ GITypeTag key_type = g_type_info_get_tag(key_param_info); if (key_type == GI_TYPE_TAG_UTF8 || key_type == GI_TYPE_TAG_FILENAME) return g_hash_table_new(g_str_hash, g_str_equal); return g_hash_table_new(NULL, NULL); } template GJS_JSAPI_RETURN_CONVENTION static inline std::enable_if_t< std::is_integral_v && std::is_signed_v, bool> js_value_convert(JSContext* cx, const JS::HandleValue& value, T* out) { return JS::ToInt32(cx, value, out); } template GJS_JSAPI_RETURN_CONVENTION static inline std::enable_if_t< std::is_integral_v && std::is_unsigned_v, bool> js_value_convert(JSContext* cx, const JS::HandleValue& value, T* out) { return JS::ToUint32(cx, value, out); } template GJS_JSAPI_RETURN_CONVENTION static bool hashtable_int_key( JSContext* cx, const JS::HandleValue& value, bool* out_of_range, void** pointer_out) { Container i; static_assert(std::is_integral_v, "Need an integer"); static_assert(std::numeric_limits::max() >= std::numeric_limits::max(), "Max possible Container value must be at least the max possible IntType value"); static_assert(std::numeric_limits::min() <= std::numeric_limits::min(), "Min possible Container value must be at most the min possible IntType value"); if (!js_value_convert(cx, value, &i)) return false; if (out_of_range && (i > static_cast(std::numeric_limits::max()) || i < static_cast(std::numeric_limits::min()))) *out_of_range = true; *pointer_out = gjs_int_to_pointer(i); return true; } template GJS_JSAPI_RETURN_CONVENTION static inline std::enable_if_t< std::is_signed_v, bool> hashtable_int_key(JSContext* cx, const JS::HandleValue& value, bool* out_of_range, void** pointer_out) { return hashtable_int_key(cx, value, out_of_range, pointer_out); } template GJS_JSAPI_RETURN_CONVENTION static inline std::enable_if_t< std::is_unsigned_v, bool> hashtable_int_key(JSContext* cx, const JS::HandleValue& value, bool* out_of_range, void** pointer_out) { return hashtable_int_key(cx, value, out_of_range, pointer_out); } /* Converts a JS::Value to a GHashTable key, stuffing it into @pointer_out if * possible, otherwise giving the location of an allocated key in @pointer_out. */ GJS_JSAPI_RETURN_CONVENTION static bool value_to_ghashtable_key(JSContext *cx, JS::HandleValue value, GITypeInfo *type_info, gpointer *pointer_out) { GITypeTag type_tag = g_type_info_get_tag((GITypeInfo*) type_info); bool out_of_range = false; bool unsupported = false; g_return_val_if_fail(value.isString() || value.isInt32(), false); gjs_debug_marshal(GJS_DEBUG_GFUNCTION, "Converting JS::Value to GHashTable key %s", g_type_tag_to_string(type_tag)); switch (type_tag) { case GI_TYPE_TAG_BOOLEAN: /* This doesn't seem particularly useful, but it's easy */ *pointer_out = gjs_int_to_pointer(JS::ToBoolean(value)); break; case GI_TYPE_TAG_UNICHAR: if (value.isInt32()) { *pointer_out = gjs_int_to_pointer(value.toInt32()); } else { uint32_t ch; if (!gjs_unichar_from_string(cx, value, &ch)) return false; *pointer_out = gjs_int_to_pointer(ch); } break; case GI_TYPE_TAG_INT8: if (!hashtable_int_key(cx, value, &out_of_range, pointer_out)) return false; break; case GI_TYPE_TAG_INT16: if (!hashtable_int_key(cx, value, &out_of_range, pointer_out)) return false; break; case GI_TYPE_TAG_INT32: if (!hashtable_int_key(cx, value, &out_of_range, pointer_out)) return false; break; case GI_TYPE_TAG_UINT8: if (!hashtable_int_key(cx, value, &out_of_range, pointer_out)) return false; break; case GI_TYPE_TAG_UINT16: if (!hashtable_int_key(cx, value, &out_of_range, pointer_out)) return false; break; case GI_TYPE_TAG_UINT32: if (!hashtable_int_key(cx, value, &out_of_range, pointer_out)) return false; break; case GI_TYPE_TAG_FILENAME: { GjsAutoChar cstr; JS::RootedValue str_val(cx, value); if (!str_val.isString()) { JS::RootedString str(cx, JS::ToString(cx, str_val)); str_val.setString(str); } if (!gjs_string_to_filename(cx, str_val, &cstr)) return false; *pointer_out = cstr.release(); break; } case GI_TYPE_TAG_UTF8: { JS::RootedString str(cx); if (!value.isString()) str = JS::ToString(cx, value); else str = value.toString(); JS::UniqueChars cstr(JS_EncodeStringToUTF8(cx, str)); if (!cstr) return false; *pointer_out = g_strdup(cstr.get()); break; } case GI_TYPE_TAG_FLOAT: case GI_TYPE_TAG_DOUBLE: case GI_TYPE_TAG_INT64: case GI_TYPE_TAG_UINT64: /* FIXME: The above four could be supported, but are currently not. The ones * below cannot be key types in a regular JS object; we would need to allow * marshalling Map objects into GHashTables to support those. */ case GI_TYPE_TAG_VOID: case GI_TYPE_TAG_GTYPE: case GI_TYPE_TAG_ERROR: case GI_TYPE_TAG_INTERFACE: case GI_TYPE_TAG_GLIST: case GI_TYPE_TAG_GSLIST: case GI_TYPE_TAG_GHASH: case GI_TYPE_TAG_ARRAY: unsupported = true; break; default: g_warning("Unhandled type %s for GHashTable key conversion", g_type_tag_to_string(type_tag)); unsupported = true; break; } if (G_UNLIKELY(unsupported)) { gjs_throw(cx, "Type %s not supported for hash table keys", g_type_tag_to_string(type_tag)); return false; } if (G_UNLIKELY(out_of_range)) { gjs_throw(cx, "value is out of range for hash table key of type %s", g_type_tag_to_string(type_tag)); return false; } return true; } template [[nodiscard]] static T* heap_value_new_from_arg(GIArgument* val_arg) { T* heap_val = g_new(T, 1); *heap_val = gjs_arg_get(val_arg); return heap_val; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_object_to_g_hash(JSContext *context, JS::Value hash_value, GITypeInfo *key_param_info, GITypeInfo *val_param_info, GITransfer transfer, GHashTable **hash_p) { size_t id_ix, id_len; g_assert(hash_value.isObjectOrNull()); JS::RootedObject props(context, hash_value.toObjectOrNull()); if (transfer == GI_TRANSFER_CONTAINER) { if (type_needs_release (key_param_info, g_type_info_get_tag(key_param_info)) || type_needs_release (val_param_info, g_type_info_get_tag(val_param_info))) { /* FIXME: to make this work, we'd have to keep a list of temporary * GArguments for the function call so we could free them after * the surrounding container had been freed by the callee. */ gjs_throw(context, "Container transfer for in parameters not supported"); return false; } transfer = GI_TRANSFER_NOTHING; } JS::Rooted ids(context, context); if (!JS_Enumerate(context, props, &ids)) return false; GjsAutoPointer result = create_hash_table_for_key_type(key_param_info); JS::RootedValue key_js(context), val_js(context); JS::RootedId cur_id(context); for (id_ix = 0, id_len = ids.length(); id_ix < id_len; ++id_ix) { cur_id = ids[id_ix]; gpointer key_ptr, val_ptr; GIArgument val_arg = { 0 }; if (!JS_IdToValue(context, cur_id, &key_js) || // Type check key type. !value_to_ghashtable_key(context, key_js, key_param_info, &key_ptr) || !JS_GetPropertyById(context, props, cur_id, &val_js) || // Type check and convert value to a C type !gjs_value_to_g_argument(context, val_js, val_param_info, nullptr, GJS_ARGUMENT_HASH_ELEMENT, transfer, true /* allow null */, &val_arg)) return false; GITypeTag val_type = g_type_info_get_tag(val_param_info); /* Use heap-allocated values for types that don't fit in a pointer */ if (val_type == GI_TYPE_TAG_INT64) { val_ptr = heap_value_new_from_arg(&val_arg); } else if (val_type == GI_TYPE_TAG_UINT64) { val_ptr = heap_value_new_from_arg(&val_arg); } else if (val_type == GI_TYPE_TAG_FLOAT) { val_ptr = heap_value_new_from_arg(&val_arg); } else if (val_type == GI_TYPE_TAG_DOUBLE) { val_ptr = heap_value_new_from_arg(&val_arg); } else { // Other types are simply stuffed inside the pointer val_ptr = _g_type_info_hash_pointer_from_argument(val_param_info, &val_arg); } #if __GNUC__ >= 8 // clang-format off _Pragma("GCC diagnostic push") _Pragma("GCC diagnostic ignored \"-Wmaybe-uninitialized\"") #endif // The compiler isn't smart enough to figure out that key_ptr will // always be initialized if value_to_ghashtable_key() returns true. g_hash_table_insert(result, key_ptr, val_ptr); #if __GNUC__ >= 8 _Pragma("GCC diagnostic pop") #endif // clang-format on } *hash_p = result.release(); return true; } bool gjs_array_from_strv(JSContext *context, JS::MutableHandleValue value_p, const char **strv) { guint i; JS::RootedValueVector elems(context); /* We treat a NULL strv as an empty array, since this function should always * set an array value when returning true. * Another alternative would be to set value_p to JS::NullValue, but clients * would need to always check for both an empty array and null if that was * the case. */ for (i = 0; strv != NULL && strv[i] != NULL; i++) { if (!elems.growBy(1)) { JS_ReportOutOfMemory(context); return false; } if (!gjs_string_from_utf8(context, strv[i], elems[i])) return false; } JS::RootedObject obj(context, JS::NewArrayObject(context, elems)); if (!obj) return false; value_p.setObject(*obj); return true; } bool gjs_array_to_strv(JSContext *context, JS::Value array_value, unsigned int length, void **arr_p) { char **result; guint32 i; JS::RootedObject array(context, array_value.toObjectOrNull()); JS::RootedValue elem(context); result = g_new0(char *, length+1); for (i = 0; i < length; ++i) { elem = JS::UndefinedValue(); if (!JS_GetElement(context, array, i, &elem)) { g_free(result); gjs_throw(context, "Missing array element %u", i); return false; } JS::UniqueChars tmp_result = gjs_string_to_utf8(context, elem); if (!tmp_result) { g_strfreev(result); return false; } result[i] = g_strdup(tmp_result.get()); } *arr_p = result; return true; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_string_to_intarray(JSContext *context, JS::HandleString str, GITypeInfo *param_info, void **arr_p, size_t *length) { GITypeTag element_type; char16_t *result16; element_type = g_type_info_get_tag(param_info); if (element_type == GI_TYPE_TAG_INT8 || element_type == GI_TYPE_TAG_UINT8) { JS::UniqueChars result(JS_EncodeStringToUTF8(context, str)); if (!result) return false; *length = strlen(result.get()); *arr_p = g_strdup(result.get()); return true; } if (element_type == GI_TYPE_TAG_INT16 || element_type == GI_TYPE_TAG_UINT16) { if (!gjs_string_get_char16_data(context, str, &result16, length)) return false; *arr_p = result16; return true; } if (element_type == GI_TYPE_TAG_UNICHAR) { gunichar *result_ucs4; if (!gjs_string_to_ucs4(context, str, &result_ucs4, length)) return false; *arr_p = result_ucs4; return true; } /* can't convert a string to this type */ gjs_throw(context, "Cannot convert string to array of '%s'", g_type_tag_to_string (element_type)); return false; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_array_to_gboolean_array(JSContext *cx, JS::Value array_value, unsigned length, void **arr_p) { unsigned i; JS::RootedObject array(cx, array_value.toObjectOrNull()); JS::RootedValue elem(cx); gboolean *result = g_new0(gboolean, length); for (i = 0; i < length; i++) { if (!JS_GetElement(cx, array, i, &elem)) { g_free(result); gjs_throw(cx, "Missing array element %u", i); return false; } bool val = JS::ToBoolean(elem); result[i] = val; } *arr_p = result; return true; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_array_to_intarray(JSContext *context, JS::Value array_value, unsigned int length, void **arr_p, unsigned intsize, bool is_signed) { /* nasty union types in an attempt to unify the various int types */ union { uint64_t u; int64_t i; } intval; void *result; unsigned i; JS::RootedObject array(context, array_value.toObjectOrNull()); JS::RootedValue elem(context); /* add one so we're always zero terminated */ result = g_malloc0((length+1) * intsize); for (i = 0; i < length; ++i) { bool success; elem = JS::UndefinedValue(); if (!JS_GetElement(context, array, i, &elem)) { g_free(result); gjs_throw(context, "Missing array element %u", i); return false; } /* do whatever sign extension is appropriate */ success = (is_signed) ? JS::ToInt64(context, elem, &(intval.i)) : JS::ToUint64(context, elem, &(intval.u)); if (!success) { g_free(result); gjs_throw(context, "Invalid element in int array"); return false; } /* Note that this is truncating assignment. */ switch (intsize) { case 1: ((guint8*)result)[i] = (gint8) intval.u; break; case 2: ((guint16*)result)[i] = (gint16) intval.u; break; case 4: ((guint32*)result)[i] = (gint32) intval.u; break; case 8: ((uint64_t *)result)[i] = (int64_t) intval.u; break; default: g_assert_not_reached(); } } *arr_p = result; return true; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_gtypearray_to_array(JSContext *context, JS::Value array_value, unsigned int length, void **arr_p) { unsigned i; /* add one so we're always zero terminated */ GjsAutoPointer result = static_cast(g_malloc0((length + 1) * sizeof(GType))); JS::RootedObject elem_obj(context), array(context, array_value.toObjectOrNull()); JS::RootedValue elem(context); for (i = 0; i < length; ++i) { GType gtype; elem = JS::UndefinedValue(); if (!JS_GetElement(context, array, i, &elem)) return false; if (!elem.isObject()) { gjs_throw(context, "Invalid element in GType array"); return false; } elem_obj = &elem.toObject(); if (!gjs_gtype_get_actual_gtype(context, elem_obj, >ype)) return false; if (gtype == G_TYPE_INVALID) { gjs_throw(context, "Invalid element in GType array"); return false; } result[i] = gtype; } *arr_p = result.release(); return true; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_array_to_floatarray(JSContext *context, JS::Value array_value, unsigned int length, void **arr_p, bool is_double) { unsigned int i; void *result; JS::RootedObject array(context, array_value.toObjectOrNull()); JS::RootedValue elem(context); /* add one so we're always zero terminated */ result = g_malloc0((length+1) * (is_double ? sizeof(double) : sizeof(float))); for (i = 0; i < length; ++i) { double val; bool success; elem = JS::UndefinedValue(); if (!JS_GetElement(context, array, i, &elem)) { g_free(result); gjs_throw(context, "Missing array element %u", i); return false; } /* do whatever sign extension is appropriate */ success = JS::ToNumber(context, elem, &val); if (!success) { g_free(result); gjs_throw(context, "Invalid element in array"); return false; } /* Note that this is truncating assignment. */ if (is_double) { double *darray = (double*)result; darray[i] = val; } else { float *farray = (float*)result; farray[i] = val; } } *arr_p = result; return true; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_array_to_ptrarray(JSContext *context, JS::Value array_value, unsigned int length, GITransfer transfer, GITypeInfo *param_info, void **arr_p) { unsigned int i; JS::RootedObject array_obj(context, array_value.toObjectOrNull()); JS::RootedValue elem(context); /* Always one extra element, to cater for null terminated arrays */ void **array = (void **) g_malloc((length + 1) * sizeof(gpointer)); array[length] = NULL; for (i = 0; i < length; i++) { GIArgument arg; gjs_arg_unset(&arg); bool success; elem = JS::UndefinedValue(); if (!JS_GetElement(context, array_obj, i, &elem)) { g_free(array); gjs_throw(context, "Missing array element %u", i); return false; } success = gjs_value_to_g_argument (context, elem, param_info, NULL, /* arg name */ GJS_ARGUMENT_ARRAY_ELEMENT, transfer, false, /* absent better information, false for now */ &arg); if (!success) { g_free(array); gjs_throw(context, "Invalid element in array"); return false; } array[i] = gjs_arg_get(&arg); } *arr_p = array; return true; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_array_to_flat_struct_array(JSContext* cx, JS::HandleValue array_value, unsigned length, GITypeInfo* param_info, GIBaseInfo* interface_info, GIInfoType info_type, void** arr_p) { g_assert( (info_type == GI_INFO_TYPE_STRUCT || info_type == GI_INFO_TYPE_UNION) && "Only flat arrays of unboxed structs or unions are supported"); size_t struct_size; if (info_type == GI_INFO_TYPE_UNION) struct_size = g_union_info_get_size(interface_info); else struct_size = g_struct_info_get_size(interface_info); GjsAutoPointer flat_array = g_new0(uint8_t, struct_size * length); JS::RootedObject array(cx, &array_value.toObject()); JS::RootedValue elem(cx); for (unsigned i = 0; i < length; i++) { elem = JS::UndefinedValue(); if (!JS_GetElement(cx, array, i, &elem)) { gjs_throw(cx, "Missing array element %u", i); return false; } GIArgument arg; if (!gjs_value_to_g_argument(cx, elem, param_info, /* arg_name = */ nullptr, GJS_ARGUMENT_ARRAY_ELEMENT, GI_TRANSFER_NOTHING, /* may_be_null = */ false, &arg)) return false; memcpy(&flat_array[struct_size * i], gjs_arg_get(&arg), struct_size); } *arr_p = flat_array.release(); return true; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_array_to_flat_gvalue_array(JSContext *context, JS::Value array_value, unsigned int length, void **arr_p) { GValue *values = g_new0(GValue, length); unsigned int i; bool result = true; JS::RootedObject array(context, array_value.toObjectOrNull()); JS::RootedValue elem(context); for (i = 0; i < length; i ++) { elem = JS::UndefinedValue(); if (!JS_GetElement(context, array, i, &elem)) { g_free(values); gjs_throw(context, "Missing array element %u", i); return false; } result = gjs_value_to_g_value(context, elem, &values[i]); if (!result) break; } if (result) *arr_p = values; return result; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_array_from_flat_gvalue_array(JSContext *context, gpointer array, unsigned length, JS::MutableHandleValue value) { GValue *values = (GValue *)array; // a null array pointer takes precedence over whatever `length` says if (!values) { JSObject* jsarray = JS::NewArrayObject(context, 0); if (!jsarray) return false; value.setObject(*jsarray); return true; } unsigned int i; JS::RootedValueVector elems(context); if (!elems.resize(length)) { JS_ReportOutOfMemory(context); return false; } bool result = true; for (i = 0; i < length; i ++) { GValue *gvalue = &values[i]; result = gjs_value_from_g_value(context, elems[i], gvalue); if (!result) break; } if (result) { JSObject *jsarray; jsarray = JS::NewArrayObject(context, elems); value.setObjectOrNull(jsarray); } return result; } [[nodiscard]] static bool is_gvalue(GIBaseInfo* info, GIInfoType info_type) { if (info_type == GI_INFO_TYPE_VALUE) return true; if (info_type == GI_INFO_TYPE_STRUCT || info_type == GI_INFO_TYPE_OBJECT || info_type == GI_INFO_TYPE_INTERFACE || info_type == GI_INFO_TYPE_BOXED) { GType gtype = g_registered_type_info_get_g_type((GIRegisteredTypeInfo *) info); return g_type_is_a(gtype, G_TYPE_VALUE); } return false; } [[nodiscard]] static bool is_gvalue_flat_array(GITypeInfo* param_info, GITypeTag element_type) { GIBaseInfo *interface_info; GIInfoType info_type; bool result; if (element_type != GI_TYPE_TAG_INTERFACE) return false; interface_info = g_type_info_get_interface(param_info); info_type = g_base_info_get_type(interface_info); /* Special case for GValue "flat arrays" */ result = (is_gvalue(interface_info, info_type) && !g_type_info_is_pointer(param_info)); g_base_info_unref(interface_info); return result; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_array_to_array(JSContext* context, JS::HandleValue array_value, size_t length, GITransfer transfer, GITypeInfo* param_info, void** arr_p) { enum { UNSIGNED=false, SIGNED=true }; GITypeTag element_type = _g_type_info_get_storage_type(param_info); /* Special case for GValue "flat arrays" */ if (is_gvalue_flat_array(param_info, element_type)) return gjs_array_to_flat_gvalue_array(context, array_value, length, arr_p); switch (element_type) { case GI_TYPE_TAG_UTF8: return gjs_array_to_strv (context, array_value, length, arr_p); case GI_TYPE_TAG_BOOLEAN: return gjs_array_to_gboolean_array(context, array_value, length, arr_p); case GI_TYPE_TAG_UNICHAR: return gjs_array_to_intarray(context, array_value, length, arr_p, sizeof(gunichar), UNSIGNED); case GI_TYPE_TAG_UINT8: return gjs_array_to_intarray (context, array_value, length, arr_p, 1, UNSIGNED); case GI_TYPE_TAG_INT8: return gjs_array_to_intarray (context, array_value, length, arr_p, 1, SIGNED); case GI_TYPE_TAG_UINT16: return gjs_array_to_intarray (context, array_value, length, arr_p, 2, UNSIGNED); case GI_TYPE_TAG_INT16: return gjs_array_to_intarray (context, array_value, length, arr_p, 2, SIGNED); case GI_TYPE_TAG_UINT32: return gjs_array_to_intarray (context, array_value, length, arr_p, 4, UNSIGNED); case GI_TYPE_TAG_INT32: return gjs_array_to_intarray (context, array_value, length, arr_p, 4, SIGNED); case GI_TYPE_TAG_INT64: return gjs_array_to_intarray(context, array_value, length, arr_p, 8, SIGNED); case GI_TYPE_TAG_UINT64: return gjs_array_to_intarray(context, array_value, length, arr_p, 8, UNSIGNED); case GI_TYPE_TAG_FLOAT: return gjs_array_to_floatarray (context, array_value, length, arr_p, false); case GI_TYPE_TAG_DOUBLE: return gjs_array_to_floatarray (context, array_value, length, arr_p, true); case GI_TYPE_TAG_GTYPE: return gjs_gtypearray_to_array (context, array_value, length, arr_p); /* Everything else is a pointer type */ case GI_TYPE_TAG_INTERFACE: if (!g_type_info_is_pointer(param_info)) { GjsAutoBaseInfo interface_info = g_type_info_get_interface(param_info); GIInfoType info_type = g_base_info_get_type(interface_info); if (info_type == GI_INFO_TYPE_STRUCT || info_type == GI_INFO_TYPE_UNION) { // Ignore transfer in the case of a flat struct array. Structs // are copied by value. return gjs_array_to_flat_struct_array( context, array_value, length, param_info, interface_info, info_type, arr_p); } } [[fallthrough]]; case GI_TYPE_TAG_ARRAY: case GI_TYPE_TAG_GLIST: case GI_TYPE_TAG_GSLIST: case GI_TYPE_TAG_GHASH: case GI_TYPE_TAG_ERROR: case GI_TYPE_TAG_FILENAME: return gjs_array_to_ptrarray(context, array_value, length, transfer == GI_TRANSFER_CONTAINER ? GI_TRANSFER_NOTHING : transfer, param_info, arr_p); case GI_TYPE_TAG_VOID: default: gjs_throw(context, "Unhandled array element type %d", element_type); return false; } } GJS_JSAPI_RETURN_CONVENTION static GArray* gjs_g_array_new_for_type(JSContext *context, unsigned int length, GITypeInfo *param_info) { guint element_size; GITypeTag element_type = _g_type_info_get_storage_type(param_info); switch (element_type) { case GI_TYPE_TAG_BOOLEAN: element_size = sizeof(gboolean); break; case GI_TYPE_TAG_UNICHAR: element_size = sizeof(char32_t); break; case GI_TYPE_TAG_UINT8: case GI_TYPE_TAG_INT8: element_size = sizeof(uint8_t); break; case GI_TYPE_TAG_UINT16: case GI_TYPE_TAG_INT16: element_size = sizeof(uint16_t); break; case GI_TYPE_TAG_UINT32: case GI_TYPE_TAG_INT32: element_size = sizeof(uint32_t); break; case GI_TYPE_TAG_UINT64: case GI_TYPE_TAG_INT64: element_size = sizeof(uint64_t); break; case GI_TYPE_TAG_FLOAT: element_size = sizeof(float); break; case GI_TYPE_TAG_DOUBLE: element_size = sizeof(double); break; case GI_TYPE_TAG_GTYPE: element_size = sizeof(GType); break; case GI_TYPE_TAG_INTERFACE: case GI_TYPE_TAG_UTF8: case GI_TYPE_TAG_FILENAME: case GI_TYPE_TAG_ARRAY: case GI_TYPE_TAG_GLIST: case GI_TYPE_TAG_GSLIST: case GI_TYPE_TAG_GHASH: case GI_TYPE_TAG_ERROR: element_size = sizeof(void*); break; case GI_TYPE_TAG_VOID: default: gjs_throw(context, "Unhandled GArray element-type %d", element_type); return NULL; } return g_array_sized_new(true, false, element_size, length); } char* gjs_argument_display_name(const char* arg_name, GjsArgumentType arg_type) { switch (arg_type) { case GJS_ARGUMENT_ARGUMENT: return g_strdup_printf("Argument '%s'", arg_name); case GJS_ARGUMENT_RETURN_VALUE: return g_strdup("Return value"); case GJS_ARGUMENT_FIELD: return g_strdup_printf("Field '%s'", arg_name); case GJS_ARGUMENT_LIST_ELEMENT: return g_strdup("List element"); case GJS_ARGUMENT_HASH_ELEMENT: return g_strdup("Hash element"); case GJS_ARGUMENT_ARRAY_ELEMENT: return g_strdup("Array element"); default: g_assert_not_reached (); } } [[nodiscard]] static const char* type_tag_to_human_string( GITypeInfo* type_info) { GITypeTag tag; tag = g_type_info_get_tag(type_info); if (tag == GI_TYPE_TAG_INTERFACE) { GIBaseInfo *interface; const char *ret; interface = g_type_info_get_interface(type_info); ret = g_info_type_to_string(g_base_info_get_type(interface)); g_base_info_unref(interface); return ret; } else { return g_type_tag_to_string(tag); } } static void throw_invalid_argument(JSContext *context, JS::HandleValue value, GITypeInfo *arginfo, const char *arg_name, GjsArgumentType arg_type) { GjsAutoChar display_name = gjs_argument_display_name(arg_name, arg_type); gjs_throw(context, "Expected type %s for %s but got type '%s'", type_tag_to_human_string(arginfo), display_name.get(), JS::InformalValueTypeName(value)); } GJS_JSAPI_RETURN_CONVENTION bool gjs_array_to_explicit_array(JSContext* context, JS::HandleValue value, GITypeInfo* type_info, const char* arg_name, GjsArgumentType arg_type, GITransfer transfer, bool may_be_null, void** contents, size_t* length_p) { bool found_length; gjs_debug_marshal( GJS_DEBUG_GFUNCTION, "Converting argument '%s' JS value %s to C array, transfer %d", arg_name, gjs_debug_value(value).c_str(), transfer); GjsAutoTypeInfo param_info = g_type_info_get_param_type(type_info, 0); if ((value.isNull() && !may_be_null) || (!value.isString() && !value.isObjectOrNull())) { throw_invalid_argument(context, value, param_info, arg_name, arg_type); return false; } if (value.isNull()) { *contents = NULL; *length_p = 0; } else if (value.isString()) { /* Allow strings as int8/uint8/int16/uint16 arrays */ JS::RootedString str(context, value.toString()); if (!gjs_string_to_intarray(context, str, param_info, contents, length_p)) return false; } else { JS::RootedObject array_obj(context, &value.toObject()); const GjsAtoms& atoms = GjsContextPrivate::atoms(context); GITypeTag element_type = g_type_info_get_tag(param_info); if (JS_IsUint8Array(array_obj) && (element_type == GI_TYPE_TAG_INT8 || element_type == GI_TYPE_TAG_UINT8)) { GBytes* bytes = gjs_byte_array_get_bytes(array_obj); *contents = g_bytes_unref_to_data(bytes, length_p); } else if (JS_HasPropertyById(context, array_obj, atoms.length(), &found_length) && found_length) { guint32 length; if (!gjs_object_require_converted_property( context, array_obj, nullptr, atoms.length(), &length)) { return false; } else { if (!gjs_array_to_array(context, value, length, transfer, param_info, contents)) return false; *length_p = length; } } else { throw_invalid_argument(context, value, param_info, arg_name, arg_type); return false; } } return true; } [[nodiscard]] static bool is_gdk_atom(GIBaseInfo* info) { return (strcmp("Atom", g_base_info_get_name(info)) == 0 && strcmp("Gdk", g_base_info_get_namespace(info)) == 0); } static void intern_gdk_atom(const char *name, GArgument *ret) { GjsAutoFunctionInfo atom_intern_fun = g_irepository_find_by_name(nullptr, "Gdk", "atom_intern"); GIArgument atom_intern_args[2]; /* Can only store char * in GIArgument. First argument to gdk_atom_intern * is const char *, string isn't modified. */ gjs_arg_set(&atom_intern_args[0], name); gjs_arg_set(&atom_intern_args[1], false); g_function_info_invoke(atom_intern_fun, atom_intern_args, 2, nullptr, 0, ret, nullptr); } static bool value_to_interface_gi_argument( JSContext* cx, JS::HandleValue value, GIBaseInfo* interface_info, GIInfoType interface_type, GITransfer transfer, bool expect_object, GIArgument* arg, GjsArgumentType arg_type, bool* report_type_mismatch) { g_assert(report_type_mismatch); GType gtype = G_TYPE_NONE; if (interface_type == GI_INFO_TYPE_STRUCT || interface_type == GI_INFO_TYPE_ENUM || interface_type == GI_INFO_TYPE_FLAGS || interface_type == GI_INFO_TYPE_OBJECT || interface_type == GI_INFO_TYPE_INTERFACE || interface_type == GI_INFO_TYPE_UNION || interface_type == GI_INFO_TYPE_BOXED) { // These are subtypes of GIRegisteredTypeInfo for which the cast is safe gtype = g_registered_type_info_get_g_type(interface_info); } else if (interface_type == GI_INFO_TYPE_VALUE) { // Special case for GValues gtype = G_TYPE_VALUE; } if (gtype != G_TYPE_NONE) gjs_debug_marshal(GJS_DEBUG_GFUNCTION, "gtype of INTERFACE is %s", g_type_name(gtype)); if (gtype == G_TYPE_VALUE) { GValue gvalue = G_VALUE_INIT; if (!gjs_value_to_g_value(cx, value, &gvalue)) { gjs_arg_unset(arg); return false; } gjs_arg_set(arg, g_boxed_copy(G_TYPE_VALUE, &gvalue)); g_value_unset(&gvalue); return true; } else if (is_gdk_atom(interface_info)) { if (!value.isNull() && !value.isString()) { *report_type_mismatch = true; return false; } else if (value.isNull()) { intern_gdk_atom("NONE", arg); return true; } JS::RootedString str(cx, value.toString()); JS::UniqueChars name(JS_EncodeStringToUTF8(cx, str)); if (!name) return false; intern_gdk_atom(name.get(), arg); return true; } else if (expect_object != value.isObjectOrNull()) { *report_type_mismatch = true; return false; } else if (value.isNull()) { gjs_arg_set(arg, nullptr); return true; } else if (value.isObject()) { JS::RootedObject obj(cx, &value.toObject()); if (interface_type == GI_INFO_TYPE_STRUCT && g_struct_info_is_gtype_struct(interface_info)) { GType actual_gtype; if (!gjs_gtype_get_actual_gtype(cx, obj, &actual_gtype)) return false; if (actual_gtype == G_TYPE_NONE) { *report_type_mismatch = true; return false; } // We use peek here to simplify reference counting (we just ignore // transfer annotation, as GType classes are never really freed) // We know that the GType class is referenced at least once when // the JS constructor is initialized. void* klass; if (g_type_is_a(actual_gtype, G_TYPE_INTERFACE)) klass = g_type_default_interface_peek(actual_gtype); else klass = g_type_class_peek(actual_gtype); gjs_arg_set(arg, klass); return true; } else if ((interface_type == GI_INFO_TYPE_STRUCT || interface_type == GI_INFO_TYPE_BOXED) && !g_type_is_a(gtype, G_TYPE_CLOSURE)) { // Handle Struct/Union first since we don't necessarily need a GType // for them. We special case Closures later, so skip them here. if (g_type_is_a(gtype, G_TYPE_BYTES) && JS_IsUint8Array(obj)) { gjs_arg_set(arg, gjs_byte_array_get_bytes(obj)); return true; } if (g_type_is_a(gtype, G_TYPE_ERROR)) { return ErrorBase::transfer_to_gi_argument( cx, obj, arg, GI_DIRECTION_IN, transfer); } return BoxedBase::transfer_to_gi_argument( cx, obj, arg, GI_DIRECTION_IN, transfer, gtype, interface_info); } else if (interface_type == GI_INFO_TYPE_UNION) { return UnionBase::transfer_to_gi_argument( cx, obj, arg, GI_DIRECTION_IN, transfer, gtype, interface_info); } else if (gtype != G_TYPE_NONE) { if (g_type_is_a(gtype, G_TYPE_OBJECT)) { return ObjectBase::transfer_to_gi_argument( cx, obj, arg, GI_DIRECTION_IN, transfer, gtype); } else if (g_type_is_a(gtype, G_TYPE_PARAM)) { if (!gjs_typecheck_param(cx, obj, gtype, true)) { gjs_arg_unset(arg); return false; } gjs_arg_set(arg, gjs_g_param_from_param(cx, obj)); if (transfer != GI_TRANSFER_NOTHING) g_param_spec_ref(gjs_arg_get(arg)); return true; } else if (g_type_is_a(gtype, G_TYPE_BOXED)) { if (g_type_is_a(gtype, G_TYPE_CLOSURE)) { GClosure* closure = gjs_closure_new_marshaled( cx, JS_GetObjectFunction(obj), "boxed"); // GI doesn't know about floating GClosure references. We // guess that if this is a return value going from JS::Value // to GArgument, it's intended to be passed to a C API that // will consume the floating reference. if (arg_type != GJS_ARGUMENT_RETURN_VALUE) { g_closure_ref(closure); g_closure_sink(closure); } gjs_arg_set(arg, closure); return true; } // Should have been caught above as STRUCT/BOXED/UNION gjs_throw( cx, "Boxed type %s registered for unexpected interface_type %d", g_type_name(gtype), interface_type); return false; } else if (G_TYPE_IS_INSTANTIATABLE(gtype)) { return FundamentalBase::transfer_to_gi_argument( cx, obj, arg, GI_DIRECTION_IN, transfer, gtype); } else if (G_TYPE_IS_INTERFACE(gtype)) { // Could be a GObject interface that's missing a prerequisite, // or could be a fundamental if (ObjectBase::typecheck(cx, obj, nullptr, gtype, GjsTypecheckNoThrow())) { return ObjectBase::transfer_to_gi_argument( cx, obj, arg, GI_DIRECTION_IN, transfer, gtype); } // If this typecheck fails, then it's neither an object nor a // fundamental return FundamentalBase::transfer_to_gi_argument( cx, obj, arg, GI_DIRECTION_IN, transfer, gtype); } gjs_throw(cx, "Unhandled GType %s unpacking GIArgument from Object", g_type_name(gtype)); gjs_arg_unset(arg); return false; } gjs_debug(GJS_DEBUG_GFUNCTION, "conversion of JSObject value %s to type %s failed", gjs_debug_value(value).c_str(), g_base_info_get_name(interface_info)); gjs_throw(cx, "Unexpected unregistered type unpacking GIArgument from " "Object"); return false; } else if (value.isNumber()) { if (interface_type == GI_INFO_TYPE_ENUM) { int64_t value_int64; if (!JS::ToInt64(cx, value, &value_int64) || !_gjs_enum_value_is_valid(cx, interface_info, value_int64)) return false; gjs_arg_set( arg, _gjs_enum_to_int(value_int64)); return true; } else if (interface_type == GI_INFO_TYPE_FLAGS) { int64_t value_int64; if (!JS::ToInt64(cx, value, &value_int64) || !_gjs_flags_value_is_valid(cx, gtype, value_int64)) return false; gjs_arg_set( arg, _gjs_enum_to_int(value_int64)); return true; } else if (gtype == G_TYPE_NONE) { gjs_throw(cx, "Unexpected unregistered type unpacking GIArgument from " "Number"); return false; } gjs_throw(cx, "Unhandled GType %s unpacking GIArgument from Number", g_type_name(gtype)); return false; } gjs_debug(GJS_DEBUG_GFUNCTION, "JSObject type '%s' is neither null nor an object", JS::InformalValueTypeName(value)); *report_type_mismatch = true; return false; } bool gjs_value_to_g_argument(JSContext *context, JS::HandleValue value, GITypeInfo *type_info, const char *arg_name, GjsArgumentType arg_type, GITransfer transfer, bool may_be_null, GArgument *arg) { GITypeTag type_tag = g_type_info_get_tag(type_info); gjs_debug_marshal( GJS_DEBUG_GFUNCTION, "Converting argument '%s' JS value %s to GIArgument type %s", arg_name, gjs_debug_value(value).c_str(), g_type_tag_to_string(type_tag)); bool nullable_type = false; bool wrong = false; // return false bool out_of_range = false; bool report_type_mismatch = false; // wrong=true, and still need to // gjs_throw a type problem switch (type_tag) { case GI_TYPE_TAG_VOID: nullable_type = true; // just so it isn't uninitialized gjs_arg_unset(arg); break; case GI_TYPE_TAG_INT8: { gint32 i; if (!JS::ToInt32(context, value, &i)) wrong = true; if (i > G_MAXINT8 || i < G_MININT8) out_of_range = true; gjs_arg_set(arg, i); break; } case GI_TYPE_TAG_UINT8: { guint32 i; if (!JS::ToUint32(context, value, &i)) wrong = true; if (i > G_MAXUINT8) out_of_range = true; gjs_arg_set(arg, i); break; } case GI_TYPE_TAG_INT16: { gint32 i; if (!JS::ToInt32(context, value, &i)) wrong = true; if (i > G_MAXINT16 || i < G_MININT16) out_of_range = true; gjs_arg_set(arg, i); break; } case GI_TYPE_TAG_UINT16: { guint32 i; if (!JS::ToUint32(context, value, &i)) wrong = true; if (i > G_MAXUINT16) out_of_range = true; gjs_arg_set(arg, i); break; } case GI_TYPE_TAG_INT32: if (!JS::ToInt32(context, value, &gjs_arg_member(arg))) wrong = true; break; case GI_TYPE_TAG_UINT32: { gdouble i; if (!JS::ToNumber(context, value, &i)) wrong = true; if (i > G_MAXUINT32 || i < 0) out_of_range = true; gjs_arg_set(arg, CLAMP(i, 0, G_MAXUINT32)); break; } case GI_TYPE_TAG_INT64: { double v; if (!JS::ToNumber(context, value, &v)) wrong = true; if (v > G_MAXINT64 || v < G_MININT64) out_of_range = true; gjs_arg_set(arg, v); } break; case GI_TYPE_TAG_UINT64: { double v; if (!JS::ToNumber(context, value, &v)) wrong = true; if (v < 0) out_of_range = true; /* XXX we fail with values close to G_MAXUINT64 */ gjs_arg_set(arg, MAX(v, 0)); } break; case GI_TYPE_TAG_BOOLEAN: gjs_arg_set(arg, JS::ToBoolean(value)); break; case GI_TYPE_TAG_FLOAT: { double v; if (!JS::ToNumber(context, value, &v)) wrong = true; if (v > G_MAXFLOAT || v < - G_MAXFLOAT) out_of_range = true; gjs_arg_set(arg, v); } break; case GI_TYPE_TAG_DOUBLE: if (!JS::ToNumber(context, value, &gjs_arg_member(arg))) wrong = true; break; case GI_TYPE_TAG_UNICHAR: if (value.isString()) { if (!gjs_unichar_from_string(context, value, &gjs_arg_member(arg))) wrong = true; } else { wrong = true; report_type_mismatch = true; } break; case GI_TYPE_TAG_GTYPE: if (value.isObjectOrNull()) { GType gtype; JS::RootedObject obj(context, value.toObjectOrNull()); if (!gjs_gtype_get_actual_gtype(context, obj, >ype)) { wrong = true; break; } if (gtype == G_TYPE_INVALID) wrong = true; gjs_arg_set(arg, gtype); } else { wrong = true; report_type_mismatch = true; } break; case GI_TYPE_TAG_FILENAME: nullable_type = true; if (value.isNull()) { gjs_arg_set(arg, nullptr); } else if (value.isString()) { GjsAutoChar filename_str; if (gjs_string_to_filename(context, value, &filename_str)) gjs_arg_set(arg, filename_str.release()); else wrong = true; } else { wrong = true; report_type_mismatch = true; } break; case GI_TYPE_TAG_UTF8: nullable_type = true; if (value.isNull()) { gjs_arg_set(arg, nullptr); } else if (value.isString()) { JS::RootedString str(context, value.toString()); JS::UniqueChars utf8_str(JS_EncodeStringToUTF8(context, str)); if (utf8_str) gjs_arg_set(arg, g_strdup(utf8_str.get())); else wrong = true; } else { wrong = true; report_type_mismatch = true; } break; case GI_TYPE_TAG_ERROR: nullable_type = true; if (value.isNull()) { gjs_arg_set(arg, nullptr); } else if (value.isObject()) { JS::RootedObject obj(context, &value.toObject()); if (!ErrorBase::transfer_to_gi_argument(context, obj, arg, GI_DIRECTION_IN, transfer)) wrong = true; } else { wrong = true; report_type_mismatch = true; } break; case GI_TYPE_TAG_INTERFACE: { bool expect_object; GjsAutoBaseInfo interface_info = g_type_info_get_interface(type_info); g_assert(interface_info); GIInfoType interface_type = g_base_info_get_type(interface_info); if (interface_type == GI_INFO_TYPE_ENUM || interface_type == GI_INFO_TYPE_FLAGS) { nullable_type = false; expect_object = false; } else { nullable_type = true; expect_object = true; } if (interface_type == GI_INFO_TYPE_STRUCT && g_struct_info_is_foreign(interface_info)) { return gjs_struct_foreign_convert_to_g_argument( context, value, interface_info, arg_name, arg_type, transfer, may_be_null, arg); } if (!value_to_interface_gi_argument( context, value, interface_info, interface_type, transfer, expect_object, arg, arg_type, &report_type_mismatch)) wrong = true; } break; case GI_TYPE_TAG_GLIST: case GI_TYPE_TAG_GSLIST: { /* nullable_type=false; while a list can be NULL in C, that * means empty array in JavaScript, it doesn't mean null in * JavaScript. */ if (value.isObject()) { bool found_length; const GjsAtoms& atoms = GjsContextPrivate::atoms(context); JS::RootedObject array_obj(context, &value.toObject()); if (JS_HasPropertyById(context, array_obj, atoms.length(), &found_length) && found_length) { guint32 length; if (!gjs_object_require_converted_property( context, array_obj, nullptr, atoms.length(), &length)) { wrong = true; } else { GList *list; GSList *slist; GITypeInfo *param_info; param_info = g_type_info_get_param_type(type_info, 0); g_assert(param_info != NULL); list = NULL; slist = NULL; if (!gjs_array_to_g_list(context, value, length, param_info, transfer, type_tag, &list, &slist)) { wrong = true; } if (type_tag == GI_TYPE_TAG_GLIST) { gjs_arg_set(arg, list); } else { gjs_arg_set(arg, slist); } g_base_info_unref((GIBaseInfo*) param_info); } break; } } /* At this point we should have broken out already if the value was an * object and had a length property */ wrong = true; report_type_mismatch = true; break; } case GI_TYPE_TAG_GHASH: if (value.isNull()) { gjs_arg_set(arg, nullptr); if (!may_be_null) { wrong = true; report_type_mismatch = true; } } else if (!value.isObject()) { wrong = true; report_type_mismatch = true; } else { GITypeInfo *key_param_info, *val_param_info; GHashTable *ghash; key_param_info = g_type_info_get_param_type(type_info, 0); g_assert(key_param_info != NULL); val_param_info = g_type_info_get_param_type(type_info, 1); g_assert(val_param_info != NULL); if (!gjs_object_to_g_hash(context, value, key_param_info, val_param_info, transfer, &ghash)) { wrong = true; } else { #if __GNUC__ >= 8 // clang-format off _Pragma("GCC diagnostic push") _Pragma("GCC diagnostic ignored \"-Wmaybe-uninitialized\"") #endif /* The compiler isn't smart enough to figure out that ghash * will always be initialized if gjs_object_to_g_hash() * returns true. */ gjs_arg_set(arg, ghash); #if __GNUC__ >= 8 _Pragma("GCC diagnostic pop") #endif // clang-format on } g_base_info_unref((GIBaseInfo*) key_param_info); g_base_info_unref((GIBaseInfo*) val_param_info); } break; case GI_TYPE_TAG_ARRAY: { gpointer data; gsize length; GIArrayType array_type = g_type_info_get_array_type(type_info); /* First, let's handle the case where we're passed an instance * of Uint8Array and it needs to be marshalled to GByteArray. */ if (value.isObject()) { JSObject* bytearray_obj = &value.toObject(); if (JS_IsUint8Array(bytearray_obj) && array_type == GI_ARRAY_TYPE_BYTE_ARRAY) { gjs_arg_set(arg, gjs_byte_array_get_byte_array(bytearray_obj)); break; } else { /* Fall through, !handled */ } } if (!gjs_array_to_explicit_array(context, value, type_info, arg_name, arg_type, transfer, may_be_null, &data, &length)) { wrong = true; break; } GITypeInfo *param_info = g_type_info_get_param_type(type_info, 0); if (array_type == GI_ARRAY_TYPE_C) { gjs_arg_set(arg, data); } else if (array_type == GI_ARRAY_TYPE_ARRAY) { GArray *array = gjs_g_array_new_for_type(context, length, param_info); if (!array) wrong = true; else { if (data) g_array_append_vals(array, data, length); gjs_arg_set(arg, array); } g_free(data); } else if (array_type == GI_ARRAY_TYPE_BYTE_ARRAY) { GByteArray *byte_array = g_byte_array_sized_new(length); if (data) g_byte_array_append(byte_array, static_cast(data), length); gjs_arg_set(arg, byte_array); g_free(data); } else if (array_type == GI_ARRAY_TYPE_PTR_ARRAY) { GPtrArray *array = g_ptr_array_sized_new(length); g_ptr_array_set_size(array, length); if (data) memcpy(array->pdata, data, sizeof(void*) * length); gjs_arg_set(arg, array); g_free(data); } g_base_info_unref((GIBaseInfo*) param_info); break; } default: g_warning("Unhandled type %s for JavaScript to GArgument conversion", g_type_tag_to_string(type_tag)); wrong = true; report_type_mismatch = true; break; } if (G_UNLIKELY(wrong)) { if (report_type_mismatch) { throw_invalid_argument(context, value, type_info, arg_name, arg_type); } return false; } else if (G_UNLIKELY(out_of_range)) { GjsAutoChar display_name = gjs_argument_display_name(arg_name, arg_type); gjs_throw(context, "value is out of range for %s (type %s)", display_name.get(), g_type_tag_to_string(type_tag)); return false; } else if (nullable_type && !gjs_arg_get(arg) && !may_be_null) { GjsAutoChar display_name = gjs_argument_display_name(arg_name, arg_type); gjs_throw(context, "%s (type %s) may not be null", display_name.get(), g_type_tag_to_string(type_tag)); return false; } else { return true; } } /* If a callback function with a return value throws, we still have * to return something to C. This function defines what that something * is. It basically boils down to memset(arg, 0, sizeof(*arg)), but * gives as a bit more future flexibility and also will work if * libffi passes us a buffer that only has room for the appropriate * branch of GArgument. (Currently it appears that the return buffer * has a fixed size large enough for the union of all types.) */ void gjs_gi_argument_init_default(GITypeInfo* type_info, GIArgument* arg) { GITypeTag type_tag; type_tag = g_type_info_get_tag( (GITypeInfo*) type_info); switch (type_tag) { case GI_TYPE_TAG_VOID: // just so it isn't uninitialized gjs_arg_unset(arg); break; case GI_TYPE_TAG_INT8: gjs_arg_unset(arg); break; case GI_TYPE_TAG_UINT8: gjs_arg_unset(arg); break; case GI_TYPE_TAG_INT16: gjs_arg_unset(arg); break; case GI_TYPE_TAG_UINT16: gjs_arg_unset(arg); break; case GI_TYPE_TAG_INT32: gjs_arg_unset(arg); break; case GI_TYPE_TAG_UINT32: gjs_arg_unset(arg); break; case GI_TYPE_TAG_UNICHAR: gjs_arg_unset(arg); break; case GI_TYPE_TAG_INT64: gjs_arg_unset(arg); break; case GI_TYPE_TAG_UINT64: gjs_arg_unset(arg); break; case GI_TYPE_TAG_BOOLEAN: gjs_arg_unset(arg); break; case GI_TYPE_TAG_FLOAT: gjs_arg_unset(arg); break; case GI_TYPE_TAG_DOUBLE: gjs_arg_unset(arg); break; case GI_TYPE_TAG_GTYPE: gjs_arg_unset(arg); break; case GI_TYPE_TAG_FILENAME: case GI_TYPE_TAG_UTF8: case GI_TYPE_TAG_GLIST: case GI_TYPE_TAG_GSLIST: case GI_TYPE_TAG_ERROR: gjs_arg_unset(arg); break; case GI_TYPE_TAG_INTERFACE: { GIBaseInfo* interface_info; GIInfoType interface_type; interface_info = g_type_info_get_interface(type_info); g_assert(interface_info != NULL); interface_type = g_base_info_get_type(interface_info); if (interface_type == GI_INFO_TYPE_ENUM || interface_type == GI_INFO_TYPE_FLAGS) gjs_arg_unset(arg); else if (interface_type == GI_INFO_TYPE_VALUE) /* Better to use a non-NULL value holding NULL? */ gjs_arg_unset(arg); else gjs_arg_unset(arg); g_base_info_unref( (GIBaseInfo*) interface_info); } break; case GI_TYPE_TAG_GHASH: // Possibly better to return an empty hash table? gjs_arg_unset(arg); break; case GI_TYPE_TAG_ARRAY: gjs_arg_unset(arg); break; default: g_warning("Unhandled type %s for default GArgument initialization", g_type_tag_to_string(type_tag)); break; } } bool gjs_value_to_arg(JSContext *context, JS::HandleValue value, GIArgInfo *arg_info, GIArgument *arg) { GITypeInfo type_info; g_arg_info_load_type(arg_info, &type_info); return gjs_value_to_g_argument(context, value, &type_info, g_base_info_get_name( (GIBaseInfo*) arg_info), (g_arg_info_is_return_value(arg_info) ? GJS_ARGUMENT_RETURN_VALUE : GJS_ARGUMENT_ARGUMENT), g_arg_info_get_ownership_transfer(arg_info), g_arg_info_may_be_null(arg_info), arg); } GJS_JSAPI_RETURN_CONVENTION static bool gjs_array_from_g_list (JSContext *context, JS::MutableHandleValue value_p, GITypeTag list_tag, GITypeInfo *param_info, GList *list, GSList *slist) { unsigned int i; GArgument arg; JS::RootedValueVector elems(context); i = 0; if (list_tag == GI_TYPE_TAG_GLIST) { for ( ; list != NULL; list = list->next) { _g_type_info_argument_from_hash_pointer(param_info, list->data, &arg); if (!elems.growBy(1)) { JS_ReportOutOfMemory(context); return false; } if (!gjs_value_from_g_argument(context, elems[i], param_info, &arg, true)) return false; ++i; } } else { for ( ; slist != NULL; slist = slist->next) { _g_type_info_argument_from_hash_pointer(param_info, slist->data, &arg); if (!elems.growBy(1)) { JS_ReportOutOfMemory(context); return false; } if (!gjs_value_from_g_argument(context, elems[i], param_info, &arg, true)) return false; ++i; } } JS::RootedObject obj(context, JS::NewArrayObject(context, elems)); if (!obj) return false; value_p.setObject(*obj); return true; } template GJS_JSAPI_RETURN_CONVENTION static bool fill_vector_from_carray( JSContext* cx, JS::RootedValueVector& elems, // NOLINT(runtime/references) GITypeInfo* param_info, GIArgument* arg, void* array, size_t length) { for (size_t i = 0; i < length; i++) { gjs_arg_set(arg, *(static_cast(array) + i)); if (!gjs_value_from_g_argument(cx, elems[i], param_info, arg, true)) return false; } return true; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_array_from_carray_internal (JSContext *context, JS::MutableHandleValue value_p, GIArrayType array_type, GITypeInfo *param_info, guint length, gpointer array) { GArgument arg; GITypeTag element_type; guint i; element_type = g_type_info_get_tag(param_info); if (is_gvalue_flat_array(param_info, element_type)) return gjs_array_from_flat_gvalue_array(context, array, length, value_p); /* Special case array(guint8) */ if (element_type == GI_TYPE_TAG_UINT8) { JSObject* obj = gjs_byte_array_from_data(context, length, array); if (!obj) return false; value_p.setObject(*obj); return true; } /* Special case array(unichar) to be a string in JS */ if (element_type == GI_TYPE_TAG_UNICHAR) return gjs_string_from_ucs4(context, (gunichar *) array, length, value_p); // a null array pointer takes precedence over whatever `length` says if (!array) { JSObject* jsarray = JS::NewArrayObject(context, 0); if (!jsarray) return false; value_p.setObject(*jsarray); return true; } JS::RootedValueVector elems(context); if (!elems.resize(length)) { JS_ReportOutOfMemory(context); return false; } switch (element_type) { /* Special cases handled above */ case GI_TYPE_TAG_UINT8: case GI_TYPE_TAG_UNICHAR: g_assert_not_reached(); case GI_TYPE_TAG_BOOLEAN: if (!fill_vector_from_carray( context, elems, param_info, &arg, array, length)) return false; break; case GI_TYPE_TAG_INT8: if (!fill_vector_from_carray(context, elems, param_info, &arg, array, length)) return false; break; case GI_TYPE_TAG_UINT16: if (!fill_vector_from_carray(context, elems, param_info, &arg, array, length)) return false; break; case GI_TYPE_TAG_INT16: if (!fill_vector_from_carray(context, elems, param_info, &arg, array, length)) return false; break; case GI_TYPE_TAG_UINT32: if (!fill_vector_from_carray(context, elems, param_info, &arg, array, length)) return false; break; case GI_TYPE_TAG_INT32: if (!fill_vector_from_carray(context, elems, param_info, &arg, array, length)) return false; break; case GI_TYPE_TAG_UINT64: if (!fill_vector_from_carray(context, elems, param_info, &arg, array, length)) return false; break; case GI_TYPE_TAG_INT64: if (!fill_vector_from_carray(context, elems, param_info, &arg, array, length)) return false; break; case GI_TYPE_TAG_FLOAT: if (!fill_vector_from_carray(context, elems, param_info, &arg, array, length)) return false; break; case GI_TYPE_TAG_DOUBLE: if (!fill_vector_from_carray(context, elems, param_info, &arg, array, length)) return false; break; case GI_TYPE_TAG_INTERFACE: { GIBaseInfo *interface_info = g_type_info_get_interface (param_info); GIInfoType info_type = g_base_info_get_type (interface_info); if (array_type != GI_ARRAY_TYPE_PTR_ARRAY && (info_type == GI_INFO_TYPE_STRUCT || info_type == GI_INFO_TYPE_UNION) && !g_type_info_is_pointer(param_info)) { size_t struct_size; if (info_type == GI_INFO_TYPE_UNION) struct_size = g_union_info_get_size(interface_info); else struct_size = g_struct_info_get_size(interface_info); for (i = 0; i < length; i++) { gjs_arg_set(&arg, static_cast(array) + (struct_size * i)); if (!gjs_value_from_g_argument(context, elems[i], param_info, &arg, true)) return false; } g_base_info_unref(interface_info); break; } g_base_info_unref(interface_info); } /* fallthrough */ case GI_TYPE_TAG_GTYPE: case GI_TYPE_TAG_UTF8: case GI_TYPE_TAG_FILENAME: case GI_TYPE_TAG_ARRAY: case GI_TYPE_TAG_GLIST: case GI_TYPE_TAG_GSLIST: case GI_TYPE_TAG_GHASH: case GI_TYPE_TAG_ERROR: if (!fill_vector_from_carray(context, elems, param_info, &arg, array, length)) return false; break; case GI_TYPE_TAG_VOID: default: gjs_throw(context, "Unknown Array element-type %d", element_type); return false; } JS::RootedObject obj(context, JS::NewArrayObject(context, elems)); if (!obj) return false; value_p.setObject(*obj); return true; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_array_from_fixed_size_array (JSContext *context, JS::MutableHandleValue value_p, GITypeInfo *type_info, gpointer array) { gint length; GITypeInfo *param_info; bool res; length = g_type_info_get_array_fixed_size(type_info); g_assert (length != -1); param_info = g_type_info_get_param_type(type_info, 0); res = gjs_array_from_carray_internal(context, value_p, g_type_info_get_array_type(type_info), param_info, length, array); g_base_info_unref((GIBaseInfo*)param_info); return res; } bool gjs_value_from_explicit_array(JSContext *context, JS::MutableHandleValue value_p, GITypeInfo *type_info, GIArgument *arg, int length) { GITypeInfo *param_info; param_info = g_type_info_get_param_type(type_info, 0); bool res = gjs_array_from_carray_internal( context, value_p, g_type_info_get_array_type(type_info), param_info, length, gjs_arg_get(arg)); g_base_info_unref((GIBaseInfo*)param_info); return res; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_array_from_boxed_array (JSContext *context, JS::MutableHandleValue value_p, GIArrayType array_type, GITypeInfo *param_info, GArgument *arg) { GArray *array; GPtrArray *ptr_array; gpointer data = NULL; gsize length = 0; if (!gjs_arg_get(arg)) { value_p.setNull(); return true; } switch(array_type) { case GI_ARRAY_TYPE_BYTE_ARRAY: /* GByteArray is just a typedef for GArray internally */ case GI_ARRAY_TYPE_ARRAY: array = gjs_arg_get(arg); data = array->data; length = array->len; break; case GI_ARRAY_TYPE_PTR_ARRAY: ptr_array = gjs_arg_get(arg); data = ptr_array->pdata; length = ptr_array->len; break; case GI_ARRAY_TYPE_C: /* already checked in gjs_value_from_g_argument() */ default: g_assert_not_reached(); } return gjs_array_from_carray_internal(context, value_p, array_type, param_info, length, data); } template GJS_JSAPI_RETURN_CONVENTION static bool fill_vector_from_zero_terminated_carray( JSContext* cx, JS::RootedValueVector& elems, // NOLINT(runtime/references) GITypeInfo* param_info, GIArgument* arg, void* c_array) { T* array = static_cast(c_array); for (size_t i = 0; array[i]; i++) { gjs_arg_set(arg, array[i]); if (!elems.growBy(1)) { JS_ReportOutOfMemory(cx); return false; } if (!gjs_value_from_g_argument(cx, elems[i], param_info, arg, true)) return false; } return true; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_array_from_zero_terminated_c_array (JSContext *context, JS::MutableHandleValue value_p, GITypeInfo *param_info, gpointer c_array) { GArgument arg; GITypeTag element_type; element_type = g_type_info_get_tag(param_info); /* Special case array(guint8) */ if (element_type == GI_TYPE_TAG_UINT8) { size_t len = strlen(static_cast(c_array)); JSObject* obj = gjs_byte_array_from_data(context, len, c_array); if (!obj) return false; value_p.setObject(*obj); return true; } /* Special case array(gunichar) to JS string */ if (element_type == GI_TYPE_TAG_UNICHAR) return gjs_string_from_ucs4(context, (gunichar *) c_array, -1, value_p); JS::RootedValueVector elems(context); switch (element_type) { /* Special cases handled above. */ case GI_TYPE_TAG_UINT8: case GI_TYPE_TAG_UNICHAR: g_assert_not_reached(); case GI_TYPE_TAG_INT8: if (!fill_vector_from_zero_terminated_carray( context, elems, param_info, &arg, c_array)) return false; break; case GI_TYPE_TAG_UINT16: if (!fill_vector_from_zero_terminated_carray( context, elems, param_info, &arg, c_array)) return false; break; case GI_TYPE_TAG_INT16: if (!fill_vector_from_zero_terminated_carray( context, elems, param_info, &arg, c_array)) return false; break; case GI_TYPE_TAG_UINT32: if (!fill_vector_from_zero_terminated_carray( context, elems, param_info, &arg, c_array)) return false; break; case GI_TYPE_TAG_INT32: if (!fill_vector_from_zero_terminated_carray( context, elems, param_info, &arg, c_array)) return false; break; case GI_TYPE_TAG_UINT64: if (!fill_vector_from_zero_terminated_carray( context, elems, param_info, &arg, c_array)) return false; break; case GI_TYPE_TAG_INT64: if (!fill_vector_from_zero_terminated_carray( context, elems, param_info, &arg, c_array)) return false; break; case GI_TYPE_TAG_FLOAT: if (!fill_vector_from_zero_terminated_carray( context, elems, param_info, &arg, c_array)) return false; break; case GI_TYPE_TAG_DOUBLE: if (!fill_vector_from_zero_terminated_carray( context, elems, param_info, &arg, c_array)) return false; break; case GI_TYPE_TAG_GTYPE: case GI_TYPE_TAG_UTF8: case GI_TYPE_TAG_FILENAME: case GI_TYPE_TAG_ARRAY: case GI_TYPE_TAG_INTERFACE: case GI_TYPE_TAG_GLIST: case GI_TYPE_TAG_GSLIST: case GI_TYPE_TAG_GHASH: case GI_TYPE_TAG_ERROR: if (!fill_vector_from_zero_terminated_carray( context, elems, param_info, &arg, c_array)) return false; break; /* Boolean zero-terminated array makes no sense, because FALSE is also * zero */ case GI_TYPE_TAG_BOOLEAN: gjs_throw(context, "Boolean zero-terminated array not supported"); return false; case GI_TYPE_TAG_VOID: default: gjs_throw(context, "Unknown element-type %d", element_type); return false; } JS::RootedObject obj(context, JS::NewArrayObject(context, elems)); if (!obj) return false; value_p.setObject(*obj); return true; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_object_from_g_hash (JSContext *context, JS::MutableHandleValue value_p, GITypeInfo *key_param_info, GITypeInfo *val_param_info, GHashTable *hash) { GHashTableIter iter; GArgument keyarg, valarg; // a NULL hash table becomes a null JS value if (hash==NULL) { value_p.setNull(); return true; } JS::RootedObject obj(context, JS_NewPlainObject(context)); if (!obj) return false; value_p.setObject(*obj); JS::RootedValue keyjs(context), valjs(context); JS::RootedString keystr(context); g_hash_table_iter_init(&iter, hash); void* key_pointer; void* val_pointer; while (g_hash_table_iter_next(&iter, &key_pointer, &val_pointer)) { _g_type_info_argument_from_hash_pointer(key_param_info, key_pointer, &keyarg); if (!gjs_value_from_g_argument(context, &keyjs, key_param_info, &keyarg, true)) return false; keystr = JS::ToString(context, keyjs); if (!keystr) return false; JS::UniqueChars keyutf8(JS_EncodeStringToUTF8(context, keystr)); if (!keyutf8) return false; _g_type_info_argument_from_hash_pointer(val_param_info, val_pointer, &valarg); if (!gjs_value_from_g_argument(context, &valjs, val_param_info, &valarg, true)) return false; if (!JS_DefineProperty(context, obj, keyutf8.get(), valjs, JSPROP_ENUMERATE)) return false; } return true; } bool gjs_value_from_g_argument (JSContext *context, JS::MutableHandleValue value_p, GITypeInfo *type_info, GArgument *arg, bool copy_structs) { GITypeTag type_tag; type_tag = g_type_info_get_tag( (GITypeInfo*) type_info); gjs_debug_marshal(GJS_DEBUG_GFUNCTION, "Converting GArgument %s to JS::Value", g_type_tag_to_string(type_tag)); value_p.setNull(); switch (type_tag) { case GI_TYPE_TAG_VOID: value_p.setUndefined(); /* or .setNull() ? */ break; case GI_TYPE_TAG_BOOLEAN: value_p.setBoolean(gjs_arg_get(arg)); break; case GI_TYPE_TAG_INT32: value_p.setInt32(gjs_arg_get(arg)); break; case GI_TYPE_TAG_UINT32: value_p.setNumber(gjs_arg_get(arg)); break; case GI_TYPE_TAG_INT64: value_p.setNumber(gjs_arg_get_maybe_rounded(arg)); break; case GI_TYPE_TAG_UINT64: value_p.setNumber(gjs_arg_get_maybe_rounded(arg)); break; case GI_TYPE_TAG_UINT16: value_p.setInt32(gjs_arg_get(arg)); break; case GI_TYPE_TAG_INT16: value_p.setInt32(gjs_arg_get(arg)); break; case GI_TYPE_TAG_UINT8: value_p.setInt32(gjs_arg_get(arg)); break; case GI_TYPE_TAG_INT8: value_p.setInt32(gjs_arg_get(arg)); break; case GI_TYPE_TAG_FLOAT: value_p.setNumber(gjs_arg_get(arg)); break; case GI_TYPE_TAG_DOUBLE: value_p.setNumber(gjs_arg_get(arg)); break; case GI_TYPE_TAG_GTYPE: { GType gtype = gjs_arg_get(arg); if (gtype == 0) return true; /* value_p is set to JS null */ JS::RootedObject obj(context, gjs_gtype_create_gtype_wrapper(context, gtype)); if (!obj) return false; value_p.setObject(*obj); return true; } break; case GI_TYPE_TAG_UNICHAR: { char32_t value = gjs_arg_get(arg); // Preserve the bidirectional mapping between 0 and "" if (value == 0) { value_p.set(JS_GetEmptyStringValue(context)); return true; } else if (!g_unichar_validate(value)) { gjs_throw(context, "Invalid unicode codepoint %" G_GUINT32_FORMAT, value); return false; } char utf8[7]; int bytes = g_unichar_to_utf8(value, utf8); return gjs_string_from_utf8_n(context, utf8, bytes, value_p); } case GI_TYPE_TAG_FILENAME: case GI_TYPE_TAG_UTF8: { const char* str = gjs_arg_get(arg); // For nullptr we'll return JS::NullValue(), which is already set // in *value_p if (!str) return true; if (type_tag == GI_TYPE_TAG_FILENAME) return gjs_string_from_filename(context, str, -1, value_p); return gjs_string_from_utf8(context, str, value_p); } case GI_TYPE_TAG_ERROR: { GError* ptr = gjs_arg_get(arg); if (!ptr) return true; JSObject* obj = ErrorInstance::object_for_c_ptr(context, ptr); if (!obj) return false; value_p.setObject(*obj); return true; } case GI_TYPE_TAG_INTERFACE: { GIInfoType interface_type; GType gtype; GjsAutoBaseInfo interface_info = g_type_info_get_interface(type_info); g_assert(interface_info); interface_type = g_base_info_get_type(interface_info); if (interface_type == GI_INFO_TYPE_UNRESOLVED) { gjs_throw(context, "Unable to resolve arg type '%s'", g_base_info_get_name(interface_info)); return false; } /* Enum/Flags are aren't pointer types, unlike the other interface subtypes */ if (interface_type == GI_INFO_TYPE_ENUM) { int64_t value_int64 = _gjs_enum_from_int( interface_info, gjs_arg_get(arg)); if (!_gjs_enum_value_is_valid(context, interface_info, value_int64)) return false; value_p.setNumber(static_cast(value_int64)); return true; } if (interface_type == GI_INFO_TYPE_FLAGS) { int64_t value_int64 = _gjs_enum_from_int( interface_info, gjs_arg_get(arg)); gtype = g_registered_type_info_get_g_type((GIRegisteredTypeInfo*)interface_info); if (!_gjs_flags_value_is_valid(context, gtype, value_int64)) return false; value_p.setNumber(static_cast(value_int64)); return true; } if (interface_type == GI_INFO_TYPE_STRUCT && g_struct_info_is_foreign((GIStructInfo*)interface_info)) { return gjs_struct_foreign_convert_from_g_argument( context, value_p, interface_info, arg); } /* Everything else is a pointer type, NULL is the easy case */ if (!gjs_arg_get(arg)) { value_p.setNull(); return true; } if (interface_type == GI_INFO_TYPE_STRUCT && g_struct_info_is_gtype_struct((GIStructInfo*)interface_info)) { /* XXX: here we make the implicit assumption that GTypeClass is the same as GTypeInterface. This is true for the GType field, which is what we use, but not for the rest of the structure! */ gtype = G_TYPE_FROM_CLASS(gjs_arg_get(arg)); if (g_type_is_a(gtype, G_TYPE_INTERFACE)) { return gjs_lookup_interface_constructor(context, gtype, value_p); } return gjs_lookup_object_constructor(context, gtype, value_p); } gtype = g_registered_type_info_get_g_type((GIRegisteredTypeInfo*)interface_info); if (G_TYPE_IS_INSTANTIATABLE(gtype) || G_TYPE_IS_INTERFACE(gtype)) gtype = G_TYPE_FROM_INSTANCE(gjs_arg_get(arg)); gjs_debug_marshal(GJS_DEBUG_GFUNCTION, "gtype of INTERFACE is %s", g_type_name(gtype)); /* Test GValue and GError before Struct, or it will be handled as the latter */ if (g_type_is_a(gtype, G_TYPE_VALUE)) { return gjs_value_from_g_value(context, value_p, gjs_arg_get(arg)); } if (g_type_is_a(gtype, G_TYPE_ERROR)) { JSObject* obj = ErrorInstance::object_for_c_ptr( context, gjs_arg_get(arg)); if (!obj) return false; value_p.setObject(*obj); return true; } if (interface_type == GI_INFO_TYPE_STRUCT || interface_type == GI_INFO_TYPE_BOXED) { if (is_gdk_atom(interface_info)) { GIFunctionInfo *atom_name_fun = g_struct_info_find_method(interface_info, "name"); GIArgument atom_name_ret; g_function_info_invoke(atom_name_fun, arg, 1, nullptr, 0, &atom_name_ret, nullptr); g_base_info_unref(atom_name_fun); GjsAutoChar name = gjs_arg_get(&atom_name_ret); if (g_strcmp0("NONE", name) == 0) { value_p.setNull(); return true; } return gjs_string_from_utf8(context, name, value_p); } JSObject *obj; if (copy_structs || g_type_is_a(gtype, G_TYPE_VARIANT)) obj = BoxedInstance::new_for_c_struct( context, interface_info, gjs_arg_get(arg)); else obj = BoxedInstance::new_for_c_struct( context, interface_info, gjs_arg_get(arg), BoxedInstance::NoCopy()); if (!obj) return false; value_p.setObject(*obj); return true; } if (interface_type == GI_INFO_TYPE_UNION) { JSObject* obj = gjs_union_from_c_union( context, static_cast(interface_info), gjs_arg_get(arg)); if (!obj) return false; value_p.setObject(*obj); return true; } if (g_type_is_a(gtype, G_TYPE_OBJECT)) { // Null arg is already handled above JSObject* obj = ObjectInstance::wrapper_from_gobject( context, G_OBJECT(gjs_arg_get(arg))); if (!obj) return false; value_p.setObject(*obj); return true; } if (g_type_is_a(gtype, G_TYPE_BOXED) || g_type_is_a(gtype, G_TYPE_ENUM) || g_type_is_a(gtype, G_TYPE_FLAGS)) { /* Should have been handled above */ gjs_throw(context, "Type %s registered for unexpected interface_type %d", g_type_name(gtype), interface_type); return false; } if (g_type_is_a(gtype, G_TYPE_PARAM)) { JSObject* obj = gjs_param_from_g_param( context, G_PARAM_SPEC(gjs_arg_get(arg))); if (!obj) return false; value_p.setObject(*obj); return true; } if (gtype == G_TYPE_NONE) { gjs_throw(context, "Unexpected unregistered type packing GArgument into JS::Value"); return false; } if (G_TYPE_IS_INSTANTIATABLE(gtype) || G_TYPE_IS_INTERFACE(gtype)) { JSObject* obj = FundamentalInstance::object_for_c_ptr( context, gjs_arg_get(arg)); if (!obj) return false; value_p.setObject(*obj); return true; } gjs_throw(context, "Unhandled GType %s packing GArgument into JS::Value", g_type_name(gtype)); return false; } case GI_TYPE_TAG_ARRAY: if (!gjs_arg_get(arg)) { /* OK, but no conversion to do */ } else if (g_type_info_get_array_type(type_info) == GI_ARRAY_TYPE_C) { if (g_type_info_is_zero_terminated(type_info)) { GITypeInfo *param_info; param_info = g_type_info_get_param_type(type_info, 0); g_assert(param_info != NULL); bool result = gjs_array_from_zero_terminated_c_array( context, value_p, param_info, gjs_arg_get(arg)); g_base_info_unref((GIBaseInfo*) param_info); return result; } else { /* arrays with length are handled outside of this function */ g_assert(((void) "Use gjs_value_from_explicit_array() for " "arrays with length param", g_type_info_get_array_length(type_info) == -1)); return gjs_array_from_fixed_size_array( context, value_p, type_info, gjs_arg_get(arg)); } } else if (g_type_info_get_array_type(type_info) == GI_ARRAY_TYPE_BYTE_ARRAY) { auto* byte_array = gjs_arg_get(arg); JSObject* array = gjs_byte_array_from_byte_array(context, byte_array); if (!array) { gjs_throw(context, "Couldn't convert GByteArray to a Uint8Array"); return false; } value_p.setObject(*array); } else { /* this assumes the array type is one of GArray, GPtrArray or * GByteArray */ GITypeInfo *param_info; bool result; param_info = g_type_info_get_param_type(type_info, 0); g_assert(param_info != NULL); result = gjs_array_from_boxed_array(context, value_p, g_type_info_get_array_type(type_info), param_info, arg); g_base_info_unref((GIBaseInfo*) param_info); return result; } break; case GI_TYPE_TAG_GLIST: case GI_TYPE_TAG_GSLIST: { GITypeInfo *param_info; param_info = g_type_info_get_param_type(type_info, 0); g_assert(param_info != NULL); bool result = gjs_array_from_g_list( context, value_p, type_tag, param_info, type_tag == GI_TYPE_TAG_GLIST ? gjs_arg_get(arg) : nullptr, type_tag == GI_TYPE_TAG_GSLIST ? gjs_arg_get(arg) : nullptr); g_base_info_unref((GIBaseInfo*) param_info); return result; } break; case GI_TYPE_TAG_GHASH: { GITypeInfo *key_param_info, *val_param_info; key_param_info = g_type_info_get_param_type(type_info, 0); g_assert(key_param_info != NULL); val_param_info = g_type_info_get_param_type(type_info, 1); g_assert(val_param_info != NULL); bool result = gjs_object_from_g_hash(context, value_p, key_param_info, val_param_info, gjs_arg_get(arg)); g_base_info_unref((GIBaseInfo*) key_param_info); g_base_info_unref((GIBaseInfo*) val_param_info); return result; } break; default: g_warning("Unhandled type %s converting GArgument to JavaScript", g_type_tag_to_string(type_tag)); return false; } return true; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_g_arg_release_internal(JSContext *context, GITransfer transfer, GITypeInfo *type_info, GITypeTag type_tag, GArgument *arg); typedef struct { JSContext *context; GITypeInfo *key_param_info, *val_param_info; GITransfer transfer; bool failed; } GHR_closure; static gboolean gjs_ghr_helper(gpointer key, gpointer val, gpointer user_data) { GHR_closure *c = (GHR_closure *) user_data; GArgument key_arg, val_arg; gjs_arg_set(&key_arg, key); gjs_arg_set(&val_arg, val); if (!gjs_g_arg_release_internal(c->context, c->transfer, c->key_param_info, g_type_info_get_tag(c->key_param_info), &key_arg)) c->failed = true; GITypeTag val_type = g_type_info_get_tag(c->val_param_info); if (val_type == GI_TYPE_TAG_INT64 || val_type == GI_TYPE_TAG_UINT64 || val_type == GI_TYPE_TAG_FLOAT || val_type == GI_TYPE_TAG_DOUBLE) { g_clear_pointer(&gjs_arg_member(&val_arg), g_free); } else if (!gjs_g_arg_release_internal(c->context, c->transfer, c->val_param_info, val_type, &val_arg)) { c->failed = true; } return true; } /* We need to handle GI_TRANSFER_NOTHING differently for out parameters * (free nothing) and for in parameters (free any temporaries we've * allocated */ #define TRANSFER_IN_NOTHING (GI_TRANSFER_EVERYTHING + 1) GJS_JSAPI_RETURN_CONVENTION static bool gjs_g_arg_release_internal(JSContext *context, GITransfer transfer, GITypeInfo *type_info, GITypeTag type_tag, GArgument *arg) { bool failed; g_assert(transfer != GI_TRANSFER_NOTHING); failed = false; switch (type_tag) { case GI_TYPE_TAG_VOID: case GI_TYPE_TAG_BOOLEAN: case GI_TYPE_TAG_INT8: case GI_TYPE_TAG_UINT8: case GI_TYPE_TAG_INT16: case GI_TYPE_TAG_UINT16: case GI_TYPE_TAG_INT32: case GI_TYPE_TAG_UINT32: case GI_TYPE_TAG_INT64: case GI_TYPE_TAG_UINT64: case GI_TYPE_TAG_FLOAT: case GI_TYPE_TAG_DOUBLE: case GI_TYPE_TAG_UNICHAR: case GI_TYPE_TAG_GTYPE: break; case GI_TYPE_TAG_FILENAME: case GI_TYPE_TAG_UTF8: g_clear_pointer(&gjs_arg_member(arg), g_free); break; case GI_TYPE_TAG_ERROR: if (transfer != TRANSFER_IN_NOTHING) g_clear_error(&gjs_arg_member(arg)); break; case GI_TYPE_TAG_INTERFACE: { GIInfoType interface_type; GType gtype; GjsAutoBaseInfo interface_info = g_type_info_get_interface(type_info); g_assert(interface_info); interface_type = g_base_info_get_type(interface_info); if (interface_type == GI_INFO_TYPE_STRUCT && g_struct_info_is_foreign((GIStructInfo*)interface_info)) return gjs_struct_foreign_release_g_argument(context, transfer, interface_info, arg); if (interface_type == GI_INFO_TYPE_ENUM || interface_type == GI_INFO_TYPE_FLAGS) return true; /* Anything else is a pointer */ if (!gjs_arg_get(arg)) return true; gtype = g_registered_type_info_get_g_type((GIRegisteredTypeInfo*)interface_info); if (G_TYPE_IS_INSTANTIATABLE(gtype) || G_TYPE_IS_INTERFACE(gtype)) gtype = G_TYPE_FROM_INSTANCE(gjs_arg_get(arg)); gjs_debug_marshal(GJS_DEBUG_GFUNCTION, "gtype of INTERFACE is %s", g_type_name(gtype)); /* In gjs_value_from_g_argument we handle Struct/Union types without a * registered GType, but here we are specifically handling a GArgument that * *owns* its value, and that is non-sensical for such types, so we * don't have to worry about it. */ if (g_type_is_a(gtype, G_TYPE_OBJECT)) { if (transfer != TRANSFER_IN_NOTHING) g_clear_object(&gjs_arg_member(arg)); } else if (g_type_is_a(gtype, G_TYPE_PARAM)) { if (transfer != TRANSFER_IN_NOTHING) g_clear_pointer(&gjs_arg_member(arg), g_param_spec_unref); } else if (g_type_is_a(gtype, G_TYPE_CLOSURE)) { g_clear_pointer(&gjs_arg_member(arg), g_closure_unref); } else if (g_type_is_a(gtype, G_TYPE_VALUE)) { /* G_TYPE_VALUE is-a G_TYPE_BOXED, but we special case it */ if (g_type_info_is_pointer (type_info)) g_boxed_free( gtype, g_steal_pointer(&gjs_arg_member(arg))); else g_clear_pointer(&gjs_arg_member(arg), g_value_unset); } else if (g_type_is_a(gtype, G_TYPE_BOXED)) { if (transfer != TRANSFER_IN_NOTHING) g_boxed_free( gtype, g_steal_pointer(&gjs_arg_member(arg))); } else if (g_type_is_a(gtype, G_TYPE_VARIANT)) { if (transfer != TRANSFER_IN_NOTHING) g_clear_pointer(&gjs_arg_member(arg), g_variant_unref); } else if (gtype == G_TYPE_NONE) { if (transfer != TRANSFER_IN_NOTHING) { gjs_throw(context, "Don't know how to release GArgument: not an object or boxed type"); failed = true; } } else if (G_TYPE_IS_INSTANTIATABLE(gtype)) { if (transfer != TRANSFER_IN_NOTHING) { auto* priv = FundamentalPrototype::for_gtype(context, gtype); priv->call_unref_function( g_steal_pointer(&gjs_arg_member(arg))); } } else { gjs_throw(context, "Unhandled GType %s releasing GArgument", g_type_name(gtype)); return false; } } return true; case GI_TYPE_TAG_GLIST: if (transfer != GI_TRANSFER_CONTAINER) { GITypeInfo *param_info; GList *list; param_info = g_type_info_get_param_type(type_info, 0); g_assert(param_info != NULL); for (list = gjs_arg_get(arg); list; list = list->next) { GArgument elem; gjs_arg_set(&elem, list->data); if (!gjs_g_arg_release_internal(context, transfer, param_info, g_type_info_get_tag(param_info), &elem)) { failed = true; } } g_base_info_unref((GIBaseInfo*) param_info); } g_clear_pointer(&gjs_arg_member(arg), g_list_free); break; case GI_TYPE_TAG_ARRAY: { GIArrayType array_type = g_type_info_get_array_type(type_info); if (!gjs_arg_get(arg)) { /* OK */ } else if (array_type == GI_ARRAY_TYPE_C) { GITypeInfo *param_info; GITypeTag element_type; param_info = g_type_info_get_param_type(type_info, 0); element_type = g_type_info_get_tag(param_info); if (is_gvalue_flat_array(param_info, element_type)) { if (transfer != GI_TRANSFER_CONTAINER) { gint len = g_type_info_get_array_fixed_size(type_info); gint i; if (len < 0) { gjs_throw(context, "Releasing a flat GValue array that was not fixed-size or was nested" "inside another container. This is not supported (and will leak)"); g_base_info_unref(param_info); return false; } for (i = 0; i < len; i++) { GValue* v = gjs_arg_get(arg) + i; g_value_unset(v); } } g_clear_pointer(&gjs_arg_member(arg), g_free); g_base_info_unref(param_info); return true; } switch (element_type) { case GI_TYPE_TAG_UTF8: case GI_TYPE_TAG_FILENAME: if (transfer == GI_TRANSFER_CONTAINER) g_clear_pointer(&gjs_arg_member(arg), g_free); else g_clear_pointer(&gjs_arg_member(arg), g_strfreev); break; case GI_TYPE_TAG_BOOLEAN: case GI_TYPE_TAG_UINT8: case GI_TYPE_TAG_UINT16: case GI_TYPE_TAG_UINT32: case GI_TYPE_TAG_UINT64: case GI_TYPE_TAG_INT8: case GI_TYPE_TAG_INT16: case GI_TYPE_TAG_INT32: case GI_TYPE_TAG_INT64: case GI_TYPE_TAG_FLOAT: case GI_TYPE_TAG_DOUBLE: case GI_TYPE_TAG_UNICHAR: case GI_TYPE_TAG_GTYPE: g_clear_pointer(&gjs_arg_member(arg), g_free); break; case GI_TYPE_TAG_INTERFACE: if (!g_type_info_is_pointer(param_info)) { GjsAutoBaseInfo interface_info = g_type_info_get_interface(param_info); GIInfoType info_type = g_base_info_get_type(interface_info); if (info_type == GI_INFO_TYPE_STRUCT || info_type == GI_INFO_TYPE_UNION) { g_clear_pointer(&gjs_arg_member(arg), g_free); break; } } [[fallthrough]]; case GI_TYPE_TAG_GLIST: case GI_TYPE_TAG_GSLIST: case GI_TYPE_TAG_ARRAY: case GI_TYPE_TAG_GHASH: case GI_TYPE_TAG_ERROR: if (transfer != GI_TRANSFER_CONTAINER && type_needs_out_release(param_info, element_type)) { if (g_type_info_is_zero_terminated (type_info)) { gpointer *array; GArgument elem; for (array = gjs_arg_get(arg); *array; array++) { gjs_arg_set(&elem, *array); if (!gjs_g_arg_release_internal(context, GI_TRANSFER_EVERYTHING, param_info, element_type, &elem)) { failed = true; } } } else { gint len = g_type_info_get_array_fixed_size(type_info); gint i; GArgument elem; g_assert(len != -1); for (i = 0; i < len; i++) { gjs_arg_set(&elem, gjs_arg_get(arg)[i]); if (!gjs_g_arg_release_internal(context, GI_TRANSFER_EVERYTHING, param_info, element_type, &elem)) { failed = true; } } } } g_clear_pointer(&gjs_arg_member(arg), g_free); break; case GI_TYPE_TAG_VOID: default: gjs_throw(context, "Releasing a C array with explicit length, that was nested" "inside another container. This is not supported (and will leak)"); failed = true; } g_base_info_unref((GIBaseInfo*) param_info); } else if (array_type == GI_ARRAY_TYPE_ARRAY) { GITypeInfo *param_info; GITypeTag element_type; param_info = g_type_info_get_param_type(type_info, 0); element_type = g_type_info_get_tag(param_info); switch (element_type) { case GI_TYPE_TAG_BOOLEAN: case GI_TYPE_TAG_UNICHAR: case GI_TYPE_TAG_UINT8: case GI_TYPE_TAG_UINT16: case GI_TYPE_TAG_UINT32: case GI_TYPE_TAG_UINT64: case GI_TYPE_TAG_INT8: case GI_TYPE_TAG_INT16: case GI_TYPE_TAG_INT32: case GI_TYPE_TAG_INT64: case GI_TYPE_TAG_FLOAT: case GI_TYPE_TAG_DOUBLE: case GI_TYPE_TAG_GTYPE: g_clear_pointer(&gjs_arg_member(arg), g_array_unref); break; case GI_TYPE_TAG_UTF8: case GI_TYPE_TAG_FILENAME: case GI_TYPE_TAG_ARRAY: case GI_TYPE_TAG_INTERFACE: case GI_TYPE_TAG_GLIST: case GI_TYPE_TAG_GSLIST: case GI_TYPE_TAG_GHASH: case GI_TYPE_TAG_ERROR: if (transfer == GI_TRANSFER_CONTAINER) { g_clear_pointer(&gjs_arg_member(arg), g_array_unref); } else if (type_needs_out_release (param_info, element_type)) { GArray* array = gjs_arg_get(arg); guint i; for (i = 0; i < array->len; i++) { GArgument arg_iter; gjs_arg_set(&arg_iter, g_array_index(array, gpointer, i)); failed = !gjs_g_arg_release_internal( context, transfer, param_info, element_type, &arg_iter); } g_array_free (array, true); } break; case GI_TYPE_TAG_VOID: default: gjs_throw(context, "Don't know how to release GArray element-type %d", element_type); failed = true; } g_base_info_unref((GIBaseInfo*) param_info); } else if (array_type == GI_ARRAY_TYPE_BYTE_ARRAY) { g_clear_pointer(&gjs_arg_member(arg), g_byte_array_unref); } else if (array_type == GI_ARRAY_TYPE_PTR_ARRAY) { GITypeInfo *param_info; GPtrArray *array; param_info = g_type_info_get_param_type(type_info, 0); array = gjs_arg_get(arg); if (transfer != GI_TRANSFER_CONTAINER) { guint i; for (i = 0; i < array->len; i++) { GArgument arg_iter; gjs_arg_set(&arg_iter, g_ptr_array_index(array, i)); failed = !gjs_g_argument_release(context, transfer, param_info, &arg_iter); } } g_ptr_array_free(array, true); g_base_info_unref((GIBaseInfo*) param_info); } else { g_assert_not_reached(); } break; } case GI_TYPE_TAG_GSLIST: if (transfer != GI_TRANSFER_CONTAINER) { GITypeInfo *param_info; GSList *slist; param_info = g_type_info_get_param_type(type_info, 0); g_assert(param_info != NULL); for (slist = gjs_arg_get(arg); slist; slist = slist->next) { GArgument elem; gjs_arg_set(&elem, slist->data); if (!gjs_g_arg_release_internal(context, transfer, param_info, g_type_info_get_tag(param_info), &elem)) { failed = true; } } g_base_info_unref((GIBaseInfo*) param_info); } g_clear_pointer(&gjs_arg_member(arg), g_slist_free); break; case GI_TYPE_TAG_GHASH: if (gjs_arg_get(arg)) { if (transfer == GI_TRANSFER_CONTAINER) g_hash_table_steal_all(gjs_arg_get(arg)); else { GHR_closure c = { context, NULL, NULL, transfer, false }; c.key_param_info = g_type_info_get_param_type(type_info, 0); g_assert(c.key_param_info != NULL); c.val_param_info = g_type_info_get_param_type(type_info, 1); g_assert(c.val_param_info != NULL); g_hash_table_foreach_steal(gjs_arg_get(arg), gjs_ghr_helper, &c); failed = c.failed; g_base_info_unref ((GIBaseInfo *)c.key_param_info); g_base_info_unref ((GIBaseInfo *)c.val_param_info); } g_clear_pointer(&gjs_arg_member(arg), g_hash_table_destroy); } break; default: g_warning("Unhandled type %s releasing GArgument", g_type_tag_to_string(type_tag)); return false; } return !failed; } bool gjs_g_argument_release(JSContext *context, GITransfer transfer, GITypeInfo *type_info, GArgument *arg) { GITypeTag type_tag; if (transfer == GI_TRANSFER_NOTHING) return true; type_tag = g_type_info_get_tag( (GITypeInfo*) type_info); gjs_debug_marshal(GJS_DEBUG_GFUNCTION, "Releasing GArgument %s out param or return value", g_type_tag_to_string(type_tag)); return gjs_g_arg_release_internal(context, transfer, type_info, type_tag, arg); } bool gjs_g_argument_release_in_arg(JSContext *context, GITransfer transfer, GITypeInfo *type_info, GArgument *arg) { GITypeTag type_tag; /* GI_TRANSFER_EVERYTHING: we don't own the argument anymore. * GI_TRANSFER_CONTAINER: * - non-containers: treated as GI_TRANSFER_EVERYTHING * - containers: See FIXME in gjs_array_to_g_list(); currently * an error and we won't get here. */ if (transfer != GI_TRANSFER_NOTHING) return true; type_tag = g_type_info_get_tag( (GITypeInfo*) type_info); gjs_debug_marshal(GJS_DEBUG_GFUNCTION, "Releasing GArgument %s in param", g_type_tag_to_string(type_tag)); if (type_needs_release (type_info, type_tag)) return gjs_g_arg_release_internal(context, (GITransfer) TRANSFER_IN_NOTHING, type_info, type_tag, arg); return true; } bool gjs_g_argument_release_in_array(JSContext* context, GITransfer transfer, GITypeInfo* type_info, unsigned length, GIArgument* arg) { GITypeInfo *param_type; GArgument elem; guint i; bool ret = true; GITypeTag type_tag; if (transfer != GI_TRANSFER_NOTHING) return true; gjs_debug_marshal(GJS_DEBUG_GFUNCTION, "Releasing GArgument array in param"); void** array = gjs_arg_get(arg); param_type = g_type_info_get_param_type(type_info, 0); type_tag = g_type_info_get_tag(param_type); if (is_gvalue_flat_array(param_type, type_tag)) { for (i = 0; i < length; i++) { GValue *v = ((GValue*)array) + i; g_value_unset(v); } } if (type_needs_release(param_type, type_tag)) { for (i = 0; i < length; i++) { gjs_arg_set(&elem, array[i]); if (!gjs_g_arg_release_internal(context, (GITransfer) TRANSFER_IN_NOTHING, param_type, type_tag, &elem)) { ret = false; break; } } } g_base_info_unref(param_type); g_free(array); return ret; } bool gjs_g_argument_release_out_array(JSContext* context, GITransfer transfer, GITypeInfo* type_info, unsigned length, GIArgument* arg) { GITypeInfo *param_type; GArgument elem; guint i; bool ret = true; GITypeTag type_tag; if (transfer == GI_TRANSFER_NOTHING) return true; gjs_debug_marshal(GJS_DEBUG_GFUNCTION, "Releasing GArgument array out param"); void** array = gjs_arg_get(arg); param_type = g_type_info_get_param_type(type_info, 0); type_tag = g_type_info_get_tag(param_type); if (transfer != GI_TRANSFER_CONTAINER && type_needs_out_release(param_type, type_tag)) { for (i = 0; i < length; i++) { gjs_arg_set(&elem, array[i]); JS::AutoSaveExceptionState saved_exc(context); if (!gjs_g_arg_release_internal(context, GI_TRANSFER_EVERYTHING, param_type, type_tag, &elem)) { ret = false; } } } g_base_info_unref(param_type); g_free(array); return ret; } cjs-5.2.0/gi/fundamental.h0000644000175000017500000001501614144444702015510 0ustar jpeisachjpeisach/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* * Copyright (c) 2013 Intel Corporation * Copyright (c) 2008-2010 litl, LLC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #ifndef GI_FUNDAMENTAL_H_ #define GI_FUNDAMENTAL_H_ #include #include #include #include #include "gi/wrapperutils.h" #include "cjs/macros.h" #include "util/log.h" class FundamentalPrototype; class FundamentalInstance; namespace JS { class CallArgs; } /* To conserve memory, we have two different kinds of private data for JS * wrappers for fundamental types: FundamentalInstance, and * FundamentalPrototype. Both inherit from FundamentalBase for their common * functionality. For more information, see the notes in wrapperutils.h. */ class FundamentalBase : public GIWrapperBase { friend class GIWrapperBase; protected: explicit FundamentalBase(FundamentalPrototype* proto = nullptr) : GIWrapperBase(proto) {} ~FundamentalBase(void) {} static const GjsDebugTopic debug_topic = GJS_DEBUG_GFUNDAMENTAL; static constexpr const char* debug_tag = "fundamental"; static const struct JSClassOps class_ops; static const struct JSClass klass; // Helper methods [[nodiscard]] const char* to_string_kind() const { return "fundamental"; } // Public API public: GJS_JSAPI_RETURN_CONVENTION static bool to_gvalue(JSContext* cx, JS::HandleObject obj, GValue* gvalue); }; class FundamentalPrototype : public GIWrapperPrototype { friend class GIWrapperPrototype; friend class GIWrapperBase; GIObjectInfoRefFunction m_ref_function; GIObjectInfoUnrefFunction m_unref_function; GIObjectInfoGetValueFunction m_get_value_function; GIObjectInfoSetValueFunction m_set_value_function; GICallableInfo* m_constructor_info; explicit FundamentalPrototype(GIObjectInfo* info, GType gtype); ~FundamentalPrototype(void); static constexpr InfoType::Tag info_type_tag = InfoType::Object; public: GJS_JSAPI_RETURN_CONVENTION static FundamentalPrototype* for_gtype(JSContext* cx, GType gtype); // Accessors [[nodiscard]] GICallableInfo* constructor_info() const { return m_constructor_info; } void* call_ref_function(void* ptr) const { return m_ref_function(ptr); } void call_unref_function(void* ptr) const { m_unref_function(ptr); } [[nodiscard]] void* call_get_value_function(const GValue* value) const { return m_get_value_function(value); } void call_set_value_function(GValue* value, void* object) const { m_set_value_function(value, object); } // Helper methods private: GJS_JSAPI_RETURN_CONVENTION bool get_parent_proto(JSContext* cx, JS::MutableHandleObject proto) const; [[nodiscard]] unsigned constructor_nargs() const; GJS_JSAPI_RETURN_CONVENTION bool resolve_interface(JSContext* cx, JS::HandleObject obj, bool* resolved, const char* name); // JSClass operations GJS_JSAPI_RETURN_CONVENTION bool resolve_impl(JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool* resolved); // Public API public: GJS_JSAPI_RETURN_CONVENTION static bool define_class(JSContext* cx, JS::HandleObject in_object, GIObjectInfo* info, JS::MutableHandleObject constructor); }; class FundamentalInstance : public GIWrapperInstance { friend class FundamentalBase; // for set_value() friend class GIWrapperInstance; friend class GIWrapperBase; explicit FundamentalInstance(JSContext* cx, JS::HandleObject obj); ~FundamentalInstance(void); // Helper methods GJS_JSAPI_RETURN_CONVENTION bool invoke_constructor(JSContext* cx, JS::HandleObject obj, const JS::CallArgs& args, GIArgument* rvalue); void ref(void) { get_prototype()->call_ref_function(m_ptr); } void unref(void) { get_prototype()->call_unref_function(m_ptr); } void set_value(GValue* gvalue) const { get_prototype()->call_set_value_function(gvalue, m_ptr); } GJS_JSAPI_RETURN_CONVENTION bool associate_js_instance(JSContext* cx, JSObject* object, void* gfundamental); // JS constructor GJS_JSAPI_RETURN_CONVENTION bool constructor_impl(JSContext* cx, JS::HandleObject obj, const JS::CallArgs& args); public: GJS_JSAPI_RETURN_CONVENTION static JSObject* object_for_c_ptr(JSContext* cx, void* gfundamental); GJS_JSAPI_RETURN_CONVENTION static JSObject* object_for_gvalue(JSContext* cx, const GValue* gvalue, GType gtype); static void* copy_ptr(JSContext* cx, GType gtype, void* gfundamental); }; #endif // GI_FUNDAMENTAL_H_ cjs-5.2.0/gi/param.h0000644000175000017500000000362614144444702014316 0ustar jpeisachjpeisach/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* * Copyright (c) 2008 litl, LLC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #ifndef GI_PARAM_H_ #define GI_PARAM_H_ #include #include #include #include "cjs/macros.h" GJS_JSAPI_RETURN_CONVENTION bool gjs_define_param_class(JSContext *context, JS::HandleObject in_object); GJS_JSAPI_RETURN_CONVENTION GParamSpec *gjs_g_param_from_param (JSContext *context, JS::HandleObject obj); GJS_JSAPI_RETURN_CONVENTION JSObject* gjs_param_from_g_param (JSContext *context, GParamSpec *param); [[nodiscard]] bool gjs_typecheck_param(JSContext* cx, JS::HandleObject obj, GType expected_type, bool throw_error); #endif // GI_PARAM_H_ cjs-5.2.0/gi/wrapperutils.cpp0000644000175000017500000002013114144444702016300 0ustar jpeisachjpeisach/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* * Copyright (c) 2012 Red Hat, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #include #include #include #include // for JSPROP_PERMANENT #include #include // for JS_DefinePropertyById #include "gi/function.h" #include "gi/gtype.h" #include "gi/wrapperutils.h" #include "cjs/atoms.h" #include "cjs/context-private.h" #include "cjs/jsapi-util.h" /* Default spidermonkey toString is worthless. Replace it * with something that gives us both the introspection name * and a memory address. */ bool gjs_wrapper_to_string_func(JSContext* context, JSObject* this_obj, const char* objtype, GIBaseInfo* info, GType gtype, const void* native_address, JS::MutableHandleValue rval) { GString *buf; bool ret = false; buf = g_string_new(""); g_string_append_c(buf, '['); g_string_append(buf, objtype); if (!native_address) g_string_append(buf, " prototype of"); else g_string_append(buf, " instance wrapper"); if (info) { g_string_append_printf(buf, " GIName:%s.%s", g_base_info_get_namespace(info), g_base_info_get_name(info)); } else { g_string_append(buf, " GType:"); g_string_append(buf, g_type_name(gtype)); } g_string_append_printf(buf, " jsobj@%p", this_obj); if (native_address) g_string_append_printf(buf, " native@%p", native_address); g_string_append_c(buf, ']'); if (!gjs_string_from_utf8(context, buf->str, rval)) goto out; ret = true; out: g_string_free(buf, true); return ret; } bool gjs_wrapper_throw_nonexistent_field(JSContext* cx, GType gtype, const char* field_name) { gjs_throw(cx, "No property %s on %s", field_name, g_type_name(gtype)); return false; } bool gjs_wrapper_throw_readonly_field(JSContext* cx, GType gtype, const char* field_name) { gjs_throw(cx, "Property %s.%s is not writable", g_type_name(gtype), field_name); return false; } bool gjs_wrapper_define_gtype_prop(JSContext* cx, JS::HandleObject constructor, GType gtype) { JS::RootedObject gtype_obj(cx, gjs_gtype_create_gtype_wrapper(cx, gtype)); if (!gtype_obj) return false; const GjsAtoms& atoms = GjsContextPrivate::atoms(cx); return JS_DefinePropertyById(cx, constructor, atoms.gtype(), gtype_obj, JSPROP_PERMANENT); } // These policies work around having separate g_foo_info_get_n_methods() and // g_foo_info_get_method() functions for different GIInfoTypes. It's not // possible to use GIFooInfo* as the template parameter, because the GIFooInfo // structs are all typedefs of GIBaseInfo. It's also not possible to use the // GIInfoType enum value as the template parameter, because GI_INFO_TYPE_BOXED // could be either a GIStructInfo or GIUnionInfo. template static inline GIStructInfo* no_type_struct(InfoT*) { return nullptr; } template > struct InfoMethodsPolicy { static constexpr decltype(NMethods) n_methods = NMethods; static constexpr decltype(Method) method = Method; static constexpr decltype(TypeStruct) type_struct = TypeStruct; }; template <> struct InfoMethodsPolicy : InfoMethodsPolicy {}; template <> struct InfoMethodsPolicy : InfoMethodsPolicy< InfoType::Interface, GIInterfaceInfo, &g_interface_info_get_n_methods, &g_interface_info_get_method, &g_interface_info_get_iface_struct> {}; template <> struct InfoMethodsPolicy : InfoMethodsPolicy {}; template <> struct InfoMethodsPolicy : InfoMethodsPolicy {}; template <> struct InfoMethodsPolicy : InfoMethodsPolicy { }; template bool gjs_define_static_methods(JSContext* cx, JS::HandleObject constructor, GType gtype, GIBaseInfo* info) { int n_methods = InfoMethodsPolicy::n_methods(info); for (int ix = 0; ix < n_methods; ix++) { GjsAutoFunctionInfo meth_info = InfoMethodsPolicy::method(info, ix); GIFunctionInfoFlags flags = g_function_info_get_flags(meth_info); // Anything that isn't a method we put on the constructor. This // includes introspection methods, as well as static // methods. We may want to change this to use // GI_FUNCTION_IS_CONSTRUCTOR and GI_FUNCTION_IS_STATIC or the like // in the future. if (!(flags & GI_FUNCTION_IS_METHOD)) { if (!gjs_define_function(cx, constructor, gtype, meth_info)) return false; } } // Also define class/interface methods if there is a gtype struct GjsAutoStructInfo type_struct = InfoMethodsPolicy::type_struct(info); // Not an error for it to be null even in the case of Object and Interface; // documentation says g_object_info_get_class_struct() and // g_interface_info_get_iface_struct() can validly return a null pointer. if (!type_struct) return true; n_methods = g_struct_info_get_n_methods(type_struct); for (int ix = 0; ix < n_methods; ix++) { GjsAutoFunctionInfo meth_info = g_struct_info_get_method(type_struct, ix); if (!gjs_define_function(cx, constructor, gtype, meth_info)) return false; } return true; } // All possible instantiations are needed template bool gjs_define_static_methods( JSContext* cx, JS::HandleObject constructor, GType gtype, GIBaseInfo* info); template bool gjs_define_static_methods( JSContext* cx, JS::HandleObject constructor, GType gtype, GIBaseInfo* info); template bool gjs_define_static_methods( JSContext* cx, JS::HandleObject constructor, GType gtype, GIBaseInfo* info); template bool gjs_define_static_methods( JSContext* cx, JS::HandleObject constructor, GType gtype, GIBaseInfo* info); template bool gjs_define_static_methods( JSContext* cx, JS::HandleObject constructor, GType gtype, GIBaseInfo* info); cjs-5.2.0/gi/arg-inl.h0000644000175000017500000001671414144444702014551 0ustar jpeisachjpeisach/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* * SPDX-License-Identifier: MIT OR LGPL-2.0-or-later * * Copyright (c) 2020 Marco Trevisan */ #pragma once #include #include // for nullptr_t #include #include // for to_string #include #include #include // for GType #include // for gboolean #include "gi/utils-inl.h" // GIArgument accessor templates // // These are intended to make access to the GIArgument union more type-safe and // reduce bugs that occur from assigning to one member and reading from another. // (These bugs often work fine on one processor architecture but crash on // another.) // // gjs_arg_member(GIArgument*) - returns a reference to the appropriate union // member that would hold the type T. Rarely used, unless as a pointer to a // return location. // gjs_arg_get(GIArgument*) - returns the value of type T from the // appropriate union member. // gjs_arg_set(GIArgument*, T) - sets the appropriate union member for type T. // gjs_arg_unset(GIArgument*) - sets the appropriate zero value in the // appropriate union member for type T. template [[nodiscard]] inline decltype(auto) gjs_arg_member(GIArgument* arg, T GIArgument::*member) { return (arg->*member); } /* The tag is needed to disambiguate types such as gboolean and GType * which are in fact typedef's of other generic types. * Setting a tag for a type allows to perform proper specialization. */ template [[nodiscard]] inline decltype(auto) gjs_arg_member(GIArgument* arg) { if constexpr (TAG == GI_TYPE_TAG_VOID) { if constexpr (std::is_same_v) return gjs_arg_member(arg, &GIArgument::v_boolean); if constexpr (std::is_same_v) return gjs_arg_member(arg, &GIArgument::v_int8); if constexpr (std::is_same_v) return gjs_arg_member(arg, &GIArgument::v_uint8); if constexpr (std::is_same_v) return gjs_arg_member(arg, &GIArgument::v_int16); if constexpr (std::is_same_v) return gjs_arg_member(arg, &GIArgument::v_uint16); if constexpr (std::is_same_v) return gjs_arg_member(arg, &GIArgument::v_int32); if constexpr (std::is_same_v) return gjs_arg_member(arg, &GIArgument::v_uint32); if constexpr (std::is_same_v) return gjs_arg_member(arg, &GIArgument::v_int64); if constexpr (std::is_same_v) return gjs_arg_member(arg, &GIArgument::v_uint64); // gunichar is stored in v_uint32 if constexpr (std::is_same_v) return gjs_arg_member(arg, &GIArgument::v_uint32); if constexpr (std::is_same_v) return gjs_arg_member(arg, &GIArgument::v_float); if constexpr (std::is_same_v) return gjs_arg_member(arg, &GIArgument::v_double); if constexpr (std::is_same_v) return gjs_arg_member(arg, &GIArgument::v_string); if constexpr (std::is_same_v) return gjs_arg_member(arg, &GIArgument::v_pointer); if constexpr (std::is_same_v) return gjs_arg_member(arg, &GIArgument::v_pointer); if constexpr (std::is_pointer()) { using NonconstPtrT = std::add_pointer_t< std::remove_const_t>>; return reinterpret_cast( gjs_arg_member(arg, &GIArgument::v_pointer)); } } if constexpr (TAG == GI_TYPE_TAG_BOOLEAN && std::is_same_v) return gjs_arg_member(arg, &GIArgument::v_boolean); if constexpr (TAG == GI_TYPE_TAG_GTYPE && std::is_same_v) { // GType is defined differently on 32-bit vs. 64-bit architectures. if constexpr (std::is_same_v) return gjs_arg_member(arg, &GIArgument::v_size); else if constexpr (std::is_same_v) return gjs_arg_member(arg, &GIArgument::v_ulong); } if constexpr (TAG == GI_TYPE_TAG_INTERFACE && std::is_integral_v) { if constexpr (std::is_signed_v) return gjs_arg_member(arg, &GIArgument::v_int); else return gjs_arg_member(arg, &GIArgument::v_uint); } } template inline void gjs_arg_set(GIArgument* arg, T v) { if constexpr (std::is_pointer_v) { using NonconstPtrT = std::add_pointer_t>>; gjs_arg_member(arg) = const_cast(v); } else { if constexpr (std::is_same_v || (std::is_same_v && TAG == GI_TYPE_TAG_BOOLEAN)) v = !!v; gjs_arg_member(arg) = v; } } // Store function pointers as void*. It is a requirement of GLib that your // compiler can do this template inline void gjs_arg_set(GIArgument* arg, ReturnT (*v)(Args...)) { gjs_arg_member(arg) = reinterpret_cast(v); } template inline std::enable_if_t> gjs_arg_set(GIArgument* arg, void *v) { gjs_arg_set(arg, gjs_pointer_to_int(v)); } template [[nodiscard]] inline T gjs_arg_get(GIArgument* arg) { if constexpr (std::is_same_v || (std::is_same_v && TAG == GI_TYPE_TAG_BOOLEAN)) return !!gjs_arg_member(arg); return gjs_arg_member(arg); } template [[nodiscard]] inline void* gjs_arg_get_as_pointer(GIArgument* arg) { return gjs_int_to_pointer(gjs_arg_get(arg)); } template inline void gjs_arg_unset(GIArgument* arg) { if constexpr (std::is_pointer_v) gjs_arg_set(arg, nullptr); else gjs_arg_set(arg, static_cast(0)); } // Implementation to store rounded (u)int64_t numbers into double template [[nodiscard]] inline constexpr BigT max_safe_big_number() { return (BigT(1) << std::numeric_limits::digits) - 1; } template [[nodiscard]] inline constexpr BigT min_safe_big_number() { if constexpr (std::is_signed_v) return -(max_safe_big_number()); return std::numeric_limits::lowest(); } template [[nodiscard]] inline std::enable_if_t && (std::numeric_limits::max() > std::numeric_limits::max()), double> gjs_arg_get_maybe_rounded(GIArgument* arg) { BigT val = gjs_arg_get(arg); if (val < min_safe_big_number() || val > max_safe_big_number()) { g_warning( "Value %s cannot be safely stored in a JS Number " "and may be rounded", std::to_string(val).c_str()); } return static_cast(val); } cjs-5.2.0/gi/arg-cache.h0000644000175000017500000001532514144444702015027 0ustar jpeisachjpeisach/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* * Copyright (c) 2013 Giovanni Campagna * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #ifndef GI_ARG_CACHE_H_ #define GI_ARG_CACHE_H_ #include #include #include #include #include #include // for g_assert #include #include #include "cjs/macros.h" struct GjsFunctionCallState; struct GjsArgumentCache; struct GjsArgumentMarshallers { bool (*in)(JSContext* cx, GjsArgumentCache* cache, GjsFunctionCallState* state, GIArgument* in_argument, JS::HandleValue value); bool (*out)(JSContext* cx, GjsArgumentCache* cache, GjsFunctionCallState* state, GIArgument* out_argument, JS::MutableHandleValue value); bool (*release)(JSContext* cx, GjsArgumentCache* cache, GjsFunctionCallState* state, GIArgument* in_argument, GIArgument* out_argument); void (*free)(GjsArgumentCache* cache); }; struct GjsArgumentCache { const GjsArgumentMarshallers* marshallers; const char* arg_name; GITypeInfo type_info; uint8_t arg_pos; bool skip_in : 1; bool skip_out : 1; GITransfer transfer : 2; bool nullable : 1; bool is_unsigned : 1; // number and enum only union { // for explicit array only struct { uint8_t length_pos; GITypeTag length_tag : 5; } array; struct { uint8_t closure_pos; uint8_t destroy_pos; GIScopeType scope : 2; } callback; struct { GITypeTag number_tag : 5; } number; // boxed / union / GObject struct { GType gtype; GIBaseInfo* info; } object; // foreign structures GIStructInfo* tmp_foreign_info; // enum / flags struct { uint32_t enum_min; uint32_t enum_max; } enum_type; unsigned flags_mask; // string / filename bool string_is_filename : 1; // out caller allocates (FIXME: should be in object) size_t caller_allocates_size; } contents; GJS_JSAPI_RETURN_CONVENTION bool handle_nullable(JSContext* cx, GIArgument* arg); // Introspected functions can have up to 253 arguments. 255 is a placeholder // for the return value and 254 for the instance parameter. The callback // closure or destroy notify parameter may have a value of 255 to indicate // that it is absent. static constexpr uint8_t MAX_ARGS = 253; static constexpr uint8_t INSTANCE_PARAM = 254; static constexpr uint8_t RETURN_VALUE = 255; static constexpr uint8_t ABSENT = 255; void set_arg_pos(int pos) { g_assert(pos <= MAX_ARGS && "No more than 253 arguments allowed"); arg_pos = pos; } void set_array_length_pos(int pos) { g_assert(pos <= MAX_ARGS && "No more than 253 arguments allowed"); contents.array.length_pos = pos; } void set_callback_destroy_pos(int pos) { g_assert(pos <= MAX_ARGS && "No more than 253 arguments allowed"); contents.callback.destroy_pos = pos < 0 ? ABSENT : pos; } [[nodiscard]] bool has_callback_destroy() { return contents.callback.destroy_pos != ABSENT; } void set_callback_closure_pos(int pos) { g_assert(pos <= MAX_ARGS && "No more than 253 arguments allowed"); contents.callback.closure_pos = pos < 0 ? ABSENT : pos; } [[nodiscard]] bool has_callback_closure() { return contents.callback.closure_pos != ABSENT; } void set_instance_parameter() { arg_pos = INSTANCE_PARAM; arg_name = "instance parameter"; // Some calls accept null for the instance, but generally in an object // oriented language it's wrong to call a method on null nullable = false; skip_out = true; } void set_return_value() { arg_pos = RETURN_VALUE; arg_name = "return value"; nullable = false; // We don't really care for return values } [[nodiscard]] bool is_return_value() { return arg_pos == RETURN_VALUE; } }; // This is a trick to print out the sizes of the structs at compile time, in // an error message: // template struct Measure; // Measure arg_cache_size; #if defined(__x86_64__) && defined(__clang__) && !defined (_MSC_VER) // This isn't meant to be comprehensive, but should trip on at least one CI job // if sizeof(GjsArgumentCache) is increased. // Note that this check is not applicable for clang-cl builds, as Windows is // an LLP64 system static_assert(sizeof(GjsArgumentCache) <= 112, "Think very hard before increasing the size of GjsArgumentCache. " "One is allocated for every argument to every introspected " "function."); #endif // x86-64 clang GJS_JSAPI_RETURN_CONVENTION bool gjs_arg_cache_build_arg(JSContext* cx, GjsArgumentCache* self, GjsArgumentCache* arguments, uint8_t gi_index, GIDirection direction, GIArgInfo* arg, GICallableInfo* callable, bool* inc_counter_out); GJS_JSAPI_RETURN_CONVENTION bool gjs_arg_cache_build_return(JSContext* cx, GjsArgumentCache* self, GjsArgumentCache* arguments, GICallableInfo* callable, bool* inc_counter_out); GJS_JSAPI_RETURN_CONVENTION bool gjs_arg_cache_build_instance(JSContext* cx, GjsArgumentCache* self, GICallableInfo* callable); #endif // GI_ARG_CACHE_H_ cjs-5.2.0/gi/toggle.h0000644000175000017500000000766314144444702014504 0ustar jpeisachjpeisach/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* * Copyright (c) 2017 Endless Mobile, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. * * Authored by: Philip Chimento , */ #ifndef GI_TOGGLE_H_ #define GI_TOGGLE_H_ #include #include #include #include // for pair #include #include #include "util/log.h" /* Thread-safe queue for enqueueing toggle-up or toggle-down events on GObjects * from any thread. For more information, see object.cpp, comments near * wrapped_gobj_toggle_notify(). */ class ToggleQueue { public: enum Direction { DOWN, UP }; typedef void (*Handler)(GObject *, Direction); private: struct Item { GObject *gobj; ToggleQueue::Direction direction; unsigned needs_unref : 1; }; mutable std::mutex lock; std::deque q; std::atomic_bool m_shutdown = ATOMIC_VAR_INIT(false); unsigned m_idle_id; Handler m_toggle_handler; /* No-op unless GJS_VERBOSE_ENABLE_LIFECYCLE is defined to 1. */ inline void debug(const char* did GJS_USED_VERBOSE_LIFECYCLE, const void* what GJS_USED_VERBOSE_LIFECYCLE) { gjs_debug_lifecycle(GJS_DEBUG_GOBJECT, "ToggleQueue %s %p", did, what); } [[nodiscard]] std::deque::iterator find_operation_locked( const GObject* gobj, Direction direction); [[nodiscard]] std::deque::const_iterator find_operation_locked( const GObject* gobj, Direction direction) const; [[nodiscard]] bool find_and_erase_operation_locked(const GObject* gobj, Direction direction); static gboolean idle_handle_toggle(void *data); static void idle_destroy_notify(void *data); public: /* These two functions return a pair DOWN, UP signifying whether toggles * are / were queued. is_queued() just checks and does not modify. */ [[nodiscard]] std::pair is_queued(GObject* gobj) const; /* Cancels pending toggles and returns whether any were queued. */ std::pair cancel(GObject *gobj); /* Pops a toggle from the queue and processes it. Call this if you don't * want to wait for it to be processed in idle time. Returns false if queue * is empty. */ bool handle_toggle(Handler handler); /* After calling this, the toggle queue won't accept any more toggles. Only * intended for use when destroying the JSContext and breaking the * associations between C and JS objects. */ void shutdown(void); /* Queues a toggle to be processed in idle time. */ void enqueue(GObject *gobj, Direction direction, Handler handler); [[nodiscard]] static ToggleQueue& get_default() { static ToggleQueue the_singleton; return the_singleton; } }; #endif // GI_TOGGLE_H_ cjs-5.2.0/gi/enumeration.cpp0000644000175000017500000001223414144444702016072 0ustar jpeisachjpeisach/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* * Copyright (c) 2008 litl, LLC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #include #include #include #include #include #include #include // for JS_DefineProperty, JS_NewPlainObject #include "gi/enumeration.h" #include "gi/wrapperutils.h" #include "cjs/jsapi-util.h" #include "util/log.h" GJS_JSAPI_RETURN_CONVENTION static bool gjs_define_enum_value(JSContext *context, JS::HandleObject in_object, GIValueInfo *info) { const char *value_name; char *fixed_name; gsize i; gint64 value_val; value_name = g_base_info_get_name( (GIBaseInfo*) info); value_val = g_value_info_get_value(info); /* g-i converts enum members such as GDK_GRAVITY_SOUTH_WEST to * Gdk.GravityType.south-west (where 'south-west' is value_name) * Convert back to all SOUTH_WEST. */ fixed_name = g_ascii_strup(value_name, -1); for (i = 0; fixed_name[i]; ++i) { char c = fixed_name[i]; if (!(('A' <= c && c <= 'Z') || ('0' <= c && c <= '9'))) fixed_name[i] = '_'; } gjs_debug(GJS_DEBUG_GENUM, "Defining enum value %s (fixed from %s) %" G_GINT64_MODIFIER "d", fixed_name, value_name, value_val); if (!JS_DefineProperty(context, in_object, fixed_name, (double) value_val, GJS_MODULE_PROP_FLAGS)) { gjs_throw(context, "Unable to define enumeration value %s %" G_GINT64_FORMAT " (no memory most likely)", fixed_name, value_val); g_free(fixed_name); return false; } g_free(fixed_name); return true; } bool gjs_define_enum_values(JSContext *context, JS::HandleObject in_object, GIEnumInfo *info) { int i, n_values; /* Fill in enum values first, so we don't define the enum itself until we're * sure we can finish successfully. */ n_values = g_enum_info_get_n_values(info); for (i = 0; i < n_values; ++i) { GIValueInfo *value_info = g_enum_info_get_value(info, i); bool failed; failed = !gjs_define_enum_value(context, in_object, value_info); g_base_info_unref( (GIBaseInfo*) value_info); if (failed) { return false; } } return true; } bool gjs_define_enumeration(JSContext *context, JS::HandleObject in_object, GIEnumInfo *info) { const char *enum_name; /* An enumeration is simply an object containing integer attributes for * each enum value. It does not have a special JSClass. * * We could make this more typesafe and also print enum values as strings * if we created a class for each enum and made the enum values instances * of that class. However, it would have a lot more overhead and just * be more complicated in general. I think this is fine. */ enum_name = g_base_info_get_name( (GIBaseInfo*) info); JS::RootedObject enum_obj(context, JS_NewPlainObject(context)); if (!enum_obj) { gjs_throw(context, "Could not create enumeration %s.%s", g_base_info_get_namespace(info), enum_name); return false; } GType gtype = g_registered_type_info_get_g_type(info); if (!gjs_define_enum_values(context, enum_obj, info) || !gjs_define_static_methods(context, enum_obj, gtype, info) || !gjs_wrapper_define_gtype_prop(context, enum_obj, gtype)) return false; gjs_debug(GJS_DEBUG_GENUM, "Defining %s.%s as %p", g_base_info_get_namespace( (GIBaseInfo*) info), enum_name, enum_obj.get()); if (!JS_DefineProperty(context, in_object, enum_name, enum_obj, GJS_MODULE_PROP_FLAGS)) { gjs_throw(context, "Unable to define enumeration property (no memory most likely)"); return false; } return true; } cjs-5.2.0/gi/closure.cpp0000644000175000017500000002432414144444702015223 0ustar jpeisachjpeisach/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* * Copyright (c) 2008 litl, LLC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #include #include #include #include #include #include #include // for JS_IsExceptionPending, Call, JS_Get... #include "gi/closure.h" #include "cjs/context-private.h" #include "cjs/jsapi-util-root.h" #include "cjs/jsapi-util.h" #include "cjs/mem-private.h" #include "util/log.h" struct Closure { JSContext *context; GjsMaybeOwned func; }; struct GjsClosure { GClosure base; /* We need a separate object to be able to call the C++ constructor without stomping on the base */ Closure priv; }; /* * Memory management of closures is "interesting" because we're keeping around * a JSContext* and then trying to use it spontaneously from the main loop. * I don't think that's really quite kosher, and perhaps the problem is that * (in xulrunner) we just need to save a different context. * * Or maybe the right fix is to create our own context just for this? * * But for the moment, we save the context that was used to create the closure. * * Here's the problem: this context can be destroyed. AFTER the * context is destroyed, or at least potentially after, the objects in * the context's global object may be garbage collected. Remember that * JSObject* belong to a runtime, not a context. * * There is apparently no robust way to track context destruction in * SpiderMonkey, because the context can be destroyed without running * the garbage collector, and xulrunner takes over the JS_SetContextCallback() * callback. So there's no callback for us. * * So, when we go to use our context, we iterate the contexts in the runtime * and see if ours is still in the valid list, and decide to invalidate * the closure if it isn't. * * The closure can thus be destroyed in several cases: * - invalidation by unref, e.g. when a signal is disconnected, closure is unref'd * - invalidation because we were invoked while the context was dead * - invalidation through finalization (we were garbage collected) * * These don't have to happen in the same order; garbage collection can * be either before, or after, context destruction. * */ static void invalidate_js_pointers(GjsClosure *gc) { Closure *c; c = &gc->priv; if (!c->func) return; c->func.reset(); c->context = nullptr; /* Notify any closure reference holders they * may want to drop references. */ g_closure_invalidate(&gc->base); } static void global_context_finalized(JS::HandleFunction func, void* data) { GjsClosure *gc = (GjsClosure*) data; Closure *c = &gc->priv; gjs_debug_closure( "Context global object destroy notifier on closure %p which calls " "object %p", c, c->func.debug_addr()); if (c->func) { g_assert(c->func == func.get()); invalidate_js_pointers(gc); } } /* Invalidation is like "dispose" - it is guaranteed to happen at * finalize, but may happen before finalize. Normally, g_closure_invalidate() * is called when the "target" of the closure becomes invalid, so that the * source (the signal connection, say can be removed.) The usage above * in invalidate_js_pointers() is typical. Since the target of the closure * is under our control, it's unlikely that g_closure_invalidate() will ever * be called by anyone else, but in case it ever does, it's slightly better * to remove the "keep alive" here rather than in the finalize notifier. * * Unlike "dispose" invalidation only happens once. */ static void closure_invalidated(void*, GClosure* closure) { Closure *c; c = &((GjsClosure*) closure)->priv; GJS_DEC_COUNTER(closure); gjs_debug_closure("Invalidating closure %p which calls function %p", closure, c->func.debug_addr()); if (!c->func) { gjs_debug_closure(" (closure %p already dead, nothing to do)", closure); return; } /* The context still exists, remove our destroy notifier. Otherwise we * would call the destroy notifier on an already-freed closure. * * This happens in the normal case, when the closure is * invalidated for some reason other than destruction of the * JSContext. */ gjs_debug_closure(" (closure %p's context was alive, " "removing our destroy notifier on global object)", closure); c->func.reset(); c->context = nullptr; } static void closure_set_invalid(void*, GClosure* closure) { Closure *self = &((GjsClosure*) closure)->priv; gjs_debug_closure("Invalidating signal closure %p which calls function %p", closure, self->func.debug_addr()); self->func.prevent_collection(); self->func.reset(); self->context = nullptr; GJS_DEC_COUNTER(closure); } static void closure_finalize(void*, GClosure* closure) { Closure *self = &((GjsClosure*) closure)->priv; self->~Closure(); } bool gjs_closure_invoke(GClosure *closure, JS::HandleObject this_obj, const JS::HandleValueArray& args, JS::MutableHandleValue retval, bool return_exception) { Closure *c; JSContext *context; c = &((GjsClosure*) closure)->priv; if (!c->func) { /* We were destroyed; become a no-op */ c->context = nullptr; return false; } context = c->context; JSAutoRealm ar(context, JS_GetFunctionObject(c->func)); if (gjs_log_exception(context)) { gjs_debug_closure("Exception was pending before invoking callback??? " "Not expected - closure %p", closure); } JS::RootedFunction func(context, c->func); if (!JS::Call(context, this_obj, func, args, retval)) { /* Exception thrown... */ gjs_debug_closure( "Closure invocation failed (exception should have been thrown) " "closure %p function %p", closure, c->func.debug_addr()); /* If an exception has been thrown, log it, unless the caller * explicitly wants to handle it manually (for example to turn it * into a GError), in which case it replaces the return value * (which is not valid anyway) */ if (JS_IsExceptionPending(context)) { if (return_exception) JS_GetPendingException(context, retval); else gjs_log_exception_uncaught(context); } else { retval.setUndefined(); gjs_debug_closure("Closure invocation failed but no exception was set?" "closure %p", closure); } return false; } if (gjs_log_exception_uncaught(context)) { gjs_debug_closure("Closure invocation succeeded but an exception was set" " - closure %p", closure); } GjsContextPrivate* gjs = GjsContextPrivate::from_cx(context); gjs->schedule_gc_if_needed(); return true; } bool gjs_closure_is_valid(GClosure *closure) { Closure *c; c = &((GjsClosure*) closure)->priv; return !!c->context; } JSContext* gjs_closure_get_context(GClosure *closure) { Closure *c; c = &((GjsClosure*) closure)->priv; return c->context; } JSFunction* gjs_closure_get_callable(GClosure* closure) { Closure *c; c = &((GjsClosure*) closure)->priv; return c->func; } void gjs_closure_trace(GClosure *closure, JSTracer *tracer) { Closure *c; c = &((GjsClosure*) closure)->priv; if (!c->func) return; c->func.trace(tracer, "signal connection"); } GClosure* gjs_closure_new(JSContext* context, JSFunction* callable, const char* description GJS_USED_VERBOSE_GCLOSURE, bool root_function) { Closure *c; auto* gc = reinterpret_cast( g_closure_new_simple(sizeof(GjsClosure), nullptr)); c = new (&gc->priv) Closure(); /* The saved context is used for lifetime management, so that the closure will * be torn down with the context that created it. The context could be attached to * the default context of the runtime using if we wanted the closure to survive * the context that created it. */ c->context = context; GJS_INC_COUNTER(closure); if (root_function) { /* Fully manage closure lifetime if so asked */ c->func.root(context, callable, global_context_finalized, gc); g_closure_add_invalidate_notifier(&gc->base, nullptr, closure_invalidated); } else { c->func = callable; /* Only mark the closure as invalid if memory is managed outside (i.e. by object.c for signals) */ g_closure_add_invalidate_notifier(&gc->base, nullptr, closure_set_invalid); } g_closure_add_finalize_notifier(&gc->base, nullptr, closure_finalize); gjs_debug_closure("Create closure %p which calls function %p '%s'", gc, c->func.debug_addr(), description); return &gc->base; } cjs-5.2.0/gi/object.cpp0000644000175000017500000026623514144444702015026 0ustar jpeisachjpeisach/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* * Copyright (c) 2008 litl, LLC * Copyright (c) 2018 Philip Chimento * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #include #include #include // for memset, strcmp #include // for find #include // for mem_fn #include #include // for tie #include // for remove_reference<>::type #include // for move #include #include #include #include #include #include #include #include #include // for JS_AddWeakPointerCompartmentCallback #include // for MutableWrappedPtrOperations #include // for AddAssociatedMemory, RemoveAssoci... #include // for JSPROP_PERMANENT, JSPROP_READONLY #include #include // for UniqueChars #include #include #include // for JS_ReportOutOfMemory, IsCallable #include // for JS_GetObjectFunction, IsFunctionO... #include #include #include "gi/arg-inl.h" #include "gi/arg.h" #include "gi/closure.h" #include "gi/function.h" #include "gi/gjs_gi_trace.h" #include "gi/object.h" #include "gi/repo.h" #include "gi/toggle.h" #include "gi/value.h" #include "gi/wrapperutils.h" #include "cjs/atoms.h" #include "cjs/context-private.h" #include "cjs/context.h" #include "cjs/deprecation.h" #include "cjs/jsapi-class.h" #include "cjs/jsapi-util-args.h" #include "cjs/jsapi-util-root.h" #include "cjs/mem-private.h" #include "util/log.h" class JSTracer; /* This is a trick to print out the sizes of the structs at compile time, in * an error message. */ // template struct Measure; // Measure instance_size; // Measure prototype_size; #if defined(__x86_64__) && defined(__clang__) /* This isn't meant to be comprehensive, but should trip on at least one CI job * if sizeof(ObjectInstance) is increased. */ static_assert(sizeof(ObjectInstance) <= 88, "Think very hard before increasing the size of ObjectInstance. " "There can be tens of thousands of them alive in a typical " "gnome-shell run."); #endif // x86-64 clang bool ObjectInstance::s_weak_pointer_callback = false; ObjectInstance *ObjectInstance::wrapped_gobject_list = nullptr; // clang-format off G_DEFINE_QUARK(gjs::custom-type, ObjectBase::custom_type) G_DEFINE_QUARK(gjs::custom-property, ObjectBase::custom_property) // clang-format on [[nodiscard]] static GQuark gjs_object_priv_quark() { static GQuark val = 0; if (G_UNLIKELY (!val)) val = g_quark_from_static_string ("gjs::private"); return val; } bool ObjectBase::is_custom_js_class() { return !!g_type_get_qdata(gtype(), ObjectBase::custom_type_quark()); } // Plain g_type_query fails and leaves @query uninitialized for dynamic types. // See https://gitlab.gnome.org/GNOME/glib/issues/623 void ObjectBase::type_query_dynamic_safe(GTypeQuery* query) { GType type = gtype(); while (g_type_get_qdata(type, ObjectBase::custom_type_quark())) type = g_type_parent(type); g_type_query(type, query); } void GjsListLink::prepend(ObjectInstance *this_instance, ObjectInstance *head) { GjsListLink *elem = head->get_link(); g_assert(this_instance->get_link() == this); if (elem->m_prev) { GjsListLink *prev = elem->m_prev->get_link(); prev->m_next = this_instance; this->m_prev = elem->m_prev; } elem->m_prev = this_instance; this->m_next = head; } void GjsListLink::unlink(void) { if (m_prev) m_prev->get_link()->m_next = m_next; if (m_next) m_next->get_link()->m_prev = m_prev; m_prev = m_next = nullptr; } size_t GjsListLink::size(void) const { const GjsListLink *elem = this; size_t count = 0; do { count++; if (!elem->m_next) break; elem = elem->m_next->get_link(); } while (elem); return count; } void ObjectInstance::link(void) { if (wrapped_gobject_list) m_instance_link.prepend(this, wrapped_gobject_list); wrapped_gobject_list = this; } void ObjectInstance::unlink(void) { if (wrapped_gobject_list == this) wrapped_gobject_list = m_instance_link.next(); m_instance_link.unlink(); } const void* ObjectBase::jsobj_addr(void) const { if (is_prototype()) return nullptr; return to_instance()->m_wrapper.debug_addr(); } // Overrides GIWrapperBase::typecheck(). We only override the overload that // throws, so that we can throw our own more informative error. bool ObjectBase::typecheck(JSContext* cx, JS::HandleObject obj, GIObjectInfo* expected_info, GType expected_gtype) { if (GIWrapperBase::typecheck(cx, obj, expected_info, expected_gtype)) return true; gjs_throw(cx, "This JS object wrapper isn't wrapping a GObject." " If this is a custom subclass, are you sure you chained" " up to the parent _init properly?"); return false; } bool ObjectInstance::check_gobject_disposed(const char* for_what) const { if (!m_gobj_disposed) return true; g_critical( "Object %s.%s (%p), has been already deallocated — impossible to %s " "it. This might be caused by the object having been destroyed from C " "code using something such as destroy(), dispose(), or remove() " "vfuncs.", ns(), name(), m_ptr, for_what); gjs_dumpstack(); return false; } ObjectInstance * ObjectInstance::for_gobject(GObject *gobj) { auto priv = static_cast(g_object_get_qdata(gobj, gjs_object_priv_quark())); if (priv) priv->check_js_object_finalized(); return priv; } void ObjectInstance::check_js_object_finalized(void) { if (!m_uses_toggle_ref) return; if (G_UNLIKELY(m_wrapper_finalized)) { g_critical( "Object %p (a %s) resurfaced after the JS wrapper was finalized. " "This is some library doing dubious memory management inside " "dispose()", m_ptr, type_name()); m_wrapper_finalized = false; g_assert(!m_wrapper); /* should associate again with a new wrapper */ } } ObjectPrototype* ObjectPrototype::for_gtype(GType gtype) { return static_cast( g_type_get_qdata(gtype, gjs_object_priv_quark())); } void ObjectPrototype::set_type_qdata(void) { g_type_set_qdata(m_gtype, gjs_object_priv_quark(), this); } void ObjectInstance::set_object_qdata(void) { g_object_set_qdata(m_ptr, gjs_object_priv_quark(), this); } void ObjectInstance::unset_object_qdata(void) { g_object_set_qdata(m_ptr, gjs_object_priv_quark(), nullptr); } GParamSpec* ObjectPrototype::find_param_spec_from_id(JSContext* cx, JS::HandleString key) { /* First check for the ID in the cache */ auto entry = m_property_cache.lookupForAdd(key); if (entry) return entry->value(); JS::UniqueChars js_prop_name(JS_EncodeStringToUTF8(cx, key)); if (!js_prop_name) return nullptr; GjsAutoChar gname = gjs_hyphen_from_camel(js_prop_name.get()); GjsAutoTypeClass gobj_class(m_gtype); GParamSpec* pspec = g_object_class_find_property(gobj_class, gname); GjsAutoParam param_spec(pspec, GjsAutoTakeOwnership()); if (!param_spec) { gjs_wrapper_throw_nonexistent_field(cx, m_gtype, js_prop_name.get()); return nullptr; } if (!m_property_cache.add(entry, key, std::move(param_spec))) { JS_ReportOutOfMemory(cx); return nullptr; } return pspec; /* owned by property cache */ } /* A hook on adding a property to an object. This is called during a set * property operation after all the resolve hooks on the prototype chain have * failed to resolve. We use this to mark an object as needing toggle refs when * custom state is set on it, because we need to keep the JS GObject wrapper * alive in order not to lose custom "expando" properties. */ bool ObjectBase::add_property(JSContext* cx, JS::HandleObject obj, JS::HandleId id, JS::HandleValue value) { auto* priv = ObjectBase::for_js(cx, obj); /* priv is null during init: property is not being added from JS */ if (!priv) { debug_jsprop_static("Add property hook", id, obj); return true; } if (priv->is_prototype()) return true; return priv->to_instance()->add_property_impl(cx, obj, id, value); } bool ObjectInstance::add_property_impl(JSContext* cx, JS::HandleObject obj, JS::HandleId id, JS::HandleValue) { debug_jsprop("Add property hook", id, obj); if (is_custom_js_class() || m_gobj_disposed) return true; ensure_uses_toggle_ref(cx); return true; } bool ObjectBase::prop_getter(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_GET_WRAPPER_PRIV(cx, argc, vp, args, obj, ObjectBase, priv); JS::RootedString name(cx, gjs_dynamic_property_private_slot(&args.callee()).toString()); priv->debug_jsprop("Property getter", name, obj); if (priv->is_prototype()) return true; /* Ignore silently; note that this is different from what we do for * boxed types, for historical reasons */ return priv->to_instance()->prop_getter_impl(cx, name, args.rval()); } bool ObjectInstance::prop_getter_impl(JSContext* cx, JS::HandleString name, JS::MutableHandleValue rval) { if (!check_gobject_disposed("get any property from")) return true; GValue gvalue = { 0, }; ObjectPrototype* proto_priv = get_prototype(); GParamSpec *param = proto_priv->find_param_spec_from_id(cx, name); /* This is guaranteed because we resolved the property before */ g_assert(param); /* Do not fetch JS overridden properties from GObject, to avoid * infinite recursion. */ if (g_param_spec_get_qdata(param, ObjectBase::custom_property_quark())) return true; if ((param->flags & G_PARAM_READABLE) == 0) { rval.setUndefined(); return true; } gjs_debug_jsprop(GJS_DEBUG_GOBJECT, "Accessing GObject property %s", param->name); g_value_init(&gvalue, G_PARAM_SPEC_VALUE_TYPE(param)); g_object_get_property(m_ptr, param->name, &gvalue); if (!gjs_value_from_g_value(cx, rval, &gvalue)) { g_value_unset(&gvalue); return false; } g_value_unset(&gvalue); return true; } [[nodiscard]] static GjsAutoFieldInfo lookup_field_info(GIObjectInfo* info, const char* name) { int n_fields = g_object_info_get_n_fields(info); int ix; GjsAutoFieldInfo retval; for (ix = 0; ix < n_fields; ix++) { retval = g_object_info_get_field(info, ix); if (strcmp(name, retval.name()) == 0) break; retval.reset(); } if (!retval || !(g_field_info_get_flags(retval) & GI_FIELD_IS_READABLE)) return nullptr; return retval; } bool ObjectBase::field_getter(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_GET_WRAPPER_PRIV(cx, argc, vp, args, obj, ObjectBase, priv); JS::RootedString name(cx, gjs_dynamic_property_private_slot(&args.callee()).toString()); priv->debug_jsprop("Field getter", name, obj); if (priv->is_prototype()) return true; /* Ignore silently; note that this is different from what we do for * boxed types, for historical reasons */ return priv->to_instance()->field_getter_impl(cx, name, args.rval()); } bool ObjectInstance::field_getter_impl(JSContext* cx, JS::HandleString name, JS::MutableHandleValue rval) { if (!check_gobject_disposed("get any property from")) return true; ObjectPrototype* proto_priv = get_prototype(); GIFieldInfo* field = proto_priv->lookup_cached_field_info(cx, name); GITypeTag tag; GIArgument arg = { 0 }; gjs_debug_jsprop(GJS_DEBUG_GOBJECT, "Overriding %s with GObject field", gjs_debug_string(name).c_str()); GjsAutoTypeInfo type = g_field_info_get_type(field); tag = g_type_info_get_tag(type); if (tag == GI_TYPE_TAG_ARRAY || tag == GI_TYPE_TAG_INTERFACE || tag == GI_TYPE_TAG_GLIST || tag == GI_TYPE_TAG_GSLIST || tag == GI_TYPE_TAG_GHASH || tag == GI_TYPE_TAG_ERROR) { gjs_throw(cx, "Can't get field %s; GObject introspection supports only " "fields with simple types, not %s", gjs_debug_string(name).c_str(), g_type_tag_to_string(tag)); return false; } if (!g_field_info_get_field(field, m_ptr, &arg)) { gjs_throw(cx, "Error getting field %s from object", gjs_debug_string(name).c_str()); return false; } return gjs_value_from_g_argument(cx, rval, type, &arg, true); /* copy_structs is irrelevant because g_field_info_get_field() doesn't * handle boxed types */ } /* Dynamic setter for GObject properties. Returns false on OOM/exception. * args.rval() becomes the "stored value" for the property. */ bool ObjectBase::prop_setter(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_GET_WRAPPER_PRIV(cx, argc, vp, args, obj, ObjectBase, priv); JS::RootedString name(cx, gjs_dynamic_property_private_slot(&args.callee()).toString()); priv->debug_jsprop("Property setter", name, obj); if (priv->is_prototype()) return true; /* Ignore silently; note that this is different from what we do for * boxed types, for historical reasons */ /* Clear the JS stored value, to avoid keeping additional references */ args.rval().setUndefined(); return priv->to_instance()->prop_setter_impl(cx, name, args[0]); } bool ObjectInstance::prop_setter_impl(JSContext* cx, JS::HandleString name, JS::HandleValue value) { if (!check_gobject_disposed("set any property on")) return true; ObjectPrototype* proto_priv = get_prototype(); GParamSpec *param_spec = proto_priv->find_param_spec_from_id(cx, name); if (!param_spec) return false; /* Do not set JS overridden properties through GObject, to avoid * infinite recursion (unless constructing) */ if (g_param_spec_get_qdata(param_spec, ObjectBase::custom_property_quark())) return true; if (!(param_spec->flags & G_PARAM_WRITABLE)) /* prevent setting the prop even in JS */ return gjs_wrapper_throw_readonly_field(cx, gtype(), param_spec->name); if (param_spec->flags & G_PARAM_DEPRECATED) _gjs_warn_deprecated_once_per_callsite(cx, DeprecatedGObjectProperty); gjs_debug_jsprop(GJS_DEBUG_GOBJECT, "Setting GObject prop %s", param_spec->name); GValue gvalue = G_VALUE_INIT; g_value_init(&gvalue, G_PARAM_SPEC_VALUE_TYPE(param_spec)); if (!gjs_value_to_g_value(cx, value, &gvalue)) { g_value_unset(&gvalue); return false; } g_object_set_property(m_ptr, param_spec->name, &gvalue); g_value_unset(&gvalue); return true; } bool ObjectBase::field_setter(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_GET_WRAPPER_PRIV(cx, argc, vp, args, obj, ObjectBase, priv); JS::RootedString name(cx, gjs_dynamic_property_private_slot(&args.callee()).toString()); priv->debug_jsprop("Field setter", name, obj); if (priv->is_prototype()) return true; /* Ignore silently; note that this is different from what we do for * boxed types, for historical reasons */ /* We have to update args.rval(), because JS caches it as the property's "stored * value" (https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/JSAPI_reference/Stored_value) * and so subsequent gets would get the stored value instead of accessing * the field */ args.rval().setUndefined(); return priv->to_instance()->field_setter_not_impl(cx, name); } bool ObjectInstance::field_setter_not_impl(JSContext* cx, JS::HandleString name) { if (!check_gobject_disposed("set GObject field on")) return true; ObjectPrototype* proto_priv = get_prototype(); GIFieldInfo* field = proto_priv->lookup_cached_field_info(cx, name); /* As far as I know, GI never exposes GObject instance struct fields as * writable, so no need to implement this for the time being */ if (g_field_info_get_flags(field) & GI_FIELD_IS_WRITABLE) { g_message("Field %s of a GObject is writable, but setting it is not " "implemented", gjs_debug_string(name).c_str()); return true; } return gjs_wrapper_throw_readonly_field(cx, gtype(), g_base_info_get_name(field)); } bool ObjectPrototype::is_vfunc_unchanged(GIVFuncInfo* info) { GType ptype = g_type_parent(m_gtype); GError *error = NULL; gpointer addr1, addr2; addr1 = g_vfunc_info_get_address(info, m_gtype, &error); if (error) { g_clear_error(&error); return false; } addr2 = g_vfunc_info_get_address(info, ptype, &error); if (error) { g_clear_error(&error); return false; } return addr1 == addr2; } [[nodiscard]] static GjsAutoVFuncInfo find_vfunc_on_parents( GIObjectInfo* info, const char* name, bool* out_defined_by_parent) { bool defined_by_parent = false; /* ref the first info so that we don't destroy * it when unrefing parents later */ GjsAutoObjectInfo parent = g_base_info_ref(info); /* Since it isn't possible to override a vfunc on * an interface without reimplementing it, we don't need * to search the parent types when looking for a vfunc. */ GjsAutoVFuncInfo vfunc = g_object_info_find_vfunc_using_interfaces(parent, name, nullptr); while (!vfunc && parent) { parent = g_object_info_get_parent(parent); if (parent) vfunc = g_object_info_find_vfunc(parent, name); defined_by_parent = true; } if (out_defined_by_parent) *out_defined_by_parent = defined_by_parent; return vfunc; } /* Taken from GLib */ static void canonicalize_key(const GjsAutoChar& key) { for (char* p = key.get(); *p != 0; p++) { char c = *p; if (c != '-' && (c < '0' || c > '9') && (c < 'A' || c > 'Z') && (c < 'a' || c > 'z')) *p = '-'; } } /* @name must already be canonicalized */ [[nodiscard]] static bool is_ginterface_property_name(GIInterfaceInfo* info, const char* name) { int n_props = g_interface_info_get_n_properties(info); GjsAutoPropertyInfo prop_info; for (int ix = 0; ix < n_props; ix++) { prop_info = g_interface_info_get_property(info, ix); if (strcmp(name, prop_info.name()) == 0) break; prop_info.reset(); } return !!prop_info; } bool ObjectPrototype::lazy_define_gobject_property(JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool* resolved, const char* name) { bool found = false; if (!JS_AlreadyHasOwnPropertyById(cx, obj, id, &found)) return false; if (found) { /* Already defined, so *resolved = false because we didn't just * define it */ *resolved = false; return true; } debug_jsprop("Defining lazy GObject property", id, obj); JS::RootedValue private_id(cx, JS::StringValue(JSID_TO_STRING(id))); if (!gjs_define_property_dynamic( cx, obj, name, "gobject_prop", &ObjectBase::prop_getter, &ObjectBase::prop_setter, private_id, // Make property configurable so that interface properties can be // overridden by GObject.ParamSpec.override in the class that // implements them GJS_MODULE_PROP_FLAGS & ~JSPROP_PERMANENT)) return false; *resolved = true; return true; } bool ObjectPrototype::resolve_no_info(JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool* resolved, const char* name, ResolveWhat resolve_props) { guint n_interfaces; guint i; GjsAutoChar canonical_name; if (resolve_props == ConsiderMethodsAndProperties) { // Optimization: GObject property names must start with a letter if (g_ascii_isalpha(name[0])) { canonical_name = gjs_hyphen_from_camel(name); canonicalize_key(canonical_name); } } GjsAutoFree interfaces = g_type_interfaces(m_gtype, &n_interfaces); /* Fallback to GType system for non custom GObjects with no GI information */ if (canonical_name && G_TYPE_IS_CLASSED(m_gtype) && !is_custom_js_class()) { GjsAutoTypeClass oclass(m_gtype); if (g_object_class_find_property(oclass, canonical_name)) return lazy_define_gobject_property(cx, obj, id, resolved, name); for (i = 0; i < n_interfaces; i++) { if (!G_TYPE_IS_CLASSED(interfaces[i])) continue; GjsAutoTypeClass iclass(interfaces[i]); if (g_object_class_find_property(iclass, canonical_name)) return lazy_define_gobject_property(cx, obj, id, resolved, name); } } for (i = 0; i < n_interfaces; i++) { GjsAutoInterfaceInfo iface_info = g_irepository_find_by_gtype(nullptr, interfaces[i]); if (!iface_info) continue; GjsAutoFunctionInfo method_info = g_interface_info_find_method(iface_info, name); if (method_info) { if (g_function_info_get_flags (method_info) & GI_FUNCTION_IS_METHOD) { if (!gjs_define_function(cx, obj, m_gtype, method_info)) return false; *resolved = true; return true; } } if (!canonical_name) continue; /* If the name refers to a GObject property, lazily define the property * in JS as we do below in the real resolve hook. We ignore fields here * because I don't think interfaces can have fields */ if (is_ginterface_property_name(iface_info, canonical_name)) { GjsAutoTypeClass oclass(m_gtype); // unowned GParamSpec* pspec = g_object_class_find_property( oclass, canonical_name); // unowned if (pspec && pspec->owner_type == m_gtype) { return lazy_define_gobject_property(cx, obj, id, resolved, name); } } } *resolved = false; return true; } [[nodiscard]] static bool is_gobject_property_name(GIObjectInfo* info, const char* name) { // Optimization: GObject property names must start with a letter if (!g_ascii_isalpha(name[0])) return false; int n_props = g_object_info_get_n_properties(info); int n_ifaces = g_object_info_get_n_interfaces(info); int ix; GjsAutoChar canonical_name = gjs_hyphen_from_camel(name); canonicalize_key(canonical_name); for (ix = 0; ix < n_props; ix++) { GjsAutoPropertyInfo prop_info = g_object_info_get_property(info, ix); if (strcmp(canonical_name, prop_info.name()) == 0) return true; } for (ix = 0; ix < n_ifaces; ix++) { GjsAutoInterfaceInfo iface_info = g_object_info_get_interface(info, ix); if (is_ginterface_property_name(iface_info, canonical_name)) return true; } return false; } // Override of GIWrapperBase::id_is_never_lazy() bool ObjectBase::id_is_never_lazy(jsid name, const GjsAtoms& atoms) { // Keep this list in sync with ObjectBase::proto_properties and // ObjectBase::proto_methods. However, explicitly do not include // connect() in it, because there are a few cases where the lazy property // should override the predefined one, such as Gio.Cancellable.connect(). return name == atoms.init() || name == atoms.connect_after() || name == atoms.emit(); } bool ObjectPrototype::resolve_impl(JSContext* context, JS::HandleObject obj, JS::HandleId id, bool* resolved) { if (m_unresolvable_cache.has(id)) { *resolved = false; return true; } JS::UniqueChars prop_name; if (!gjs_get_string_id(context, id, &prop_name)) return false; if (!prop_name) { *resolved = false; return true; // not resolved, but no error } if (!uncached_resolve(context, obj, id, prop_name.get(), resolved)) return false; if (!*resolved && !m_unresolvable_cache.putNew(id)) { JS_ReportOutOfMemory(context); return false; } return true; } bool ObjectPrototype::uncached_resolve(JSContext* context, JS::HandleObject obj, JS::HandleId id, const char* name, bool* resolved) { // If we have no GIRepository information (we're a JS GObject subclass or an // internal non-introspected class such as GLocalFile), we need to look at // exposing interfaces. Look up our interfaces through GType data, and then // hope that *those* are introspectable. if (!info()) return resolve_no_info(context, obj, id, resolved, name, ConsiderMethodsAndProperties); if (g_str_has_prefix(name, "vfunc_")) { /* The only time we find a vfunc info is when we're the base * class that defined the vfunc. If we let regular prototype * chaining resolve this, we'd have the implementation for the base's * vfunc on the base class, without any other "real" implementations * in the way. If we want to expose a "real" vfunc implementation, * we need to go down to the parent infos and look at their VFuncInfos. * * This is good, but it's memory-hungry -- we would define every * possible vfunc on every possible object, even if it's the same * "real" vfunc underneath. Instead, only expose vfuncs that are * different from their parent, and let prototype chaining do the * rest. */ const char *name_without_vfunc_ = &(name[6]); /* lifetime tied to name */ bool defined_by_parent; GjsAutoVFuncInfo vfunc = find_vfunc_on_parents( m_info, name_without_vfunc_, &defined_by_parent); if (vfunc) { /* In the event that the vfunc is unchanged, let regular * prototypal inheritance take over. */ if (defined_by_parent && is_vfunc_unchanged(vfunc)) { *resolved = false; return true; } if (!gjs_define_function(context, obj, m_gtype, vfunc)) return false; *resolved = true; return true; } /* If the vfunc wasn't found, fall through, back to normal * method resolution. */ } if (is_gobject_property_name(m_info, name)) return lazy_define_gobject_property(context, obj, id, resolved, name); GjsAutoFieldInfo field_info = lookup_field_info(m_info, name); if (field_info) { bool found = false; if (!JS_AlreadyHasOwnPropertyById(context, obj, id, &found)) return false; if (found) { *resolved = false; return true; } debug_jsprop("Defining lazy GObject field", id, obj); unsigned flags = GJS_MODULE_PROP_FLAGS; if (!(g_field_info_get_flags(field_info) & GI_FIELD_IS_WRITABLE)) flags |= JSPROP_READONLY; JS::RootedString key(context, JSID_TO_STRING(id)); if (!m_field_cache.putNew(key, field_info.release())) { JS_ReportOutOfMemory(context); return false; } JS::RootedValue private_id(context, JS::StringValue(key)); if (!gjs_define_property_dynamic( context, obj, name, "gobject_field", &ObjectBase::field_getter, &ObjectBase::field_setter, private_id, flags)) return false; *resolved = true; return true; } /* find_method does not look at methods on parent classes, * we rely on javascript to walk up the __proto__ chain * and find those and define them in the right prototype. * * Note that if it isn't a method on the object, since JS * lacks multiple inheritance, we're sticking the iface * methods in the object prototype, which means there are many * copies of the iface methods (one per object class node that * introduces the iface) */ GjsAutoFunctionInfo method_info = g_object_info_find_method_using_interfaces(m_info, name, nullptr); /** * Search through any interfaces implemented by the GType; * this could be done better. See * https://bugzilla.gnome.org/show_bug.cgi?id=632922 */ if (!method_info) return resolve_no_info(context, obj, id, resolved, name, ConsiderOnlyMethods); #if GJS_VERBOSE_ENABLE_GI_USAGE _gjs_log_info_usage(method_info); #endif if (g_function_info_get_flags (method_info) & GI_FUNCTION_IS_METHOD) { gjs_debug(GJS_DEBUG_GOBJECT, "Defining method %s in prototype for %s (%s.%s)", method_info.name(), type_name(), ns(), this->name()); if (!gjs_define_function(context, obj, m_gtype, method_info)) return false; *resolved = true; /* we defined the prop in obj */ } else { *resolved = false; } return true; } bool ObjectPrototype::new_enumerate_impl(JSContext* cx, JS::HandleObject, JS::MutableHandleIdVector properties, bool only_enumerable [[maybe_unused]]) { unsigned n_interfaces; GType* interfaces = g_type_interfaces(gtype(), &n_interfaces); for (unsigned k = 0; k < n_interfaces; k++) { GjsAutoInterfaceInfo iface_info = g_irepository_find_by_gtype(nullptr, interfaces[k]); if (!iface_info) { continue; } int n_methods = g_interface_info_get_n_methods(iface_info); int n_properties = g_interface_info_get_n_properties(iface_info); if (!properties.reserve(properties.length() + n_methods + n_properties)) { JS_ReportOutOfMemory(cx); return false; } // Methods for (int i = 0; i < n_methods; i++) { GjsAutoFunctionInfo meth_info = g_interface_info_get_method(iface_info, i); GIFunctionInfoFlags flags = g_function_info_get_flags(meth_info); if (flags & GI_FUNCTION_IS_METHOD) { const char* name = meth_info.name(); jsid id = gjs_intern_string_to_id(cx, name); if (id == JSID_VOID) return false; properties.infallibleAppend(id); } } // Properties for (int i = 0; i < n_properties; i++) { GjsAutoPropertyInfo prop_info = g_interface_info_get_property(iface_info, i); GjsAutoChar js_name = gjs_hyphen_to_underscore(prop_info.name()); jsid id = gjs_intern_string_to_id(cx, js_name); if (id == JSID_VOID) return false; properties.infallibleAppend(id); } } g_free(interfaces); if (info()) { int n_methods = g_object_info_get_n_methods(info()); int n_properties = g_object_info_get_n_properties(info()); if (!properties.reserve(properties.length() + n_methods + n_properties)) { JS_ReportOutOfMemory(cx); return false; } // Methods for (int i = 0; i < n_methods; i++) { GjsAutoFunctionInfo meth_info = g_object_info_get_method(info(), i); GIFunctionInfoFlags flags = g_function_info_get_flags(meth_info); if (flags & GI_FUNCTION_IS_METHOD) { const char* name = meth_info.name(); jsid id = gjs_intern_string_to_id(cx, name); if (id == JSID_VOID) return false; properties.infallibleAppend(id); } } // Properties for (int i = 0; i < n_properties; i++) { GjsAutoPropertyInfo prop_info = g_object_info_get_property(info(), i); GjsAutoChar js_name = gjs_hyphen_to_underscore(prop_info.name()); jsid id = gjs_intern_string_to_id(cx, js_name); if (id == JSID_VOID) return false; properties.infallibleAppend(id); } } return true; } /* Set properties from args to constructor (args[0] is supposed to be * a hash) */ bool ObjectPrototype::props_to_g_parameters(JSContext* context, JS::HandleObject props, std::vector* names, AutoGValueVector* values) { size_t ix, length; JS::RootedId prop_id(context); JS::RootedValue value(context); JS::Rooted ids(context, context); if (!JS_Enumerate(context, props, &ids)) { gjs_throw(context, "Failed to create property iterator for object props hash"); return false; } for (ix = 0, length = ids.length(); ix < length; ix++) { GValue gvalue = G_VALUE_INIT; /* ids[ix] is reachable because props is rooted, but require_property * doesn't know that */ prop_id = ids[ix]; if (!JSID_IS_STRING(prop_id)) return gjs_wrapper_throw_nonexistent_field( context, m_gtype, gjs_debug_id(prop_id).c_str()); JS::RootedString js_prop_name(context, JSID_TO_STRING(prop_id)); GParamSpec *param_spec = find_param_spec_from_id(context, js_prop_name); if (!param_spec) return false; if (!JS_GetPropertyById(context, props, prop_id, &value)) return false; if (value.isUndefined()) { gjs_throw(context, "Invalid value 'undefined' for property %s in " "object initializer.", param_spec->name); return false; } if (!(param_spec->flags & G_PARAM_WRITABLE)) return gjs_wrapper_throw_readonly_field(context, m_gtype, param_spec->name); /* prevent setting the prop even in JS */ g_value_init(&gvalue, G_PARAM_SPEC_VALUE_TYPE(param_spec)); if (!gjs_value_to_g_value(context, value, &gvalue)) { g_value_unset(&gvalue); return false; } names->push_back(param_spec->name); /* owned by GParamSpec in cache */ values->push_back(gvalue); } return true; } static void wrapped_gobj_dispose_notify( void* data, GObject* where_the_object_was GJS_USED_VERBOSE_LIFECYCLE) { auto *priv = static_cast(data); priv->gobj_dispose_notify(); gjs_debug_lifecycle(GJS_DEBUG_GOBJECT, "Wrapped GObject %p disposed", where_the_object_was); } void ObjectInstance::gobj_dispose_notify(void) { m_gobj_disposed = true; } void ObjectInstance::iterate_wrapped_gobjects( const ObjectInstance::Action& action) { ObjectInstance *link = ObjectInstance::wrapped_gobject_list; while (link) { ObjectInstance *next = link->next(); action(link); link = next; } } void ObjectInstance::remove_wrapped_gobjects_if( const ObjectInstance::Predicate& predicate, const ObjectInstance::Action& action) { std::vector removed; iterate_wrapped_gobjects([&predicate, &removed](ObjectInstance* link) { if (predicate(link)) { removed.push_back(link); link->unlink(); } }); for (ObjectInstance *priv : removed) action(priv); } /* * ObjectInstance::context_dispose_notify: * * Callback called when the #GjsContext is disposed. It just calls * handle_context_dispose() on every ObjectInstance. */ void ObjectInstance::context_dispose_notify(void*, GObject* where_the_object_was [[maybe_unused]]) { ObjectInstance::iterate_wrapped_gobjects( std::mem_fn(&ObjectInstance::handle_context_dispose)); } /* * ObjectInstance::handle_context_dispose: * * Called on each existing ObjectInstance when the #GjsContext is disposed. */ void ObjectInstance::handle_context_dispose(void) { if (wrapper_is_rooted()) { debug_lifecycle("Was rooted, but unrooting due to GjsContext dispose"); discard_wrapper(); unlink(); } } void ObjectInstance::toggle_down(void) { debug_lifecycle("Toggle notify DOWN"); /* Change to weak ref so the wrapper-wrappee pair can be * collected by the GC */ if (wrapper_is_rooted()) { debug_lifecycle("Unrooting wrapper"); GjsContextPrivate* gjs = GjsContextPrivate::from_current_context(); switch_to_unrooted(gjs->context()); /* During a GC, the collector asks each object which other * objects that it wants to hold on to so if there's an entire * section of the heap graph that's not connected to anything * else, and not reachable from the root set, then it can be * trashed all at once. * * GObjects, however, don't work like that, there's only a * reference count but no notion of who owns the reference so, * a JS object that's wrapping a GObject is unconditionally held * alive as long as the GObject has >1 references. * * Since we cannot know how many more wrapped GObjects are going * be marked for garbage collection after the owner is destroyed, * always queue a garbage collection when a toggle reference goes * down. */ if (!gjs->destroying()) gjs->schedule_gc(); } } void ObjectInstance::toggle_up(void) { /* We need to root the JSObject associated with the passed in GObject so it * doesn't get garbage collected (and lose any associated javascript state * such as custom properties). */ if (!has_wrapper()) /* Object already GC'd */ return; debug_lifecycle("Toggle notify UP"); /* Change to strong ref so the wrappee keeps the wrapper alive * in case the wrapper has data in it that the app cares about */ if (!wrapper_is_rooted()) { // FIXME: thread the context through somehow. Maybe by looking up the // realm that obj belongs to. debug_lifecycle("Rooting wrapper"); auto* cx = GjsContextPrivate::from_current_context()->context(); switch_to_rooted(cx); } } static void toggle_handler(GObject *gobj, ToggleQueue::Direction direction) { switch (direction) { case ToggleQueue::UP: ObjectInstance::for_gobject(gobj)->toggle_up(); break; case ToggleQueue::DOWN: ObjectInstance::for_gobject(gobj)->toggle_down(); break; default: g_assert_not_reached(); } } static void wrapped_gobj_toggle_notify(void*, GObject* gobj, gboolean is_last_ref) { bool is_main_thread; bool toggle_up_queued, toggle_down_queued; GjsContextPrivate* gjs = GjsContextPrivate::from_current_context(); if (gjs->destroying()) { /* Do nothing here - we're in the process of disassociating * the objects. */ return; } /* We only want to touch javascript from one thread. * If we're not in that thread, then we need to defer processing * to it. * In case we're toggling up (and thus rooting the JS object) we * also need to take care if GC is running. The marking side * of it is taken care by JS::Heap, which we use in GjsMaybeOwned, * so we're safe. As for sweeping, it is too late: the JS object * is dead, and attempting to keep it alive would soon crash * the process. Plus, if we touch the JSAPI from another thread, libmozjs * aborts in most cases when in debug mode. * Thus, we drain the toggle queue when GC starts, in order to * prevent this from happening. * In practice, a toggle up during JS finalize can only happen * for temporary refs/unrefs of objects that are garbage anyway, * because JS code is never invoked while the finalizers run * and C code needs to clean after itself before it returns * from dispose()/finalize(). * On the other hand, toggling down is a lot simpler, because * we're creating more garbage. So we just unroot the object, make it a * weak pointer, and wait for the next GC cycle. * * Note that one would think that toggling up only happens * in the main thread (because toggling up is the result of * the JS object, previously visible only to JS code, becoming * visible to the refcounted C world), but because of weird * weak singletons like g_bus_get_sync() objects can see toggle-ups * from different threads too. */ is_main_thread = gjs->is_owner_thread(); auto& toggle_queue = ToggleQueue::get_default(); std::tie(toggle_down_queued, toggle_up_queued) = toggle_queue.is_queued(gobj); if (is_last_ref) { /* We've transitions from 2 -> 1 references, * The JSObject is rooted and we need to unroot it so it * can be garbage collected */ if (is_main_thread) { if (G_UNLIKELY (toggle_up_queued || toggle_down_queued)) { g_critical("toggling down object %s (%p) that's already queued to toggle %s\n", G_OBJECT_TYPE_NAME(gobj), gobj, toggle_up_queued && toggle_down_queued? "up and down" : toggle_up_queued? "up" : "down"); toggle_queue.enqueue(gobj, ToggleQueue::DOWN, toggle_handler); } else { ObjectInstance::for_gobject(gobj)->toggle_down(); } } else { toggle_queue.enqueue(gobj, ToggleQueue::DOWN, toggle_handler); } } else { /* We've transitioned from 1 -> 2 references. * * The JSObject associated with the gobject is not rooted, * but it needs to be. We'll root it. */ if (is_main_thread && !toggle_down_queued) { if (G_UNLIKELY (toggle_up_queued)) { g_error("toggling up object %s that's already queued to toggle up\n", G_OBJECT_TYPE_NAME(gobj)); } ObjectInstance::for_gobject(gobj)->toggle_up(); } else { toggle_queue.enqueue(gobj, ToggleQueue::UP, toggle_handler); } } } void ObjectInstance::release_native_object(void) { discard_wrapper(); if (m_uses_toggle_ref) g_object_remove_toggle_ref(m_ptr, wrapped_gobj_toggle_notify, nullptr); else g_object_unref(m_ptr); m_ptr = nullptr; } /* At shutdown, we need to ensure we've cleared the context of any * pending toggle references. */ void gjs_object_clear_toggles(void) { auto& toggle_queue = ToggleQueue::get_default(); while (toggle_queue.handle_toggle(toggle_handler)) ; } void gjs_object_shutdown_toggle_queue(void) { auto& toggle_queue = ToggleQueue::get_default(); toggle_queue.shutdown(); } /* * ObjectInstance::prepare_shutdown: * * Called when the #GjsContext is disposed, in order to release all GC roots of * JSObjects that are held by GObjects. */ void ObjectInstance::prepare_shutdown(void) { /* We iterate over all of the objects, breaking the JS <-> C * association. We avoid the potential recursion implied in: * toggle ref removal -> gobj dispose -> toggle ref notify * by emptying the toggle queue earlier in the shutdown sequence. */ ObjectInstance::remove_wrapped_gobjects_if( std::mem_fn(&ObjectInstance::wrapper_is_rooted), std::mem_fn(&ObjectInstance::release_native_object)); } ObjectInstance::ObjectInstance(JSContext* cx, JS::HandleObject object) : GIWrapperInstance(cx, object) { GTypeQuery query; type_query_dynamic_safe(&query); if (G_LIKELY(query.type)) JS::AddAssociatedMemory(object, query.instance_size, MemoryUse::GObjectInstanceStruct); GJS_INC_COUNTER(object_instance); } ObjectPrototype::ObjectPrototype(GIObjectInfo* info, GType gtype) : GIWrapperPrototype(info, gtype) { g_type_class_ref(gtype); GJS_INC_COUNTER(object_prototype); } /* * ObjectInstance::update_heap_wrapper_weak_pointers: * * Private callback, called after the JS engine finishes garbage collection, and * notifies when weak pointers need to be either moved or swept. */ void ObjectInstance::update_heap_wrapper_weak_pointers(JSContext*, JS::Compartment*, void*) { gjs_debug_lifecycle(GJS_DEBUG_GOBJECT, "Weak pointer update callback, " "%zu wrapped GObject(s) to examine", ObjectInstance::num_wrapped_gobjects()); ObjectInstance::remove_wrapped_gobjects_if( std::mem_fn(&ObjectInstance::weak_pointer_was_finalized), std::mem_fn(&ObjectInstance::disassociate_js_gobject)); } bool ObjectInstance::weak_pointer_was_finalized(void) { if (has_wrapper() && !wrapper_is_rooted() && update_after_gc()) { /* Ouch, the JS object is dead already. Disassociate the * GObject and hope the GObject dies too. (Remove it from * the weak pointer list first, since the disassociation * may also cause it to be erased.) */ debug_lifecycle("Found GObject weak pointer whose JS wrapper is about " "to be finalized"); return true; } return false; } /* * ObjectInstance::ensure_weak_pointer_callback: * * Private method called when adding a weak pointer for the first time. */ void ObjectInstance::ensure_weak_pointer_callback(JSContext* cx) { if (!s_weak_pointer_callback) { JS_AddWeakPointerCompartmentCallback( cx, &ObjectInstance::update_heap_wrapper_weak_pointers, nullptr); s_weak_pointer_callback = true; } } void ObjectInstance::associate_js_gobject(JSContext *context, JS::HandleObject object, GObject *gobj) { g_assert(!wrapper_is_rooted()); m_uses_toggle_ref = false; m_ptr = gobj; set_object_qdata(); m_wrapper = object; ensure_weak_pointer_callback(context); link(); g_object_weak_ref(gobj, wrapped_gobj_dispose_notify, this); } void ObjectInstance::ensure_uses_toggle_ref(JSContext *cx) { if (m_uses_toggle_ref) return; debug_lifecycle("Switching object instance to toggle ref"); g_assert(!wrapper_is_rooted()); /* OK, here is where things get complicated. We want the * wrapped gobj to keep the JSObject* wrapper alive, because * people might set properties on the JSObject* that they care * about. Therefore, whenever the refcount on the wrapped gobj * is >1, i.e. whenever something other than the wrapper is * referencing the wrapped gobj, the wrapped gobj has a strong * ref (gc-roots the wrapper). When the refcount on the * wrapped gobj is 1, then we change to a weak ref to allow * the wrapper to be garbage collected (and thus unref the * wrappee). */ m_uses_toggle_ref = true; switch_to_rooted(cx); g_object_add_toggle_ref(m_ptr, wrapped_gobj_toggle_notify, nullptr); /* We now have both a ref and a toggle ref, we only want the toggle ref. * This may immediately remove the GC root we just added, since refcount * may drop to 1. */ g_object_unref(m_ptr); } static void invalidate_closure_list(std::forward_list* closures) { g_assert(closures); // Can't loop directly through the items, since invalidating an item's // closure might have the effect of removing the item from the list in the // invalidate notifier while (!closures->empty()) { // This will also free the closure data, through the closure // invalidation mechanism, but adding a temporary reference to // ensure that the closure is still valid when calling invalidation // notify callbacks using GjsAutoGClosure = GjsAutoPointer; GjsAutoGClosure closure(closures->front(), GjsAutoTakeOwnership()); g_closure_invalidate(closure); /* Erase element if not already erased */ closures->remove(closure); } } // Note: m_wrapper (the JS object) may already be null when this is called, if // it was finalized while the GObject was toggled down. void ObjectInstance::disassociate_js_gobject(void) { bool had_toggle_down, had_toggle_up; if (!m_gobj_disposed) g_object_weak_unref(m_ptr, wrapped_gobj_dispose_notify, this); auto& toggle_queue = ToggleQueue::get_default(); std::tie(had_toggle_down, had_toggle_up) = toggle_queue.cancel(m_ptr); if (had_toggle_down != had_toggle_up) { g_error( "JS object wrapper for GObject %p (%s) is being released while " "toggle references are still pending.", m_ptr, type_name()); } /* Fist, remove the wrapper pointer from the wrapped GObject */ unset_object_qdata(); /* Now release all the resources the current wrapper has */ invalidate_closure_list(&m_closures); release_native_object(); /* Mark that a JS object once existed, but it doesn't any more */ m_wrapper_finalized = true; } bool ObjectInstance::init_impl(JSContext *context, const JS::CallArgs& args, JS::MutableHandleObject object) { g_assert(gtype() != G_TYPE_NONE); if (args.length() > 1 && !JS::WarnUTF8(context, "Too many arguments to the constructor of %s: expected " "1, got %u", name(), args.length())) return false; std::vector names; AutoGValueVector values; if (args.length() > 0 && !args[0].isUndefined()) { if (!args[0].isObject()) { gjs_throw(context, "Argument to the constructor of %s should be an object " "with properties to set", name()); return false; } JS::RootedObject props(context, &args[0].toObject()); if (!m_proto->props_to_g_parameters(context, props, &names, &values)) return false; } if (G_TYPE_IS_ABSTRACT(gtype())) { gjs_throw(context, "Cannot instantiate abstract type %s", g_type_name(gtype())); return false; } // Mark this object in the construction stack, it will be popped in // gjs_object_custom_init() in gi/gobject.cpp. if (is_custom_js_class()) { GjsContextPrivate* gjs = GjsContextPrivate::from_cx(context); if (!gjs->object_init_list().append(object)) { JS_ReportOutOfMemory(context); return false; } } g_assert(names.size() == values.size()); GObject* gobj = g_object_new_with_properties(gtype(), values.size(), names.data(), values.data()); ObjectInstance *other_priv = ObjectInstance::for_gobject(gobj); if (other_priv && other_priv->m_wrapper != object.get()) { /* g_object_new_with_properties() returned an object that's already * tracked by a JS object. Let's assume this is a singleton like * IBus.IBus and return the existing JS wrapper object. * * 'object' has a value that was originally created by * JS_NewObjectForConstructor in GJS_NATIVE_CONSTRUCTOR_PRELUDE, but * we're not actually using it, so just let it get collected. Avoiding * this would require a non-trivial amount of work. * */ other_priv->ensure_uses_toggle_ref(context); object.set(other_priv->m_wrapper); g_object_unref(gobj); /* We already own a reference */ gobj = NULL; return true; } if (G_IS_INITIALLY_UNOWNED(gobj) && !g_object_is_floating(gobj)) { /* GtkWindow does not return a ref to caller of g_object_new. * Need a flag in gobject-introspection to tell us this. */ gjs_debug(GJS_DEBUG_GOBJECT, "Newly-created object is initially unowned but we did not get the " "floating ref, probably GtkWindow, using hacky workaround"); g_object_ref(gobj); } else if (g_object_is_floating(gobj)) { g_object_ref_sink(gobj); } else { /* we should already have a ref */ } if (!m_ptr) associate_js_gobject(context, object, gobj); TRACE(GJS_OBJECT_WRAPPER_NEW(this, m_ptr, ns(), name())); args.rval().setObject(*object); return true; } // See GIWrapperBase::constructor() bool ObjectInstance::constructor_impl(JSContext* context, JS::HandleObject object, const JS::CallArgs& argv) { JS::RootedValue initer(context); GjsContextPrivate* gjs = GjsContextPrivate::from_cx(context); const auto& new_target = argv.newTarget(); bool has_gtype; g_assert(new_target.isObject() && "new.target needs to be an object"); JS::RootedObject rooted_target(context, &new_target.toObject()); if (!JS_HasOwnPropertyById(context, rooted_target, gjs->atoms().gtype(), &has_gtype)) return false; if (!has_gtype) { gjs_throw(context, "Tried to construct an object without a GType; are " "you using GObject.registerClass() when inheriting " "from a GObject type?"); return false; } return gjs_object_require_property(context, object, "GObject instance", gjs->atoms().init(), &initer) && gjs->call_function(object, initer, argv, argv.rval()); } void ObjectInstance::trace_impl(JSTracer* tracer) { for (GClosure *closure : m_closures) gjs_closure_trace(closure, tracer); } void ObjectPrototype::trace_impl(JSTracer* tracer) { m_property_cache.trace(tracer); m_field_cache.trace(tracer); m_unresolvable_cache.trace(tracer); for (GClosure* closure : m_vfuncs) gjs_closure_trace(closure, tracer); } void ObjectInstance::finalize_impl(JSFreeOp* fop, JSObject* obj) { GTypeQuery query; type_query_dynamic_safe(&query); if (G_LIKELY(query.type)) JS::RemoveAssociatedMemory(obj, query.instance_size, MemoryUse::GObjectInstanceStruct); GIWrapperInstance::finalize_impl(fop, obj); } ObjectInstance::~ObjectInstance() { TRACE(GJS_OBJECT_WRAPPER_FINALIZE(this, m_ptr, ns(), name())); invalidate_closure_list(&m_closures); /* GObject is not already freed */ if (m_ptr) { bool had_toggle_up; bool had_toggle_down; if (G_UNLIKELY(m_ptr->ref_count <= 0)) { g_error( "Finalizing wrapper for an already freed object of type: " "%s.%s\n", ns(), name()); } auto& toggle_queue = ToggleQueue::get_default(); std::tie(had_toggle_down, had_toggle_up) = toggle_queue.cancel(m_ptr); if (!had_toggle_up && had_toggle_down) { g_error( "Finalizing wrapper for an object that's scheduled to be " "unrooted: %s.%s\n", ns(), name()); } if (!m_gobj_disposed) g_object_weak_unref(m_ptr, wrapped_gobj_dispose_notify, this); release_native_object(); } if (wrapper_is_rooted()) { /* This happens when the refcount on the object is still >1, * for example with global objects GDK never frees like GdkDisplay, * when we close down the JS runtime. */ gjs_debug(GJS_DEBUG_GOBJECT, "Wrapper was finalized despite being kept alive, has refcount >1"); debug_lifecycle("Unrooting object"); discard_wrapper(); } unlink(); GJS_DEC_COUNTER(object_instance); } ObjectPrototype::~ObjectPrototype() { invalidate_closure_list(&m_vfuncs); g_clear_pointer(&m_info, g_base_info_unref); g_type_class_unref(g_type_class_peek(m_gtype)); GJS_DEC_COUNTER(object_prototype); } JSObject* gjs_lookup_object_constructor_from_info(JSContext* context, GIObjectInfo* info, GType gtype) { JS::RootedObject in_object(context); const char *constructor_name; if (info) { in_object = gjs_lookup_namespace_object(context, (GIBaseInfo*) info); constructor_name = g_base_info_get_name((GIBaseInfo*) info); } else { in_object = gjs_lookup_private_namespace(context); constructor_name = g_type_name(gtype); } if (G_UNLIKELY (!in_object)) return NULL; JS::RootedValue value(context); if (!JS_GetProperty(context, in_object, constructor_name, &value)) return NULL; JS::RootedObject constructor(context); if (value.isUndefined()) { /* In case we're looking for a private type, and we don't find it, we need to define it first. */ JS::RootedObject ignored(context); if (!ObjectPrototype::define_class(context, in_object, nullptr, gtype, &constructor, &ignored)) return nullptr; } else { if (G_UNLIKELY (!value.isObject())) return NULL; constructor = &value.toObject(); } g_assert(constructor); return constructor; } GJS_JSAPI_RETURN_CONVENTION static JSObject * gjs_lookup_object_prototype_from_info(JSContext *context, GIObjectInfo *info, GType gtype) { JS::RootedObject constructor(context, gjs_lookup_object_constructor_from_info(context, info, gtype)); if (G_UNLIKELY(!constructor)) return NULL; const GjsAtoms& atoms = GjsContextPrivate::atoms(context); JS::RootedObject prototype(context); if (!gjs_object_require_property(context, constructor, "constructor object", atoms.prototype(), &prototype)) return NULL; return prototype; } GJS_JSAPI_RETURN_CONVENTION static JSObject * gjs_lookup_object_prototype(JSContext *context, GType gtype) { GjsAutoObjectInfo info = g_irepository_find_by_gtype(nullptr, gtype); return gjs_lookup_object_prototype_from_info(context, info, gtype); } // Retrieves a GIFieldInfo for a field named @key. This is for use in // field_getter_impl() and field_setter_not_impl(), where the field info *must* // have been cached previously in resolve_impl() on this ObjectPrototype or one // of its parent ObjectPrototypes. This will fail an assertion if there is no // cached field info. // // The caller does not own the return value, and it can never be null. GIFieldInfo* ObjectPrototype::lookup_cached_field_info(JSContext* cx, JS::HandleString key) { if (!info()) { // Custom JS classes can't have fields, and fields on internal classes // are not available. We must be looking up a field on a // GObject-introspected parent. GType parent_gtype = g_type_parent(m_gtype); g_assert(parent_gtype != G_TYPE_INVALID && "Custom JS class must have parent"); ObjectPrototype* parent_proto = ObjectPrototype::for_gtype(parent_gtype); g_assert(parent_proto && "Custom JS class's parent must have been accessed in JS"); return parent_proto->lookup_cached_field_info(cx, key); } gjs_debug_jsprop(GJS_DEBUG_GOBJECT, "Looking up cached field info for '%s' in '%s' prototype", gjs_debug_string(key).c_str(), g_type_name(m_gtype)); auto entry = m_field_cache.lookupForAdd(key); if (entry) return entry->value().get(); // We must be looking up a field defined on a parent. Look up the prototype // object via its GIObjectInfo. GjsAutoObjectInfo parent_info = g_object_info_get_parent(m_info); JS::RootedObject parent_proto(cx, gjs_lookup_object_prototype_from_info( cx, parent_info, G_TYPE_INVALID)); ObjectPrototype* parent = ObjectPrototype::for_js(cx, parent_proto); return parent->lookup_cached_field_info(cx, key); } void ObjectInstance::associate_closure(JSContext* cx, GClosure* closure) { if (!is_prototype()) to_instance()->ensure_uses_toggle_ref(cx); /* This is a weak reference, and will be cleared when the closure is * invalidated */ auto already_has = std::find(m_closures.begin(), m_closures.end(), closure); g_assert(already_has == m_closures.end() && "This closure was already associated with this object"); m_closures.push_front(closure); g_closure_add_invalidate_notifier( closure, this, &ObjectInstance::closure_invalidated_notify); } void ObjectInstance::closure_invalidated_notify(void* data, GClosure* closure) { auto* priv = static_cast(data); priv->m_closures.remove(closure); } bool ObjectBase::connect(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_GET_WRAPPER_PRIV(cx, argc, vp, args, obj, ObjectBase, priv); if (!priv->check_is_instance(cx, "connect to signals")) return false; return priv->to_instance()->connect_impl(cx, args, false); } bool ObjectBase::connect_after(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_GET_WRAPPER_PRIV(cx, argc, vp, args, obj, ObjectBase, priv); if (!priv->check_is_instance(cx, "connect to signals")) return false; return priv->to_instance()->connect_impl(cx, args, true); } bool ObjectInstance::connect_impl(JSContext *context, const JS::CallArgs& args, bool after) { GClosure *closure; gulong id; guint signal_id; GQuark signal_detail; gjs_debug_gsignal("connect obj %p priv %p", m_wrapper.get(), this); if (!check_gobject_disposed("connect to any signal on")) return true; JS::UniqueChars signal_name; JS::RootedObject callback(context); if (!gjs_parse_call_args(context, after ? "connect_after" : "connect", args, "so", "signal name", &signal_name, "callback", &callback)) return false; if (!JS::IsCallable(callback)) { gjs_throw(context, "second arg must be a callback"); return false; } if (!g_signal_parse_name(signal_name.get(), gtype(), &signal_id, &signal_detail, true)) { gjs_throw(context, "No signal '%s' on object '%s'", signal_name.get(), type_name()); return false; } closure = gjs_closure_new_for_signal( context, JS_GetObjectFunction(callback), "signal callback", signal_id); if (closure == NULL) return false; associate_closure(context, closure); id = g_signal_connect_closure_by_id(m_ptr, signal_id, signal_detail, closure, after); args.rval().setDouble(id); return true; } bool ObjectBase::emit(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_GET_WRAPPER_PRIV(cx, argc, vp, args, obj, ObjectBase, priv); if (!priv->check_is_instance(cx, "emit signal")) return false; return priv->to_instance()->emit_impl(cx, args); } bool ObjectInstance::emit_impl(JSContext *context, const JS::CallArgs& argv) { guint signal_id; GQuark signal_detail; GSignalQuery signal_query; GValue *instance_and_args; GValue rvalue = G_VALUE_INIT; unsigned int i; bool failed; gjs_debug_gsignal("emit obj %p priv %p argc %d", m_wrapper.get(), this, argv.length()); if (!check_gobject_disposed("emit any signal on")) return true; JS::UniqueChars signal_name; if (!gjs_parse_call_args(context, "emit", argv, "!s", "signal name", &signal_name)) return false; if (!g_signal_parse_name(signal_name.get(), gtype(), &signal_id, &signal_detail, false)) { gjs_throw(context, "No signal '%s' on object '%s'", signal_name.get(), type_name()); return false; } g_signal_query(signal_id, &signal_query); if ((argv.length() - 1) != signal_query.n_params) { gjs_throw(context, "Signal '%s' on %s requires %d args got %d", signal_name.get(), type_name(), signal_query.n_params, argv.length() - 1); return false; } if (signal_query.return_type != G_TYPE_NONE) { g_value_init(&rvalue, signal_query.return_type & ~G_SIGNAL_TYPE_STATIC_SCOPE); } instance_and_args = g_newa(GValue, signal_query.n_params + 1); memset(instance_and_args, 0, sizeof(GValue) * (signal_query.n_params + 1)); g_value_init(&instance_and_args[0], gtype()); g_value_set_instance(&instance_and_args[0], m_ptr); failed = false; for (i = 0; i < signal_query.n_params; ++i) { GValue *value; value = &instance_and_args[i + 1]; g_value_init(value, signal_query.param_types[i] & ~G_SIGNAL_TYPE_STATIC_SCOPE); if ((signal_query.param_types[i] & G_SIGNAL_TYPE_STATIC_SCOPE) != 0) failed = !gjs_value_to_g_value_no_copy(context, argv[i + 1], value); else failed = !gjs_value_to_g_value(context, argv[i + 1], value); if (failed) break; } if (!failed) { g_signal_emitv(instance_and_args, signal_id, signal_detail, &rvalue); } if (signal_query.return_type != G_TYPE_NONE) { if (!gjs_value_from_g_value(context, argv.rval(), &rvalue)) failed = true; g_value_unset(&rvalue); } else { argv.rval().setUndefined(); } for (i = 0; i < (signal_query.n_params + 1); ++i) { g_value_unset(&instance_and_args[i]); } return !failed; } bool ObjectInstance::signal_match_arguments_from_object( JSContext* cx, JS::HandleObject match_obj, GSignalMatchType* mask_out, unsigned* signal_id_out, GQuark* detail_out, JS::MutableHandleFunction func_out) { g_assert(mask_out && signal_id_out && detail_out && "forgot out parameter"); int mask = 0; const GjsAtoms& atoms = GjsContextPrivate::atoms(cx); bool has_id; unsigned signal_id = 0; if (!JS_HasOwnPropertyById(cx, match_obj, atoms.signal_id(), &has_id)) return false; if (has_id) { mask |= G_SIGNAL_MATCH_ID; JS::RootedValue value(cx); if (!JS_GetPropertyById(cx, match_obj, atoms.signal_id(), &value)) return false; JS::UniqueChars signal_name = gjs_string_to_utf8(cx, value); if (!signal_name) return false; signal_id = g_signal_lookup(signal_name.get(), gtype()); } bool has_detail; GQuark detail = 0; if (!JS_HasOwnPropertyById(cx, match_obj, atoms.detail(), &has_detail)) return false; if (has_detail) { mask |= G_SIGNAL_MATCH_DETAIL; JS::RootedValue value(cx); if (!JS_GetPropertyById(cx, match_obj, atoms.detail(), &value)) return false; JS::UniqueChars detail_string = gjs_string_to_utf8(cx, value); if (!detail_string) return false; detail = g_quark_from_string(detail_string.get()); } bool has_func; JS::RootedFunction func(cx); if (!JS_HasOwnPropertyById(cx, match_obj, atoms.func(), &has_func)) return false; if (has_func) { mask |= G_SIGNAL_MATCH_CLOSURE; JS::RootedValue value(cx); if (!JS_GetPropertyById(cx, match_obj, atoms.func(), &value)) return false; if (!value.isObject() || !JS_ObjectIsFunction(&value.toObject())) { gjs_throw(cx, "'func' property must be a function"); return false; } func = JS_GetObjectFunction(&value.toObject()); } if (!has_id && !has_detail && !has_func) { gjs_throw(cx, "Must specify at least one of signalId, detail, or func"); return false; } *mask_out = GSignalMatchType(mask); if (has_id) *signal_id_out = signal_id; if (has_detail) *detail_out = detail; if (has_func) func_out.set(func); return true; } bool ObjectBase::signal_find(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_GET_WRAPPER_PRIV(cx, argc, vp, args, obj, ObjectBase, priv); if (!priv->check_is_instance(cx, "find signal")) return false; return priv->to_instance()->signal_find_impl(cx, args); } bool ObjectInstance::signal_find_impl(JSContext* cx, const JS::CallArgs& args) { gjs_debug_gsignal("[Gi.signal_find_symbol]() obj %p priv %p argc %d", m_wrapper.get(), this, args.length()); if (!check_gobject_disposed("find any signal on")) return true; JS::RootedObject match(cx); if (!gjs_parse_call_args(cx, "[Gi.signal_find_symbol]", args, "o", "match", &match)) return false; GSignalMatchType mask; unsigned signal_id; GQuark detail; JS::RootedFunction func(cx); if (!signal_match_arguments_from_object(cx, match, &mask, &signal_id, &detail, &func)) return false; uint64_t handler = 0; if (!func) { handler = g_signal_handler_find(m_ptr, mask, signal_id, detail, nullptr, nullptr, nullptr); } else { for (GClosure* candidate : m_closures) { if (gjs_closure_get_callable(candidate) == func) { handler = g_signal_handler_find(m_ptr, mask, signal_id, detail, candidate, nullptr, nullptr); if (handler != 0) break; } } } args.rval().setNumber(static_cast(handler)); return true; } template static inline const char* signal_match_to_action_name(); template <> inline const char* signal_match_to_action_name<&g_signal_handlers_block_matched>() { return "block"; } template <> inline const char* signal_match_to_action_name<&g_signal_handlers_unblock_matched>() { return "unblock"; } template <> inline const char* signal_match_to_action_name<&g_signal_handlers_disconnect_matched>() { return "disconnect"; } template bool ObjectBase::signals_action(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_GET_WRAPPER_PRIV(cx, argc, vp, args, obj, ObjectBase, priv); const std::string action_name = signal_match_to_action_name(); if (!priv->check_is_instance(cx, (action_name + " signal").c_str())) return false; return priv->to_instance()->signals_action_impl(cx, args); } template bool ObjectInstance::signals_action_impl(JSContext* cx, const JS::CallArgs& args) { const std::string action_name = signal_match_to_action_name(); const std::string action_tag = "[Gi.signals_" + action_name + "_symbol]"; gjs_debug_gsignal("[%s]() obj %p priv %p argc %d", action_tag.c_str(), m_wrapper.get(), this, args.length()); if (!check_gobject_disposed((action_name + " any signal on").c_str())) { return true; } JS::RootedObject match(cx); if (!gjs_parse_call_args(cx, action_tag.c_str(), args, "o", "match", &match)) { return false; } GSignalMatchType mask; unsigned signal_id; GQuark detail; JS::RootedFunction func(cx); if (!signal_match_arguments_from_object(cx, match, &mask, &signal_id, &detail, &func)) { return false; } unsigned n_matched = 0; if (!func) { n_matched = MatchFunc(m_ptr, mask, signal_id, detail, nullptr, nullptr, nullptr); } else { std::vector candidates; for (GClosure* candidate : m_closures) { if (gjs_closure_get_callable(candidate) == func) candidates.push_back(candidate); } for (GClosure* candidate : candidates) { n_matched += MatchFunc(m_ptr, mask, signal_id, detail, candidate, nullptr, nullptr); } } args.rval().setNumber(n_matched); return true; } bool ObjectBase::to_string(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_GET_WRAPPER_PRIV(cx, argc, vp, args, obj, ObjectBase, priv); return gjs_wrapper_to_string_func( cx, obj, priv->to_string_kind(), priv->info(), priv->gtype(), priv->is_prototype() ? nullptr : priv->to_instance()->ptr(), args.rval()); } // Override of GIWrapperBase::to_string_kind() const char* ObjectBase::to_string_kind(void) const { if (is_prototype()) return "object"; return to_instance()->to_string_kind(); } /* * ObjectInstance::to_string_kind: * * Instance-only version of GIWrapperBase::to_string_kind(). ObjectInstance * shows a "finalized" marker in its toString() method if the wrapped GObject * has already been finalized. */ const char* ObjectInstance::to_string_kind(void) const { return m_gobj_disposed ? "object (FINALIZED)" : "object"; } /* * ObjectBase::init_gobject: * * This is named "init_gobject()" but corresponds to "_init()" in JS. The reason * for the name is that an "init()" method is used within SpiderMonkey to * indicate fallible initialization that must be done before an object can be * used, which is not the case here. */ bool ObjectBase::init_gobject(JSContext* context, unsigned argc, JS::Value* vp) { GJS_GET_WRAPPER_PRIV(context, argc, vp, argv, obj, ObjectBase, priv); if (!priv->check_is_instance(context, "initialize")) return false; return priv->to_instance()->init_impl(context, argv, &obj); } // clang-format off const struct JSClassOps ObjectBase::class_ops = { &ObjectBase::add_property, nullptr, // deleteProperty nullptr, // enumerate &ObjectBase::new_enumerate, &ObjectBase::resolve, nullptr, // mayResolve &ObjectBase::finalize, NULL, NULL, NULL, &ObjectBase::trace, }; const struct JSClass ObjectBase::klass = { "GObject_Object", JSCLASS_HAS_PRIVATE | JSCLASS_FOREGROUND_FINALIZE, &ObjectBase::class_ops }; JSFunctionSpec ObjectBase::proto_methods[] = { JS_FN("_init", &ObjectBase::init_gobject, 0, 0), JS_FN("connect", &ObjectBase::connect, 0, 0), JS_FN("connect_after", &ObjectBase::connect_after, 0, 0), JS_FN("emit", &ObjectBase::emit, 0, 0), JS_FS_END }; JSPropertySpec ObjectBase::proto_properties[] = { JS_STRING_SYM_PS(toStringTag, "GObject_Object", JSPROP_READONLY), JS_PS_END}; // clang-format on // Override of GIWrapperPrototype::get_parent_proto() bool ObjectPrototype::get_parent_proto(JSContext* cx, JS::MutableHandleObject proto) const { GType parent_type = g_type_parent(gtype()); if (parent_type != G_TYPE_INVALID) { proto.set(gjs_lookup_object_prototype(cx, parent_type)); if (!proto) return false; } return true; } /* * ObjectPrototype::define_class: * @in_object: Object where the constructor is stored, typically a repo object. * @info: Introspection info for the GObject class. * @gtype: #GType for the GObject class. * @constructor: Return location for the constructor object. * @prototype: Return location for the prototype object. * * Define a GObject class constructor and prototype, including all the * necessary methods and properties that are not introspected. Provides the * constructor and prototype objects as out parameters, for convenience * elsewhere. */ bool ObjectPrototype::define_class(JSContext* context, JS::HandleObject in_object, GIObjectInfo* info, GType gtype, JS::MutableHandleObject constructor, JS::MutableHandleObject prototype) { if (!ObjectPrototype::create_class(context, in_object, info, gtype, constructor, prototype)) return false; // hook_up_vfunc and the signal handler matcher functions can't be included // in gjs_object_instance_proto_funcs because they are custom symbols. const GjsAtoms& atoms = GjsContextPrivate::atoms(context); return JS_DefineFunctionById(context, prototype, atoms.hook_up_vfunc(), &ObjectBase::hook_up_vfunc, 3, GJS_MODULE_PROP_FLAGS) && JS_DefineFunctionById(context, prototype, atoms.signal_find(), &ObjectBase::signal_find, 1, GJS_MODULE_PROP_FLAGS) && JS_DefineFunctionById( context, prototype, atoms.signals_block(), &ObjectBase::signals_action<&g_signal_handlers_block_matched>, 1, GJS_MODULE_PROP_FLAGS) && JS_DefineFunctionById( context, prototype, atoms.signals_unblock(), &ObjectBase::signals_action<&g_signal_handlers_unblock_matched>, 1, GJS_MODULE_PROP_FLAGS) && JS_DefineFunctionById(context, prototype, atoms.signals_disconnect(), &ObjectBase::signals_action< &g_signal_handlers_disconnect_matched>, 1, GJS_MODULE_PROP_FLAGS); } /* * ObjectInstance::init_custom_class_from_gobject: * * Does all the necessary initialization for an ObjectInstance and JSObject * wrapper, given a newly-created GObject pointer, of a GObject class that was * created in JS with GObject.registerClass(). This is called from the GObject's * instance init function in gobject.cpp, and that's the only reason it's a * public method. */ bool ObjectInstance::init_custom_class_from_gobject(JSContext* cx, JS::HandleObject wrapper, GObject* gobj) { associate_js_gobject(cx, wrapper, gobj); // Custom JS objects will most likely have visible state, so just do this // from the start. ensure_uses_toggle_ref(cx); const GjsAtoms& atoms = GjsContextPrivate::atoms(cx); JS::RootedValue v(cx); if (!JS_GetPropertyById(cx, wrapper, atoms.instance_init(), &v)) return false; if (v.isUndefined()) return true; if (!v.isObject() || !JS::IsCallable(&v.toObject())) { gjs_throw(cx, "_instance_init property was not a function"); return false; } JS::RootedValue ignored_rval(cx); return JS_CallFunctionValue(cx, wrapper, v, JS::HandleValueArray::empty(), &ignored_rval); } /* * ObjectInstance::new_for_gobject: * * Creates a new JSObject wrapper for the GObject pointer @gobj, and an * ObjectInstance private structure to go along with it. */ ObjectInstance* ObjectInstance::new_for_gobject(JSContext* cx, GObject* gobj) { g_assert(gobj && "Cannot create JSObject for null GObject pointer"); GType gtype = G_TYPE_FROM_INSTANCE(gobj); gjs_debug_marshal(GJS_DEBUG_GOBJECT, "Wrapping %s with JSObject", g_type_name(gtype)); JS::RootedObject proto(cx, gjs_lookup_object_prototype(cx, gtype)); if (!proto) return nullptr; JS::RootedObject obj( cx, JS_NewObjectWithGivenProto(cx, JS_GetClass(proto), proto)); if (!obj) return nullptr; ObjectInstance* priv = ObjectInstance::new_for_js_object(cx, obj); g_object_ref_sink(gobj); priv->associate_js_gobject(cx, obj, gobj); g_assert(priv->wrapper() == obj.get()); return priv; } /* * ObjectInstance::wrapper_from_gobject: * * Gets a JSObject wrapper for the GObject pointer @gobj. If one already exists, * then it is returned. Otherwise a new one is created with * ObjectInstance::new_for_gobject(). */ JSObject* ObjectInstance::wrapper_from_gobject(JSContext* cx, GObject* gobj) { g_assert(gobj && "Cannot get JSObject for null GObject pointer"); ObjectInstance* priv = ObjectInstance::for_gobject(gobj); if (!priv) { /* We have to create a wrapper */ priv = new_for_gobject(cx, gobj); if (!priv) return nullptr; } return priv->wrapper(); } // Replaces GIWrapperBase::to_c_ptr(). The GIWrapperBase version is deleted. bool ObjectBase::to_c_ptr(JSContext* cx, JS::HandleObject obj, GObject** ptr) { g_assert(ptr); auto* priv = ObjectBase::for_js(cx, obj); if (!priv || priv->is_prototype()) return false; ObjectInstance* instance = priv->to_instance(); if (!instance->check_gobject_disposed("access")) { *ptr = nullptr; return true; } *ptr = instance->ptr(); return true; } // Overrides GIWrapperBase::transfer_to_gi_argument(). bool ObjectBase::transfer_to_gi_argument(JSContext* cx, JS::HandleObject obj, GIArgument* arg, GIDirection transfer_direction, GITransfer transfer_ownership, GType expected_gtype, GIBaseInfo* expected_info) { g_assert(transfer_direction != GI_DIRECTION_INOUT && "transfer_to_gi_argument() must choose between in or out"); if (!ObjectBase::typecheck(cx, obj, expected_info, expected_gtype)) { gjs_arg_unset(arg); return false; } GObject* ptr; if (!ObjectBase::to_c_ptr(cx, obj, &ptr)) return false; gjs_arg_set(arg, ptr); // Pointer can be null if object was already disposed by C code if (!ptr) return true; if ((transfer_direction == GI_DIRECTION_IN && transfer_ownership != GI_TRANSFER_NOTHING) || (transfer_direction == GI_DIRECTION_OUT && transfer_ownership == GI_TRANSFER_EVERYTHING)) { gjs_arg_set(arg, ObjectInstance::copy_ptr(cx, expected_gtype, gjs_arg_get(arg))); if (!gjs_arg_get(arg)) return false; } return true; } // Overrides GIWrapperInstance::typecheck_impl() bool ObjectInstance::typecheck_impl(JSContext* cx, GIBaseInfo* expected_info, GType expected_type) const { g_assert(m_gobj_disposed || gtype() == G_OBJECT_TYPE(m_ptr)); return GIWrapperInstance::typecheck_impl(cx, expected_info, expected_type); } GJS_JSAPI_RETURN_CONVENTION static bool find_vfunc_info(JSContext* context, GType implementor_gtype, GIBaseInfo* vfunc_info, const char* vfunc_name, void** implementor_vtable_ret, GjsAutoFieldInfo* field_info_ret) { GType ancestor_gtype; int length, i; GIBaseInfo *ancestor_info; GjsAutoStructInfo struct_info; bool is_interface; field_info_ret->reset(); *implementor_vtable_ret = NULL; ancestor_info = g_base_info_get_container(vfunc_info); ancestor_gtype = g_registered_type_info_get_g_type((GIRegisteredTypeInfo*)ancestor_info); is_interface = g_base_info_get_type(ancestor_info) == GI_INFO_TYPE_INTERFACE; GjsAutoTypeClass implementor_class(implementor_gtype); if (is_interface) { GTypeInstance *implementor_iface_class; implementor_iface_class = (GTypeInstance*) g_type_interface_peek(implementor_class, ancestor_gtype); if (implementor_iface_class == NULL) { gjs_throw (context, "Couldn't find GType of implementor of interface %s.", g_type_name(ancestor_gtype)); return false; } *implementor_vtable_ret = implementor_iface_class; struct_info = g_interface_info_get_iface_struct((GIInterfaceInfo*)ancestor_info); } else { struct_info = g_object_info_get_class_struct((GIObjectInfo*)ancestor_info); *implementor_vtable_ret = implementor_class; } length = g_struct_info_get_n_fields(struct_info); for (i = 0; i < length; i++) { GjsAutoFieldInfo field_info = g_struct_info_get_field(struct_info, i); if (strcmp(field_info.name(), vfunc_name) != 0) continue; GjsAutoTypeInfo type_info = g_field_info_get_type(field_info); if (g_type_info_get_tag(type_info) != GI_TYPE_TAG_INTERFACE) { /* We have a field with the same name, but it's not a callback. * There's no hope of being another field with a correct name, * so just abort early. */ return true; } else { *field_info_ret = std::move(field_info); return true; } } return true; } bool ObjectBase::hook_up_vfunc(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_GET_WRAPPER_PRIV(cx, argc, vp, args, prototype, ObjectBase, priv); /* Normally we wouldn't assert is_prototype(), but this method can only be * called internally so it's OK to crash if done wrongly */ return priv->to_prototype()->hook_up_vfunc_impl(cx, args); } bool ObjectPrototype::hook_up_vfunc_impl(JSContext* cx, const JS::CallArgs& args) { JS::UniqueChars name; JS::RootedObject function(cx); if (!gjs_parse_call_args(cx, "hook_up_vfunc", args, "so", "name", &name, "function", &function)) return false; args.rval().setUndefined(); /* find the first class that actually has repository information */ GIObjectInfo *info = m_info; GType info_gtype = m_gtype; while (!info && info_gtype != G_TYPE_OBJECT) { info_gtype = g_type_parent(info_gtype); info = g_irepository_find_by_gtype(nullptr, info_gtype); } /* If we don't have 'info', we don't have the base class (GObject). * This is awful, so abort now. */ g_assert(info != NULL); GjsAutoVFuncInfo vfunc = find_vfunc_on_parents(info, name.get(), nullptr); if (!vfunc) { guint i, n_interfaces; GType *interface_list; interface_list = g_type_interfaces(m_gtype, &n_interfaces); for (i = 0; i < n_interfaces; i++) { GjsAutoInterfaceInfo interface = g_irepository_find_by_gtype(nullptr, interface_list[i]); /* The interface doesn't have to exist -- it could be private * or dynamic. */ if (interface) { vfunc = g_interface_info_find_vfunc(interface, name.get()); if (vfunc) break; } } g_free(interface_list); } if (!vfunc) { gjs_throw(cx, "Could not find definition of virtual function %s", name.get()); return false; } void *implementor_vtable; GjsAutoFieldInfo field_info; if (!find_vfunc_info(cx, m_gtype, vfunc, name.get(), &implementor_vtable, &field_info)) return false; if (field_info) { gint offset; gpointer method_ptr; GjsCallbackTrampoline *trampoline; offset = g_field_info_get_offset(field_info); method_ptr = G_STRUCT_MEMBER_P(implementor_vtable, offset); if (!js::IsFunctionObject(function)) { gjs_throw(cx, "Tried to deal with a vfunc that wasn't a function"); return false; } JS::RootedFunction func(cx, JS_GetObjectFunction(function)); trampoline = gjs_callback_trampoline_new( cx, func, vfunc, GI_SCOPE_TYPE_NOTIFIED, true, true); if (!trampoline) return false; // This is traced, and will be cleared from the list when the closure is // invalidated g_assert(std::find(m_vfuncs.begin(), m_vfuncs.end(), trampoline->js_function) == m_vfuncs.end() && "This vfunc was already associated with this class"); m_vfuncs.push_front(trampoline->js_function); g_closure_add_invalidate_notifier( trampoline->js_function, this, &ObjectPrototype::vfunc_invalidated_notify); g_closure_add_invalidate_notifier( trampoline->js_function, trampoline, [](void* data, GClosure*) { auto* trampoline = static_cast(data); gjs_callback_trampoline_unref(trampoline); }); *((ffi_closure **)method_ptr) = trampoline->closure; } return true; } void ObjectPrototype::vfunc_invalidated_notify(void* data, GClosure* closure) { auto* priv = static_cast(data); priv->m_vfuncs.remove(closure); } bool gjs_lookup_object_constructor(JSContext *context, GType gtype, JS::MutableHandleValue value_p) { JSObject *constructor; GjsAutoObjectInfo object_info = g_irepository_find_by_gtype(nullptr, gtype); constructor = gjs_lookup_object_constructor_from_info(context, object_info, gtype); if (G_UNLIKELY (constructor == NULL)) return false; value_p.setObject(*constructor); return true; } cjs-5.2.0/gi/param.cpp0000644000175000017500000002116014144444702014642 0ustar jpeisachjpeisach/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* * Copyright (c) 2008 litl, LLC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #include #include #include #include #include #include #include #include // for UniqueChars #include // for JS_GetClass, JS_GetPropertyById #include // for JSProto_TypeError #include "gi/function.h" #include "gi/param.h" #include "gi/repo.h" #include "gi/wrapperutils.h" #include "cjs/atoms.h" #include "cjs/context-private.h" #include "cjs/jsapi-class.h" #include "cjs/jsapi-util.h" #include "cjs/mem-private.h" #include "util/log.h" extern struct JSClass gjs_param_class; GJS_DEFINE_PRIV_FROM_JS(GParamSpec, gjs_param_class) /* * The *resolved out parameter, on success, should be false to indicate that id * was not resolved; and true if id was resolved. */ GJS_JSAPI_RETURN_CONVENTION static bool param_resolve(JSContext *context, JS::HandleObject obj, JS::HandleId id, bool *resolved) { if (!priv_from_js(context, obj)) { /* instance, not prototype */ *resolved = false; return true; } JS::UniqueChars name; if (!gjs_get_string_id(context, id, &name)) return false; if (!name) { *resolved = false; return true; /* not resolved, but no error */ } GjsAutoObjectInfo info = g_irepository_find_by_gtype(nullptr, G_TYPE_PARAM); GjsAutoFunctionInfo method_info = g_object_info_find_method(info, name.get()); if (!method_info) { *resolved = false; return true; } #if GJS_VERBOSE_ENABLE_GI_USAGE _gjs_log_info_usage(method_info); #endif if (g_function_info_get_flags (method_info) & GI_FUNCTION_IS_METHOD) { gjs_debug(GJS_DEBUG_GOBJECT, "Defining method %s in prototype for GObject.ParamSpec", method_info.name()); if (!gjs_define_function(context, obj, G_TYPE_PARAM, method_info)) return false; *resolved = true; /* we defined the prop in obj */ } return true; } GJS_NATIVE_CONSTRUCTOR_DECLARE(param) { GJS_NATIVE_CONSTRUCTOR_VARIABLES(param) GJS_NATIVE_CONSTRUCTOR_PRELUDE(param); GJS_INC_COUNTER(param); GJS_NATIVE_CONSTRUCTOR_FINISH(param); return true; } static void param_finalize(JSFreeOp*, JSObject* obj) { GjsAutoParam param = static_cast(JS_GetPrivate(obj)); gjs_debug_lifecycle(GJS_DEBUG_GPARAM, "finalize, obj %p priv %p", obj, param.get()); if (!param) return; /* wrong class? */ GJS_DEC_COUNTER(param); JS_SetPrivate(obj, nullptr); } /* The bizarre thing about this vtable is that it applies to both * instances of the object, and to the prototype that instances of the * class have. */ static const struct JSClassOps gjs_param_class_ops = { nullptr, // addProperty nullptr, // deleteProperty nullptr, // enumerate nullptr, // newEnumerate param_resolve, nullptr, // mayResolve param_finalize}; struct JSClass gjs_param_class = { "GObject_ParamSpec", JSCLASS_HAS_PRIVATE | JSCLASS_BACKGROUND_FINALIZE, &gjs_param_class_ops }; JSPropertySpec gjs_param_proto_props[] = { JS_PS_END }; JSFunctionSpec gjs_param_proto_funcs[] = { JS_FS_END }; static JSFunctionSpec gjs_param_constructor_funcs[] = { JS_FS_END }; GJS_JSAPI_RETURN_CONVENTION static JSObject* gjs_lookup_param_prototype(JSContext *context) { const GjsAtoms& atoms = GjsContextPrivate::atoms(context); JS::RootedObject in_object( context, gjs_lookup_namespace_object_by_name(context, atoms.gobject())); if (G_UNLIKELY (!in_object)) return nullptr; JS::RootedValue value(context); if (!JS_GetPropertyById(context, in_object, atoms.param_spec(), &value) || G_UNLIKELY(!value.isObject())) return nullptr; JS::RootedObject constructor(context, &value.toObject()); g_assert(constructor); if (!JS_GetPropertyById(context, constructor, atoms.prototype(), &value) || G_UNLIKELY(!value.isObjectOrNull())) return nullptr; return value.toObjectOrNull(); } bool gjs_define_param_class(JSContext *context, JS::HandleObject in_object) { const char *constructor_name; JS::RootedObject prototype(context), constructor(context); constructor_name = "ParamSpec"; if (!gjs_init_class_dynamic( context, in_object, nullptr, "GObject", constructor_name, &gjs_param_class, gjs_param_constructor, 0, gjs_param_proto_props, // props of prototype gjs_param_proto_funcs, // funcs of prototype nullptr, // props of constructor, MyConstructor.myprop gjs_param_constructor_funcs, // funcs of constructor &prototype, &constructor)) return false; if (!gjs_wrapper_define_gtype_prop(context, constructor, G_TYPE_PARAM)) return false; GjsAutoObjectInfo info = g_irepository_find_by_gtype(nullptr, G_TYPE_PARAM); if (!gjs_define_static_methods(context, constructor, G_TYPE_PARAM, info)) return false; gjs_debug(GJS_DEBUG_GPARAM, "Defined class %s prototype is %p class %p in object %p", constructor_name, prototype.get(), JS_GetClass(prototype), in_object.get()); return true; } JSObject* gjs_param_from_g_param(JSContext *context, GParamSpec *gparam) { JSObject *obj; if (!gparam) return nullptr; gjs_debug(GJS_DEBUG_GPARAM, "Wrapping %s '%s' on %s with JSObject", g_type_name(G_TYPE_FROM_INSTANCE((GTypeInstance*) gparam)), gparam->name, g_type_name(gparam->owner_type)); JS::RootedObject proto(context, gjs_lookup_param_prototype(context)); obj = JS_NewObjectWithGivenProto(context, JS_GetClass(proto), proto); GJS_INC_COUNTER(param); JS_SetPrivate(obj, gparam); g_param_spec_ref (gparam); gjs_debug(GJS_DEBUG_GPARAM, "JSObject created with param instance %p type %s", gparam, g_type_name(G_TYPE_FROM_INSTANCE(gparam))); return obj; } GParamSpec* gjs_g_param_from_param(JSContext *context, JS::HandleObject obj) { if (!obj) return nullptr; return priv_from_js(context, obj); } bool gjs_typecheck_param(JSContext *context, JS::HandleObject object, GType expected_type, bool throw_error) { bool result; if (!do_base_typecheck(context, object, throw_error)) return false; GParamSpec* param = priv_from_js(context, object); if (!param) { if (throw_error) { gjs_throw_custom(context, JSProto_TypeError, nullptr, "Object is GObject.ParamSpec.prototype, not an object instance - " "cannot convert to a GObject.ParamSpec instance"); } return false; } if (expected_type != G_TYPE_NONE) result = g_type_is_a(G_TYPE_FROM_INSTANCE(param), expected_type); else result = true; if (!result && throw_error) { gjs_throw_custom(context, JSProto_TypeError, nullptr, "Object is of type %s - cannot convert to %s", g_type_name(G_TYPE_FROM_INSTANCE(param)), g_type_name(expected_type)); } return result; } cjs-5.2.0/gi/ns.h0000644000175000017500000000261614144444702013634 0ustar jpeisachjpeisach/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* * Copyright (c) 2008 litl, LLC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #ifndef GI_NS_H_ #define GI_NS_H_ #include "cjs/macros.h" class JSObject; struct JSContext; GJS_JSAPI_RETURN_CONVENTION JSObject* gjs_create_ns(JSContext *context, const char *ns_name); #endif // GI_NS_H_ cjs-5.2.0/gi/union.h0000644000175000017500000001002614144444702014336 0ustar jpeisachjpeisach/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* * Copyright (c) 2008 litl, LLC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #ifndef GI_UNION_H_ #define GI_UNION_H_ #include #include #include #include #include "gi/wrapperutils.h" #include "cjs/macros.h" #include "util/log.h" namespace JS { class CallArgs; } struct JSClass; struct JSClassOps; class UnionPrototype; class UnionInstance; class UnionBase : public GIWrapperBase { friend class GIWrapperBase; protected: explicit UnionBase(UnionPrototype* proto = nullptr) : GIWrapperBase(proto) {} ~UnionBase(void) {} static const GjsDebugTopic debug_topic = GJS_DEBUG_GBOXED; static constexpr const char* debug_tag = "union"; static const JSClassOps class_ops; static const JSClass klass; [[nodiscard]] static const char* to_string_kind(void) { return "union"; } }; class UnionPrototype : public GIWrapperPrototype { friend class GIWrapperPrototype; friend class GIWrapperBase; static constexpr InfoType::Tag info_type_tag = InfoType::Union; explicit UnionPrototype(GIUnionInfo* info, GType gtype); ~UnionPrototype(void); GJS_JSAPI_RETURN_CONVENTION bool resolve_impl(JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool* resolved); // Overrides GIWrapperPrototype::constructor_nargs(). [[nodiscard]] unsigned constructor_nargs(void) const { return 0; } }; class UnionInstance : public GIWrapperInstance { friend class GIWrapperInstance; friend class GIWrapperBase; explicit UnionInstance(JSContext* cx, JS::HandleObject obj); ~UnionInstance(void); GJS_JSAPI_RETURN_CONVENTION bool constructor_impl(JSContext* cx, JS::HandleObject obj, const JS::CallArgs& args); public: /* * UnionInstance::copy_union: * * Allocate a new union pointer using g_boxed_copy(), from a raw union * pointer. */ void copy_union(void* ptr) { m_ptr = g_boxed_copy(gtype(), ptr); } GJS_JSAPI_RETURN_CONVENTION static void* copy_ptr(JSContext* cx, GType gtype, void* ptr); }; GJS_JSAPI_RETURN_CONVENTION bool gjs_define_union_class(JSContext *context, JS::HandleObject in_object, GIUnionInfo *info); GJS_JSAPI_RETURN_CONVENTION JSObject* gjs_union_from_c_union (JSContext *context, GIUnionInfo *info, void *gboxed); #endif // GI_UNION_H_ cjs-5.2.0/gi/interface.h0000644000175000017500000001210514144444702015146 0ustar jpeisachjpeisach/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* * Copyright (c) 2008 litl, LLC * Copyright (c) 2012 Red Hat, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #ifndef GI_INTERFACE_H_ #define GI_INTERFACE_H_ #include #include #include #include #include #include #include #include #include "gi/wrapperutils.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" #include "util/log.h" class InterfacePrototype; class InterfaceInstance; /* For more information on this Base/Prototype/Interface scheme, see the notes * in wrapperutils.h. * * What's unusual about this subclass is that InterfaceInstance should never * actually be instantiated. Interfaces can't be constructed, and * GIWrapperBase::constructor() is overridden to just throw an exception and not * create any JS wrapper object. * * We use the template classes from wrapperutils.h anyway, because there is * still a lot of common code. */ class InterfaceBase : public GIWrapperBase { friend class GIWrapperBase; protected: explicit InterfaceBase(InterfacePrototype* proto = nullptr) : GIWrapperBase(proto) {} ~InterfaceBase(void) {} static const GjsDebugTopic debug_topic = GJS_DEBUG_GINTERFACE; static constexpr const char* debug_tag = "GInterface"; static const struct JSClassOps class_ops; static const struct JSClass klass; static JSFunctionSpec static_methods[]; [[nodiscard]] const char* to_string_kind(void) const { return "interface"; } // JSNative methods // Overrides GIWrapperBase::constructor(). GJS_JSAPI_RETURN_CONVENTION static bool constructor(JSContext* cx, unsigned argc, JS::Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); gjs_throw_abstract_constructor_error(cx, args); return false; } GJS_JSAPI_RETURN_CONVENTION static bool has_instance(JSContext* cx, unsigned argc, JS::Value* vp); }; class InterfacePrototype : public GIWrapperPrototype { friend class GIWrapperPrototype; friend class GIWrapperBase; friend class InterfaceBase; // for has_instance_impl // the GTypeInterface vtable wrapped by this JS object GTypeInterface* m_vtable; static constexpr InfoType::Tag info_type_tag = InfoType::Interface; explicit InterfacePrototype(GIInterfaceInfo* info, GType gtype); ~InterfacePrototype(void); // JSClass operations GJS_JSAPI_RETURN_CONVENTION bool resolve_impl(JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool* resolved); // JS methods GJS_JSAPI_RETURN_CONVENTION bool has_instance_impl(JSContext* cx, const JS::CallArgs& args); }; class InterfaceInstance : public GIWrapperInstance { friend class GIWrapperInstance; friend class GIWrapperBase; [[noreturn]] InterfaceInstance(JSContext* cx, JS::HandleObject obj) : GIWrapperInstance(cx, obj) { g_assert_not_reached(); } [[noreturn]] ~InterfaceInstance(void) { g_assert_not_reached(); } }; GJS_JSAPI_RETURN_CONVENTION bool gjs_lookup_interface_constructor(JSContext *context, GType gtype, JS::MutableHandleValue value_p); #endif // GI_INTERFACE_H_ cjs-5.2.0/gi/gerror.cpp0000644000175000017500000004363414144444702015054 0ustar jpeisachjpeisach/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* * Copyright (c) 2008 litl, LLC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #include #include #include #include #include #include #include // for JSPROP_ENUMERATE #include #include #include #include // for UniqueChars #include #include #include // for JS_DefinePropertyById, JS_GetProp... #include // for JSProtoKey, JSProto_Error, JSProt... #include "gi/arg-inl.h" #include "gi/boxed.h" #include "gi/enumeration.h" #include "gi/gerror.h" #include "gi/repo.h" #include "cjs/atoms.h" #include "cjs/context-private.h" #include "cjs/error-types.h" #include "cjs/jsapi-util.h" #include "cjs/mem-private.h" #include "util/log.h" ErrorPrototype::ErrorPrototype(GIEnumInfo* info, GType gtype) : GIWrapperPrototype(info, gtype), m_domain(g_quark_from_string(g_enum_info_get_error_domain(info))) { GJS_INC_COUNTER(gerror_prototype); } ErrorPrototype::~ErrorPrototype(void) { GJS_DEC_COUNTER(gerror_prototype); } ErrorInstance::ErrorInstance(JSContext* cx, JS::HandleObject obj) : GIWrapperInstance(cx, obj) { GJS_INC_COUNTER(gerror_instance); } ErrorInstance::~ErrorInstance(void) { g_clear_error(&m_ptr); GJS_DEC_COUNTER(gerror_instance); } /* * ErrorBase::domain: * * Fetches ErrorPrototype::domain() for instances as well as prototypes. */ GQuark ErrorBase::domain(void) const { return get_prototype()->domain(); } // See GIWrapperBase::constructor(). bool ErrorInstance::constructor_impl(JSContext* context, JS::HandleObject object, const JS::CallArgs& argv) { if (argv.length() != 1 || !argv[0].isObject()) { gjs_throw(context, "Invalid parameters passed to GError constructor, expected one object"); return false; } JS::RootedObject params_obj(context, &argv[0].toObject()); JS::UniqueChars message; const GjsAtoms& atoms = GjsContextPrivate::atoms(context); if (!gjs_object_require_property(context, params_obj, "GError constructor", atoms.message(), &message)) return false; int32_t code; if (!gjs_object_require_property(context, params_obj, "GError constructor", atoms.code(), &code)) return false; m_ptr = g_error_new_literal(domain(), code, message.get()); /* We assume this error will be thrown in the same line as the constructor */ return gjs_define_error_properties(context, object); } /* * ErrorBase::get_domain: * * JSNative property getter for `domain`. This property works on prototypes as * well as instances. */ bool ErrorBase::get_domain(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_GET_WRAPPER_PRIV(cx, argc, vp, args, obj, ErrorBase, priv); args.rval().setInt32(priv->domain()); return true; } // JSNative property getter for `message`. bool ErrorBase::get_message(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_GET_WRAPPER_PRIV(cx, argc, vp, args, obj, ErrorBase, priv); if (!priv->check_is_instance(cx, "get a field")) return false; return gjs_string_from_utf8(cx, priv->to_instance()->message(), args.rval()); } // JSNative property getter for `code`. bool ErrorBase::get_code(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_GET_WRAPPER_PRIV(cx, argc, vp, args, obj, ErrorBase, priv); if (!priv->check_is_instance(cx, "get a field")) return false; args.rval().setInt32(priv->to_instance()->code()); return true; } // JSNative implementation of `toString()`. bool ErrorBase::to_string(JSContext* context, unsigned argc, JS::Value* vp) { GJS_GET_THIS(context, argc, vp, rec, self); GjsAutoChar descr; // An error created via `new GLib.Error` will have a Boxed* private pointer, // not an Error*, so we can't call regular to_string() on it. if (BoxedBase::typecheck(context, self, nullptr, G_TYPE_ERROR, GjsTypecheckNoThrow())) { auto* gerror = BoxedBase::to_c_ptr(context, self); if (!gerror) return false; descr = g_strdup_printf("GLib.Error %s: %s", g_quark_to_string(gerror->domain), gerror->message); return gjs_string_from_utf8(context, descr, rec.rval()); } ErrorBase* priv = ErrorBase::for_js_typecheck(context, self, rec); if (!priv) return false; /* We follow the same pattern as standard JS errors, at the expense of hiding some useful information */ if (priv->is_prototype()) { descr = g_strdup_printf("%s.%s", priv->ns(), priv->name()); } else { descr = g_strdup_printf("%s.%s: %s", priv->ns(), priv->name(), priv->to_instance()->message()); } return gjs_string_from_utf8(context, descr, rec.rval()); } // JSNative implementation of `valueOf()`. bool ErrorBase::value_of(JSContext* context, unsigned argc, JS::Value* vp) { GJS_GET_THIS(context, argc, vp, rec, self); JS::RootedObject prototype(context); const GjsAtoms& atoms = GjsContextPrivate::atoms(context); if (!gjs_object_require_property(context, self, "constructor", atoms.prototype(), &prototype)) { /* This error message will be more informative */ JS_ClearPendingException(context); gjs_throw(context, "GLib.Error.valueOf() called on something that is not" " a constructor"); return false; } ErrorBase* priv = ErrorBase::for_js_typecheck(context, prototype, rec); if (!priv) return false; rec.rval().setInt32(priv->domain()); return true; } // clang-format off const struct JSClassOps ErrorBase::class_ops = { nullptr, // addProperty nullptr, // deleteProperty nullptr, // enumerate nullptr, // newEnumerate nullptr, // resolve nullptr, // mayResolve &ErrorBase::finalize, }; const struct JSClass ErrorBase::klass = { "GLib_Error", JSCLASS_HAS_PRIVATE | JSCLASS_BACKGROUND_FINALIZE, &ErrorBase::class_ops }; /* We need to shadow all fields of GError, to prevent calling the getter from GBoxed (which would trash memory accessing the instance private data) */ JSPropertySpec ErrorBase::proto_properties[] = { JS_PSG("domain", &ErrorBase::get_domain, GJS_MODULE_PROP_FLAGS), JS_PSG("code", &ErrorBase::get_code, GJS_MODULE_PROP_FLAGS), JS_PSG("message", &ErrorBase::get_message, GJS_MODULE_PROP_FLAGS), JS_PS_END }; JSFunctionSpec ErrorBase::static_methods[] = { JS_FN("valueOf", &ErrorBase::value_of, 0, GJS_MODULE_PROP_FLAGS), JS_FS_END }; // clang-format on // Overrides GIWrapperPrototype::get_parent_proto(). bool ErrorPrototype::get_parent_proto(JSContext* cx, JS::MutableHandleObject proto) const { g_irepository_require(nullptr, "GLib", "2.0", GIRepositoryLoadFlags(0), nullptr); GjsAutoStructInfo glib_error_info = g_irepository_find_by_name(nullptr, "GLib", "Error"); proto.set(gjs_lookup_generic_prototype(cx, glib_error_info)); return !!proto; } bool ErrorPrototype::define_class(JSContext* context, JS::HandleObject in_object, GIEnumInfo* info) { JS::RootedObject prototype(context), constructor(context); if (!ErrorPrototype::create_class(context, in_object, info, G_TYPE_ERROR, &constructor, &prototype)) return false; // Define a toString() on the prototype, as it does not exist on the // prototype of GLib.Error; and create_class() will not define it since we // supply a parent in get_parent_proto(). const GjsAtoms& atoms = GjsContextPrivate::atoms(context); return JS_DefineFunctionById(context, prototype, atoms.to_string(), &ErrorBase::to_string, 0, GJS_MODULE_PROP_FLAGS) && gjs_define_enum_values(context, constructor, info); } [[nodiscard]] static GIEnumInfo* find_error_domain_info(GQuark domain) { GIEnumInfo *info; /* first an attempt without loading extra libraries */ info = g_irepository_find_by_error_domain(nullptr, domain); if (info) return info; /* load standard stuff */ g_irepository_require(nullptr, "GLib", "2.0", GIRepositoryLoadFlags(0), nullptr); g_irepository_require(nullptr, "GObject", "2.0", GIRepositoryLoadFlags(0), nullptr); g_irepository_require(nullptr, "Gio", "2.0", GIRepositoryLoadFlags(0), nullptr); info = g_irepository_find_by_error_domain(nullptr, domain); if (info) return info; /* last attempt: load GIRepository (for invoke errors, rarely needed) */ g_irepository_require(nullptr, "GIRepository", "1.0", GIRepositoryLoadFlags(0), nullptr); info = g_irepository_find_by_error_domain(nullptr, domain); return info; } /* define properties that JS Error() expose, such as fileName, lineNumber and stack */ GJS_JSAPI_RETURN_CONVENTION bool gjs_define_error_properties(JSContext* cx, JS::HandleObject obj) { JS::RootedObject frame(cx); JS::RootedString stack(cx); JS::RootedString source(cx); uint32_t line, column; if (!JS::CaptureCurrentStack(cx, &frame) || !JS::BuildStackString(cx, nullptr, frame, &stack)) return false; auto ok = JS::SavedFrameResult::Ok; if (JS::GetSavedFrameSource(cx, nullptr, frame, &source) != ok || JS::GetSavedFrameLine(cx, nullptr, frame, &line) != ok || JS::GetSavedFrameColumn(cx, nullptr, frame, &column) != ok) { gjs_throw(cx, "Error getting saved frame information"); return false; } const GjsAtoms& atoms = GjsContextPrivate::atoms(cx); return JS_DefinePropertyById(cx, obj, atoms.stack(), stack, JSPROP_ENUMERATE) && JS_DefinePropertyById(cx, obj, atoms.file_name(), source, JSPROP_ENUMERATE) && JS_DefinePropertyById(cx, obj, atoms.line_number(), line, JSPROP_ENUMERATE) && JS_DefinePropertyById(cx, obj, atoms.column_number(), column, JSPROP_ENUMERATE); } [[nodiscard]] static JSProtoKey proto_key_from_error_enum(int val) { switch (val) { case GJS_JS_ERROR_EVAL_ERROR: return JSProto_EvalError; case GJS_JS_ERROR_INTERNAL_ERROR: return JSProto_InternalError; case GJS_JS_ERROR_RANGE_ERROR: return JSProto_RangeError; case GJS_JS_ERROR_REFERENCE_ERROR: return JSProto_ReferenceError; case GJS_JS_ERROR_SYNTAX_ERROR: return JSProto_SyntaxError; case GJS_JS_ERROR_TYPE_ERROR: return JSProto_TypeError; case GJS_JS_ERROR_URI_ERROR: return JSProto_URIError; case GJS_JS_ERROR_ERROR: default: return JSProto_Error; } } GJS_JSAPI_RETURN_CONVENTION static JSObject * gjs_error_from_js_gerror(JSContext *cx, GError *gerror) { JS::RootedValueArray<1> error_args(cx); if (!gjs_string_from_utf8(cx, gerror->message, error_args[0])) return nullptr; JSProtoKey error_kind = proto_key_from_error_enum(gerror->code); JS::RootedObject error_constructor(cx); if (!JS_GetClassObject(cx, error_kind, &error_constructor)) return nullptr; return JS_New(cx, error_constructor, error_args); } JSObject* ErrorInstance::object_for_c_ptr(JSContext* context, GError* gerror) { GIEnumInfo *info; if (!gerror) return nullptr; if (gerror->domain == GJS_JS_ERROR) return gjs_error_from_js_gerror(context, gerror); info = find_error_domain_info(gerror->domain); if (!info) { /* We don't have error domain metadata */ /* Marshal the error as a plain GError */ GIBaseInfo *glib_boxed; JSObject *retval; glib_boxed = g_irepository_find_by_name(nullptr, "GLib", "Error"); retval = BoxedInstance::new_for_c_struct(context, glib_boxed, gerror); g_base_info_unref(glib_boxed); return retval; } gjs_debug_marshal(GJS_DEBUG_GBOXED, "Wrapping struct %s with JSObject", g_base_info_get_name((GIBaseInfo *)info)); JS::RootedObject obj(context, gjs_new_object_with_generic_prototype(context, info)); if (!obj) return nullptr; ErrorInstance* priv = ErrorInstance::new_for_js_object(context, obj); priv->copy_gerror(gerror); return obj; } GError* ErrorBase::to_c_ptr(JSContext* cx, JS::HandleObject obj) { /* If this is a plain GBoxed (i.e. a GError without metadata), delegate marshalling. */ if (BoxedBase::typecheck(cx, obj, nullptr, G_TYPE_ERROR, GjsTypecheckNoThrow())) return BoxedBase::to_c_ptr(cx, obj); return GIWrapperBase::to_c_ptr(cx, obj); } bool ErrorBase::transfer_to_gi_argument(JSContext* cx, JS::HandleObject obj, GIArgument* arg, GIDirection transfer_direction, GITransfer transfer_ownership) { g_assert(transfer_direction != GI_DIRECTION_INOUT && "transfer_to_gi_argument() must choose between in or out"); if (!ErrorBase::typecheck(cx, obj)) { gjs_arg_unset(arg); return false; } gjs_arg_set(arg, ErrorBase::to_c_ptr(cx, obj)); if (!gjs_arg_get(arg)) return false; if ((transfer_direction == GI_DIRECTION_IN && transfer_ownership != GI_TRANSFER_NOTHING) || (transfer_direction == GI_DIRECTION_OUT && transfer_ownership == GI_TRANSFER_EVERYTHING)) { gjs_arg_set(arg, ErrorInstance::copy_ptr(cx, G_TYPE_ERROR, gjs_arg_get(arg))); if (!gjs_arg_get(arg)) return false; } return true; } // Overrides GIWrapperBase::typecheck() bool ErrorBase::typecheck(JSContext* cx, JS::HandleObject obj) { if (BoxedBase::typecheck(cx, obj, nullptr, G_TYPE_ERROR, GjsTypecheckNoThrow())) return true; return GIWrapperBase::typecheck(cx, obj, nullptr, G_TYPE_ERROR); } bool ErrorBase::typecheck(JSContext* cx, JS::HandleObject obj, GjsTypecheckNoThrow no_throw) { if (BoxedBase::typecheck(cx, obj, nullptr, G_TYPE_ERROR, no_throw)) return true; return GIWrapperBase::typecheck(cx, obj, nullptr, G_TYPE_ERROR, no_throw); } GError * gjs_gerror_make_from_error(JSContext *cx, JS::HandleObject obj) { if (ErrorBase::typecheck(cx, obj, GjsTypecheckNoThrow())) { /* This is already a GError, just copy it */ GError* inner = ErrorBase::to_c_ptr(cx, obj); if (!inner) return nullptr; return g_error_copy(inner); } /* Try to make something useful from the error name and message (in case this is a JS error) */ const GjsAtoms& atoms = GjsContextPrivate::atoms(cx); JS::RootedValue v_name(cx); if (!JS_GetPropertyById(cx, obj, atoms.name(), &v_name)) return nullptr; JS::UniqueChars name = gjs_string_to_utf8(cx, v_name); if (!name) return nullptr; JS::RootedValue v_message(cx); if (!JS_GetPropertyById(cx, obj, atoms.message(), &v_message)) return nullptr; JS::UniqueChars message = gjs_string_to_utf8(cx, v_message); if (!message) return nullptr; GjsAutoTypeClass klass(GJS_TYPE_JS_ERROR); const GEnumValue *value = g_enum_get_value_by_name(klass, name.get()); int code; if (value) code = value->value; else code = GJS_JS_ERROR_ERROR; return g_error_new_literal(GJS_JS_ERROR, code, message.get()); } /* * gjs_throw_gerror: * * Converts a GError into a JavaScript exception, and frees the GError. * Differently from gjs_throw(), it will overwrite an existing exception, as it * is used to report errors from C functions. * * Returns: false, for convenience in returning from the calling function. */ bool gjs_throw_gerror(JSContext* cx, GError* error) { // return false even if the GError is null, as presumably something failed // in the calling code, and the caller expects to throw. g_return_val_if_fail(error, false); JS::RootedObject err_obj(cx, ErrorInstance::object_for_c_ptr(cx, error)); if (!err_obj || !gjs_define_error_properties(cx, err_obj)) return false; g_error_free(error); JS::RootedValue err(cx, JS::ObjectValue(*err_obj)); JS_SetPendingException(cx, err); return false; } cjs-5.2.0/gi/gjs_gi_probes.d0000644000175000017500000000021514144444702016015 0ustar jpeisachjpeisachprovider gjs { probe object__wrapper__new(void*, void*, char *, char *); probe object__wrapper__finalize(void*, void*, char *, char *); }; cjs-5.2.0/gi/object.h0000644000175000017500000004546714144444702014475 0ustar jpeisachjpeisach/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* * Copyright (c) 2008 litl, LLC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #ifndef GI_OBJECT_H_ #define GI_OBJECT_H_ #include #include // for size_t #include #include #include #include #include #include #include // for GCHashMap #include #include #include #include #include // for JSID_IS_ATOM, JSID_TO_ATOM #include // for HashGeneric, HashNumber #include // for DefaultHasher #include // for MOZ_LIKELY #include "gi/wrapperutils.h" #include "cjs/jsapi-util-root.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" #include "util/log.h" class GjsAtoms; class JSTracer; namespace JS { class CallArgs; } namespace js { class SystemAllocPolicy; } class ObjectInstance; class ObjectPrototype; class GjsListLink { private: ObjectInstance* m_prev; ObjectInstance* m_next; public: [[nodiscard]] ObjectInstance* prev() const { return m_prev; } [[nodiscard]] ObjectInstance* next() const { return m_next; } void prepend(ObjectInstance* this_instance, ObjectInstance* head); void unlink(void); [[nodiscard]] size_t size() const; }; struct AutoGValueVector : public std::vector { ~AutoGValueVector() { for (GValue value : *this) g_value_unset(&value); } }; /* * ObjectBase: * * Specialization of GIWrapperBase for GObject instances. See the documentation * in wrapperutils.h. * * It's important that ObjectBase and ObjectInstance not grow in size without a * very good reason. There can be tens, maybe hundreds of thousands of these * objects alive in a typical gnome-shell run, so even 8 more bytes will add up. * It's less critical that ObjectPrototype stay small, since only one of these * is allocated per GType. */ class ObjectBase : public GIWrapperBase { friend class GIWrapperBase; protected: explicit ObjectBase(ObjectPrototype* proto = nullptr) : GIWrapperBase(proto) {} public: using SignalMatchFunc = guint(gpointer, GSignalMatchType, guint, GQuark, GClosure*, gpointer, gpointer); static const GjsDebugTopic debug_topic = GJS_DEBUG_GOBJECT; static constexpr const char* debug_tag = "GObject"; static const struct JSClassOps class_ops; static const struct JSClass klass; static JSFunctionSpec proto_methods[]; static JSPropertySpec proto_properties[]; static GObject* to_c_ptr(JSContext* cx, JS::HandleObject obj) = delete; GJS_JSAPI_RETURN_CONVENTION static bool to_c_ptr(JSContext* cx, JS::HandleObject obj, GObject** ptr); GJS_JSAPI_RETURN_CONVENTION static bool transfer_to_gi_argument(JSContext* cx, JS::HandleObject obj, GIArgument* arg, GIDirection transfer_direction, GITransfer transfer_ownership, GType expected_gtype, GIBaseInfo* expected_info = nullptr); private: // This is used in debug methods only. [[nodiscard]] const void* jsobj_addr() const; /* Helper methods */ protected: void debug_lifecycle(const char* message) const { GIWrapperBase::debug_lifecycle(jsobj_addr(), message); } [[nodiscard]] bool id_is_never_lazy(jsid name, const GjsAtoms& atoms); [[nodiscard]] bool is_custom_js_class(); public: void type_query_dynamic_safe(GTypeQuery* query); GJS_JSAPI_RETURN_CONVENTION static bool typecheck(JSContext* cx, JS::HandleObject obj, GIObjectInfo* expected_info, GType expected_gtype); [[nodiscard]] static bool typecheck(JSContext* cx, JS::HandleObject obj, GIObjectInfo* expected_info, GType expected_gtype, GjsTypecheckNoThrow no_throw) { return GIWrapperBase::typecheck(cx, obj, expected_info, expected_gtype, no_throw); } /* JSClass operations */ static bool add_property(JSContext* cx, JS::HandleObject obj, JS::HandleId id, JS::HandleValue value); /* JS property getters/setters */ public: GJS_JSAPI_RETURN_CONVENTION static bool prop_getter(JSContext* cx, unsigned argc, JS::Value* vp); GJS_JSAPI_RETURN_CONVENTION static bool field_getter(JSContext* cx, unsigned argc, JS::Value* vp); GJS_JSAPI_RETURN_CONVENTION static bool prop_setter(JSContext* cx, unsigned argc, JS::Value* vp); GJS_JSAPI_RETURN_CONVENTION static bool field_setter(JSContext* cx, unsigned argc, JS::Value* vp); /* JS methods */ GJS_JSAPI_RETURN_CONVENTION static bool connect(JSContext* cx, unsigned argc, JS::Value* vp); GJS_JSAPI_RETURN_CONVENTION static bool connect_after(JSContext* cx, unsigned argc, JS::Value* vp); GJS_JSAPI_RETURN_CONVENTION static bool emit(JSContext* cx, unsigned argc, JS::Value* vp); GJS_JSAPI_RETURN_CONVENTION static bool signal_find(JSContext* cx, unsigned argc, JS::Value* vp); template GJS_JSAPI_RETURN_CONVENTION static bool signals_action(JSContext* cx, unsigned argc, JS::Value* vp); GJS_JSAPI_RETURN_CONVENTION static bool to_string(JSContext* cx, unsigned argc, JS::Value* vp); [[nodiscard]] const char* to_string_kind() const; GJS_JSAPI_RETURN_CONVENTION static bool init_gobject(JSContext* cx, unsigned argc, JS::Value* vp); GJS_JSAPI_RETURN_CONVENTION static bool hook_up_vfunc(JSContext* cx, unsigned argc, JS::Value* vp); /* Quarks */ [[nodiscard]] static GQuark custom_type_quark(); [[nodiscard]] static GQuark custom_property_quark(); }; // See https://bugzilla.mozilla.org/show_bug.cgi?id=1614220 struct IdHasher { typedef jsid Lookup; static mozilla::HashNumber hash(jsid id) { if (MOZ_LIKELY(JSID_IS_ATOM(id))) return js::DefaultHasher::hash(JSID_TO_ATOM(id)); if (JSID_IS_SYMBOL(id)) return js::DefaultHasher::hash(JSID_TO_SYMBOL(id)); return mozilla::HashGeneric(JSID_BITS(id)); } static bool match(jsid id1, jsid id2) { return id1 == id2; } }; class ObjectPrototype : public GIWrapperPrototype { friend class GIWrapperPrototype; friend class GIWrapperBase; using PropertyCache = JS::GCHashMap, GjsAutoParam, js::DefaultHasher, js::SystemAllocPolicy>; using FieldCache = JS::GCHashMap, GjsAutoInfo, js::DefaultHasher, js::SystemAllocPolicy>; using NegativeLookupCache = JS::GCHashSet, IdHasher, js::SystemAllocPolicy>; PropertyCache m_property_cache; FieldCache m_field_cache; NegativeLookupCache m_unresolvable_cache; // a list of vfunc GClosures installed on this prototype, used when tracing std::forward_list m_vfuncs; ObjectPrototype(GIObjectInfo* info, GType gtype); ~ObjectPrototype(); static constexpr InfoType::Tag info_type_tag = InfoType::Object; public: [[nodiscard]] static ObjectPrototype* for_gtype(GType gtype); /* Helper methods */ private: GJS_JSAPI_RETURN_CONVENTION bool get_parent_proto(JSContext* cx, JS::MutableHandleObject proto) const; [[nodiscard]] bool is_vfunc_unchanged(GIVFuncInfo* info); static void vfunc_invalidated_notify(void* data, GClosure* closure); GJS_JSAPI_RETURN_CONVENTION bool lazy_define_gobject_property(JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool* resolved, const char* name); enum ResolveWhat { ConsiderOnlyMethods, ConsiderMethodsAndProperties }; GJS_JSAPI_RETURN_CONVENTION bool resolve_no_info(JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool* resolved, const char* name, ResolveWhat resolve_props); GJS_JSAPI_RETURN_CONVENTION bool uncached_resolve(JSContext* cx, JS::HandleObject obj, JS::HandleId id, const char* name, bool* resolved); public: void set_type_qdata(void); GJS_JSAPI_RETURN_CONVENTION GParamSpec* find_param_spec_from_id(JSContext* cx, JS::HandleString key); GJS_JSAPI_RETURN_CONVENTION GIFieldInfo* lookup_cached_field_info(JSContext* cx, JS::HandleString key); GJS_JSAPI_RETURN_CONVENTION bool props_to_g_parameters(JSContext* cx, JS::HandleObject props, std::vector* names, AutoGValueVector* values); GJS_JSAPI_RETURN_CONVENTION static bool define_class(JSContext* cx, JS::HandleObject in_object, GIObjectInfo* info, GType gtype, JS::MutableHandleObject constructor, JS::MutableHandleObject prototype); void ref_vfuncs(void) { for (GClosure* closure : m_vfuncs) g_closure_ref(closure); } void unref_vfuncs(void) { for (GClosure* closure : m_vfuncs) g_closure_unref(closure); } /* JSClass operations */ private: GJS_JSAPI_RETURN_CONVENTION bool resolve_impl(JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool* resolved); GJS_JSAPI_RETURN_CONVENTION bool new_enumerate_impl(JSContext* cx, JS::HandleObject obj, JS::MutableHandleIdVector properties, bool only_enumerable); void trace_impl(JSTracer* tracer); /* JS methods */ public: GJS_JSAPI_RETURN_CONVENTION bool hook_up_vfunc_impl(JSContext* cx, const JS::CallArgs& args); }; class ObjectInstance : public GIWrapperInstance { friend class GIWrapperInstance; friend class GIWrapperBase; friend class ObjectBase; // for add_property, prop_getter, etc. // GIWrapperInstance::m_ptr may be null in ObjectInstance. GjsMaybeOwned m_wrapper; // a list of all GClosures installed on this object (from signal connections // and scope-notify callbacks passed to methods), used when tracing std::forward_list m_closures; GjsListLink m_instance_link; bool m_wrapper_finalized : 1; bool m_gobj_disposed : 1; /* True if this object has visible JS state, and thus its lifecycle is * managed using toggle references. False if this object just keeps a * hard ref on the underlying GObject, and may be finalized at will. */ bool m_uses_toggle_ref : 1; static bool s_weak_pointer_callback; /* Constructors */ private: ObjectInstance(JSContext* cx, JS::HandleObject obj); ~ObjectInstance(); GJS_JSAPI_RETURN_CONVENTION static ObjectInstance* new_for_gobject(JSContext* cx, GObject* gobj); // Extra method to get an existing ObjectInstance from qdata public: [[nodiscard]] static ObjectInstance* for_gobject(GObject* gobj); /* Accessors */ private: [[nodiscard]] bool has_wrapper() const { return !!m_wrapper; } public: [[nodiscard]] JSObject* wrapper() const { return m_wrapper; } /* Methods to manipulate the JS object wrapper */ private: void discard_wrapper(void) { m_wrapper.reset(); } void switch_to_rooted(JSContext* cx) { m_wrapper.switch_to_rooted(cx); } void switch_to_unrooted(JSContext* cx) { m_wrapper.switch_to_unrooted(cx); } [[nodiscard]] bool update_after_gc() { return m_wrapper.update_after_gc(); } [[nodiscard]] bool wrapper_is_rooted() const { return m_wrapper.rooted(); } void release_native_object(void); void associate_js_gobject(JSContext* cx, JS::HandleObject obj, GObject* gobj); void disassociate_js_gobject(void); void handle_context_dispose(void); [[nodiscard]] bool weak_pointer_was_finalized(); static void ensure_weak_pointer_callback(JSContext* cx); static void update_heap_wrapper_weak_pointers(JSContext* cx, JS::Compartment* compartment, void* data); public: void toggle_down(void); void toggle_up(void); GJS_JSAPI_RETURN_CONVENTION static JSObject* wrapper_from_gobject(JSContext* cx, GObject* ptr); /* Methods to manipulate the list of closures */ private: static void closure_invalidated_notify(void* data, GClosure* closure); public: void associate_closure(JSContext* cx, GClosure* closure); /* Helper methods */ private: void set_object_qdata(void); void unset_object_qdata(void); void check_js_object_finalized(void); void ensure_uses_toggle_ref(JSContext* cx); [[nodiscard]] bool check_gobject_disposed(const char* for_what) const; GJS_JSAPI_RETURN_CONVENTION bool signal_match_arguments_from_object(JSContext* cx, JS::HandleObject props_obj, GSignalMatchType* mask_out, unsigned* signal_id_out, GQuark* detail_out, JS::MutableHandleFunction func_out); public: static GObject* copy_ptr(JSContext*, GType, void* ptr) { return G_OBJECT(g_object_ref(G_OBJECT(ptr))); } GJS_JSAPI_RETURN_CONVENTION bool init_custom_class_from_gobject(JSContext* cx, JS::HandleObject wrapper, GObject* gobj); /* Methods to manipulate the linked list of instances */ private: static ObjectInstance* wrapped_gobject_list; [[nodiscard]] ObjectInstance* next() const { return m_instance_link.next(); } void link(void); void unlink(void); [[nodiscard]] static size_t num_wrapped_gobjects() { return wrapped_gobject_list ? wrapped_gobject_list->m_instance_link.size() : 0; } using Action = std::function; using Predicate = std::function; static void iterate_wrapped_gobjects(const Action& action); static void remove_wrapped_gobjects_if(const Predicate& predicate, const Action& action); public: [[nodiscard]] GjsListLink* get_link() { return &m_instance_link; } static void prepare_shutdown(void); /* JSClass operations */ private: GJS_JSAPI_RETURN_CONVENTION bool add_property_impl(JSContext* cx, JS::HandleObject obj, JS::HandleId id, JS::HandleValue value); void finalize_impl(JSFreeOp* fop, JSObject* obj); void trace_impl(JSTracer* trc); /* JS property getters/setters */ private: GJS_JSAPI_RETURN_CONVENTION bool prop_getter_impl(JSContext* cx, JS::HandleString name, JS::MutableHandleValue rval); GJS_JSAPI_RETURN_CONVENTION bool field_getter_impl(JSContext* cx, JS::HandleString name, JS::MutableHandleValue rval); GJS_JSAPI_RETURN_CONVENTION bool prop_setter_impl(JSContext* cx, JS::HandleString name, JS::HandleValue value); GJS_JSAPI_RETURN_CONVENTION bool field_setter_not_impl(JSContext* cx, JS::HandleString name); // JS constructor GJS_JSAPI_RETURN_CONVENTION bool constructor_impl(JSContext* cx, JS::HandleObject obj, const JS::CallArgs& args); /* JS methods */ private: GJS_JSAPI_RETURN_CONVENTION bool connect_impl(JSContext* cx, const JS::CallArgs& args, bool after); GJS_JSAPI_RETURN_CONVENTION bool emit_impl(JSContext* cx, const JS::CallArgs& args); GJS_JSAPI_RETURN_CONVENTION bool signal_find_impl(JSContext* cx, const JS::CallArgs& args); template GJS_JSAPI_RETURN_CONVENTION bool signals_action_impl( JSContext* cx, const JS::CallArgs& args); GJS_JSAPI_RETURN_CONVENTION bool init_impl(JSContext* cx, const JS::CallArgs& args, JS::MutableHandleObject obj); [[nodiscard]] const char* to_string_kind() const; GJS_JSAPI_RETURN_CONVENTION bool typecheck_impl(JSContext* cx, GIBaseInfo* expected_info, GType expected_type) const; /* Notification callbacks */ public: void gobj_dispose_notify(void); static void context_dispose_notify(void* data, GObject* where_the_object_was); }; GJS_JSAPI_RETURN_CONVENTION bool gjs_lookup_object_constructor(JSContext *context, GType gtype, JS::MutableHandleValue value_p); GJS_JSAPI_RETURN_CONVENTION JSObject* gjs_lookup_object_constructor_from_info(JSContext* cx, GIObjectInfo* info, GType gtype); void gjs_object_clear_toggles(void); void gjs_object_shutdown_toggle_queue(void); #endif // GI_OBJECT_H_ cjs-5.2.0/gi/repo.cpp0000644000175000017500000006256714144444702014527 0ustar jpeisachjpeisach/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* * Copyright (c) 2008 litl, LLC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #include #include #include // for strlen #include #include #include #include #include // for JSID_IS_STRING, JSID_VOID #include // for JSPROP_PERMANENT, JSPROP_RESOLVING #include #include #include #include // for UniqueChars #include #include #include // for JS_DefinePropertyById, JS_GetProp... #include "gi/arg.h" #include "gi/boxed.h" #include "gi/enumeration.h" #include "gi/function.h" #include "gi/fundamental.h" #include "gi/gerror.h" #include "gi/interface.h" #include "gi/ns.h" #include "gi/object.h" #include "gi/param.h" #include "gi/repo.h" #include "gi/union.h" #include "cjs/atoms.h" #include "cjs/context-private.h" #include "cjs/global.h" #include "cjs/jsapi-class.h" #include "cjs/jsapi-util.h" #include "cjs/mem-private.h" #include "util/log.h" typedef struct { void *dummy; } Repo; extern struct JSClass gjs_repo_class; GJS_DEFINE_PRIV_FROM_JS(Repo, gjs_repo_class) GJS_JSAPI_RETURN_CONVENTION static bool lookup_override_function(JSContext *, JS::HandleId, JS::MutableHandleValue); GJS_JSAPI_RETURN_CONVENTION static bool get_version_for_ns(JSContext* context, JS::HandleObject repo_obj, JS::HandleId ns_id, JS::UniqueChars* version) { JS::RootedObject versions(context); bool found; const GjsAtoms& atoms = GjsContextPrivate::atoms(context); if (!gjs_object_require_property(context, repo_obj, "GI repository object", atoms.versions(), &versions)) return false; if (!JS_AlreadyHasOwnPropertyById(context, versions, ns_id, &found)) return false; if (!found) return true; return gjs_object_require_property(context, versions, NULL, ns_id, version); } GJS_JSAPI_RETURN_CONVENTION static bool resolve_namespace_object(JSContext* context, JS::HandleObject repo_obj, JS::HandleId ns_id) { GError *error; JS::UniqueChars version; if (!get_version_for_ns(context, repo_obj, ns_id, &version)) return false; JS::UniqueChars ns_name; if (!gjs_get_string_id(context, ns_id, &ns_name)) return false; if (!ns_name) { gjs_throw(context, "Requiring invalid namespace on imports.gi"); return false; } GList* versions = g_irepository_enumerate_versions(nullptr, ns_name.get()); unsigned nversions = g_list_length(versions); if (nversions > 1 && !version && !g_irepository_is_registered(nullptr, ns_name.get(), nullptr) && !JS::WarnUTF8(context, "Requiring %s but it has %u versions available; use " "imports.gi.versions to pick one", ns_name.get(), nversions)) return false; g_list_free_full(versions, g_free); error = NULL; g_irepository_require(nullptr, ns_name.get(), version.get(), GIRepositoryLoadFlags(0), &error); if (error != NULL) { gjs_throw(context, "Requiring %s, version %s: %s", ns_name.get(), version ? version.get() : "none", error->message); g_error_free(error); return false; } /* Defines a property on "obj" (the javascript repo object) * with the given namespace name, pointing to that namespace * in the repo. */ JS::RootedObject gi_namespace(context, gjs_create_ns(context, ns_name.get())); /* 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 */ override, /* callee */ JS::HandleValueArray::empty(), &result)) return false; gjs_debug(GJS_DEBUG_GNAMESPACE, "Defined namespace '%s' %p in GIRepository %p", ns_name.get(), gi_namespace.get(), repo_obj.get()); GjsContextPrivate* gjs = GjsContextPrivate::from_cx(context); gjs->schedule_gc_if_needed(); return true; } /* * The *resolved out parameter, on success, should be false to indicate that id * was not resolved; and true if id was resolved. */ GJS_JSAPI_RETURN_CONVENTION static bool repo_resolve(JSContext *context, JS::HandleObject obj, JS::HandleId id, bool *resolved) { Repo *priv; if (!JSID_IS_STRING(id)) { *resolved = false; return true; /* not resolved, but no error */ } /* let Object.prototype resolve these */ const GjsAtoms& atoms = GjsContextPrivate::atoms(context); if (id == atoms.to_string() || id == atoms.value_of()) { *resolved = false; return true; } priv = priv_from_js(context, obj); gjs_debug_jsprop(GJS_DEBUG_GREPO, "Resolve prop '%s' hook, obj %s, priv %p", gjs_debug_id(id).c_str(), gjs_debug_object(obj).c_str(), priv); if (priv == NULL) { /* we are the prototype, or have the wrong class */ *resolved = false; return true; } if (!JSID_IS_STRING(id)) { *resolved = false; return true; } if (!resolve_namespace_object(context, obj, id)) return false; *resolved = true; return true; } GJS_NATIVE_CONSTRUCTOR_DEFINE_ABSTRACT(repo) static void repo_finalize(JSFreeOp*, JSObject* obj) { Repo *priv; priv = (Repo*) JS_GetPrivate(obj); gjs_debug_lifecycle(GJS_DEBUG_GREPO, "finalize, obj %p priv %p", obj, priv); if (priv == NULL) return; /* we are the prototype, not a real instance */ GJS_DEC_COUNTER(repo); g_slice_free(Repo, priv); } /* The bizarre thing about this vtable is that it applies to both * instances of the object, and to the prototype that instances of the * class have. */ static const struct JSClassOps gjs_repo_class_ops = { nullptr, // addProperty nullptr, // deleteProperty nullptr, // enumerate nullptr, // newEnumerate repo_resolve, nullptr, // mayResolve repo_finalize}; struct JSClass gjs_repo_class = { "GIRepository", /* means "new GIRepository()" works */ JSCLASS_HAS_PRIVATE | JSCLASS_FOREGROUND_FINALIZE, &gjs_repo_class_ops, }; // clang-format off static const JSPropertySpec gjs_repo_proto_props[] = { JS_STRING_SYM_PS(toStringTag, "GIRepository", JSPROP_READONLY), JS_PS_END}; // clang-format on static JSFunctionSpec *gjs_repo_proto_funcs = nullptr; static JSFunctionSpec *gjs_repo_static_funcs = nullptr; GJS_DEFINE_PROTO_FUNCS(repo) GJS_JSAPI_RETURN_CONVENTION static JSObject* repo_new(JSContext *context) { Repo *priv; JS::RootedObject proto(context); if (!gjs_repo_define_proto(context, nullptr, &proto)) return nullptr; JS::RootedObject repo(context, JS_NewObjectWithGivenProto(context, &gjs_repo_class, proto)); if (repo == nullptr) return nullptr; priv = g_slice_new0(Repo); GJS_INC_COUNTER(repo); g_assert(priv_from_js(context, repo) == NULL); JS_SetPrivate(repo, priv); gjs_debug_lifecycle(GJS_DEBUG_GREPO, "repo constructor, obj %p priv %p", repo.get(), priv); const GjsAtoms& atoms = GjsContextPrivate::atoms(context); JS::RootedObject versions(context, JS_NewPlainObject(context)); if (!JS_DefinePropertyById(context, repo, atoms.versions(), versions, JSPROP_PERMANENT | JSPROP_RESOLVING)) return nullptr; /* GLib/GObject/Gio are fixed at 2.0, since we depend on them * internally. */ JS::RootedString two_point_oh(context, JS_NewStringCopyZ(context, "2.0")); if (!JS_DefinePropertyById(context, versions, atoms.glib(), two_point_oh, JSPROP_PERMANENT)) return nullptr; if (!JS_DefinePropertyById(context, versions, atoms.gobject(), two_point_oh, JSPROP_PERMANENT)) return nullptr; if (!JS_DefinePropertyById(context, versions, atoms.gio(), two_point_oh, JSPROP_PERMANENT)) return nullptr; JS::RootedObject private_ns(context, JS_NewPlainObject(context)); if (!JS_DefinePropertyById(context, repo, atoms.private_ns_marker(), private_ns, JSPROP_PERMANENT | JSPROP_RESOLVING)) return nullptr; return repo; } bool gjs_define_repo(JSContext *cx, JS::MutableHandleObject repo) { repo.set(repo_new(cx)); return true; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_value_from_constant_info(JSContext* cx, GIConstantInfo* info, JS::MutableHandleValue value) { GIArgument garg; g_constant_info_get_value(info, &garg); GjsAutoTypeInfo type_info = g_constant_info_get_type(info); bool ok = gjs_value_from_g_argument(cx, value, type_info, &garg, true); g_constant_info_free_value(info, &garg); return ok; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_define_constant(JSContext *context, JS::HandleObject in_object, GIConstantInfo *info) { JS::RootedValue value(context); const char *name; if (!gjs_value_from_constant_info(context, info, &value)) return false; name = g_base_info_get_name((GIBaseInfo*) info); return JS_DefineProperty(context, in_object, name, value, GJS_MODULE_PROP_FLAGS); } #if GJS_VERBOSE_ENABLE_GI_USAGE void _gjs_log_info_usage(GIBaseInfo *info) { #define DIRECTION_STRING(d) ( ((d) == GI_DIRECTION_IN) ? "IN" : ((d) == GI_DIRECTION_OUT) ? "OUT" : "INOUT" ) #define TRANSFER_STRING(t) ( ((t) == GI_TRANSFER_NOTHING) ? "NOTHING" : ((t) == GI_TRANSFER_CONTAINER) ? "CONTAINER" : "EVERYTHING" ) { char *details; GIInfoType info_type; GIBaseInfo *container; info_type = g_base_info_get_type(info); if (info_type == GI_INFO_TYPE_FUNCTION) { GString *args; int n_args; int i; GITransfer retval_transfer; args = g_string_new("{ "); n_args = g_callable_info_get_n_args((GICallableInfo*) info); for (i = 0; i < n_args; ++i) { GIArgInfo *arg; GIDirection direction; GITransfer transfer; arg = g_callable_info_get_arg((GICallableInfo*)info, i); direction = g_arg_info_get_direction(arg); transfer = g_arg_info_get_ownership_transfer(arg); g_string_append_printf(args, "{ GI_DIRECTION_%s, GI_TRANSFER_%s }, ", DIRECTION_STRING(direction), TRANSFER_STRING(transfer)); g_base_info_unref((GIBaseInfo*) arg); } if (args->len > 2) g_string_truncate(args, args->len - 2); /* chop comma */ g_string_append(args, " }"); retval_transfer = g_callable_info_get_caller_owns((GICallableInfo*) info); details = g_strdup_printf(".details = { .func = { .retval_transfer = GI_TRANSFER_%s, .n_args = %d, .args = %s } }", TRANSFER_STRING(retval_transfer), n_args, args->str); g_string_free(args, true); } else { details = g_strdup_printf(".details = { .nothing = {} }"); } container = g_base_info_get_container(info); gjs_debug_gi_usage("{ GI_INFO_TYPE_%s, \"%s\", \"%s\", \"%s\", %s },", gjs_info_type_name(info_type), g_base_info_get_namespace(info), container ? g_base_info_get_name(container) : "", g_base_info_get_name(info), details); g_free(details); } } #endif /* GJS_VERBOSE_ENABLE_GI_USAGE */ bool gjs_define_info(JSContext *context, JS::HandleObject in_object, GIBaseInfo *info, bool *defined) { #if GJS_VERBOSE_ENABLE_GI_USAGE _gjs_log_info_usage(info); #endif *defined = true; switch (g_base_info_get_type(info)) { case GI_INFO_TYPE_FUNCTION: { JSObject *f; f = gjs_define_function(context, in_object, 0, (GICallableInfo*) info); if (f == NULL) return false; } break; case GI_INFO_TYPE_OBJECT: { GType gtype; gtype = g_registered_type_info_get_g_type((GIRegisteredTypeInfo*)info); if (g_type_is_a (gtype, G_TYPE_PARAM)) { if (!gjs_define_param_class(context, in_object)) return false; } else if (g_type_is_a (gtype, G_TYPE_OBJECT)) { JS::RootedObject ignored1(context), ignored2(context); if (!ObjectPrototype::define_class(context, in_object, info, gtype, &ignored1, &ignored2)) return false; } else if (G_TYPE_IS_INSTANTIATABLE(gtype)) { JS::RootedObject ignored(context); if (!FundamentalPrototype::define_class(context, in_object, info, &ignored)) return false; } else { gjs_throw (context, "Unsupported type %s, deriving from fundamental %s", g_type_name(gtype), g_type_name(g_type_fundamental(gtype))); return false; } } break; case GI_INFO_TYPE_STRUCT: /* We don't want GType structures in the namespace, we expose their fields as vfuncs and their methods as static methods */ if (g_struct_info_is_gtype_struct((GIStructInfo*) info)) { *defined = false; break; } /* Fall through */ case GI_INFO_TYPE_BOXED: if (!BoxedPrototype::define_class(context, in_object, info)) return false; break; case GI_INFO_TYPE_UNION: if (!gjs_define_union_class(context, in_object, (GIUnionInfo*) info)) return false; break; case GI_INFO_TYPE_ENUM: if (g_enum_info_get_error_domain((GIEnumInfo*) info)) { /* define as GError subclass */ if (!ErrorPrototype::define_class(context, in_object, info)) return false; break; } [[fallthrough]]; case GI_INFO_TYPE_FLAGS: if (!gjs_define_enumeration(context, in_object, (GIEnumInfo*) info)) return false; break; case GI_INFO_TYPE_CONSTANT: if (!gjs_define_constant(context, in_object, (GIConstantInfo*) info)) return false; break; case GI_INFO_TYPE_INTERFACE: { JS::RootedObject ignored1(context), ignored2(context); if (!InterfacePrototype::create_class( context, in_object, info, g_registered_type_info_get_g_type(info), &ignored1, &ignored2)) return false; } break; case GI_INFO_TYPE_INVALID: case GI_INFO_TYPE_INVALID_0: case GI_INFO_TYPE_CALLBACK: case GI_INFO_TYPE_VALUE: case GI_INFO_TYPE_SIGNAL: case GI_INFO_TYPE_VFUNC: case GI_INFO_TYPE_PROPERTY: case GI_INFO_TYPE_FIELD: case GI_INFO_TYPE_ARG: case GI_INFO_TYPE_TYPE: case GI_INFO_TYPE_UNRESOLVED: default: gjs_throw(context, "API of type %s not implemented, cannot define %s.%s", gjs_info_type_name(g_base_info_get_type(info)), g_base_info_get_namespace(info), g_base_info_get_name(info)); return false; } return true; } /* Get the "unknown namespace", which should be used for unnamespaced types */ JSObject* gjs_lookup_private_namespace(JSContext *context) { const GjsAtoms& atoms = GjsContextPrivate::atoms(context); return gjs_lookup_namespace_object_by_name(context, atoms.private_ns_marker()); } /* Get the namespace object that the GIBaseInfo should be inside */ JSObject* gjs_lookup_namespace_object(JSContext *context, GIBaseInfo *info) { const char *ns; ns = g_base_info_get_namespace(info); if (ns == NULL) { gjs_throw(context, "%s '%s' does not have a namespace", gjs_info_type_name(g_base_info_get_type(info)), g_base_info_get_name(info)); return NULL; } JS::RootedId ns_name(context, gjs_intern_string_to_id(context, ns)); if (ns_name == JSID_VOID) return nullptr; return gjs_lookup_namespace_object_by_name(context, ns_name); } /* Check if an exception's 'name' property is equal to compare_name. Ignores * all errors that might arise. */ [[nodiscard]] static bool error_has_name(JSContext* cx, JS::HandleValue thrown_value, JSString* compare_name) { if (!thrown_value.isObject()) return false; JS::AutoSaveExceptionState saved_exc(cx); JS::RootedObject exc(cx, &thrown_value.toObject()); JS::RootedValue exc_name(cx); bool retval = false; const GjsAtoms& atoms = GjsContextPrivate::atoms(cx); if (!JS_GetPropertyById(cx, exc, atoms.name(), &exc_name)) goto out; int32_t cmp_result; if (!JS_CompareStrings(cx, exc_name.toString(), compare_name, &cmp_result)) goto out; if (cmp_result == 0) retval = true; out: saved_exc.restore(); return retval; } GJS_JSAPI_RETURN_CONVENTION static bool lookup_override_function(JSContext *cx, JS::HandleId ns_name, JS::MutableHandleValue function) { JS::AutoSaveExceptionState saved_exc(cx); JS::RootedObject global(cx, gjs_get_import_global(cx)); JS::RootedValue importer( cx, gjs_get_global_slot(global, GjsGlobalSlot::IMPORTS)); g_assert(importer.isObject()); JS::RootedObject overridespkg(cx), module(cx); JS::RootedObject importer_obj(cx, &importer.toObject()); const GjsAtoms& atoms = GjsContextPrivate::atoms(cx); if (!gjs_object_require_property(cx, importer_obj, "importer", atoms.overrides(), &overridespkg)) goto fail; if (!gjs_object_require_property(cx, overridespkg, "GI repository object", ns_name, &module)) { JS::RootedValue exc(cx); JS_GetPendingException(cx, &exc); /* If the exception was an ImportError (i.e., module not found) then * we simply didn't have an override, don't throw an exception */ if (error_has_name(cx, exc, JS_AtomizeAndPinString(cx, "ImportError"))) { saved_exc.restore(); return true; } goto fail; } if (!gjs_object_require_property(cx, module, "override module", atoms.init(), function) || !function.isObjectOrNull()) { gjs_throw(cx, "Unexpected value for _init in overrides module"); goto fail; } return true; fail: saved_exc.drop(); return false; } JSObject* gjs_lookup_namespace_object_by_name(JSContext *context, JS::HandleId ns_name) { JS::RootedObject global(context, gjs_get_import_global(context)); JS::RootedValue importer( context, gjs_get_global_slot(global, GjsGlobalSlot::IMPORTS)); g_assert(importer.isObject()); JS::RootedObject repo(context), importer_obj(context, &importer.toObject()); const GjsAtoms& atoms = GjsContextPrivate::atoms(context); if (!gjs_object_require_property(context, importer_obj, "importer", atoms.gi(), &repo)) { gjs_log_exception(context); gjs_throw(context, "No gi property in importer"); return NULL; } JS::RootedObject retval(context); if (!gjs_object_require_property(context, repo, "GI repository object", ns_name, &retval)) return NULL; return retval; } const char* gjs_info_type_name(GIInfoType type) { switch (type) { case GI_INFO_TYPE_INVALID: return "INVALID"; case GI_INFO_TYPE_FUNCTION: return "FUNCTION"; case GI_INFO_TYPE_CALLBACK: return "CALLBACK"; case GI_INFO_TYPE_STRUCT: return "STRUCT"; case GI_INFO_TYPE_BOXED: return "BOXED"; case GI_INFO_TYPE_ENUM: return "ENUM"; case GI_INFO_TYPE_FLAGS: return "FLAGS"; case GI_INFO_TYPE_OBJECT: return "OBJECT"; case GI_INFO_TYPE_INTERFACE: return "INTERFACE"; case GI_INFO_TYPE_CONSTANT: return "CONSTANT"; case GI_INFO_TYPE_UNION: return "UNION"; case GI_INFO_TYPE_VALUE: return "VALUE"; case GI_INFO_TYPE_SIGNAL: return "SIGNAL"; case GI_INFO_TYPE_VFUNC: return "VFUNC"; case GI_INFO_TYPE_PROPERTY: return "PROPERTY"; case GI_INFO_TYPE_FIELD: return "FIELD"; case GI_INFO_TYPE_ARG: return "ARG"; case GI_INFO_TYPE_TYPE: return "TYPE"; case GI_INFO_TYPE_UNRESOLVED: return "UNRESOLVED"; case GI_INFO_TYPE_INVALID_0: g_assert_not_reached(); break; default: return "???"; } } char* gjs_hyphen_from_camel(const char *camel_name) { GString *s; const char *p; /* four hyphens should be reasonable guess */ s = g_string_sized_new(strlen(camel_name) + 4 + 1); for (p = camel_name; *p; ++p) { if (g_ascii_isupper(*p)) { g_string_append_c(s, '-'); g_string_append_c(s, g_ascii_tolower(*p)); } else { g_string_append_c(s, *p); } } return g_string_free(s, false); } JSObject * gjs_lookup_generic_constructor(JSContext *context, GIBaseInfo *info) { const char *constructor_name; JS::RootedObject in_object(context, gjs_lookup_namespace_object(context, (GIBaseInfo*) info)); constructor_name = g_base_info_get_name((GIBaseInfo*) info); if (G_UNLIKELY (!in_object)) return NULL; JS::RootedValue value(context); if (!JS_GetProperty(context, in_object, constructor_name, &value)) return NULL; if (G_UNLIKELY(!value.isObject())) { gjs_throw(context, "Constructor of %s.%s was the wrong type, expected an object", g_base_info_get_namespace(info), constructor_name); return NULL; } return &value.toObject(); } JSObject * gjs_lookup_generic_prototype(JSContext *context, GIBaseInfo *info) { JS::RootedObject constructor(context, gjs_lookup_generic_constructor(context, info)); if (G_UNLIKELY(!constructor)) return NULL; const GjsAtoms& atoms = GjsContextPrivate::atoms(context); JS::RootedValue value(context); if (!JS_GetPropertyById(context, constructor, atoms.prototype(), &value)) return NULL; if (G_UNLIKELY(!value.isObject())) { gjs_throw(context, "Prototype of %s.%s was the wrong type, expected an object", g_base_info_get_namespace(info), g_base_info_get_name(info)); return NULL; } return &value.toObject(); } JSObject* gjs_new_object_with_generic_prototype(JSContext* cx, GIBaseInfo* info) { JS::RootedObject proto(cx, gjs_lookup_generic_prototype(cx, info)); if (!proto) return nullptr; return JS_NewObjectWithGivenProto(cx, JS_GetClass(proto), proto); } cjs-5.2.0/gi/fundamental.cpp0000644000175000017500000003777114144444702016057 0ustar jpeisachjpeisach/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* * Copyright (c) 2013 Intel Corporation * Copyright (c) 2008-2010 litl, LLC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #include #include #include #include // for SystemAllocPolicy #include #include // for WeakCache #include #include #include // for InformalValueTypeName, JS_GetClass #include #include "gi/arg-inl.h" #include "gi/arg.h" #include "gi/function.h" #include "gi/fundamental.h" #include "gi/repo.h" #include "gi/wrapperutils.h" #include "cjs/atoms.h" #include "cjs/context-private.h" #include "cjs/jsapi-util.h" #include "cjs/mem-private.h" #include "util/log.h" namespace JS { class CallArgs; } FundamentalInstance::FundamentalInstance(JSContext* cx, JS::HandleObject obj) : GIWrapperInstance(cx, obj) { GJS_INC_COUNTER(fundamental_instance); } /* * FundamentalInstance::associate_js_instance: * * Associates @gfundamental with @object so that @object can be retrieved in the * future if you have a pointer to @gfundamental. (Assuming @object has not been * garbage collected in the meantime.) */ bool FundamentalInstance::associate_js_instance(JSContext* cx, JSObject* object, void* gfundamental) { m_ptr = gfundamental; GjsContextPrivate* gjs = GjsContextPrivate::from_cx(cx); if (!gjs->fundamental_table().putNew(gfundamental, object)) { JS_ReportOutOfMemory(cx); return false; } debug_lifecycle(object, "associated JSObject with fundamental"); ref(); return true; } /**/ /* Find the first constructor */ [[nodiscard]] static GIFunctionInfo* find_fundamental_constructor( GIObjectInfo* info) { int i, n_methods; n_methods = g_object_info_get_n_methods(info); for (i = 0; i < n_methods; ++i) { GIFunctionInfo *func_info; GIFunctionInfoFlags flags; func_info = g_object_info_get_method(info, i); flags = g_function_info_get_flags(func_info); if ((flags & GI_FUNCTION_IS_CONSTRUCTOR) != 0) return func_info; g_base_info_unref((GIBaseInfo *) func_info); } return nullptr; } /**/ bool FundamentalPrototype::resolve_interface(JSContext* cx, JS::HandleObject obj, bool* resolved, const char* name) { bool ret; GType *interfaces; guint n_interfaces; guint i; ret = true; interfaces = g_type_interfaces(gtype(), &n_interfaces); for (i = 0; i < n_interfaces; i++) { GjsAutoInterfaceInfo iface_info = g_irepository_find_by_gtype(nullptr, interfaces[i]); if (!iface_info) continue; GjsAutoFunctionInfo method_info = g_interface_info_find_method(iface_info, name); if (method_info && g_function_info_get_flags(method_info) & GI_FUNCTION_IS_METHOD) { if (gjs_define_function(cx, obj, gtype(), method_info)) { *resolved = true; } else { ret = false; } } } g_free(interfaces); return ret; } // See GIWrapperBase::resolve(). bool FundamentalPrototype::resolve_impl(JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool* resolved) { JS::UniqueChars prop_name; if (!gjs_get_string_id(cx, id, &prop_name)) return false; if (!prop_name) { *resolved = false; return true; // not resolved, but no error } /* We are the prototype, so look for methods and other class properties */ GjsAutoFunctionInfo method_info = g_object_info_find_method(info(), prop_name.get()); if (method_info) { #if GJS_VERBOSE_ENABLE_GI_USAGE _gjs_log_info_usage(method_info); #endif if (g_function_info_get_flags (method_info) & GI_FUNCTION_IS_METHOD) { /* we do not define deprecated methods in the prototype */ if (g_base_info_is_deprecated(method_info)) { gjs_debug(GJS_DEBUG_GFUNDAMENTAL, "Ignoring definition of deprecated method %s in " "prototype %s.%s", method_info.name(), ns(), name()); *resolved = false; return true; } gjs_debug(GJS_DEBUG_GFUNDAMENTAL, "Defining method %s in prototype for %s.%s", method_info.name(), ns(), name()); if (!gjs_define_function(cx, obj, gtype(), method_info)) return false; *resolved = true; } } else { *resolved = false; } return resolve_interface(cx, obj, resolved, prop_name.get()); } /* * FundamentalInstance::invoke_constructor: * * Finds the type's static constructor method (the static method given by * FundamentalPrototype::constructor_info()) and invokes it with the given * arguments. */ bool FundamentalInstance::invoke_constructor(JSContext* context, JS::HandleObject obj, const JS::CallArgs& args, GIArgument* rvalue) { GIFunctionInfo* constructor_info = get_prototype()->constructor_info(); if (!constructor_info) { gjs_throw(context, "Couldn't find a constructor for type %s.%s", ns(), name()); return false; } return gjs_invoke_constructor_from_c(context, constructor_info, obj, args, rvalue); } // See GIWrapperBase::constructor(). bool FundamentalInstance::constructor_impl(JSContext* cx, JS::HandleObject object, const JS::CallArgs& argv) { GArgument ret_value; GITypeInfo return_info; if (!invoke_constructor(cx, object, argv, &ret_value) || !associate_js_instance(cx, object, gjs_arg_get(&ret_value))) return false; GICallableInfo* constructor_info = get_prototype()->constructor_info(); g_callable_info_load_return_type(constructor_info, &return_info); return gjs_g_argument_release( cx, g_callable_info_get_caller_owns(constructor_info), &return_info, &ret_value); } FundamentalInstance::~FundamentalInstance(void) { if (m_ptr) { unref(); m_ptr = nullptr; } GJS_DEC_COUNTER(fundamental_instance); } FundamentalPrototype::FundamentalPrototype(GIObjectInfo* info, GType gtype) : GIWrapperPrototype(info, gtype), m_ref_function(g_object_info_get_ref_function_pointer(info)), m_unref_function(g_object_info_get_unref_function_pointer(info)), m_get_value_function(g_object_info_get_get_value_function_pointer(info)), m_set_value_function(g_object_info_get_set_value_function_pointer(info)), m_constructor_info(find_fundamental_constructor(info)) { g_assert(m_ref_function); g_assert(m_unref_function); g_assert(m_set_value_function); g_assert(m_get_value_function); GJS_INC_COUNTER(fundamental_prototype); } FundamentalPrototype::~FundamentalPrototype(void) { g_clear_pointer(&m_constructor_info, g_base_info_unref); GJS_DEC_COUNTER(fundamental_prototype); } // clang-format off const struct JSClassOps FundamentalBase::class_ops = { nullptr, // addProperty nullptr, // deleteProperty nullptr, // enumerate nullptr, // newEnumerate &FundamentalBase::resolve, nullptr, // mayResolve &FundamentalBase::finalize, nullptr, // call nullptr, // hasInstance nullptr, // construct &FundamentalBase::trace }; const struct JSClass FundamentalBase::klass = { "GFundamental_Object", JSCLASS_HAS_PRIVATE | JSCLASS_FOREGROUND_FINALIZE, &FundamentalBase::class_ops }; // clang-format on GJS_JSAPI_RETURN_CONVENTION static JSObject * gjs_lookup_fundamental_prototype(JSContext *context, GIObjectInfo *info, GType gtype) { JS::RootedObject in_object(context); const char *constructor_name; if (info) { in_object = gjs_lookup_namespace_object(context, (GIBaseInfo*) info); constructor_name = g_base_info_get_name((GIBaseInfo*) info); } else { in_object = gjs_lookup_private_namespace(context); constructor_name = g_type_name(gtype); } if (G_UNLIKELY (!in_object)) return nullptr; JS::RootedValue value(context); if (!JS_GetProperty(context, in_object, constructor_name, &value)) return nullptr; JS::RootedObject constructor(context); if (value.isUndefined()) { /* In case we're looking for a private type, and we don't find it, we need to define it first. */ if (!FundamentalPrototype::define_class(context, in_object, info, &constructor)) return nullptr; } else { if (G_UNLIKELY(!value.isObject())) { gjs_throw(context, "Fundamental constructor was not an object, it was a %s", JS::InformalValueTypeName(value)); return nullptr; } constructor = &value.toObject(); } g_assert(constructor); const GjsAtoms& atoms = GjsContextPrivate::atoms(context); JS::RootedObject prototype(context); if (!gjs_object_require_property(context, constructor, "constructor object", atoms.prototype(), &prototype)) return nullptr; return prototype; } GJS_JSAPI_RETURN_CONVENTION static JSObject* gjs_lookup_fundamental_prototype_from_gtype(JSContext *context, GType gtype) { GjsAutoObjectInfo info; /* A given gtype might not have any definition in the introspection * data. If that's the case, try to look for a definition of any of the * parent type. */ while (gtype != G_TYPE_INVALID && !(info = g_irepository_find_by_gtype(nullptr, gtype))) gtype = g_type_parent(gtype); return gjs_lookup_fundamental_prototype(context, info, gtype); } // Overrides GIWrapperPrototype::get_parent_proto(). bool FundamentalPrototype::get_parent_proto( JSContext* cx, JS::MutableHandleObject proto) const { GType parent_gtype = g_type_parent(gtype()); if (parent_gtype != G_TYPE_INVALID) { proto.set( gjs_lookup_fundamental_prototype_from_gtype(cx, parent_gtype)); if (!proto) return false; } return true; } // Overrides GIWrapperPrototype::constructor_nargs(). unsigned FundamentalPrototype::constructor_nargs(void) const { if (m_constructor_info) return g_callable_info_get_n_args(m_constructor_info); return 0; } /* * FundamentalPrototype::define_class: * @in_object: Object where the constructor is stored, typically a repo object. * @info: Introspection info for the fundamental class. * @constructor: Return location for the constructor object. * * Define a fundamental class constructor and prototype, including all the * necessary methods and properties. Provides the constructor object as an out * parameter, for convenience elsewhere. */ bool FundamentalPrototype::define_class(JSContext* cx, JS::HandleObject in_object, GIObjectInfo* info, JS::MutableHandleObject constructor) { GType gtype; gtype = g_registered_type_info_get_g_type (info); JS::RootedObject prototype(cx); FundamentalPrototype* priv = FundamentalPrototype::create_class( cx, in_object, info, gtype, constructor, &prototype); if (!priv) return false; if (g_object_info_get_n_fields(info) > 0) { gjs_debug(GJS_DEBUG_GFUNDAMENTAL, "Fundamental type '%s.%s' apparently has accessible fields. " "Gjs has no support for this yet, ignoring these.", priv->ns(), priv->name()); } return true; } /* * FundamentalInstance::object_for_c_ptr: * * Given a pointer to a C fundamental object, returns a JS object. This JS * object may have been cached, or it may be newly created. */ JSObject* FundamentalInstance::object_for_c_ptr(JSContext* context, void* gfundamental) { if (!gfundamental) { gjs_throw(context, "Cannot get JSObject for null fundamental pointer"); return nullptr; } GjsContextPrivate* gjs = GjsContextPrivate::from_cx(context); auto p = gjs->fundamental_table().lookup(gfundamental); if (p) return p->value(); gjs_debug_marshal(GJS_DEBUG_GFUNDAMENTAL, "Wrapping fundamental %p with JSObject", gfundamental); JS::RootedObject proto(context, gjs_lookup_fundamental_prototype_from_gtype(context, G_TYPE_FROM_INSTANCE(gfundamental))); if (!proto) return nullptr; JS::RootedObject object(context, JS_NewObjectWithGivenProto( context, JS_GetClass(proto), proto)); if (!object) return nullptr; auto* priv = FundamentalInstance::new_for_js_object(context, object); if (!priv->associate_js_instance(context, object, gfundamental)) return nullptr; return object; } /* * FundamentalPrototype::for_gtype: * * Returns the FundamentalPrototype instance associated with the given GType. * Use this if you don't have the prototype object. */ FundamentalPrototype* FundamentalPrototype::for_gtype(JSContext* cx, GType gtype) { JS::RootedObject proto( cx, gjs_lookup_fundamental_prototype_from_gtype(cx, gtype)); if (!proto) return nullptr; return FundamentalPrototype::for_js(cx, proto); } JSObject* FundamentalInstance::object_for_gvalue(JSContext* cx, const GValue* value, GType gtype) { auto* proto_priv = FundamentalPrototype::for_gtype(cx, gtype); void* fobj = proto_priv->call_get_value_function(value); if (!fobj) { gjs_throw(cx, "Failed to convert GValue to a fundamental instance"); return nullptr; } return FundamentalInstance::object_for_c_ptr(cx, fobj); } bool FundamentalBase::to_gvalue(JSContext* cx, JS::HandleObject obj, GValue* gvalue) { auto* priv = FundamentalBase::for_js_typecheck(cx, obj); if (!priv || !priv->check_is_instance(cx, "convert to GValue")) return false; priv->to_instance()->set_value(gvalue); return true; } void* FundamentalInstance::copy_ptr(JSContext* cx, GType gtype, void* gfundamental) { auto* priv = FundamentalPrototype::for_gtype(cx, gtype); return priv->call_ref_function(gfundamental); } cjs-5.2.0/gi/boxed.cpp0000644000175000017500000011361414144444702014651 0ustar jpeisachjpeisach/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* * Copyright (c) 2008 litl, LLC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #include #include #include // for memcpy, size_t, strcmp #include // for string #include // for remove_reference #include // for move, forward #include #include #include #include #include // for GCHashMap #include // for MutableWrappedPtrOperations #include #include #include #include #include // for IdVector, JS_AtomizeAndPinJSString #include #include "gi/arg-inl.h" #include "gi/arg.h" #include "gi/boxed.h" #include "gi/function.h" #include "gi/gerror.h" #include "gi/repo.h" #include "gi/wrapperutils.h" #include "cjs/atoms.h" #include "cjs/context-private.h" #include "cjs/jsapi-class.h" #include "cjs/mem-private.h" #include "util/log.h" BoxedInstance::BoxedInstance(JSContext* cx, JS::HandleObject obj) : GIWrapperInstance(cx, obj), m_allocated_directly(false), m_owning_ptr(false) { m_ptr = nullptr; GJS_INC_COUNTER(boxed_instance); } [[nodiscard]] static bool struct_is_simple(GIStructInfo* info); // See GIWrapperBase::resolve(). bool BoxedPrototype::resolve_impl(JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool* resolved) { JS::UniqueChars prop_name; if (!gjs_get_string_id(cx, id, &prop_name)) return false; if (!prop_name) { *resolved = false; return true; // not resolved, but no error } // Look for methods and other class properties GjsAutoFunctionInfo method_info = g_struct_info_find_method(info(), prop_name.get()); if (!method_info) { *resolved = false; return true; } #if GJS_VERBOSE_ENABLE_GI_USAGE _gjs_log_info_usage(method_info); #endif if (g_function_info_get_flags(method_info) & GI_FUNCTION_IS_METHOD) { gjs_debug(GJS_DEBUG_GBOXED, "Defining method %s in prototype for %s.%s", method_info.name(), ns(), name()); /* obj is the Boxed prototype */ if (!gjs_define_function(cx, obj, gtype(), method_info)) return false; *resolved = true; } else { *resolved = false; } return true; } // See GIWrapperBase::new_enumerate(). bool BoxedPrototype::new_enumerate_impl(JSContext* cx, JS::HandleObject, JS::MutableHandleIdVector properties, bool only_enumerable [[maybe_unused]]) { int n_methods = g_struct_info_get_n_methods(info()); for (int i = 0; i < n_methods; i++) { GjsAutoFunctionInfo meth_info = g_struct_info_get_method(info(), i); GIFunctionInfoFlags flags = g_function_info_get_flags(meth_info); if (flags & GI_FUNCTION_IS_METHOD) { const char* name = meth_info.name(); jsid id = gjs_intern_string_to_id(cx, name); if (id == JSID_VOID) return false; if (!properties.append(id)) { JS_ReportOutOfMemory(cx); return false; } } } return true; } /* * BoxedBase::get_copy_source(): * * Check to see if JS::Value passed in is another Boxed instance object of the * same type, and if so, retrieve the BoxedInstance private structure for it. * This function does not throw any JS exceptions. */ BoxedBase* BoxedBase::get_copy_source(JSContext* context, JS::Value value) const { if (!value.isObject()) return nullptr; JS::RootedObject object(context, &value.toObject()); BoxedBase* source_priv = BoxedBase::for_js(context, object); if (!source_priv || !g_base_info_equal(info(), source_priv->info())) return nullptr; return source_priv; } /* * BoxedInstance::allocate_directly: * * Allocate a boxed object of the correct size, set all the bytes to 0, and set * m_ptr to point to it. This is used when constructing a boxed object that can * be allocated directly (i.e., does not need to be created by a constructor * function.) */ void BoxedInstance::allocate_directly(void) { g_assert(get_prototype()->can_allocate_directly()); own_ptr(g_slice_alloc0(g_struct_info_get_size(info()))); m_allocated_directly = true; debug_lifecycle("Boxed pointer directly allocated"); } /* When initializing a boxed object from a hash of properties, we don't want * to do n O(n) lookups, so put put the fields into a hash table and store it on proto->priv * for fast lookup. */ BoxedPrototype::FieldMap* BoxedPrototype::create_field_map( JSContext* cx, GIStructInfo* struct_info) { int n_fields; int i; auto* result = new BoxedPrototype::FieldMap(); n_fields = g_struct_info_get_n_fields(struct_info); if (!result->reserve(n_fields)) { JS_ReportOutOfMemory(cx); return nullptr; } for (i = 0; i < n_fields; i++) { GjsAutoFieldInfo field_info = g_struct_info_get_field(struct_info, i); // We get the string as a jsid later, which is interned. We intern the // string here as well, so it will be the same string pointer JS::RootedString name(cx, JS_NewStringCopyZ(cx, field_info.name())); JSString* atom = JS_AtomizeAndPinJSString(cx, name); result->putNewInfallible(atom, std::move(field_info)); } return result; } /* * BoxedPrototype::ensure_field_map: * * BoxedPrototype keeps a cache of field names to introspection info. * We only create the field cache the first time it is needed. An alternative * would be to create it when the prototype is created, in BoxedPrototype::init. */ bool BoxedPrototype::ensure_field_map(JSContext* cx) { if (!m_field_map) m_field_map = create_field_map(cx, info()); return !!m_field_map; } /* * BoxedPrototype::lookup_field: * * Look up the introspection info corresponding to the field name @prop_name, * creating the field cache if necessary. */ GIFieldInfo* BoxedPrototype::lookup_field(JSContext* cx, JSString* prop_name) { if (!ensure_field_map(cx)) return nullptr; auto entry = m_field_map->lookup(prop_name); if (!entry) { gjs_throw(cx, "No field %s on boxed type %s", gjs_debug_string(prop_name).c_str(), name()); return nullptr; } return entry->value().get(); } /* Initialize a newly created Boxed from an object that is a "hash" of * properties to set as fieds of the object. We don't require that every field * of the object be set. */ bool BoxedInstance::init_from_props(JSContext* context, JS::Value props_value) { size_t ix, length; if (!props_value.isObject()) { gjs_throw(context, "argument should be a hash with fields to set"); return false; } JS::RootedObject props(context, &props_value.toObject()); JS::Rooted ids(context, context); if (!JS_Enumerate(context, props, &ids)) { gjs_throw(context, "Failed to enumerate fields hash"); return false; } JS::RootedValue value(context); for (ix = 0, length = ids.length(); ix < length; ix++) { if (!JSID_IS_STRING(ids[ix])) { gjs_throw(context, "Fields hash contained a non-string field"); return false; } GIFieldInfo* field_info = get_prototype()->lookup_field(context, JSID_TO_STRING(ids[ix])); if (!field_info) return false; /* ids[ix] is reachable because props is rooted, but require_property * doesn't know that */ if (!gjs_object_require_property(context, props, "property list", JS::HandleId::fromMarkedLocation(ids[ix].address()), &value)) return false; if (!field_setter_impl(context, field_info, value)) return false; } return true; } GJS_JSAPI_RETURN_CONVENTION static bool boxed_invoke_constructor(JSContext* context, JS::HandleObject obj, JS::HandleId constructor_name, const JS::CallArgs& args) { GjsContextPrivate* gjs = GjsContextPrivate::from_cx(context); JS::RootedObject js_constructor(context); if (!gjs_object_require_property( context, obj, nullptr, gjs->atoms().constructor(), &js_constructor)) return false; JS::RootedValue js_constructor_func(context); if (!gjs_object_require_property(context, js_constructor, NULL, constructor_name, &js_constructor_func)) return false; return gjs->call_function(nullptr, js_constructor_func, args, args.rval()); } /* * BoxedInstance::copy_boxed: * * Allocate a new boxed pointer using g_boxed_copy(), either from a raw boxed * pointer or another BoxedInstance. */ void BoxedInstance::copy_boxed(void* boxed_ptr) { own_ptr(g_boxed_copy(gtype(), boxed_ptr)); debug_lifecycle("Boxed pointer created with g_boxed_copy()"); } void BoxedInstance::copy_boxed(BoxedInstance* source) { copy_boxed(source->ptr()); } /* * BoxedInstance::copy_memory: * * Allocate a new boxed pointer by copying the contents of another boxed pointer * or another BoxedInstance. */ void BoxedInstance::copy_memory(void* boxed_ptr) { allocate_directly(); memcpy(m_ptr, boxed_ptr, g_struct_info_get_size(info())); } void BoxedInstance::copy_memory(BoxedInstance* source) { copy_memory(source->ptr()); } // See GIWrapperBase::constructor(). bool BoxedInstance::constructor_impl(JSContext* context, JS::HandleObject obj, const JS::CallArgs& args) { // Short-circuit copy-construction in the case where we can use copy_boxed() // or copy_memory() BoxedBase* source_priv; if (args.length() == 1 && (source_priv = get_copy_source(context, args[0]))) { if (!source_priv->check_is_instance(context, "construct boxed object")) return false; if (g_type_is_a(gtype(), G_TYPE_BOXED)) { copy_boxed(source_priv->to_instance()); return true; } else if (get_prototype()->can_allocate_directly()) { copy_memory(source_priv->to_instance()); return true; } } if (gtype() == G_TYPE_VARIANT) { /* Short-circuit construction for GVariants by calling into the JS packing function */ const GjsAtoms& atoms = GjsContextPrivate::atoms(context); if (!boxed_invoke_constructor(context, obj, atoms.new_internal(), args)) return false; // The return value of GLib.Variant.new_internal() gets its own // BoxedInstance, and the one we're setting up in this constructor is // discarded. debug_lifecycle( "Boxed construction delegated to GVariant constructor, " "boxed object discarded"); return true; } BoxedPrototype* proto = get_prototype(); /* If the structure is registered as a boxed, we can create a new instance by * looking for a zero-args constructor and calling it. * Constructors don't really make sense for non-boxed types, since there is no * memory management for the return value, and zero_args_constructor and * default_constructor are always -1 for them. * * For backward compatibility, we choose the zero args constructor if one * exists, otherwise we choose the internal slice allocator if possible; * finally, we fallback on the default constructor */ if (proto->has_zero_args_constructor()) { GjsAutoFunctionInfo func_info = proto->zero_args_constructor_info(); GIArgument rval_arg; GError *error = NULL; if (!g_function_info_invoke(func_info, NULL, 0, NULL, 0, &rval_arg, &error)) { gjs_throw(context, "Failed to invoke boxed constructor: %s", error->message); g_clear_error(&error); return false; } own_ptr(g_steal_pointer(&gjs_arg_member(&rval_arg))); debug_lifecycle("Boxed pointer created from zero-args constructor"); } else if (proto->can_allocate_directly()) { allocate_directly(); } else if (proto->has_default_constructor()) { /* for simplicity, we simply delegate all the work to the actual JS * constructor function (which we retrieve from the JS constructor, * that is, Namespace.BoxedType, or object.constructor, given that * object was created with the right prototype. */ if (!boxed_invoke_constructor(context, obj, proto->default_constructor_name(), args)) return false; // Define the expected Error properties if (gtype() == G_TYPE_ERROR) { JS::RootedObject gerror(context, &args.rval().toObject()); if (!gjs_define_error_properties(context, gerror)) return false; } // The return value of the JS constructor gets its own BoxedInstance, // and this one is discarded. debug_lifecycle( "Boxed construction delegated to JS constructor, " "boxed object discarded"); return true; } else { gjs_throw(context, "Unable to construct struct type %s since it has no default " "constructor and cannot be allocated directly", name()); return false; } /* If we reach this code, we need to init from a map of fields */ if (args.length() == 0) return true; if (args.length() > 1) { gjs_throw(context, "Constructor with multiple arguments not supported for %s", name()); return false; } return init_from_props(context, args[0]); } BoxedInstance::~BoxedInstance() { if (m_owning_ptr) { if (m_allocated_directly) { g_slice_free1(g_struct_info_get_size(info()), m_ptr); } else { if (g_type_is_a(gtype(), G_TYPE_BOXED)) g_boxed_free(gtype(), m_ptr); else if (g_type_is_a(gtype(), G_TYPE_VARIANT)) g_variant_unref(static_cast(m_ptr)); else g_assert_not_reached (); } m_ptr = nullptr; } GJS_DEC_COUNTER(boxed_instance); } BoxedPrototype::~BoxedPrototype(void) { g_clear_pointer(&m_info, g_base_info_unref); if (m_field_map) delete m_field_map; GJS_DEC_COUNTER(boxed_prototype); } /* * BoxedBase::get_field_info: * * Does the same thing as g_struct_info_get_field(), but throws a JS exception * if there is no such field. */ GIFieldInfo* BoxedBase::get_field_info(JSContext* cx, uint32_t id) const { GIFieldInfo* field_info = g_struct_info_get_field(info(), id); if (field_info == NULL) { gjs_throw(cx, "No field %d on boxed type %s", id, name()); return NULL; } return field_info; } /* * BoxedInstance::get_nested_interface_object: * @parent_obj: the BoxedInstance JS object that owns `this` * @field_info: introspection info for the field of the parent boxed type that * is another boxed type * @interface_info: introspection info for the nested boxed type * @value: return location for a new BoxedInstance JS object * * Some boxed types have a field that consists of another boxed type. We want to * be able to expose these nested boxed types without copying them, because * changing fields of the nested boxed struct should affect the enclosing boxed * struct. * * This method creates a new BoxedInstance and JS object for a nested boxed * struct. Since both the nested JS object and the parent boxed's JS object * refer to the same memory, the parent JS object will be prevented from being * garbage collected while the nested JS object is active. */ bool BoxedInstance::get_nested_interface_object( JSContext* context, JSObject* parent_obj, GIFieldInfo* field_info, GIBaseInfo* interface_info, JS::MutableHandleValue value) const { int offset; if (!struct_is_simple ((GIStructInfo *)interface_info)) { gjs_throw(context, "Reading field %s.%s is not supported", name(), g_base_info_get_name(field_info)); return false; } offset = g_field_info_get_offset (field_info); JS::RootedObject obj(context, gjs_new_object_with_generic_prototype( context, interface_info)); if (!obj) return false; BoxedInstance* priv = BoxedInstance::new_for_js_object(context, obj); /* A structure nested inside a parent object; doesn't have an independent allocation */ priv->share_ptr(raw_ptr() + offset); priv->debug_lifecycle( "Boxed pointer created, pointing inside memory owned by parent"); /* We never actually read the reserved slot, but we put the parent object * into it to hold onto the parent object. */ JS_SetReservedSlot(obj, 0, JS::ObjectValue(*parent_obj)); value.setObject(*obj); return true; } /* * BoxedBase::field_getter: * * JSNative property getter that is called when accessing a field defined on a * boxed type. Delegates to BoxedInstance::field_getter_impl() if the minimal * conditions have been met. */ bool BoxedBase::field_getter(JSContext* context, unsigned argc, JS::Value* vp) { GJS_GET_WRAPPER_PRIV(context, argc, vp, args, obj, BoxedBase, priv); if (!priv->check_is_instance(context, "get a field")) return false; uint32_t field_ix = gjs_dynamic_property_private_slot(&args.callee()) .toPrivateUint32(); GjsAutoFieldInfo field_info = priv->get_field_info(context, field_ix); if (!field_info) return false; return priv->to_instance()->field_getter_impl(context, obj, field_info, args.rval()); } // See BoxedBase::field_getter(). bool BoxedInstance::field_getter_impl(JSContext* cx, JSObject* obj, GIFieldInfo* field_info, JS::MutableHandleValue rval) const { GjsAutoTypeInfo type_info = g_field_info_get_type(field_info); if (!g_type_info_is_pointer(type_info) && g_type_info_get_tag(type_info) == GI_TYPE_TAG_INTERFACE) { GjsAutoBaseInfo interface_info = g_type_info_get_interface(type_info); if (interface_info.type() == GI_INFO_TYPE_STRUCT || interface_info.type() == GI_INFO_TYPE_BOXED) { return get_nested_interface_object(cx, obj, field_info, interface_info, rval); } } GIArgument arg; if (!g_field_info_get_field(field_info, m_ptr, &arg)) { gjs_throw(cx, "Reading field %s.%s is not supported", name(), g_base_info_get_name(field_info)); return false; } return gjs_value_from_g_argument(cx, rval, type_info, &arg, true); } /* * BoxedInstance::set_nested_interface_object: * @field_info: introspection info for the field of the parent boxed type that * is another boxed type * @interface_info: introspection info for the nested boxed type * @value: holds a BoxedInstance JS object of type @interface_info * * Some boxed types have a field that consists of another boxed type. This * method is called from BoxedInstance::field_setter_impl() when any such field * is being set. The contents of the BoxedInstance JS object in @value are * copied into the correct place in this BoxedInstance's memory. */ bool BoxedInstance::set_nested_interface_object(JSContext* context, GIFieldInfo* field_info, GIBaseInfo* interface_info, JS::HandleValue value) { int offset; if (!struct_is_simple ((GIStructInfo *)interface_info)) { gjs_throw(context, "Writing field %s.%s is not supported", name(), g_base_info_get_name(field_info)); return false; } JS::RootedObject proto( context, gjs_lookup_generic_prototype(context, interface_info)); if (!proto) return false; /* If we can't directly copy from the source object we need * to construct a new temporary object. */ BoxedBase* source_priv = get_copy_source(context, value); if (!source_priv) { JS::RootedValueArray<1> args(context); args[0].set(value); JS::RootedObject tmp_object(context, gjs_construct_object_dynamic(context, proto, args)); if (!tmp_object) return false; source_priv = BoxedBase::for_js_typecheck(context, tmp_object); if (!source_priv) return false; } if (!source_priv->check_is_instance(context, "copy")) return false; offset = g_field_info_get_offset (field_info); memcpy(raw_ptr() + offset, source_priv->to_instance()->ptr(), g_struct_info_get_size(source_priv->info())); return true; } // See BoxedBase::field_setter(). bool BoxedInstance::field_setter_impl(JSContext* context, GIFieldInfo* field_info, JS::HandleValue value) { GArgument arg; GjsAutoTypeInfo type_info = g_field_info_get_type(field_info); if (!g_type_info_is_pointer (type_info) && g_type_info_get_tag (type_info) == GI_TYPE_TAG_INTERFACE) { GjsAutoBaseInfo interface_info = g_type_info_get_interface(type_info); if (interface_info.type() == GI_INFO_TYPE_STRUCT || interface_info.type() == GI_INFO_TYPE_BOXED) { return set_nested_interface_object(context, field_info, interface_info, value); } } if (!gjs_value_to_g_argument(context, value, type_info, g_base_info_get_name ((GIBaseInfo *)field_info), GJS_ARGUMENT_FIELD, GI_TRANSFER_NOTHING, true, &arg)) return false; bool success = true; if (!g_field_info_set_field(field_info, m_ptr, &arg)) { gjs_throw(context, "Writing field %s.%s is not supported", name(), g_base_info_get_name(field_info)); success = false; } JS::AutoSaveExceptionState saved_exc(context); if (!gjs_g_argument_release(context, GI_TRANSFER_NOTHING, type_info, &arg)) gjs_log_exception(context); saved_exc.restore(); return success; } /* * BoxedBase::field_setter: * * JSNative property setter that is called when writing to a field defined on a * boxed type. Delegates to BoxedInstance::field_setter_impl() if the minimal * conditions have been met. */ bool BoxedBase::field_setter(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_GET_WRAPPER_PRIV(cx, argc, vp, args, obj, BoxedBase, priv); if (!priv->check_is_instance(cx, "set a field")) return false; uint32_t field_ix = gjs_dynamic_property_private_slot(&args.callee()) .toPrivateUint32(); GjsAutoFieldInfo field_info = priv->get_field_info(cx, field_ix); if (!field_info) return false; if (!priv->to_instance()->field_setter_impl(cx, field_info, args[0])) return false; args.rval().setUndefined(); /* No stored value */ return true; } /* * BoxedPrototype::define_boxed_class_fields: * * Defines properties on the JS prototype object, with JSNative getters and * setters, for all the fields exposed by GObject introspection. */ bool BoxedPrototype::define_boxed_class_fields(JSContext* cx, JS::HandleObject proto) { int n_fields = g_struct_info_get_n_fields(info()); int i; /* We define all fields as read/write so that the user gets an * error message. If we omitted fields or defined them read-only * we'd: * * - Store a new property for a non-accessible field * - Silently do nothing when writing a read-only field * * Which is pretty confusing if the only reason a field isn't * writable is language binding or memory-management restrictions. * * We just go ahead and define the fields immediately for the * class; doing it lazily in boxed_resolve() would be possible * as well if doing it ahead of time caused to much start-up * memory overhead. */ for (i = 0; i < n_fields; i++) { GjsAutoFieldInfo field = g_struct_info_get_field(info(), i); JS::RootedValue private_id(cx, JS::PrivateUint32Value(i)); if (!gjs_define_property_dynamic(cx, proto, field.name(), "boxed_field", &BoxedBase::field_getter, &BoxedBase::field_setter, private_id, GJS_MODULE_PROP_FLAGS)) return false; } return true; } // Overrides GIWrapperPrototype::trace_impl(). void BoxedPrototype::trace_impl(JSTracer* trc) { JS::TraceEdge(trc, &m_default_constructor_name, "Boxed::default_constructor_name"); if (m_field_map) m_field_map->trace(trc); } // clang-format off const struct JSClassOps BoxedBase::class_ops = { nullptr, // addProperty nullptr, // deleteProperty nullptr, // enumerate &BoxedBase::new_enumerate, &BoxedBase::resolve, nullptr, // mayResolve &BoxedBase::finalize, nullptr, // call nullptr, // hasInstance nullptr, // construct &BoxedBase::trace }; /* We allocate 1 reserved slot; this is typically unused, but if the * boxed is for a nested structure inside a parent structure, the * reserved slot is used to hold onto the parent Javascript object and * make sure it doesn't get freed. */ const struct JSClass BoxedBase::klass = { "GObject_Boxed", JSCLASS_HAS_PRIVATE | JSCLASS_FOREGROUND_FINALIZE | JSCLASS_HAS_RESERVED_SLOTS(1), &BoxedBase::class_ops }; // clang-format on [[nodiscard]] static bool type_can_be_allocated_directly( GITypeInfo* type_info) { bool is_simple = true; if (g_type_info_is_pointer(type_info)) { if (g_type_info_get_tag(type_info) == GI_TYPE_TAG_ARRAY && g_type_info_get_array_type(type_info) == GI_ARRAY_TYPE_C) { GITypeInfo *param_info; param_info = g_type_info_get_param_type(type_info, 0); is_simple = type_can_be_allocated_directly(param_info); g_base_info_unref((GIBaseInfo*)param_info); } else if (g_type_info_get_tag(type_info) == GI_TYPE_TAG_VOID) { return true; } else { is_simple = false; } } else { switch (g_type_info_get_tag(type_info)) { case GI_TYPE_TAG_INTERFACE: { GIBaseInfo *interface = g_type_info_get_interface(type_info); switch (g_base_info_get_type(interface)) { case GI_INFO_TYPE_BOXED: case GI_INFO_TYPE_STRUCT: if (!struct_is_simple((GIStructInfo *)interface)) is_simple = false; break; case GI_INFO_TYPE_UNION: /* FIXME: Need to implement */ is_simple = false; break; case GI_INFO_TYPE_OBJECT: case GI_INFO_TYPE_VFUNC: case GI_INFO_TYPE_CALLBACK: case GI_INFO_TYPE_INVALID: case GI_INFO_TYPE_INTERFACE: case GI_INFO_TYPE_FUNCTION: case GI_INFO_TYPE_CONSTANT: case GI_INFO_TYPE_VALUE: case GI_INFO_TYPE_SIGNAL: case GI_INFO_TYPE_PROPERTY: case GI_INFO_TYPE_FIELD: case GI_INFO_TYPE_ARG: case GI_INFO_TYPE_TYPE: case GI_INFO_TYPE_UNRESOLVED: is_simple = false; break; case GI_INFO_TYPE_INVALID_0: g_assert_not_reached(); break; case GI_INFO_TYPE_ENUM: case GI_INFO_TYPE_FLAGS: default: break; } g_base_info_unref(interface); break; } case GI_TYPE_TAG_BOOLEAN: case GI_TYPE_TAG_INT8: case GI_TYPE_TAG_UINT8: case GI_TYPE_TAG_INT16: case GI_TYPE_TAG_UINT16: case GI_TYPE_TAG_INT32: case GI_TYPE_TAG_UINT32: case GI_TYPE_TAG_INT64: case GI_TYPE_TAG_UINT64: case GI_TYPE_TAG_FLOAT: case GI_TYPE_TAG_DOUBLE: case GI_TYPE_TAG_UNICHAR: case GI_TYPE_TAG_VOID: case GI_TYPE_TAG_GTYPE: case GI_TYPE_TAG_ERROR: case GI_TYPE_TAG_UTF8: case GI_TYPE_TAG_FILENAME: case GI_TYPE_TAG_ARRAY: case GI_TYPE_TAG_GLIST: case GI_TYPE_TAG_GSLIST: case GI_TYPE_TAG_GHASH: default: break; } } return is_simple; } /* Check if the type of the boxed is "simple" - every field is a non-pointer * type that we know how to assign to. If so, then we can allocate and free * instances without needing a constructor. */ [[nodiscard]] static bool struct_is_simple(GIStructInfo* info) { int n_fields = g_struct_info_get_n_fields(info); bool is_simple = true; int i; /* If it's opaque, it's not simple */ if (n_fields == 0) return false; for (i = 0; i < n_fields && is_simple; i++) { GIFieldInfo *field_info = g_struct_info_get_field(info, i); GITypeInfo *type_info = g_field_info_get_type(field_info); is_simple = type_can_be_allocated_directly(type_info); g_base_info_unref((GIBaseInfo *)field_info); g_base_info_unref((GIBaseInfo *)type_info); } return is_simple; } BoxedPrototype::BoxedPrototype(GIStructInfo* info, GType gtype) : GIWrapperPrototype(info, gtype), m_zero_args_constructor(-1), m_default_constructor(-1), m_default_constructor_name(JSID_VOID), m_field_map(nullptr), m_can_allocate_directly(struct_is_simple(info)) { GJS_INC_COUNTER(boxed_prototype); } // Overrides GIWrapperPrototype::init(). bool BoxedPrototype::init(JSContext* context) { int i, n_methods; int first_constructor = -1; jsid first_constructor_name = JSID_VOID; jsid zero_args_constructor_name = JSID_VOID; if (m_gtype != G_TYPE_NONE) { /* If the structure is registered as a boxed, we can create a new instance by * looking for a zero-args constructor and calling it; constructors don't * really make sense for non-boxed types, since there is no memory management * for the return value. */ n_methods = g_struct_info_get_n_methods(m_info); for (i = 0; i < n_methods; ++i) { GIFunctionInfoFlags flags; GjsAutoFunctionInfo func_info = g_struct_info_get_method(m_info, i); flags = g_function_info_get_flags(func_info); if ((flags & GI_FUNCTION_IS_CONSTRUCTOR) != 0) { if (first_constructor < 0) { first_constructor = i; first_constructor_name = gjs_intern_string_to_id(context, func_info.name()); if (first_constructor_name == JSID_VOID) return false; } if (m_zero_args_constructor < 0 && g_callable_info_get_n_args(func_info) == 0) { m_zero_args_constructor = i; zero_args_constructor_name = gjs_intern_string_to_id(context, func_info.name()); if (zero_args_constructor_name == JSID_VOID) return false; } if (m_default_constructor < 0 && strcmp(func_info.name(), "new") == 0) { m_default_constructor = i; const GjsAtoms& atoms = GjsContextPrivate::atoms(context); m_default_constructor_name = atoms.new_(); } } } if (m_default_constructor < 0) { m_default_constructor = m_zero_args_constructor; m_default_constructor_name = zero_args_constructor_name; } if (m_default_constructor < 0) { m_default_constructor = first_constructor; m_default_constructor_name = first_constructor_name; } } return true; } /* * BoxedPrototype::define_class: * @in_object: Object where the constructor is stored, typically a repo object. * @info: Introspection info for the boxed class. * * Define a boxed class constructor and prototype, including all the necessary * methods and properties. */ bool BoxedPrototype::define_class(JSContext* context, JS::HandleObject in_object, GIStructInfo* info) { JS::RootedObject prototype(context), unused_constructor(context); GType gtype = g_registered_type_info_get_g_type(info); BoxedPrototype* priv = BoxedPrototype::create_class( context, in_object, info, gtype, &unused_constructor, &prototype); if (!priv || !priv->define_boxed_class_fields(context, prototype)) return false; if (gtype == G_TYPE_ERROR && !JS_DefineFunction(context, prototype, "toString", &ErrorBase::to_string, 0, GJS_MODULE_PROP_FLAGS)) return false; return true; } /* Helper function to make the public API more readable. The overloads are * specified explicitly in the public API, but the implementation uses * std::forward in order to avoid duplicating code. */ template JSObject* BoxedInstance::new_for_c_struct_impl(JSContext* cx, GIStructInfo* info, void* gboxed, Args&&... args) { if (gboxed == NULL) return NULL; gjs_debug_marshal(GJS_DEBUG_GBOXED, "Wrapping struct %s %p with JSObject", g_base_info_get_name((GIBaseInfo *)info), gboxed); JS::RootedObject obj(cx, gjs_new_object_with_generic_prototype(cx, info)); if (!obj) return nullptr; BoxedInstance* priv = BoxedInstance::new_for_js_object(cx, obj); if (!priv) return nullptr; if (!priv->init_from_c_struct(cx, gboxed, std::forward(args)...)) return nullptr; if (priv->gtype() == G_TYPE_ERROR && !gjs_define_error_properties(cx, obj)) return nullptr; return obj; } /* * BoxedInstance::new_for_c_struct: * * Creates a new BoxedInstance JS object from a C boxed struct pointer. * * There are two overloads of this method; the NoCopy overload will simply take * the passed-in pointer but not own it, while the normal method will take a * reference, or if the boxed type can be directly allocated, copy the memory. */ JSObject* BoxedInstance::new_for_c_struct(JSContext* cx, GIStructInfo* info, void* gboxed) { return new_for_c_struct_impl(cx, info, gboxed); } JSObject* BoxedInstance::new_for_c_struct(JSContext* cx, GIStructInfo* info, void* gboxed, NoCopy no_copy) { return new_for_c_struct_impl(cx, info, gboxed, no_copy); } /* * BoxedInstance::init_from_c_struct: * * Do the necessary initialization when creating a BoxedInstance JS object from * a C boxed struct pointer. * * There are two overloads of this method; the NoCopy overload will simply take * the passed-in pointer, while the normal method will take a reference, or if * the boxed type can be directly allocated, copy the memory. */ bool BoxedInstance::init_from_c_struct(JSContext*, void* gboxed, NoCopy) { // We need to create a JS Boxed which references the original C struct, not // a copy of it. Used for G_SIGNAL_TYPE_STATIC_SCOPE. share_ptr(gboxed); debug_lifecycle("Boxed pointer acquired, memory not owned"); return true; } bool BoxedInstance::init_from_c_struct(JSContext* cx, void* gboxed) { if (gtype() != G_TYPE_NONE && g_type_is_a(gtype(), G_TYPE_BOXED)) { copy_boxed(gboxed); return true; } else if (gtype() == G_TYPE_VARIANT) { own_ptr(g_variant_ref_sink(static_cast(gboxed))); debug_lifecycle("Boxed pointer created by sinking GVariant ref"); return true; } else if (get_prototype()->can_allocate_directly()) { copy_memory(gboxed); return true; } gjs_throw(cx, "Can't create a Javascript object for %s; no way to copy", name()); return false; } void* BoxedInstance::copy_ptr(JSContext* cx, GType gtype, void* ptr) { if (g_type_is_a(gtype, G_TYPE_BOXED)) return g_boxed_copy(gtype, ptr); if (g_type_is_a(gtype, G_TYPE_VARIANT)) return g_variant_ref(static_cast(ptr)); gjs_throw(cx, "Can't transfer ownership of a structure type not registered as " "boxed"); return nullptr; } cjs-5.2.0/gi/value.cpp0000644000175000017500000010774214144444702014671 0ustar jpeisachjpeisach/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* * Copyright (c) 2008 litl, LLC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #include #include // for SCHAR_MAX, SCHAR_MIN, UCHAR_MAX #include #include // for memset #include #include #include #include #include #include // for RootedVector #include #include #include // for UniqueChars #include #include #include // for InformalValueTypeName, JS_ClearPendingException #include #include "gi/arg-inl.h" #include "gi/arg.h" #include "gi/boxed.h" #include "gi/closure.h" #include "gi/foreign.h" #include "gi/fundamental.h" #include "gi/gerror.h" #include "gi/gtype.h" #include "gi/object.h" #include "gi/param.h" #include "gi/union.h" #include "gi/value.h" #include "gi/wrapperutils.h" #include "cjs/atoms.h" #include "cjs/context-private.h" #include "cjs/context.h" #include "cjs/jsapi-util.h" #include "util/log.h" GJS_JSAPI_RETURN_CONVENTION static bool gjs_value_from_g_value_internal(JSContext *context, JS::MutableHandleValue value_p, const GValue *gvalue, bool no_copy, GSignalQuery *signal_query, int arg_n); /* * Gets signal introspection info about closure, or NULL if not found. Currently * only works for signals on introspected GObjects, not signals on GJS-defined * GObjects nor standalone closures. The return value must be unreffed. */ [[nodiscard]] static GISignalInfo* get_signal_info_if_available( GSignalQuery* signal_query) { GIBaseInfo *obj; GIInfoType info_type; GISignalInfo *signal_info = NULL; if (!signal_query->itype) return NULL; obj = g_irepository_find_by_gtype(NULL, signal_query->itype); if (!obj) return NULL; info_type = g_base_info_get_type (obj); if (info_type == GI_INFO_TYPE_OBJECT) signal_info = g_object_info_find_signal((GIObjectInfo*)obj, signal_query->signal_name); else if (info_type == GI_INFO_TYPE_INTERFACE) signal_info = g_interface_info_find_signal((GIInterfaceInfo*)obj, signal_query->signal_name); g_base_info_unref((GIBaseInfo*)obj); return signal_info; } /* * Fill in value_p with a JS array, converted from a C array stored as a pointer * in array_value, with its length stored in array_length_value. */ GJS_JSAPI_RETURN_CONVENTION static bool gjs_value_from_array_and_length_values(JSContext *context, JS::MutableHandleValue value_p, GITypeInfo *array_type_info, const GValue *array_value, const GValue *array_length_value, bool no_copy, GSignalQuery *signal_query, int array_length_arg_n) { JS::RootedValue array_length(context); GArgument array_arg; g_assert(G_VALUE_HOLDS_POINTER(array_value)); g_assert(G_VALUE_HOLDS_INT(array_length_value)); if (!gjs_value_from_g_value_internal(context, &array_length, array_length_value, no_copy, signal_query, array_length_arg_n)) return false; gjs_arg_set(&array_arg, g_value_get_pointer(array_value)); return gjs_value_from_explicit_array(context, value_p, array_type_info, &array_arg, array_length.toInt32()); } static void closure_marshal(GClosure *closure, GValue *return_value, guint n_param_values, const GValue *param_values, gpointer invocation_hint, gpointer marshal_data) { JSContext *context; unsigned i; GSignalQuery signal_query = { 0, }; GISignalInfo *signal_info; bool *skip; int *array_len_indices_for; GITypeInfo **type_info_for; gjs_debug_marshal(GJS_DEBUG_GCLOSURE, "Marshal closure %p", closure); if (!gjs_closure_is_valid(closure)) { /* We were destroyed; become a no-op */ return; } context = gjs_closure_get_context(closure); GjsContextPrivate* gjs = GjsContextPrivate::from_cx(context); if (G_UNLIKELY(gjs->sweeping())) { GSignalInvocationHint *hint = (GSignalInvocationHint*) invocation_hint; g_critical("Attempting to call back into JSAPI during the sweeping phase of GC. " "This is most likely caused by not destroying a Clutter actor or Gtk+ " "widget with ::destroy signals connected, but can also be caused by " "using the destroy(), dispose(), or remove() vfuncs. " "Because it would crash the application, it has been " "blocked and the JS callback not invoked."); if (hint) { gpointer instance; g_signal_query(hint->signal_id, &signal_query); instance = g_value_peek_pointer(¶m_values[0]); g_critical("The offending signal was %s on %s %p.", signal_query.signal_name, g_type_name(G_TYPE_FROM_INSTANCE(instance)), instance); } gjs_dumpstack(); return; } JSFunction* func = gjs_closure_get_callable(closure); JSAutoRealm ar(context, JS_GetFunctionObject(func)); if (marshal_data) { /* we are used for a signal handler */ guint signal_id; signal_id = GPOINTER_TO_UINT(marshal_data); g_signal_query(signal_id, &signal_query); if (!signal_query.signal_id) { gjs_debug(GJS_DEBUG_GCLOSURE, "Signal handler being called on invalid signal"); return; } if (signal_query.n_params + 1 != n_param_values) { gjs_debug(GJS_DEBUG_GCLOSURE, "Signal handler being called with wrong number of parameters"); return; } } /* Check if any parameters, such as array lengths, need to be eliminated * before we invoke the closure. */ skip = g_newa(bool, n_param_values); memset(skip, 0, sizeof (bool) * n_param_values); array_len_indices_for = g_newa(int, n_param_values); for(i = 0; i < n_param_values; i++) array_len_indices_for[i] = -1; type_info_for = g_newa(GITypeInfo *, n_param_values); memset(type_info_for, 0, sizeof (gpointer) * n_param_values); signal_info = get_signal_info_if_available(&signal_query); if (signal_info) { /* Start at argument 1, skip the instance parameter */ for (i = 1; i < n_param_values; ++i) { GIArgInfo *arg_info; int array_len_pos; arg_info = g_callable_info_get_arg(signal_info, i - 1); type_info_for[i] = g_arg_info_get_type(arg_info); array_len_pos = g_type_info_get_array_length(type_info_for[i]); if (array_len_pos != -1) { skip[array_len_pos + 1] = true; array_len_indices_for[i] = array_len_pos + 1; } g_base_info_unref((GIBaseInfo *)arg_info); } g_base_info_unref((GIBaseInfo *)signal_info); } JS::RootedValueVector argv(context); /* May end up being less */ if (!argv.reserve(n_param_values)) g_error("Unable to reserve space"); JS::RootedValue argv_to_append(context); for (i = 0; i < n_param_values; ++i) { const GValue *gval = ¶m_values[i]; bool no_copy; int array_len_index; bool res; if (skip[i]) continue; no_copy = false; if (i >= 1 && signal_query.signal_id) { no_copy = (signal_query.param_types[i - 1] & G_SIGNAL_TYPE_STATIC_SCOPE) != 0; } array_len_index = array_len_indices_for[i]; if (array_len_index != -1) { const GValue *array_len_gval = ¶m_values[array_len_index]; res = gjs_value_from_array_and_length_values(context, &argv_to_append, type_info_for[i], gval, array_len_gval, no_copy, &signal_query, array_len_index); } else { res = gjs_value_from_g_value_internal(context, &argv_to_append, gval, no_copy, &signal_query, i); } if (!res) { gjs_debug(GJS_DEBUG_GCLOSURE, "Unable to convert arg %d in order to invoke closure", i); gjs_log_exception(context); return; } argv.infallibleAppend(argv_to_append); } for (i = 1; i < n_param_values; i++) if (type_info_for[i]) g_base_info_unref((GIBaseInfo *)type_info_for[i]); JS::RootedValue rval(context); mozilla::Unused << gjs_closure_invoke(closure, nullptr, argv, &rval, false); // Any exception now pending, is handled when returning control to JS if (return_value != NULL) { if (rval.isUndefined()) { /* something went wrong invoking, error should be set already */ return; } if (!gjs_value_to_g_value(context, rval, return_value)) { gjs_debug(GJS_DEBUG_GCLOSURE, "Unable to convert return value when invoking closure"); gjs_log_exception(context); return; } } } GClosure* gjs_closure_new_for_signal(JSContext* context, JSFunction* callable, const char* description, guint signal_id) { GClosure *closure; closure = gjs_closure_new(context, callable, description, false); g_closure_set_meta_marshal(closure, GUINT_TO_POINTER(signal_id), closure_marshal); return closure; } GClosure* gjs_closure_new_marshaled(JSContext* context, JSFunction* callable, const char* description) { GClosure *closure; closure = gjs_closure_new(context, callable, description, true); g_closure_set_marshal(closure, closure_marshal); return closure; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_value_guess_g_type(JSContext* context, JS::Value value, GType* gtype_out) { g_assert(gtype_out && "Invalid return location"); if (value.isNull()) { *gtype_out = G_TYPE_POINTER; return true; } if (value.isString()) { *gtype_out = G_TYPE_STRING; return true; } if (value.isInt32()) { *gtype_out = G_TYPE_INT; return true; } if (value.isDouble()) { *gtype_out = G_TYPE_DOUBLE; return true; } if (value.isBoolean()) { *gtype_out = G_TYPE_BOOLEAN; return true; } if (value.isObject()) { JS::RootedObject obj(context, &value.toObject()); return gjs_gtype_get_actual_gtype(context, obj, gtype_out); } *gtype_out = G_TYPE_INVALID; return true; } static bool throw_expect_type(JSContext *cx, JS::HandleValue value, const char *expected_type, GType gtype = 0) { gjs_throw(cx, "Wrong type %s; %s%s%s expected", JS::InformalValueTypeName(value), expected_type, gtype ? " " : "", gtype ? g_type_name(gtype) : ""); return false; /* for convenience */ } GJS_JSAPI_RETURN_CONVENTION static bool gjs_value_to_g_value_internal(JSContext *context, JS::HandleValue value, GValue *gvalue, bool no_copy) { GType gtype; gtype = G_VALUE_TYPE(gvalue); if (gtype == 0) { if (!gjs_value_guess_g_type(context, value, >ype)) return false; if (gtype == G_TYPE_INVALID) { gjs_throw(context, "Could not guess unspecified GValue type"); return false; } gjs_debug_marshal(GJS_DEBUG_GCLOSURE, "Guessed GValue type %s from JS Value", g_type_name(gtype)); g_value_init(gvalue, gtype); } gjs_debug_marshal(GJS_DEBUG_GCLOSURE, "Converting JS::Value to gtype %s", g_type_name(gtype)); if (gtype == G_TYPE_STRING) { /* Don't use ValueToString since we don't want to just toString() * everything automatically */ if (value.isNull()) { g_value_set_string(gvalue, NULL); } else if (value.isString()) { JS::RootedString str(context, value.toString()); JS::UniqueChars utf8_string(JS_EncodeStringToUTF8(context, str)); if (!utf8_string) return false; g_value_set_string(gvalue, utf8_string.get()); } else { return throw_expect_type(context, value, "string"); } } else if (gtype == G_TYPE_CHAR) { gint32 i; if (JS::ToInt32(context, value, &i) && i >= SCHAR_MIN && i <= SCHAR_MAX) { g_value_set_schar(gvalue, (signed char)i); } else { return throw_expect_type(context, value, "char"); } } else if (gtype == G_TYPE_UCHAR) { guint16 i; if (JS::ToUint16(context, value, &i) && i <= UCHAR_MAX) { g_value_set_uchar(gvalue, (unsigned char)i); } else { return throw_expect_type(context, value, "unsigned char"); } } else if (gtype == G_TYPE_INT) { gint32 i; if (JS::ToInt32(context, value, &i)) { g_value_set_int(gvalue, i); } else { return throw_expect_type(context, value, "integer"); } } else if (gtype == G_TYPE_DOUBLE) { gdouble d; if (JS::ToNumber(context, value, &d)) { g_value_set_double(gvalue, d); } else { return throw_expect_type(context, value, "double"); } } else if (gtype == G_TYPE_FLOAT) { gdouble d; if (JS::ToNumber(context, value, &d)) { g_value_set_float(gvalue, d); } else { return throw_expect_type(context, value, "float"); } } else if (gtype == G_TYPE_UINT) { guint32 i; if (JS::ToUint32(context, value, &i)) { g_value_set_uint(gvalue, i); } else { return throw_expect_type(context, value, "unsigned integer"); } } else if (gtype == G_TYPE_BOOLEAN) { /* JS::ToBoolean() can't fail */ g_value_set_boolean(gvalue, JS::ToBoolean(value)); } else if (g_type_is_a(gtype, G_TYPE_OBJECT) || g_type_is_a(gtype, G_TYPE_INTERFACE)) { GObject *gobj; gobj = NULL; if (value.isNull()) { /* nothing to do */ } else if (value.isObject()) { JS::RootedObject obj(context, &value.toObject()); if (!ObjectBase::typecheck(context, obj, nullptr, gtype) || !ObjectBase::to_c_ptr(context, obj, &gobj)) return false; if (!gobj) return true; // treat disposed object as if value.isNull() } else { return throw_expect_type(context, value, "object", gtype); } g_value_set_object(gvalue, gobj); } else if (gtype == G_TYPE_STRV) { if (value.isNull()) { /* do nothing */ } else if (value.isObject()) { bool found_length; const GjsAtoms& atoms = GjsContextPrivate::atoms(context); JS::RootedObject array_obj(context, &value.toObject()); if (JS_HasPropertyById(context, array_obj, atoms.length(), &found_length) && found_length) { guint32 length; if (!gjs_object_require_converted_property( context, array_obj, nullptr, atoms.length(), &length)) { JS_ClearPendingException(context); return throw_expect_type(context, value, "strv"); } else { void *result; char **strv; if (!gjs_array_to_strv (context, value, length, &result)) return false; /* cast to strv in a separate step to avoid type-punning */ strv = (char**) result; g_value_take_boxed (gvalue, strv); } } else { return throw_expect_type(context, value, "strv"); } } else { return throw_expect_type(context, value, "strv"); } } else if (g_type_is_a(gtype, G_TYPE_BOXED)) { void *gboxed; gboxed = NULL; if (value.isNull()) return true; /* special case GValue */ if (g_type_is_a(gtype, G_TYPE_VALUE)) { GValue nested_gvalue = G_VALUE_INIT; /* explicitly handle values that are already GValues to avoid infinite recursion */ if (value.isObject()) { JS::RootedObject obj(context, &value.toObject()); GType guessed_gtype; if (!gjs_value_guess_g_type(context, value, &guessed_gtype)) return false; if (guessed_gtype == G_TYPE_VALUE) { gboxed = BoxedBase::to_c_ptr(context, obj); g_value_set_boxed(gvalue, gboxed); return true; } } if (!gjs_value_to_g_value(context, value, &nested_gvalue)) return false; g_value_set_boxed(gvalue, &nested_gvalue); g_value_unset(&nested_gvalue); return true; } if (value.isObject()) { JS::RootedObject obj(context, &value.toObject()); if (g_type_is_a(gtype, G_TYPE_ERROR)) { /* special case GError */ gboxed = ErrorBase::to_c_ptr(context, obj); if (!gboxed) return false; } else { GIBaseInfo *registered = g_irepository_find_by_gtype (NULL, gtype); /* We don't necessarily have the typelib loaded when we first see the structure... */ if (registered) { GIInfoType info_type = g_base_info_get_type (registered); if (info_type == GI_INFO_TYPE_STRUCT && g_struct_info_is_foreign ((GIStructInfo*)registered)) { GArgument arg; if (!gjs_struct_foreign_convert_to_g_argument (context, value, registered, NULL, GJS_ARGUMENT_ARGUMENT, GI_TRANSFER_NOTHING, true, &arg)) return false; gboxed = gjs_arg_get(&arg); } } /* First try a union, if that fails, assume a boxed struct. Distinguishing which one is expected would require checking the associated GIBaseInfo, which is not necessary possible, if e.g. we see the GType without loading the typelib. */ if (!gboxed) { if (UnionBase::typecheck(context, obj, nullptr, gtype, GjsTypecheckNoThrow())) { gboxed = UnionBase::to_c_ptr(context, obj); } else { if (!BoxedBase::typecheck(context, obj, nullptr, gtype)) return false; gboxed = BoxedBase::to_c_ptr(context, obj); } if (!gboxed) return false; } } } else { return throw_expect_type(context, value, "boxed type", gtype); } if (no_copy) g_value_set_static_boxed(gvalue, gboxed); else g_value_set_boxed(gvalue, gboxed); } else if (g_type_is_a(gtype, G_TYPE_VARIANT)) { GVariant *variant = NULL; if (value.isNull()) { /* nothing to do */ } else if (value.isObject()) { JS::RootedObject obj(context, &value.toObject()); if (!BoxedBase::typecheck(context, obj, nullptr, G_TYPE_VARIANT)) return false; variant = BoxedBase::to_c_ptr(context, obj); if (!variant) return false; } else { return throw_expect_type(context, value, "boxed type", gtype); } g_value_set_variant (gvalue, variant); } else if (g_type_is_a(gtype, G_TYPE_ENUM)) { int64_t value_int64; if (JS::ToInt64(context, value, &value_int64)) { GEnumValue *v; GjsAutoTypeClass enum_class(gtype); /* See arg.c:_gjs_enum_to_int() */ v = g_enum_get_value(enum_class, (int)value_int64); if (v == NULL) { gjs_throw(context, "%d is not a valid value for enumeration %s", value.toInt32(), g_type_name(gtype)); return false; } g_value_set_enum(gvalue, v->value); } else { return throw_expect_type(context, value, "enum", gtype); } } else if (g_type_is_a(gtype, G_TYPE_FLAGS)) { int64_t value_int64; if (JS::ToInt64(context, value, &value_int64)) { if (!_gjs_flags_value_is_valid(context, gtype, value_int64)) return false; /* See arg.c:_gjs_enum_to_int() */ g_value_set_flags(gvalue, (int)value_int64); } else { return throw_expect_type(context, value, "flags", gtype); } } else if (g_type_is_a(gtype, G_TYPE_PARAM)) { void *gparam; gparam = NULL; if (value.isNull()) { /* nothing to do */ } else if (value.isObject()) { JS::RootedObject obj(context, &value.toObject()); if (!gjs_typecheck_param(context, obj, gtype, true)) return false; gparam = gjs_g_param_from_param(context, obj); } else { return throw_expect_type(context, value, "param type", gtype); } g_value_set_param(gvalue, (GParamSpec*) gparam); } else if (g_type_is_a(gtype, G_TYPE_GTYPE)) { GType type; if (!value.isObject()) return throw_expect_type(context, value, "GType object"); JS::RootedObject obj(context, &value.toObject()); if (!gjs_gtype_get_actual_gtype(context, obj, &type)) return false; g_value_set_gtype(gvalue, type); } else if (g_type_is_a(gtype, G_TYPE_POINTER)) { if (value.isNull()) { /* Nothing to do */ } else { gjs_throw(context, "Cannot convert non-null JS value to G_POINTER"); return false; } } else if (value.isNumber() && g_value_type_transformable(G_TYPE_INT, gtype)) { /* Only do this crazy gvalue transform stuff after we've * exhausted everything else. Adding this for * e.g. ClutterUnit. */ gint32 i; if (JS::ToInt32(context, value, &i)) { GValue int_value = { 0, }; g_value_init(&int_value, G_TYPE_INT); g_value_set_int(&int_value, i); g_value_transform(&int_value, gvalue); } else { return throw_expect_type(context, value, "integer"); } } else if (G_TYPE_IS_INSTANTIATABLE(gtype)) { // The gtype is none of the above, it should be derived from a custom // fundamental type. if (!value.isObject()) return throw_expect_type(context, value, "object", gtype); JS::RootedObject fundamental_object(context, &value.toObject()); if (!FundamentalBase::to_gvalue(context, fundamental_object, gvalue)) return false; } else { gjs_debug(GJS_DEBUG_GCLOSURE, "JS::Value is number %d gtype fundamental %d transformable to int %d from int %d", value.isNumber(), G_TYPE_IS_FUNDAMENTAL(gtype), g_value_type_transformable(gtype, G_TYPE_INT), g_value_type_transformable(G_TYPE_INT, gtype)); gjs_throw(context, "Don't know how to convert JavaScript object to GType %s", g_type_name(gtype)); return false; } return true; } bool gjs_value_to_g_value(JSContext *context, JS::HandleValue value, GValue *gvalue) { return gjs_value_to_g_value_internal(context, value, gvalue, false); } bool gjs_value_to_g_value_no_copy(JSContext *context, JS::HandleValue value, GValue *gvalue) { return gjs_value_to_g_value_internal(context, value, gvalue, true); } [[nodiscard]] static JS::Value convert_int_to_enum(GType gtype, int v) { double v_double; if (v > 0 && v < G_MAXINT) { /* Optimize the unambiguous case */ v_double = v; } else { /* Need to distinguish between negative integers and unsigned integers */ GjsAutoEnumInfo info = g_irepository_find_by_gtype(nullptr, gtype); g_assert (info); v_double = _gjs_enum_from_int(info, v); } return JS::NumberValue(v_double); } GJS_JSAPI_RETURN_CONVENTION static bool gjs_value_from_g_value_internal(JSContext *context, JS::MutableHandleValue value_p, const GValue *gvalue, bool no_copy, GSignalQuery *signal_query, int arg_n) { GType gtype; gtype = G_VALUE_TYPE(gvalue); gjs_debug_marshal(GJS_DEBUG_GCLOSURE, "Converting gtype %s to JS::Value", g_type_name(gtype)); if (gtype == G_TYPE_STRING) { const char *v; v = g_value_get_string(gvalue); if (v == NULL) { gjs_debug_marshal(GJS_DEBUG_GCLOSURE, "Converting NULL string to JS::NullValue()"); value_p.setNull(); } else { if (!gjs_string_from_utf8(context, v, value_p)) return false; } } else if (gtype == G_TYPE_CHAR) { char v; v = g_value_get_schar(gvalue); value_p.setInt32(v); } else if (gtype == G_TYPE_UCHAR) { unsigned char v; v = g_value_get_uchar(gvalue); value_p.setInt32(v); } else if (gtype == G_TYPE_INT) { int v; v = g_value_get_int(gvalue); value_p.set(JS::NumberValue(v)); } else if (gtype == G_TYPE_UINT) { guint v; v = g_value_get_uint(gvalue); value_p.setNumber(v); } else if (gtype == G_TYPE_DOUBLE) { double d; d = g_value_get_double(gvalue); value_p.setNumber(d); } else if (gtype == G_TYPE_FLOAT) { double d; d = g_value_get_float(gvalue); value_p.setNumber(d); } else if (gtype == G_TYPE_BOOLEAN) { bool v; v = g_value_get_boolean(gvalue); value_p.setBoolean(!!v); } else if (g_type_is_a(gtype, G_TYPE_OBJECT) || g_type_is_a(gtype, G_TYPE_INTERFACE)) { GObject *gobj; gobj = (GObject*) g_value_get_object(gvalue); if (gobj) { JSObject* obj = ObjectInstance::wrapper_from_gobject(context, gobj); if (!obj) return false; value_p.setObject(*obj); } else { value_p.setNull(); } } else if (gtype == G_TYPE_STRV) { if (!gjs_array_from_strv (context, value_p, (const char**) g_value_get_boxed (gvalue))) { gjs_throw(context, "Failed to convert strv to array"); return false; } } else if (g_type_is_a(gtype, G_TYPE_HASH_TABLE) || g_type_is_a(gtype, G_TYPE_ARRAY) || g_type_is_a(gtype, G_TYPE_BYTE_ARRAY) || g_type_is_a(gtype, G_TYPE_PTR_ARRAY)) { gjs_throw(context, "Unable to introspect element-type of container in GValue"); return false; } else if (g_type_is_a(gtype, G_TYPE_BOXED) || g_type_is_a(gtype, G_TYPE_VARIANT)) { void *gboxed; JSObject *obj; if (g_type_is_a(gtype, G_TYPE_BOXED)) gboxed = g_value_get_boxed(gvalue); else gboxed = g_value_get_variant(gvalue); if (!gboxed) { gjs_debug_marshal(GJS_DEBUG_GCLOSURE, "Converting null boxed pointer to JS::Value"); value_p.setNull(); return true; } /* special case GError */ if (g_type_is_a(gtype, G_TYPE_ERROR)) { obj = ErrorInstance::object_for_c_ptr(context, static_cast(gboxed)); if (!obj) return false; value_p.setObject(*obj); return true; } /* special case GValue */ if (g_type_is_a(gtype, G_TYPE_VALUE)) { return gjs_value_from_g_value(context, value_p, static_cast(gboxed)); } /* The only way to differentiate unions and structs is from * their g-i info as both GBoxed */ GjsAutoBaseInfo info = g_irepository_find_by_gtype(nullptr, gtype); if (!info) { gjs_throw(context, "No introspection information found for %s", g_type_name(gtype)); return false; } if (info.type() == GI_INFO_TYPE_STRUCT && g_struct_info_is_foreign(info)) { GIArgument arg; gjs_arg_set(&arg, gboxed); return gjs_struct_foreign_convert_from_g_argument(context, value_p, info, &arg); } GIInfoType type = info.type(); if (type == GI_INFO_TYPE_BOXED || type == GI_INFO_TYPE_STRUCT) { if (no_copy) obj = BoxedInstance::new_for_c_struct(context, info, gboxed, BoxedInstance::NoCopy()); else obj = BoxedInstance::new_for_c_struct(context, info, gboxed); } else if (type == GI_INFO_TYPE_UNION) { obj = gjs_union_from_c_union(context, info, gboxed); } else { gjs_throw(context, "Unexpected introspection type %d for %s", info.type(), g_type_name(gtype)); return false; } value_p.setObjectOrNull(obj); } else if (g_type_is_a(gtype, G_TYPE_ENUM)) { value_p.set(convert_int_to_enum(gtype, g_value_get_enum(gvalue))); } else if (g_type_is_a(gtype, G_TYPE_PARAM)) { GParamSpec *gparam; JSObject *obj; gparam = g_value_get_param(gvalue); obj = gjs_param_from_g_param(context, gparam); value_p.setObjectOrNull(obj); } else if (signal_query && g_type_is_a(gtype, G_TYPE_POINTER)) { bool res; GArgument arg; GIArgInfo *arg_info; GISignalInfo *signal_info; GITypeInfo type_info; signal_info = get_signal_info_if_available(signal_query); if (!signal_info) { gjs_throw(context, "Unknown signal."); return false; } arg_info = g_callable_info_get_arg(signal_info, arg_n - 1); g_arg_info_load_type(arg_info, &type_info); g_assert(((void) "Check gjs_value_from_array_and_length_values() before" " calling gjs_value_from_g_value_internal()", g_type_info_get_array_length(&type_info) == -1)); gjs_arg_set(&arg, g_value_get_pointer(gvalue)); res = gjs_value_from_g_argument(context, value_p, &type_info, &arg, true); g_base_info_unref((GIBaseInfo*)arg_info); g_base_info_unref((GIBaseInfo*)signal_info); return res; } else if (g_type_is_a(gtype, G_TYPE_POINTER)) { gpointer pointer; pointer = g_value_get_pointer(gvalue); if (pointer == NULL) { value_p.setNull(); } else { gjs_throw(context, "Can't convert non-null pointer to JS value"); return false; } } else if (g_value_type_transformable(gtype, G_TYPE_DOUBLE)) { GValue double_value = { 0, }; double v; g_value_init(&double_value, G_TYPE_DOUBLE); g_value_transform(gvalue, &double_value); v = g_value_get_double(&double_value); value_p.setNumber(v); } else if (g_value_type_transformable(gtype, G_TYPE_INT)) { GValue int_value = { 0, }; int v; g_value_init(&int_value, G_TYPE_INT); g_value_transform(gvalue, &int_value); v = g_value_get_int(&int_value); value_p.set(JS::NumberValue(v)); } else if (G_TYPE_IS_INSTANTIATABLE(gtype)) { /* The gtype is none of the above, it should be a custom fundamental type. */ JSObject* obj = FundamentalInstance::object_for_gvalue(context, gvalue, gtype); if (obj == NULL) return false; else value_p.setObject(*obj); } else { gjs_throw(context, "Don't know how to convert GType %s to JavaScript object", g_type_name(gtype)); return false; } return true; } bool gjs_value_from_g_value(JSContext *context, JS::MutableHandleValue value_p, const GValue *gvalue) { return gjs_value_from_g_value_internal(context, value_p, gvalue, false, NULL, 0); } cjs-5.2.0/gi/private.cpp0000644000175000017500000004030314144444702015214 0ustar jpeisachjpeisach/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* * Copyright (c) 2008 litl, LLC * Copyright (c) 2018 Philip Chimento * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #include #include #include #include #include // for JS::GetArrayLength, #include #include // for JSID_TO_SYMBOL #include #include #include #include // for UniqueChars #include // for JS_GetElement #include "gi/gobject.h" #include "gi/gtype.h" #include "gi/interface.h" #include "gi/object.h" #include "gi/param.h" #include "gi/private.h" #include "gi/repo.h" #include "cjs/atoms.h" #include "cjs/context-private.h" #include "cjs/jsapi-util-args.h" #include "cjs/jsapi-util.h" /* gi/private.cpp - private "imports._gi" module with operations that we need * to use from JS in order to create GObject classes, but should not be exposed * to client code. */ GJS_JSAPI_RETURN_CONVENTION static bool gjs_override_property(JSContext* cx, unsigned argc, JS::Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); JS::UniqueChars name; JS::RootedObject type(cx); if (!gjs_parse_call_args(cx, "override_property", args, "so", "name", &name, "type", &type)) return false; GType gtype; if (!gjs_gtype_get_actual_gtype(cx, type, >ype)) return false; if (gtype == G_TYPE_INVALID) { gjs_throw(cx, "Invalid parameter type was not a GType"); return false; } GParamSpec* pspec; if (g_type_is_a(gtype, G_TYPE_INTERFACE)) { auto* interface_type = static_cast(g_type_default_interface_ref(gtype)); pspec = g_object_interface_find_property(interface_type, name.get()); g_type_default_interface_unref(interface_type); } else { GjsAutoTypeClass class_type(gtype); pspec = g_object_class_find_property(class_type, name.get()); } if (!pspec) { gjs_throw(cx, "No such property '%s' to override on type '%s'", name.get(), g_type_name(gtype)); return false; } GjsAutoParam new_pspec = g_param_spec_override(name.get(), pspec); g_param_spec_set_qdata(new_pspec, ObjectBase::custom_property_quark(), GINT_TO_POINTER(1)); args.rval().setObject(*gjs_param_from_g_param(cx, new_pspec)); return true; } GJS_JSAPI_RETURN_CONVENTION static bool validate_interfaces_and_properties_args(JSContext* cx, JS::HandleObject interfaces, JS::HandleObject properties, uint32_t* n_interfaces, uint32_t* n_properties) { bool is_array; if (!JS::IsArrayObject(cx, interfaces, &is_array)) return false; if (!is_array) { gjs_throw(cx, "Invalid parameter interfaces (expected Array)"); return false; } uint32_t n_int; if (!JS::GetArrayLength(cx, interfaces, &n_int)) return false; if (!JS::IsArrayObject(cx, properties, &is_array)) return false; if (!is_array) { gjs_throw(cx, "Invalid parameter properties (expected Array)"); return false; } uint32_t n_prop; if (!JS::GetArrayLength(cx, properties, &n_prop)) return false; if (n_interfaces) *n_interfaces = n_int; if (n_properties) *n_properties = n_prop; return true; } GJS_JSAPI_RETURN_CONVENTION static bool save_properties_for_class_init(JSContext* cx, JS::HandleObject properties, uint32_t n_properties, GType gtype) { AutoParamArray properties_native; JS::RootedValue prop_val(cx); JS::RootedObject prop_obj(cx); for (uint32_t i = 0; i < n_properties; i++) { if (!JS_GetElement(cx, properties, i, &prop_val)) return false; if (!prop_val.isObject()) { gjs_throw(cx, "Invalid parameter, expected object"); return false; } prop_obj = &prop_val.toObject(); if (!gjs_typecheck_param(cx, prop_obj, G_TYPE_NONE, true)) return false; properties_native.emplace_back( g_param_spec_ref(gjs_g_param_from_param(cx, prop_obj))); } push_class_init_properties(gtype, &properties_native); return true; } GJS_JSAPI_RETURN_CONVENTION static bool get_interface_gtypes(JSContext* cx, JS::HandleObject interfaces, uint32_t n_interfaces, GType* iface_types) { for (uint32_t ix = 0; ix < n_interfaces; ix++) { JS::RootedValue iface_val(cx); if (!JS_GetElement(cx, interfaces, ix, &iface_val)) return false; if (!iface_val.isObject()) { gjs_throw( cx, "Invalid parameter interfaces (element %d was not a GType)", ix); return false; } JS::RootedObject iface(cx, &iface_val.toObject()); GType iface_type; if (!gjs_gtype_get_actual_gtype(cx, iface, &iface_type)) return false; if (iface_type == G_TYPE_INVALID) { gjs_throw( cx, "Invalid parameter interfaces (element %d was not a GType)", ix); return false; } iface_types[ix] = iface_type; } return true; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_register_interface(JSContext* cx, unsigned argc, JS::Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); JS::UniqueChars name; JS::RootedObject interfaces(cx), properties(cx); if (!gjs_parse_call_args(cx, "register_interface", args, "soo", "name", &name, "interfaces", &interfaces, "properties", &properties)) return false; uint32_t n_interfaces, n_properties; if (!validate_interfaces_and_properties_args(cx, interfaces, properties, &n_interfaces, &n_properties)) return false; GType* iface_types = g_newa(GType, n_interfaces); /* We do interface addition in two passes so that any failure is caught early, before registering the GType (which we can't undo) */ if (!get_interface_gtypes(cx, interfaces, n_interfaces, iface_types)) return false; if (g_type_from_name(name.get()) != G_TYPE_INVALID) { gjs_throw(cx, "Type name %s is already registered", name.get()); return false; } GTypeInfo type_info = gjs_gobject_interface_info; GType interface_type = g_type_register_static(G_TYPE_INTERFACE, name.get(), &type_info, GTypeFlags(0)); g_type_set_qdata(interface_type, ObjectBase::custom_type_quark(), GINT_TO_POINTER(1)); if (!save_properties_for_class_init(cx, properties, n_properties, interface_type)) return false; for (uint32_t ix = 0; ix < n_interfaces; ix++) g_type_interface_add_prerequisite(interface_type, iface_types[ix]); /* create a custom JSClass */ JS::RootedObject module(cx, gjs_lookup_private_namespace(cx)); if (!module) return false; // error will have been thrown already JS::RootedObject constructor(cx), ignored_prototype(cx); if (!InterfacePrototype::create_class(cx, module, nullptr, interface_type, &constructor, &ignored_prototype)) return false; args.rval().setObject(*constructor); return true; } static inline void gjs_add_interface(GType instance_type, GType interface_type) { static GInterfaceInfo interface_vtable{nullptr, nullptr, nullptr}; g_type_add_interface_static(instance_type, interface_type, &interface_vtable); } GJS_JSAPI_RETURN_CONVENTION static bool gjs_register_type(JSContext* cx, unsigned argc, JS::Value* vp) { JS::CallArgs argv = JS::CallArgsFromVp(argc, vp); JS::UniqueChars name; GTypeFlags type_flags; JS::RootedObject parent(cx), interfaces(cx), properties(cx); if (!gjs_parse_call_args(cx, "register_type", argv, "osioo", "parent", &parent, "name", &name, "flags", &type_flags, "interfaces", &interfaces, "properties", &properties)) return false; if (!parent) return false; /* Don't pass the argv to it, as otherwise we will log about the callee * while we only care about the parent object type. */ auto* parent_priv = ObjectBase::for_js_typecheck(cx, parent); if (!parent_priv) return false; uint32_t n_interfaces, n_properties; if (!validate_interfaces_and_properties_args(cx, interfaces, properties, &n_interfaces, &n_properties)) return false; auto* iface_types = static_cast(g_alloca(sizeof(GType) * n_interfaces)); /* We do interface addition in two passes so that any failure is caught early, before registering the GType (which we can't undo) */ if (!get_interface_gtypes(cx, interfaces, n_interfaces, iface_types)) return false; if (g_type_from_name(name.get()) != G_TYPE_INVALID) { gjs_throw(cx, "Type name %s is already registered", name.get()); return false; } /* We checked parent above, in ObjectBase::for_js_typecheck() */ g_assert(parent_priv); GTypeQuery query; parent_priv->type_query_dynamic_safe(&query); if (G_UNLIKELY(query.type == 0)) { gjs_throw(cx, "Cannot inherit from a non-gjs dynamic type [bug 687184]"); return false; } GTypeInfo type_info = gjs_gobject_class_info; type_info.class_size = query.class_size; type_info.instance_size = query.instance_size; GType instance_type = g_type_register_static( parent_priv->gtype(), name.get(), &type_info, type_flags); g_type_set_qdata(instance_type, ObjectBase::custom_type_quark(), GINT_TO_POINTER(1)); if (!save_properties_for_class_init(cx, properties, n_properties, instance_type)) return false; for (uint32_t ix = 0; ix < n_interfaces; ix++) gjs_add_interface(instance_type, iface_types[ix]); /* create a custom JSClass */ JS::RootedObject module(cx, gjs_lookup_private_namespace(cx)); JS::RootedObject constructor(cx), prototype(cx); if (!ObjectPrototype::define_class(cx, module, nullptr, instance_type, &constructor, &prototype)) return false; auto* priv = ObjectPrototype::for_js(cx, prototype); priv->set_type_qdata(); argv.rval().setObject(*constructor); return true; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_signal_new(JSContext* cx, unsigned argc, JS::Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); JS::UniqueChars signal_name; int32_t flags, accumulator_enum; JS::RootedObject gtype_obj(cx), return_gtype_obj(cx), params_obj(cx); if (!gjs_parse_call_args(cx, "signal_new", args, "osiioo", "gtype", >ype_obj, "signal name", &signal_name, "flags", &flags, "accumulator", &accumulator_enum, "return gtype", &return_gtype_obj, "params", ¶ms_obj)) return false; if (!gjs_typecheck_gtype(cx, gtype_obj, true)) return false; /* we only support standard accumulators for now */ GSignalAccumulator accumulator; switch (accumulator_enum) { case 1: accumulator = g_signal_accumulator_first_wins; break; case 2: accumulator = g_signal_accumulator_true_handled; break; case 0: default: accumulator = nullptr; } GType return_type; if (!gjs_gtype_get_actual_gtype(cx, return_gtype_obj, &return_type)) return false; if (accumulator == g_signal_accumulator_true_handled && return_type != G_TYPE_BOOLEAN) { gjs_throw(cx, "GObject.SignalAccumulator.TRUE_HANDLED can only be used " "with boolean signals"); return false; } uint32_t n_parameters; if (!JS::GetArrayLength(cx, params_obj, &n_parameters)) return false; GType* params = g_newa(GType, n_parameters); JS::RootedValue gtype_val(cx); for (uint32_t ix = 0; ix < n_parameters; ix++) { if (!JS_GetElement(cx, params_obj, ix, >ype_val) || !gtype_val.isObject()) { gjs_throw(cx, "Invalid signal parameter number %d", ix); return false; } JS::RootedObject gjs_gtype(cx, >ype_val.toObject()); if (!gjs_gtype_get_actual_gtype(cx, gjs_gtype, ¶ms[ix])) return false; } GType gtype; if (!gjs_gtype_get_actual_gtype(cx, gtype_obj, >ype)) return false; unsigned signal_id = g_signal_newv( signal_name.get(), gtype, GSignalFlags(flags), /* class closure */ nullptr, accumulator, /* accu_data */ nullptr, /* c_marshaller */ nullptr, return_type, n_parameters, params); // FIXME: what if ID is greater than int32 max? args.rval().setInt32(signal_id); return true; } template GJS_JSAPI_RETURN_CONVENTION static bool symbol_getter(JSContext* cx, unsigned argc, JS::Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); const GjsAtoms& atoms = GjsContextPrivate::atoms(cx); args.rval().setSymbol(JSID_TO_SYMBOL((atoms.*member)())); return true; } static JSFunctionSpec module_funcs[] = { JS_FN("override_property", gjs_override_property, 2, GJS_MODULE_PROP_FLAGS), JS_FN("register_interface", gjs_register_interface, 3, GJS_MODULE_PROP_FLAGS), JS_FN("register_type", gjs_register_type, 4, GJS_MODULE_PROP_FLAGS), JS_FN("signal_new", gjs_signal_new, 6, GJS_MODULE_PROP_FLAGS), JS_FS_END, }; static JSPropertySpec module_props[] = { JS_PSG("hook_up_vfunc_symbol", symbol_getter<&GjsAtoms::hook_up_vfunc>, GJS_MODULE_PROP_FLAGS), JS_PSG("signal_find_symbol", symbol_getter<&GjsAtoms::signal_find>, GJS_MODULE_PROP_FLAGS), JS_PSG("signals_block_symbol", symbol_getter<&GjsAtoms::signals_block>, GJS_MODULE_PROP_FLAGS), JS_PSG("signals_unblock_symbol", symbol_getter<&GjsAtoms::signals_unblock>, GJS_MODULE_PROP_FLAGS), JS_PSG("signals_disconnect_symbol", symbol_getter<&GjsAtoms::signals_disconnect>, GJS_MODULE_PROP_FLAGS), JS_PS_END}; bool gjs_define_private_gi_stuff(JSContext* cx, JS::MutableHandleObject module) { module.set(JS_NewPlainObject(cx)); return JS_DefineFunctions(cx, module, module_funcs) && JS_DefineProperties(cx, module, module_props); } cjs-5.2.0/gi/private.h0000644000175000017500000000263414144444702014666 0ustar jpeisachjpeisach/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* * Copyright (c) 2008 litl, LLC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #ifndef GI_PRIVATE_H_ #define GI_PRIVATE_H_ #include #include #include "cjs/macros.h" GJS_JSAPI_RETURN_CONVENTION bool gjs_define_private_gi_stuff(JSContext* cx, JS::MutableHandleObject module); #endif // GI_PRIVATE_H_ cjs-5.2.0/gi/function.h0000644000175000017500000000572514144444702015045 0ustar jpeisachjpeisach/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* * Copyright (c) 2008 litl, LLC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #ifndef GI_FUNCTION_H_ #define GI_FUNCTION_H_ #include #include #include #include #include #include #include "cjs/macros.h" namespace JS { class CallArgs; } typedef enum { PARAM_NORMAL, PARAM_SKIPPED, PARAM_ARRAY, PARAM_CALLBACK, PARAM_UNKNOWN, } GjsParamType; struct GjsCallbackTrampoline { int ref_count; GICallableInfo *info; GClosure *js_function; ffi_cif cif; ffi_closure *closure; GIScopeType scope; bool is_vfunc; GjsParamType *param_types; }; GJS_JSAPI_RETURN_CONVENTION GjsCallbackTrampoline* gjs_callback_trampoline_new( JSContext* cx, JS::HandleFunction function, GICallableInfo* callable_info, GIScopeType scope, bool has_scope_object, bool is_vfunc); void gjs_callback_trampoline_unref(GjsCallbackTrampoline *trampoline); void gjs_callback_trampoline_ref(GjsCallbackTrampoline *trampoline); // Stack allocation only! struct GjsFunctionCallState { GIArgument* in_cvalues; GIArgument* out_cvalues; GIArgument* inout_original_cvalues; JS::RootedObject instance_object; bool call_completed; explicit GjsFunctionCallState(JSContext* cx) : instance_object(cx), call_completed(false) {} }; GJS_JSAPI_RETURN_CONVENTION JSObject *gjs_define_function(JSContext *context, JS::HandleObject in_object, GType gtype, GICallableInfo *info); GJS_JSAPI_RETURN_CONVENTION bool gjs_invoke_constructor_from_c(JSContext* cx, GIFunctionInfo* info, JS::HandleObject this_obj, const JS::CallArgs& args, GIArgument* rvalue); #endif // GI_FUNCTION_H_ cjs-5.2.0/gi/foreign.cpp0000644000175000017500000001426314144444702015201 0ustar jpeisachjpeisach/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* * Copyright (c) 2010 litl, LLC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #include #include // for strcmp #include #include #include #include "gi/arg.h" #include "gi/foreign.h" #include "cjs/context-private.h" #include "cjs/jsapi-util.h" static struct { char *gi_namespace; char *module; // relative to "imports." bool loaded; } foreign_modules[] = { { (char*)"cairo", (char*)"cairo", false }, { NULL } }; static GHashTable* foreign_structs_table = NULL; [[nodiscard]] static GHashTable* get_foreign_structs() { // FIXME: look into hasing on GITypeInfo instead. if (!foreign_structs_table) { foreign_structs_table = g_hash_table_new_full(g_str_hash, g_str_equal, (GDestroyNotify)g_free, NULL); } return foreign_structs_table; } [[nodiscard]] static bool gjs_foreign_load_foreign_module( JSContext* context, const char* gi_namespace) { int i; for (i = 0; foreign_modules[i].gi_namespace; ++i) { char *script; if (strcmp(gi_namespace, foreign_modules[i].gi_namespace) != 0) continue; if (foreign_modules[i].loaded) return true; // FIXME: Find a way to check if a module is imported // and only execute this statement if isn't script = g_strdup_printf("imports.%s;", gi_namespace); JS::RootedValue retval(context); GjsContextPrivate* gjs = GjsContextPrivate::from_cx(context); if (!gjs->eval_with_scope(nullptr, script, -1, "", &retval)) { g_critical("ERROR importing foreign module %s\n", gi_namespace); g_free(script); return false; } g_free(script); foreign_modules[i].loaded = true; return true; } return false; } void gjs_struct_foreign_register(const char* gi_namespace, const char* type_name, GjsForeignInfo* info) { char *canonical_name; g_return_if_fail(info); g_return_if_fail(info->to_func); g_return_if_fail(info->from_func); canonical_name = g_strdup_printf("%s.%s", gi_namespace, type_name); g_hash_table_insert(get_foreign_structs(), canonical_name, info); } [[nodiscard]] static GjsForeignInfo* gjs_struct_foreign_lookup( JSContext* context, GIBaseInfo* interface_info) { GjsForeignInfo *retval = NULL; GHashTable *hash_table; char *key; key = g_strdup_printf("%s.%s", g_base_info_get_namespace(interface_info), g_base_info_get_name(interface_info)); hash_table = get_foreign_structs(); retval = (GjsForeignInfo*)g_hash_table_lookup(hash_table, key); if (!retval) { if (gjs_foreign_load_foreign_module(context, g_base_info_get_namespace(interface_info))) { retval = (GjsForeignInfo*)g_hash_table_lookup(hash_table, key); } } if (!retval) { gjs_throw(context, "Unable to find module implementing foreign type %s.%s", g_base_info_get_namespace(interface_info), g_base_info_get_name(interface_info)); } g_free(key); return retval; } bool gjs_struct_foreign_convert_to_g_argument(JSContext *context, JS::Value value, GIBaseInfo *interface_info, const char *arg_name, GjsArgumentType argument_type, GITransfer transfer, bool may_be_null, GArgument *arg) { GjsForeignInfo *foreign; foreign = gjs_struct_foreign_lookup(context, interface_info); if (!foreign) return false; if (!foreign->to_func(context, value, arg_name, argument_type, transfer, may_be_null, arg)) return false; return true; } bool gjs_struct_foreign_convert_from_g_argument(JSContext *context, JS::MutableHandleValue value_p, GIBaseInfo *interface_info, GIArgument *arg) { GjsForeignInfo *foreign; foreign = gjs_struct_foreign_lookup(context, interface_info); if (!foreign) return false; if (!foreign->from_func(context, value_p, arg)) return false; return true; } bool gjs_struct_foreign_release_g_argument(JSContext *context, GITransfer transfer, GIBaseInfo *interface_info, GArgument *arg) { GjsForeignInfo *foreign; foreign = gjs_struct_foreign_lookup(context, interface_info); if (!foreign) return false; if (!foreign->release_func) return true; if (!foreign->release_func(context, transfer, arg)) return false; return true; } cjs-5.2.0/gi/enumeration.h0000644000175000017500000000332314144444702015536 0ustar jpeisachjpeisach/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* * Copyright (c) 2008 litl, LLC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #ifndef GI_ENUMERATION_H_ #define GI_ENUMERATION_H_ #include #include #include #include "cjs/macros.h" GJS_JSAPI_RETURN_CONVENTION bool gjs_define_enum_values(JSContext *context, JS::HandleObject in_object, GIEnumInfo *info); GJS_JSAPI_RETURN_CONVENTION bool gjs_define_enumeration(JSContext *context, JS::HandleObject in_object, GIEnumInfo *info); #endif // GI_ENUMERATION_H_ cjs-5.2.0/gi/closure.h0000644000175000017500000000427714144444702014675 0ustar jpeisachjpeisach/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* * Copyright (c) 2008 litl, LLC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #ifndef GI_CLOSURE_H_ #define GI_CLOSURE_H_ #include #include #include class JSTracer; namespace JS { class HandleValueArray; } [[nodiscard]] GClosure* gjs_closure_new(JSContext* cx, JSFunction* callable, const char* description, bool root_function); [[nodiscard]] bool gjs_closure_invoke(GClosure* closure, JS::HandleObject this_obj, const JS::HandleValueArray& args, JS::MutableHandleValue retval, bool return_exception); [[nodiscard]] JSContext* gjs_closure_get_context(GClosure* closure); [[nodiscard]] bool gjs_closure_is_valid(GClosure* closure); [[nodiscard]] JSFunction* gjs_closure_get_callable(GClosure* closure); void gjs_closure_trace (GClosure *closure, JSTracer *tracer); #endif // GI_CLOSURE_H_ cjs-5.2.0/gi/repo.h0000644000175000017500000000547414144444702014166 0ustar jpeisachjpeisach/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* * Copyright (c) 2008 litl, LLC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #ifndef GI_REPO_H_ #define GI_REPO_H_ #include #include #include #include "cjs/macros.h" #include "util/log.h" GJS_JSAPI_RETURN_CONVENTION bool gjs_define_repo(JSContext *cx, JS::MutableHandleObject repo); [[nodiscard]] const char* gjs_info_type_name(GIInfoType type); GJS_JSAPI_RETURN_CONVENTION JSObject* gjs_lookup_private_namespace (JSContext *context); GJS_JSAPI_RETURN_CONVENTION JSObject* gjs_lookup_namespace_object (JSContext *context, GIBaseInfo *info); GJS_JSAPI_RETURN_CONVENTION JSObject *gjs_lookup_namespace_object_by_name(JSContext *context, JS::HandleId name); GJS_JSAPI_RETURN_CONVENTION JSObject * gjs_lookup_generic_constructor (JSContext *context, GIBaseInfo *info); GJS_JSAPI_RETURN_CONVENTION JSObject * gjs_lookup_generic_prototype (JSContext *context, GIBaseInfo *info); GJS_JSAPI_RETURN_CONVENTION JSObject* gjs_new_object_with_generic_prototype(JSContext* cx, GIBaseInfo* info); GJS_JSAPI_RETURN_CONVENTION bool gjs_define_info(JSContext *context, JS::HandleObject in_object, GIBaseInfo *info, bool *defined); [[nodiscard]] char* gjs_hyphen_from_camel(const char* camel_name); #if GJS_VERBOSE_ENABLE_GI_USAGE void _gjs_log_info_usage(GIBaseInfo *info); #endif #endif // GI_REPO_H_ cjs-5.2.0/gi/gtype.cpp0000644000175000017500000001601614144444702014676 0ustar jpeisachjpeisach/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* * Copyright (c) 2008 litl, LLC * Copyright (c) 2012 Red Hat, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #include #include // for (implicit) move #include #include #include // for SystemAllocPolicy #include #include #include // for WeakCache #include // for JSPROP_PERMANENT #include #include #include #include // for JS_GetPropertyById, JS_AtomizeString #include #include "gi/gtype.h" #include "cjs/atoms.h" #include "cjs/context-private.h" #include "cjs/jsapi-class.h" #include "cjs/jsapi-util.h" [[nodiscard]] [[maybe_unused]] static JSObject* gjs_gtype_get_proto(JSContext*); GJS_JSAPI_RETURN_CONVENTION static bool gjs_gtype_define_proto(JSContext *, JS::HandleObject, JS::MutableHandleObject); GJS_DEFINE_PROTO_ABSTRACT("GIRepositoryGType", gtype, JSCLASS_FOREGROUND_FINALIZE); /* priv_from_js adds a "*", so this returns "void *" */ GJS_DEFINE_PRIV_FROM_JS(void, gjs_gtype_class); static void gjs_gtype_finalize(JSFreeOp*, JSObject*) { // No private data is allocated, it's stuffed directly in the private field // of JSObject, so nothing to free } GJS_JSAPI_RETURN_CONVENTION static bool to_string_func(JSContext *cx, unsigned argc, JS::Value *vp) { GJS_GET_PRIV(cx, argc, vp, rec, obj, void, priv); GType gtype = GPOINTER_TO_SIZE(priv); if (gtype == 0) { JS::RootedString str(cx, JS_AtomizeString(cx, "[object GType prototype]")); if (!str) return false; rec.rval().setString(str); return true; } GjsAutoChar strval = g_strdup_printf("[object GType for '%s']", g_type_name(gtype)); return gjs_string_from_utf8(cx, strval, rec.rval()); } GJS_JSAPI_RETURN_CONVENTION static bool get_name_func (JSContext *context, unsigned argc, JS::Value *vp) { GJS_GET_PRIV(context, argc, vp, rec, obj, void, priv); GType gtype; gtype = GPOINTER_TO_SIZE(priv); if (gtype == 0) { rec.rval().setNull(); return true; } return gjs_string_from_utf8(context, g_type_name(gtype), rec.rval()); } /* Properties */ JSPropertySpec gjs_gtype_proto_props[] = { JS_PSG("name", get_name_func, JSPROP_PERMANENT), JS_STRING_SYM_PS(toStringTag, "GIRepositoryGType", JSPROP_READONLY), JS_PS_END, }; /* Functions */ JSFunctionSpec gjs_gtype_proto_funcs[] = { JS_FN("toString", to_string_func, 0, 0), JS_FS_END}; JSFunctionSpec gjs_gtype_static_funcs[] = { JS_FS_END }; JSObject * gjs_gtype_create_gtype_wrapper (JSContext *context, GType gtype) { g_assert(((void) "Attempted to create wrapper object for invalid GType", gtype != 0)); GjsContextPrivate* gjs = GjsContextPrivate::from_cx(context); // We cannot use gtype_table().lookupForAdd() here, because in between the // lookup and the add, GCs may take place and mutate the hash table. A GC // may only remove an element, not add one, so it's still safe to do this // without locking. auto p = gjs->gtype_table().lookup(gtype); if (p.found()) return p->value(); JS::RootedObject proto(context); if (!gjs_gtype_define_proto(context, nullptr, &proto)) return nullptr; JS::RootedObject gtype_wrapper( context, JS_NewObjectWithGivenProto(context, &gjs_gtype_class, proto)); if (!gtype_wrapper) return nullptr; JS_SetPrivate(gtype_wrapper, GSIZE_TO_POINTER(gtype)); gjs->gtype_table().put(gtype, gtype_wrapper); return gtype_wrapper; } GJS_JSAPI_RETURN_CONVENTION static bool _gjs_gtype_get_actual_gtype(JSContext* context, const GjsAtoms& atoms, JS::HandleObject object, GType* gtype_out, int recurse) { if (JS_InstanceOf(context, object, &gjs_gtype_class, nullptr)) { *gtype_out = GPOINTER_TO_SIZE(priv_from_js(context, object)); return true; } JS::RootedValue gtype_val(context); /* OK, we don't have a GType wrapper object -- grab the "$gtype" * property on that and hope it's a GType wrapper object */ if (!JS_GetPropertyById(context, object, atoms.gtype(), >ype_val)) return false; if (!gtype_val.isObject()) { /* OK, so we're not a class. But maybe we're an instance. Check for "constructor" and recurse on that. */ if (!JS_GetPropertyById(context, object, atoms.constructor(), >ype_val)) return false; } if (recurse > 0 && gtype_val.isObject()) { JS::RootedObject gtype_obj(context, >ype_val.toObject()); return _gjs_gtype_get_actual_gtype(context, atoms, gtype_obj, gtype_out, recurse - 1); } *gtype_out = G_TYPE_INVALID; return true; } bool gjs_gtype_get_actual_gtype(JSContext* context, JS::HandleObject object, GType* gtype_out) { g_assert(gtype_out && "Missing return location"); /* 2 means: recurse at most three times (including this call). The levels are calculated considering that, in the worst case we need to go from instance to class, from class to GType object and from GType object to GType value. */ const GjsAtoms& atoms = GjsContextPrivate::atoms(context); return _gjs_gtype_get_actual_gtype(context, atoms, object, gtype_out, 2); } bool gjs_typecheck_gtype (JSContext *context, JS::HandleObject obj, bool throw_error) { return do_base_typecheck(context, obj, throw_error); } cjs-5.2.0/gi/utils-inl.h0000644000175000017500000000212714144444702015131 0ustar jpeisachjpeisach/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* * SPDX-License-Identifier: MIT OR LGPL-2.0-or-later * * Copyright (c) 2020 Marco Trevisan */ #pragma once #include #include // IWYU pragma: keep template constexpr void* gjs_int_to_pointer(T v) { static_assert(std::is_integral_v, "Need integer value"); if constexpr (std::is_signed_v) return reinterpret_cast(static_cast(v)); else return reinterpret_cast(static_cast(v)); } template constexpr T gjs_pointer_to_int(void* p) { static_assert(std::is_integral_v, "Need integer value"); if constexpr (std::is_signed_v) return static_cast(reinterpret_cast(p)); else return static_cast(reinterpret_cast(p)); } template <> inline void* gjs_int_to_pointer(bool v) { return gjs_int_to_pointer(!!v); } template <> inline bool gjs_pointer_to_int(void* p) { return !!gjs_pointer_to_int(p); } cjs-5.2.0/gi/toggle.cpp0000644000175000017500000001415114144444702015025 0ustar jpeisachjpeisach/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* * Copyright (c) 2017 Endless Mobile, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. * * Authored by: Philip Chimento , */ #include // for find_if #include #include #include // for pair #include #include #include "gi/toggle.h" std::deque::iterator ToggleQueue::find_operation_locked(const GObject *gobj, ToggleQueue::Direction direction) { return std::find_if(q.begin(), q.end(), [gobj, direction](const Item& item)->bool { return item.gobj == gobj && item.direction == direction; }); } std::deque::const_iterator ToggleQueue::find_operation_locked(const GObject *gobj, ToggleQueue::Direction direction) const { return std::find_if(q.begin(), q.end(), [gobj, direction](const Item& item)->bool { return item.gobj == gobj && item.direction == direction; }); } bool ToggleQueue::find_and_erase_operation_locked(const GObject *gobj, ToggleQueue::Direction direction) { auto pos = find_operation_locked(gobj, direction); bool had_toggle = (pos != q.end()); if (had_toggle) q.erase(pos); return had_toggle; } gboolean ToggleQueue::idle_handle_toggle(void *data) { auto self = static_cast(data); while (self->handle_toggle(self->m_toggle_handler)) ; return G_SOURCE_REMOVE; } void ToggleQueue::idle_destroy_notify(void *data) { auto self = static_cast(data); std::lock_guard hold(self->lock); self->m_idle_id = 0; self->m_toggle_handler = nullptr; } std::pair ToggleQueue::is_queued(GObject *gobj) const { std::lock_guard hold(lock); bool has_toggle_down = find_operation_locked(gobj, DOWN) != q.end(); bool has_toggle_up = find_operation_locked(gobj, UP) != q.end(); return {has_toggle_down, has_toggle_up}; } std::pair ToggleQueue::cancel(GObject *gobj) { debug("cancel", gobj); std::lock_guard hold(lock); bool had_toggle_down = find_and_erase_operation_locked(gobj, DOWN); bool had_toggle_up = find_and_erase_operation_locked(gobj, UP); gjs_debug_lifecycle(GJS_DEBUG_GOBJECT, "ToggleQueue: %p (%s) was %s", gobj, G_OBJECT_TYPE_NAME(gobj), had_toggle_down && had_toggle_up ? "queued to toggle BOTH" : had_toggle_down ? "queued to toggle DOWN" : had_toggle_up ? "queued to toggle UP" : "not queued"); return {had_toggle_down, had_toggle_up}; } bool ToggleQueue::handle_toggle(Handler handler) { Item item; { std::lock_guard hold(lock); if (q.empty()) return false; item = q.front(); handler(item.gobj, item.direction); q.pop_front(); } debug("handle", item.gobj); if (item.needs_unref) g_object_unref(item.gobj); return true; } void ToggleQueue::shutdown(void) { debug("shutdown", nullptr); g_assert(((void)"Queue should have been emptied before shutting down", q.empty())); m_shutdown = true; } void ToggleQueue::enqueue(GObject *gobj, ToggleQueue::Direction direction, ToggleQueue::Handler handler) { if (G_UNLIKELY (m_shutdown)) { gjs_debug(GJS_DEBUG_GOBJECT, "Enqueuing GObject %p to toggle %s after " "shutdown, probably from another thread (%p).", gobj, direction == UP ? "UP" : "DOWN", g_thread_self()); return; } Item item{gobj, direction}; /* If we're toggling up we take a reference to the object now, * so it won't toggle down before we process it. This ensures we * only ever have at most two toggle notifications queued. * (either only up, or down-up) */ if (direction == UP) { debug("enqueue UP", gobj); g_object_ref(gobj); item.needs_unref = true; } else { debug("enqueue DOWN", gobj); } /* If we're toggling down, we don't need to take a reference since * the associated JSObject already has one, and that JSObject won't * get finalized until we've completed toggling (since it's rooted, * until we unroot it when we dispatch the toggle down idle). * * Taking a reference now would be bad anyway, since it would force * the object to toggle back up again. */ std::lock_guard hold(lock); q.push_back(item); if (m_idle_id) { g_assert(((void) "Should always enqueue with the same handler", m_toggle_handler == handler)); return; } m_toggle_handler = handler; m_idle_id = g_idle_add_full(G_PRIORITY_HIGH, idle_handle_toggle, this, idle_destroy_notify); } cjs-5.2.0/gi/function.cpp0000644000175000017500000014244714144444702015403 0ustar jpeisachjpeisach/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* * Copyright (c) 2008 litl, LLC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #include #include #include // for exit #include // for strcmp, memset, size_t #include #include #include #include #include #include #include #include #include #include #include #include // for JSPROP_PERMANENT #include #include // for GetRealmFunctionPrototype #include #include #include #include #include // for HandleValueArray, JS_GetElement #include "gi/arg-cache.h" #include "gi/arg-inl.h" #include "gi/arg.h" #include "gi/closure.h" #include "gi/function.h" #include "gi/gerror.h" #include "gi/object.h" #include "gi/utils-inl.h" #include "cjs/context-private.h" #include "cjs/context.h" #include "cjs/jsapi-class.h" #include "cjs/jsapi-util.h" #include "cjs/mem-private.h" #include "util/log.h" /* We use guint8 for arguments; functions can't * have more than this. */ #define GJS_ARG_INDEX_INVALID G_MAXUINT8 typedef struct { GICallableInfo* info; GjsArgumentCache* arguments; uint8_t js_in_argc; guint8 js_out_argc; GIFunctionInvoker invoker; } Function; extern struct JSClass gjs_function_class; /* Because we can't free the mmap'd data for a callback * while it's in use, this list keeps track of ones that * will be freed the next time we invoke a C function. */ static GSList *completed_trampolines = NULL; /* GjsCallbackTrampoline */ GJS_DEFINE_PRIV_FROM_JS(Function, gjs_function_class) void gjs_callback_trampoline_ref(GjsCallbackTrampoline *trampoline) { trampoline->ref_count++; } void gjs_callback_trampoline_unref(GjsCallbackTrampoline *trampoline) { /* Not MT-safe, like all the rest of GJS */ trampoline->ref_count--; if (trampoline->ref_count == 0) { g_clear_pointer(&trampoline->js_function, g_closure_unref); if (trampoline->info && trampoline->closure) g_callable_info_free_closure(trampoline->info, trampoline->closure); g_clear_pointer(&trampoline->info, g_base_info_unref); g_free (trampoline->param_types); g_slice_free(GjsCallbackTrampoline, trampoline); } } template static inline std::enable_if_t && std::is_signed_v> set_ffi_arg(void* result, GIArgument* value) { *static_cast(result) = gjs_arg_get(value); } template static inline std::enable_if_t || std::is_unsigned_v> set_ffi_arg(void* result, GIArgument* value) { *static_cast(result) = gjs_arg_get(value); } template static inline std::enable_if_t> set_ffi_arg( void* result, GIArgument* value) { *static_cast(result) = gjs_pointer_to_int(gjs_arg_get(value)); } static void set_return_ffi_arg_from_giargument (GITypeInfo *ret_type, void *result, GIArgument *return_value) { // Be consistent with gjs_value_to_g_argument() switch (g_type_info_get_tag(ret_type)) { case GI_TYPE_TAG_VOID: g_assert_not_reached(); case GI_TYPE_TAG_INT8: set_ffi_arg(result, return_value); break; case GI_TYPE_TAG_UINT8: set_ffi_arg(result, return_value); break; case GI_TYPE_TAG_INT16: set_ffi_arg(result, return_value); break; case GI_TYPE_TAG_UINT16: set_ffi_arg(result, return_value); break; case GI_TYPE_TAG_INT32: set_ffi_arg(result, return_value); break; case GI_TYPE_TAG_UINT32: set_ffi_arg(result, return_value); break; case GI_TYPE_TAG_BOOLEAN: set_ffi_arg(result, return_value); break; case GI_TYPE_TAG_UNICHAR: set_ffi_arg(result, return_value); break; case GI_TYPE_TAG_INT64: set_ffi_arg(result, return_value); break; case GI_TYPE_TAG_INTERFACE: { GIBaseInfo* interface_info; GIInfoType interface_type; interface_info = g_type_info_get_interface(ret_type); interface_type = g_base_info_get_type(interface_info); if (interface_type == GI_INFO_TYPE_ENUM || interface_type == GI_INFO_TYPE_FLAGS) set_ffi_arg(result, return_value); else set_ffi_arg(result, return_value); g_base_info_unref(interface_info); } break; case GI_TYPE_TAG_UINT64: // Other primitive types need to squeeze into 64-bit ffi_arg too set_ffi_arg(result, return_value); break; case GI_TYPE_TAG_FLOAT: set_ffi_arg(result, return_value); break; case GI_TYPE_TAG_DOUBLE: set_ffi_arg(result, return_value); break; case GI_TYPE_TAG_GTYPE: set_ffi_arg(result, return_value); break; case GI_TYPE_TAG_UTF8: case GI_TYPE_TAG_FILENAME: set_ffi_arg(result, return_value); break; case GI_TYPE_TAG_ARRAY: case GI_TYPE_TAG_GLIST: case GI_TYPE_TAG_GSLIST: case GI_TYPE_TAG_GHASH: case GI_TYPE_TAG_ERROR: default: set_ffi_arg(result, return_value); break; } } static void warn_about_illegal_js_callback(const GjsCallbackTrampoline *trampoline, const char *when, const char *reason) { g_critical("Attempting to run a JS callback %s. This is most likely caused " "by %s. Because it would crash the application, it has been " "blocked.", when, reason); if (trampoline->info) { const char *name = g_base_info_get_name(trampoline->info); g_critical("The offending callback was %s()%s.", name, trampoline->is_vfunc ? ", a vfunc" : ""); } return; } /* This is our main entry point for ffi_closure callbacks. * ffi_prep_closure is doing pure magic and replaces the original * function call with this one which gives us the ffi arguments, * a place to store the return value and our use data. * In other words, everything we need to call the JS function and * getting the return value back. */ static void gjs_callback_closure(ffi_cif* cif [[maybe_unused]], void* result, void** ffi_args, void* data) { JSContext *context; GjsCallbackTrampoline *trampoline; int i, n_args, n_jsargs, n_outargs, c_args_offset = 0; GITypeInfo ret_type; bool success = false; auto args = reinterpret_cast(ffi_args); trampoline = (GjsCallbackTrampoline *) data; g_assert(trampoline); gjs_callback_trampoline_ref(trampoline); if (G_UNLIKELY(!gjs_closure_is_valid(trampoline->js_function))) { warn_about_illegal_js_callback(trampoline, "during shutdown", "destroying a Clutter actor or GTK widget with ::destroy signal " "connected, or using the destroy(), dispose(), or remove() vfuncs"); gjs_dumpstack(); gjs_callback_trampoline_unref(trampoline); return; } context = gjs_closure_get_context(trampoline->js_function); GjsContextPrivate* gjs = GjsContextPrivate::from_cx(context); if (G_UNLIKELY(gjs->sweeping())) { warn_about_illegal_js_callback(trampoline, "during garbage collection", "destroying a Clutter actor or GTK widget with ::destroy signal " "connected, or using the destroy(), dispose(), or remove() vfuncs"); gjs_dumpstack(); gjs_callback_trampoline_unref(trampoline); return; } if (G_UNLIKELY(!gjs->is_owner_thread())) { warn_about_illegal_js_callback(trampoline, "on a different thread", "an API not intended to be used in JS"); gjs_callback_trampoline_unref(trampoline); return; } JSAutoRealm ar(context, JS_GetFunctionObject(gjs_closure_get_callable( trampoline->js_function))); bool can_throw_gerror = g_callable_info_can_throw_gerror(trampoline->info); n_args = g_callable_info_get_n_args(trampoline->info); g_assert(n_args >= 0); JS::RootedObject this_object(context); if (trampoline->is_vfunc) { GObject* gobj = G_OBJECT(gjs_arg_get(args[0])); if (gobj) { this_object = ObjectInstance::wrapper_from_gobject(context, gobj); if (!this_object) { gjs_log_exception(context); return; } } /* "this" is not included in the GI signature, but is in the C (and * FFI) signature */ c_args_offset = 1; } n_outargs = 0; JS::RootedValueVector jsargs(context); if (!jsargs.reserve(n_args)) g_error("Unable to reserve space for vector"); JS::RootedValue rval(context); g_callable_info_load_return_type(trampoline->info, &ret_type); bool ret_type_is_void = g_type_info_get_tag (&ret_type) == GI_TYPE_TAG_VOID; for (i = 0, n_jsargs = 0; i < n_args; i++) { GIArgInfo arg_info; GITypeInfo type_info; GjsParamType param_type; g_callable_info_load_arg(trampoline->info, i, &arg_info); g_arg_info_load_type(&arg_info, &type_info); /* Skip void * arguments */ if (g_type_info_get_tag(&type_info) == GI_TYPE_TAG_VOID) continue; if (g_arg_info_get_direction(&arg_info) == GI_DIRECTION_OUT) { n_outargs++; continue; } if (g_arg_info_get_direction(&arg_info) == GI_DIRECTION_INOUT) n_outargs++; param_type = trampoline->param_types[i]; switch (param_type) { case PARAM_SKIPPED: continue; case PARAM_ARRAY: { gint array_length_pos = g_type_info_get_array_length(&type_info); GIArgInfo array_length_arg; GITypeInfo arg_type_info; JS::RootedValue length(context); g_callable_info_load_arg(trampoline->info, array_length_pos, &array_length_arg); g_arg_info_load_type(&array_length_arg, &arg_type_info); if (!gjs_value_from_g_argument(context, &length, &arg_type_info, args[array_length_pos + c_args_offset], true)) goto out; if (!jsargs.growBy(1)) g_error("Unable to grow vector"); if (!gjs_value_from_explicit_array(context, jsargs[n_jsargs++], &type_info, args[i + c_args_offset], length.toInt32())) goto out; break; } case PARAM_NORMAL: { if (!jsargs.growBy(1)) g_error("Unable to grow vector"); GIArgument* arg = args[i + c_args_offset]; if (g_arg_info_get_direction(&arg_info) == GI_DIRECTION_INOUT) arg = *reinterpret_cast(arg); if (!gjs_value_from_g_argument(context, jsargs[n_jsargs++], &type_info, arg, false)) goto out; break; } case PARAM_CALLBACK: /* Callbacks that accept another callback as a parameter are not * supported, see gjs_callback_trampoline_new() */ case PARAM_UNKNOWN: // PARAM_UNKNOWN is currently not ever set on a callback's args. default: g_assert_not_reached(); } } if (!gjs_closure_invoke(trampoline->js_function, this_object, jsargs, &rval, true)) goto out; if (n_outargs == 0 && ret_type_is_void) { /* void return value, no out args, nothing to do */ } else if (n_outargs == 0) { GIArgument argument; GITransfer transfer; transfer = g_callable_info_get_caller_owns (trampoline->info); /* non-void return value, no out args. Should * be a single return value. */ if (!gjs_value_to_g_argument(context, rval, &ret_type, "callback", GJS_ARGUMENT_RETURN_VALUE, transfer, true, &argument)) goto out; set_return_ffi_arg_from_giargument(&ret_type, result, &argument); } else if (n_outargs == 1 && ret_type_is_void) { /* void return value, one out args. Should * be a single return value. */ for (i = 0; i < n_args; i++) { GIArgInfo arg_info; g_callable_info_load_arg(trampoline->info, i, &arg_info); if (g_arg_info_get_direction(&arg_info) == GI_DIRECTION_IN) continue; if (!gjs_value_to_arg(context, rval, &arg_info, *reinterpret_cast(args[i + c_args_offset]))) goto out; break; } } else { bool is_array = rval.isObject(); if (!JS::IsArrayObject(context, rval, &is_array)) goto out; if (!is_array) { JSFunction* fn = gjs_closure_get_callable(trampoline->js_function); gjs_throw(context, "Function %s (%s.%s) returned unexpected value, " "expecting an Array", gjs_debug_string(JS_GetFunctionDisplayId(fn)).c_str(), g_base_info_get_namespace(trampoline->info), g_base_info_get_name(trampoline->info)); goto out; } JS::RootedValue elem(context); JS::RootedObject out_array(context, rval.toObjectOrNull()); gsize elem_idx = 0; /* more than one of a return value or an out argument. * Should be an array of output values. */ if (!ret_type_is_void) { GIArgument argument; GITransfer transfer = g_callable_info_get_caller_owns(trampoline->info); if (!JS_GetElement(context, out_array, elem_idx, &elem)) goto out; if (!gjs_value_to_g_argument(context, elem, &ret_type, "callback", GJS_ARGUMENT_RETURN_VALUE, transfer, true, &argument)) goto out; set_return_ffi_arg_from_giargument(&ret_type, result, &argument); elem_idx++; } for (i = 0; i < n_args; i++) { GIArgInfo arg_info; g_callable_info_load_arg(trampoline->info, i, &arg_info); if (g_arg_info_get_direction(&arg_info) == GI_DIRECTION_IN) continue; if (!JS_GetElement(context, out_array, elem_idx, &elem)) goto out; if (!gjs_value_to_arg(context, elem, &arg_info, *(GIArgument **)args[i + c_args_offset])) goto out; elem_idx++; } } success = true; out: if (!success) { if (!JS_IsExceptionPending(context)) { /* "Uncatchable" exception thrown, we have to exit. We may be in a * main loop, or maybe not, but there's no way to tell, so we have * to exit here instead of propagating the exception back to the * original calling JS code. */ uint8_t code; if (gjs->should_exit(&code)) exit(code); /* Some other uncatchable exception, e.g. out of memory */ JSFunction* fn = gjs_closure_get_callable(trampoline->js_function); g_error("Function %s (%s.%s) terminated with uncatchable exception", gjs_debug_string(JS_GetFunctionDisplayId(fn)).c_str(), g_base_info_get_namespace(trampoline->info), g_base_info_get_name(trampoline->info)); } /* Fill in the result with some hopefully neutral value */ if (!ret_type_is_void) { GIArgument argument = {}; g_callable_info_load_return_type(trampoline->info, &ret_type); gjs_gi_argument_init_default(&ret_type, &argument); set_return_ffi_arg_from_giargument(&ret_type, result, &argument); } /* If the callback has a GError** argument and invoking the closure * returned an error, try to make a GError from it */ if (can_throw_gerror && rval.isObject()) { JS::RootedObject exc_object(context, &rval.toObject()); GError *local_error = gjs_gerror_make_from_error(context, exc_object); if (local_error) { /* the GError ** pointer is the last argument, and is not * included in the n_args */ GIArgument *error_argument = args[n_args + c_args_offset]; auto* gerror = gjs_arg_get(error_argument); g_propagate_error(gerror, local_error); JS_ClearPendingException(context); /* don't log */ } } else if (!rval.isUndefined()) { JS_SetPendingException(context, rval); } gjs_log_exception_uncaught(context); } if (trampoline->scope == GI_SCOPE_TYPE_ASYNC) { completed_trampolines = g_slist_prepend(completed_trampolines, trampoline); } gjs_callback_trampoline_unref(trampoline); gjs->schedule_gc_if_needed(); } GjsCallbackTrampoline* gjs_callback_trampoline_new( JSContext* context, JS::HandleFunction function, GICallableInfo* callable_info, GIScopeType scope, bool has_scope_object, bool is_vfunc) { GjsCallbackTrampoline *trampoline; int n_args, i; g_assert(function); trampoline = g_slice_new(GjsCallbackTrampoline); new (trampoline) GjsCallbackTrampoline(); trampoline->ref_count = 1; trampoline->info = callable_info; g_base_info_ref((GIBaseInfo*)trampoline->info); /* Analyze param types and directions, similarly to init_cached_function_data */ n_args = g_callable_info_get_n_args(trampoline->info); trampoline->param_types = g_new0(GjsParamType, n_args); for (i = 0; i < n_args; i++) { GIDirection direction; GIArgInfo arg_info; GITypeInfo type_info; GITypeTag type_tag; if (trampoline->param_types[i] == PARAM_SKIPPED) continue; g_callable_info_load_arg(trampoline->info, i, &arg_info); g_arg_info_load_type(&arg_info, &type_info); direction = g_arg_info_get_direction(&arg_info); type_tag = g_type_info_get_tag(&type_info); if (direction != GI_DIRECTION_IN) { /* INOUT and OUT arguments are handled differently. */ continue; } if (type_tag == GI_TYPE_TAG_INTERFACE) { GIBaseInfo* interface_info; GIInfoType interface_type; interface_info = g_type_info_get_interface(&type_info); interface_type = g_base_info_get_type(interface_info); if (interface_type == GI_INFO_TYPE_CALLBACK) { gjs_throw(context, "%s %s accepts another callback as a parameter. This " "is not supported", is_vfunc ? "VFunc" : "Callback", g_base_info_get_name(callable_info)); g_base_info_unref(interface_info); gjs_callback_trampoline_unref(trampoline); return NULL; } g_base_info_unref(interface_info); } else if (type_tag == GI_TYPE_TAG_ARRAY) { if (g_type_info_get_array_type(&type_info) == GI_ARRAY_TYPE_C) { int array_length_pos = g_type_info_get_array_length(&type_info); if (array_length_pos >= 0 && array_length_pos < n_args) { GIArgInfo length_arg_info; g_callable_info_load_arg(trampoline->info, array_length_pos, &length_arg_info); if (g_arg_info_get_direction(&length_arg_info) != direction) { gjs_throw(context, "%s %s has an array with different-direction " "length argument. This is not supported", is_vfunc ? "VFunc" : "Callback", g_base_info_get_name(callable_info)); gjs_callback_trampoline_unref(trampoline); return NULL; } trampoline->param_types[array_length_pos] = PARAM_SKIPPED; trampoline->param_types[i] = PARAM_ARRAY; } } } } trampoline->closure = g_callable_info_prepare_closure(callable_info, &trampoline->cif, gjs_callback_closure, trampoline); // The rule is: // - notify callbacks in GObject methods are traced from the scope object // - async and call callbacks, and other notify callbacks, are rooted // - vfuncs are traced from the GObject prototype bool should_root = scope != GI_SCOPE_TYPE_NOTIFIED || !has_scope_object; trampoline->js_function = gjs_closure_new( context, function, g_base_info_get_name(callable_info), should_root); trampoline->scope = scope; trampoline->is_vfunc = is_vfunc; return trampoline; } /* Intended for error messages. Return value must be freed */ [[nodiscard]] static char* format_function_name(Function* function) { if (g_callable_info_is_method(function->info)) return g_strdup_printf( "method %s.%s.%s", g_base_info_get_namespace(function->info), g_base_info_get_name(g_base_info_get_container(function->info)), g_base_info_get_name(function->info)); return g_strdup_printf("function %s.%s", g_base_info_get_namespace(function->info), g_base_info_get_name(function->info)); } static void complete_async_calls(void) { if (completed_trampolines) { for (GSList *iter = completed_trampolines; iter; iter = iter->next) { auto trampoline = static_cast(iter->data); gjs_callback_trampoline_unref(trampoline); } g_slist_free(completed_trampolines); completed_trampolines = nullptr; } } static void* get_return_ffi_pointer_from_giargument( GjsArgumentCache* return_arg, GIFFIReturnValue* return_value) { // This should be the inverse of gi_type_info_extract_ffi_return_value(). if (return_arg->skip_out) return nullptr; // FIXME: Note that v_long and v_ulong don't have type-safe template // overloads yet, and I don't understand why they won't compile switch (g_type_info_get_tag(&return_arg->type_info)) { case GI_TYPE_TAG_INT8: case GI_TYPE_TAG_INT16: case GI_TYPE_TAG_INT32: return &return_value->v_long; case GI_TYPE_TAG_UINT8: case GI_TYPE_TAG_UINT16: case GI_TYPE_TAG_UINT32: case GI_TYPE_TAG_BOOLEAN: case GI_TYPE_TAG_UNICHAR: return &return_value->v_ulong; case GI_TYPE_TAG_INT64: return &gjs_arg_member(return_value); case GI_TYPE_TAG_UINT64: return &gjs_arg_member(return_value); case GI_TYPE_TAG_FLOAT: return &gjs_arg_member(return_value); case GI_TYPE_TAG_DOUBLE: return &gjs_arg_member(return_value); case GI_TYPE_TAG_INTERFACE: { GjsAutoBaseInfo info = g_type_info_get_interface(&return_arg->type_info); switch (g_base_info_get_type(info)) { case GI_INFO_TYPE_ENUM: case GI_INFO_TYPE_FLAGS: return &return_value->v_long; default: return &gjs_arg_member(return_value); } break; } default: return &gjs_arg_member(return_value); } } // This function can be called in two different ways. You can either use it to // create JavaScript objects by calling it without @r_value, or you can decide // to keep the return values in #GArgument format by providing a @r_value // argument. GJS_JSAPI_RETURN_CONVENTION static bool gjs_invoke_c_function(JSContext* context, Function* function, const JS::CallArgs& args, JS::HandleObject this_obj = nullptr, GIArgument* r_value = nullptr) { g_assert((args.isConstructing() || !this_obj) && "If not a constructor, then pass the 'this' object via CallArgs"); void* return_value_p; // will point inside the return GIArgument union GIFFIReturnValue return_value; int gi_argc, gi_arg_pos; bool can_throw_gerror; bool did_throw_gerror = false; GError *local_error = NULL; bool failed, postinvoke_release_failed; bool is_method; JS::RootedValueVector return_values(context); /* Because we can't free a closure while we're in it, we defer * freeing until the next time a C function is invoked. What * we should really do instead is queue it for a GC thread. */ complete_async_calls(); is_method = g_callable_info_is_method(function->info); can_throw_gerror = g_callable_info_can_throw_gerror(function->info); unsigned ffi_argc = function->invoker.cif.nargs; gi_argc = g_callable_info_get_n_args( (GICallableInfo*) function->info); if (gi_argc > GjsArgumentCache::MAX_ARGS) { GjsAutoChar name = format_function_name(function); gjs_throw(context, "Function %s has too many arguments", name.get()); return false; } // ffi_argc is the number of arguments that the underlying C function takes. // gi_argc is the number of arguments the GICallableInfo describes (which // does not include "this" or GError**). function->js_in_argc is the number // of arguments we expect the JS function to take (which does not include // PARAM_SKIPPED args). // args.length() is the number of arguments that were actually passed. if (args.length() > function->js_in_argc) { GjsAutoChar name = format_function_name(function); if (!JS::WarnUTF8(context, "Too many arguments to %s: expected %u, got %u", name.get(), function->js_in_argc, args.length())) return false; } else if (args.length() < function->js_in_argc) { GjsAutoChar name = format_function_name(function); args.reportMoreArgsNeeded(context, name, function->js_in_argc, args.length()); return false; } // These arrays hold argument pointers. // - state.in_cvalues: C values which are passed on input (in or inout) // - state.out_cvalues: C values which are returned as arguments (out or // inout) // - state.inout_original_cvalues: For the special case of (inout) args, we // need to keep track of the original values we passed into the function, // in case we need to free it. // - ffi_arg_pointers: For passing data to FFI, we need to create another // layer of indirection; this array is a pointer to an element in // state.in_cvalues or state.out_cvalues. // - return_value: The actual return value of the C function, i.e. not an // (out) param // // The 3 GIArgument arrays are indexed by the GI argument index, with the // following exceptions: // - [-1] is the return value (which can be nothing/garbage if the function // function returns void) // - [-2] is the instance parameter, if present // ffi_arg_pointers, on the other hand, represents the actual C arguments, // in the way ffi expects them. // // Use gi_arg_pos to index inside the GIArgument array. Use ffi_arg_pos to // index inside ffi_arg_pointers. GjsFunctionCallState state(context); if (is_method) { state.in_cvalues = g_newa(GIArgument, gi_argc + 2) + 2; state.out_cvalues = g_newa(GIArgument, gi_argc + 2) + 2; state.inout_original_cvalues = g_newa(GIArgument, gi_argc + 2) + 2; } else { state.in_cvalues = g_newa(GIArgument, gi_argc + 1) + 1; state.out_cvalues = g_newa(GIArgument, gi_argc + 1) + 1; state.inout_original_cvalues = g_newa(GIArgument, gi_argc + 1) + 1; } void** ffi_arg_pointers = g_newa(void*, ffi_argc); failed = false; unsigned ffi_arg_pos = 0; // index into ffi_arg_pointers unsigned js_arg_pos = 0; // index into args JS::RootedObject obj(context, this_obj); if (!args.isConstructing() && !args.computeThis(context, &obj)) return false; if (is_method) { GjsArgumentCache* cache = &function->arguments[-2]; GIArgument* in_value = &state.in_cvalues[-2]; JS::RootedValue in_js_value(context, JS::ObjectValue(*obj)); if (!cache->marshallers->in(context, cache, &state, in_value, in_js_value)) return false; ffi_arg_pointers[ffi_arg_pos] = in_value; ++ffi_arg_pos; // Callback lifetimes will be attached to the instance object if it is // a GObject or GInterface if (g_type_is_a(cache->contents.object.gtype, G_TYPE_OBJECT) || g_type_is_a(cache->contents.object.gtype, G_TYPE_INTERFACE)) state.instance_object = obj; } unsigned processed_c_args = ffi_arg_pos; for (gi_arg_pos = 0; gi_arg_pos < gi_argc; gi_arg_pos++, ffi_arg_pos++) { GjsArgumentCache* cache = &function->arguments[gi_arg_pos]; GIArgument* in_value = &state.in_cvalues[gi_arg_pos]; gjs_debug_marshal(GJS_DEBUG_GFUNCTION, "Marshalling argument '%s' in, %d/%d GI args, %u/%u " "C args, %u/%u JS args", cache->arg_name, gi_arg_pos, gi_argc, ffi_arg_pos, ffi_argc, js_arg_pos, args.length()); ffi_arg_pointers[ffi_arg_pos] = in_value; if (!cache->marshallers->in) { gjs_throw(context, "Error invoking %s.%s: impossible to determine what " "to pass to the '%s' argument. It may be that the " "function is unsupported, or there may be a bug in " "its annotations.", g_base_info_get_namespace(function->info), g_base_info_get_name(function->info), cache->arg_name); failed = true; break; } JS::RootedValue js_in_arg(context); if (js_arg_pos < args.length()) js_in_arg = args[js_arg_pos]; if (!cache->marshallers->in(context, cache, &state, in_value, js_in_arg)) { failed = true; break; } if (!cache->skip_in) js_arg_pos++; processed_c_args++; } // This pointer needs to exist on the stack across the ffi_call() call GError** errorp = &local_error; /* Did argument conversion fail? In that case, skip invocation and jump to release * processing. */ if (failed) { did_throw_gerror = false; goto release; } if (can_throw_gerror) { g_assert(ffi_arg_pos < ffi_argc && "GError** argument number mismatch"); ffi_arg_pointers[ffi_arg_pos] = &errorp; ffi_arg_pos++; /* don't update processed_c_args as we deal with local_error * separately */ } g_assert_cmpuint(ffi_arg_pos, ==, ffi_argc); g_assert_cmpuint(gi_arg_pos, ==, gi_argc); return_value_p = get_return_ffi_pointer_from_giargument( &function->arguments[-1], &return_value); ffi_call(&(function->invoker.cif), FFI_FN(function->invoker.native_address), return_value_p, ffi_arg_pointers); /* Return value and out arguments are valid only if invocation doesn't * return error. In arguments need to be released always. */ if (can_throw_gerror) { did_throw_gerror = local_error != NULL; } else { did_throw_gerror = false; } if (!r_value) args.rval().setUndefined(); if (!function->arguments[-1].skip_out) { gi_type_info_extract_ffi_return_value( &function->arguments[-1].type_info, &return_value, &state.out_cvalues[-1]); } // Process out arguments and return values. This loop is skipped if we fail // the type conversion above, or if did_throw_gerror is true. js_arg_pos = 0; for (gi_arg_pos = -1; gi_arg_pos < gi_argc; gi_arg_pos++) { GjsArgumentCache* cache = &function->arguments[gi_arg_pos]; GIArgument* out_value = &state.out_cvalues[gi_arg_pos]; gjs_debug_marshal(GJS_DEBUG_GFUNCTION, "Marshalling argument '%s' out, %d/%d GI args", cache->arg_name, gi_arg_pos, gi_argc); JS::RootedValue js_out_arg(context); if (!r_value) { if (!cache->marshallers->out(context, cache, &state, out_value, &js_out_arg)) { failed = true; break; } } if (!cache->skip_out) { if (!r_value) { if (!return_values.append(js_out_arg)) { JS_ReportOutOfMemory(context); failed = true; break; } } js_arg_pos++; } } g_assert(failed || did_throw_gerror || js_arg_pos == function->js_out_argc); release: // If we failed before calling the function, or if the function threw an // exception, then any GI_TRANSFER_EVERYTHING or GI_TRANSFER_CONTAINER // in-parameters were not transferred. Treat them as GI_TRANSFER_NOTHING so // that they are freed. if (!failed && !did_throw_gerror) state.call_completed = true; // In this loop we use ffi_arg_pos just to ensure we don't release stuff // we haven't allocated yet, if we failed in type conversion above. // If we start from -1 (the return value), we need to process 1 more than // processed_c_args. // If we start from -2 (the instance parameter), we need to process 2 more ffi_arg_pos = is_method ? 1 : 0; unsigned ffi_arg_max = processed_c_args + (is_method ? 2 : 1); postinvoke_release_failed = false; for (gi_arg_pos = is_method ? -2 : -1; gi_arg_pos < gi_argc && ffi_arg_pos < ffi_arg_max; gi_arg_pos++, ffi_arg_pos++) { GjsArgumentCache* cache = &function->arguments[gi_arg_pos]; GIArgument* in_value = &state.in_cvalues[gi_arg_pos]; GIArgument* out_value = &state.out_cvalues[gi_arg_pos]; gjs_debug_marshal( GJS_DEBUG_GFUNCTION, "Releasing argument '%s', %d/%d GI args, %u/%u C args", cache->arg_name, gi_arg_pos, gi_argc, ffi_arg_pos, processed_c_args); // Only process in or inout arguments if we failed, the rest is garbage if (failed && cache->skip_in) continue; // Save the return GIArgument if it was requested if (r_value && gi_arg_pos == -1) { *r_value = *out_value; continue; } if (!cache->marshallers->release(context, cache, &state, in_value, out_value)) { postinvoke_release_failed = true; // continue with the release even if we fail, to avoid leaks } } if (postinvoke_release_failed) failed = true; g_assert(ffi_arg_pos == processed_c_args + (is_method ? 2 : 1)); if (!r_value && function->js_out_argc > 0 && (!failed && !did_throw_gerror)) { // If we have one return value or out arg, return that item on its // own, otherwise return a JavaScript array with [return value, // out arg 1, out arg 2, ...] if (function->js_out_argc == 1) { args.rval().set(return_values[0]); } else { JSObject* array = JS::NewArrayObject(context, return_values); if (!array) { failed = true; } else { args.rval().setObject(*array); } } } if (!failed && did_throw_gerror) { return gjs_throw_gerror(context, local_error); } else if (failed) { return false; } else { return true; } } GJS_JSAPI_RETURN_CONVENTION static bool function_call(JSContext *context, unsigned js_argc, JS::Value *vp) { JS::CallArgs js_argv = JS::CallArgsFromVp(js_argc, vp); JS::RootedObject callee(context, &js_argv.callee()); Function *priv; priv = priv_from_js(context, callee); gjs_debug_marshal(GJS_DEBUG_GFUNCTION, "Call callee %p priv %p", callee.get(), priv); if (priv == NULL) return true; /* we are the prototype, or have the wrong class */ return gjs_invoke_c_function(context, priv, js_argv); } GJS_NATIVE_CONSTRUCTOR_DEFINE_ABSTRACT(function) /* Does not actually free storage for structure, just * reverses init_cached_function_data */ static void uninit_cached_function_data (Function *function) { if (function->arguments) { g_assert(function->info && "Don't know how to free cache without GI info"); // Careful! function->arguments is offset by one or two elements inside // the allocated space, so we have to free index -1 or -2. int start_index = g_callable_info_is_method(function->info) ? -2 : -1; int gi_argc = MIN(g_callable_info_get_n_args(function->info), function->js_in_argc + function->js_out_argc); for (int i = 0; i < gi_argc; i++) { int ix = start_index + i; if (!function->arguments[ix].marshallers) break; if (function->arguments[ix].marshallers->free) function->arguments[ix].marshallers->free( &function->arguments[ix]); } g_free(&function->arguments[start_index]); function->arguments = nullptr; } g_clear_pointer(&function->info, g_base_info_unref); g_function_invoker_destroy(&function->invoker); } static void function_finalize(JSFreeOp*, JSObject* obj) { Function *priv; priv = (Function *) JS_GetPrivate(obj); gjs_debug_lifecycle(GJS_DEBUG_GFUNCTION, "finalize, obj %p priv %p", obj, priv); if (priv == NULL) return; /* we are the prototype, not a real instance, so constructor never called */ uninit_cached_function_data(priv); GJS_DEC_COUNTER(function); g_slice_free(Function, priv); } GJS_JSAPI_RETURN_CONVENTION static bool get_num_arguments (JSContext *context, unsigned argc, JS::Value *vp) { GJS_GET_PRIV(context, argc, vp, rec, to, Function, priv); rec.rval().setInt32(priv->js_in_argc); return true; } GJS_JSAPI_RETURN_CONVENTION static bool function_to_string (JSContext *context, guint argc, JS::Value *vp) { GJS_GET_PRIV(context, argc, vp, rec, to, Function, priv); int i, n_args, n_jsargs; GString *arg_names_str; gchar *arg_names; if (priv == NULL) { JSString* retval = JS_NewStringCopyZ(context, "function () {\n}"); if (!retval) return false; rec.rval().setString(retval); return true; } n_args = g_callable_info_get_n_args(priv->info); n_jsargs = 0; arg_names_str = g_string_new(""); for (i = 0; i < n_args; i++) { if (priv->arguments[i].skip_in) continue; if (n_jsargs > 0) g_string_append(arg_names_str, ", "); n_jsargs++; g_string_append(arg_names_str, priv->arguments[i].arg_name); } arg_names = g_string_free(arg_names_str, false); GjsAutoChar descr; if (g_base_info_get_type(priv->info) == GI_INFO_TYPE_FUNCTION) { descr = g_strdup_printf( "function %s(%s) {\n\t/* wrapper for native symbol %s(); */\n}", g_base_info_get_name(priv->info), arg_names, g_function_info_get_symbol(priv->info)); } else { descr = g_strdup_printf( "function %s(%s) {\n\t/* wrapper for native symbol */\n}", g_base_info_get_name(priv->info), arg_names); } g_free(arg_names); return gjs_string_from_utf8(context, descr, rec.rval()); } /* The bizarre thing about this vtable is that it applies to both * instances of the object, and to the prototype that instances of the * class have. */ static const struct JSClassOps gjs_function_class_ops = { nullptr, // addProperty nullptr, // deleteProperty nullptr, // enumerate nullptr, // newEnumerate nullptr, // resolve nullptr, // mayResolve function_finalize, function_call}; struct JSClass gjs_function_class = { "GIRepositoryFunction", /* means "new GIRepositoryFunction()" works */ JSCLASS_HAS_PRIVATE | JSCLASS_BACKGROUND_FINALIZE, &gjs_function_class_ops }; static JSPropertySpec gjs_function_proto_props[] = { JS_PSG("length", get_num_arguments, JSPROP_PERMANENT), JS_STRING_SYM_PS(toStringTag, "GIRepositoryFunction", JSPROP_READONLY), JS_PS_END}; /* The original Function.prototype.toString complains when given a GIRepository function as an argument */ static JSFunctionSpec gjs_function_proto_funcs[] = { JS_FN("toString", function_to_string, 0, 0), JS_FS_END }; static JSFunctionSpec *gjs_function_static_funcs = nullptr; GJS_JSAPI_RETURN_CONVENTION static bool init_cached_function_data (JSContext *context, Function *function, GType gtype, GICallableInfo *info) { guint8 i, n_args; GError *error = NULL; GIInfoType info_type; info_type = g_base_info_get_type((GIBaseInfo *)info); if (info_type == GI_INFO_TYPE_FUNCTION) { if (!g_function_info_prep_invoker((GIFunctionInfo *)info, &(function->invoker), &error)) { return gjs_throw_gerror(context, error); } } else if (info_type == GI_INFO_TYPE_VFUNC) { gpointer addr; addr = g_vfunc_info_get_address((GIVFuncInfo *)info, gtype, &error); if (error != NULL) { if (error->code != G_INVOKE_ERROR_SYMBOL_NOT_FOUND) return gjs_throw_gerror(context, error); gjs_throw(context, "Virtual function not implemented: %s", error->message); g_clear_error(&error); return false; } if (!g_function_invoker_new_for_address(addr, info, &(function->invoker), &error)) { return gjs_throw_gerror(context, error); } } bool is_method = g_callable_info_is_method(info); n_args = g_callable_info_get_n_args((GICallableInfo*) info); // arguments is one or two inside an array of n_args + 2, so // arguments[-1] is the return value (which can be skipped if void) // arguments[-2] is the instance parameter size_t offset = is_method ? 2 : 1; GjsArgumentCache* arguments = g_new0(GjsArgumentCache, n_args + offset) + offset; function->arguments = arguments; function->info = g_base_info_ref(info); function->js_in_argc = 0; function->js_out_argc = 0; if (is_method && !gjs_arg_cache_build_instance(context, &arguments[-2], info)) return false; bool inc_counter; if (!gjs_arg_cache_build_return(context, &arguments[-1], arguments, info, &inc_counter)) return false; function->js_out_argc = inc_counter ? 1 : 0; for (i = 0; i < n_args; i++) { GIDirection direction; GIArgInfo arg_info; if (arguments[i].skip_in || arguments[i].skip_out) continue; g_callable_info_load_arg((GICallableInfo*) info, i, &arg_info); direction = g_arg_info_get_direction(&arg_info); if (!gjs_arg_cache_build_arg(context, &arguments[i], arguments, i, direction, &arg_info, info, &inc_counter)) return false; if (inc_counter) { switch (direction) { case GI_DIRECTION_INOUT: function->js_out_argc++; [[fallthrough]]; case GI_DIRECTION_IN: function->js_in_argc++; break; case GI_DIRECTION_OUT: function->js_out_argc++; break; default: g_assert_not_reached(); } } } return true; } [[nodiscard]] static inline JSObject* gjs_builtin_function_get_proto( JSContext* cx) { return JS::GetRealmFunctionPrototype(cx); } GJS_DEFINE_PROTO_FUNCS_WITH_PARENT(function, builtin_function) GJS_JSAPI_RETURN_CONVENTION static JSObject* function_new(JSContext *context, GType gtype, GICallableInfo *info) { Function *priv; JS::RootedObject proto(context); if (!gjs_function_define_proto(context, nullptr, &proto)) return nullptr; JS::RootedObject function(context, JS_NewObjectWithGivenProto(context, &gjs_function_class, proto)); if (!function) { gjs_debug(GJS_DEBUG_GFUNCTION, "Failed to construct function"); return NULL; } priv = g_slice_new0(Function); GJS_INC_COUNTER(function); g_assert(priv_from_js(context, function) == NULL); JS_SetPrivate(function, priv); gjs_debug_lifecycle(GJS_DEBUG_GFUNCTION, "function constructor, obj %p priv %p", function.get(), priv); if (!init_cached_function_data(context, priv, gtype, (GICallableInfo *)info)) return NULL; return function; } GJS_JSAPI_RETURN_CONVENTION JSObject* gjs_define_function(JSContext *context, JS::HandleObject in_object, GType gtype, GICallableInfo *info) { GIInfoType info_type; gchar *name; bool free_name; info_type = g_base_info_get_type((GIBaseInfo *)info); JS::RootedObject function(context, function_new(context, gtype, info)); if (!function) return NULL; if (info_type == GI_INFO_TYPE_FUNCTION) { name = (gchar *) g_base_info_get_name((GIBaseInfo*) info); free_name = false; } else if (info_type == GI_INFO_TYPE_VFUNC) { name = g_strdup_printf("vfunc_%s", g_base_info_get_name((GIBaseInfo*) info)); free_name = true; } else { g_assert_not_reached (); } if (!JS_DefineProperty(context, in_object, name, function, GJS_MODULE_PROP_FLAGS)) { gjs_debug(GJS_DEBUG_GFUNCTION, "Failed to define function"); function = NULL; } if (free_name) g_free(name); return function; } bool gjs_invoke_constructor_from_c(JSContext* context, GIFunctionInfo* info, JS::HandleObject obj, const JS::CallArgs& args, GIArgument* rvalue) { Function function; memset(&function, 0, sizeof(Function)); if (!init_cached_function_data(context, &function, 0, info)) return false; bool result = gjs_invoke_c_function(context, &function, args, obj, rvalue); uninit_cached_function_data(&function); return result; } cjs-5.2.0/gi/arg-cache.cpp0000644000175000017500000020631314144444702015361 0ustar jpeisachjpeisach/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* * Copyright (c) 2013 Giovanni Campagna * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include // for UniqueChars #include #include // for JS_TypeOfValue #include // for JS_GetObjectFunction #include // for JSTYPE_FUNCTION #include "gi/arg-cache.h" #include "gi/arg-inl.h" #include "gi/arg.h" #include "gi/boxed.h" #include "gi/foreign.h" #include "gi/function.h" #include "gi/fundamental.h" #include "gi/gerror.h" #include "gi/gtype.h" #include "gi/object.h" #include "gi/param.h" #include "gi/union.h" #include "gi/value.h" #include "cjs/byteArray.h" #include "cjs/jsapi-util.h" enum ExpectedType { OBJECT, FUNCTION, STRING, LAST, }; static const char* expected_type_names[] = {"object", "function", "string"}; static_assert(G_N_ELEMENTS(expected_type_names) == ExpectedType::LAST, "Names must match the values in ExpectedType"); // The global entry point for any invocations of GDestroyNotify; look up the // callback through the user_data and then free it. static void gjs_destroy_notify_callback(void* data) { auto* trampoline = static_cast(data); g_assert(trampoline); gjs_callback_trampoline_unref(trampoline); } // A helper function to retrieve array lengths from a GIArgument (letting the // compiler generate good instructions in case of big endian machines) [[nodiscard]] static size_t gjs_g_argument_get_array_length(GITypeTag tag, GIArgument* arg) { if (tag == GI_TYPE_TAG_INT8) return gjs_arg_get(arg); if (tag == GI_TYPE_TAG_UINT8) return gjs_arg_get(arg); if (tag == GI_TYPE_TAG_INT16) return gjs_arg_get(arg); if (tag == GI_TYPE_TAG_UINT16) return gjs_arg_get(arg); if (tag == GI_TYPE_TAG_INT32) return gjs_arg_get(arg); if (tag == GI_TYPE_TAG_UINT32) return gjs_arg_get(arg); if (tag == GI_TYPE_TAG_INT64) return gjs_arg_get(arg); if (tag == GI_TYPE_TAG_UINT64) return gjs_arg_get(arg); g_assert_not_reached(); } static void gjs_g_argument_set_array_length(GITypeTag tag, GIArgument* arg, size_t value) { switch (tag) { case GI_TYPE_TAG_INT8: gjs_arg_set(arg, value); break; case GI_TYPE_TAG_UINT8: gjs_arg_set(arg, value); break; case GI_TYPE_TAG_INT16: gjs_arg_set(arg, value); break; case GI_TYPE_TAG_UINT16: gjs_arg_set(arg, value); break; case GI_TYPE_TAG_INT32: gjs_arg_set(arg, value); break; case GI_TYPE_TAG_UINT32: gjs_arg_set(arg, value); break; case GI_TYPE_TAG_INT64: gjs_arg_set(arg, value); break; case GI_TYPE_TAG_UINT64: gjs_arg_set(arg, value); break; default: g_assert_not_reached(); } } GJS_JSAPI_RETURN_CONVENTION static bool throw_not_introspectable_argument(JSContext* cx, GICallableInfo* function, const char* arg_name) { gjs_throw(cx, "Function %s.%s cannot be called: argument '%s' is not " "introspectable.", g_base_info_get_namespace(function), g_base_info_get_name(function), arg_name); return false; } GJS_JSAPI_RETURN_CONVENTION static bool throw_not_introspectable_unboxed_type(JSContext* cx, GICallableInfo* function, const char* arg_name) { gjs_throw(cx, "Function %s.%s cannot be called: unexpected unregistered type " "for argument '%s'.", g_base_info_get_namespace(function), g_base_info_get_name(function), arg_name); return false; } GJS_JSAPI_RETURN_CONVENTION static bool report_typeof_mismatch(JSContext* cx, const char* arg_name, JS::HandleValue value, ExpectedType expected) { gjs_throw(cx, "Expected type %s for argument '%s' but got type %s", expected_type_names[expected], arg_name, JS::InformalValueTypeName(value)); return false; } GJS_JSAPI_RETURN_CONVENTION static bool report_gtype_mismatch(JSContext* cx, const char* arg_name, JS::Value value, GType expected) { gjs_throw( cx, "Expected an object of type %s for argument '%s' but got type %s", g_type_name(expected), arg_name, JS::InformalValueTypeName(value)); return false; } GJS_JSAPI_RETURN_CONVENTION static bool report_out_of_range(JSContext* cx, const char* arg_name, GITypeTag tag) { gjs_throw(cx, "Argument %s: value is out of range for %s", arg_name, g_type_tag_to_string(tag)); return false; } GJS_JSAPI_RETURN_CONVENTION static bool report_invalid_null(JSContext* cx, const char* arg_name) { gjs_throw(cx, "Argument %s may not be null", arg_name); return false; } // Marshallers: // // Each argument, irrespective of the direction, is processed in three phases: // - before calling the C function [in] // - after calling it, when converting the return value and out arguments [out] // - at the end of the invocation, to release any allocated memory [release] // // The convention on the names is thus // gjs_marshal_[argument type]_[direction]_[phase]. // Some types don't have direction (for example, caller_allocates is only out, // and callback is only in), in which case it is implied. GJS_JSAPI_RETURN_CONVENTION static bool gjs_marshal_skipped_in(JSContext*, GjsArgumentCache*, GjsFunctionCallState*, GIArgument*, JS::HandleValue) { return true; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_marshal_generic_in_in(JSContext* cx, GjsArgumentCache* self, GjsFunctionCallState*, GIArgument* arg, JS::HandleValue value) { return gjs_value_to_g_argument(cx, value, &self->type_info, self->arg_name, self->is_return_value() ? GJS_ARGUMENT_RETURN_VALUE : GJS_ARGUMENT_ARGUMENT, self->transfer, self->nullable, arg); } GJS_JSAPI_RETURN_CONVENTION static bool gjs_marshal_generic_inout_in(JSContext* cx, GjsArgumentCache* self, GjsFunctionCallState* state, GIArgument* arg, JS::HandleValue value) { if (!gjs_marshal_generic_in_in(cx, self, state, arg, value)) return false; int ix = self->arg_pos; state->out_cvalues[ix] = state->inout_original_cvalues[ix] = *arg; gjs_arg_set(arg, &state->out_cvalues[ix]); return true; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_marshal_explicit_array_in_in(JSContext* cx, GjsArgumentCache* self, GjsFunctionCallState* state, GArgument* arg, JS::HandleValue value) { void* data; size_t length; if (!gjs_array_to_explicit_array( cx, value, &self->type_info, self->arg_name, GJS_ARGUMENT_ARGUMENT, self->transfer, self->nullable, &data, &length)) return false; uint8_t length_pos = self->contents.array.length_pos; gjs_g_argument_set_array_length(self->contents.array.length_tag, &state->in_cvalues[length_pos], length); gjs_arg_set(arg, data); return true; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_marshal_explicit_array_inout_in(JSContext* cx, GjsArgumentCache* self, GjsFunctionCallState* state, GIArgument* arg, JS::HandleValue value) { if (!gjs_marshal_explicit_array_in_in(cx, self, state, arg, value)) return false; uint8_t length_pos = self->contents.array.length_pos; uint8_t ix = self->arg_pos; if (!gjs_arg_get(arg)) { // Special case where we were given JS null to also pass null for // length, and not a pointer to an integer that derefs to 0. gjs_arg_unset(&state->in_cvalues[length_pos]); gjs_arg_unset(&state->out_cvalues[length_pos]); gjs_arg_unset(&state->inout_original_cvalues[length_pos]); gjs_arg_unset(&state->out_cvalues[ix]); gjs_arg_unset(&state->inout_original_cvalues[ix]); } else { state->out_cvalues[length_pos] = state->inout_original_cvalues[length_pos] = state->in_cvalues[length_pos]; gjs_arg_set(&state->in_cvalues[length_pos], &state->out_cvalues[length_pos]); state->out_cvalues[ix] = state->inout_original_cvalues[ix] = *arg; gjs_arg_set(arg, &state->out_cvalues[ix]); } return true; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_marshal_callback_in(JSContext* cx, GjsArgumentCache* self, GjsFunctionCallState* state, GIArgument* arg, JS::HandleValue value) { GjsCallbackTrampoline* trampoline; ffi_closure* closure; if (value.isNull() && self->nullable) { closure = nullptr; trampoline = nullptr; } else { if (JS_TypeOfValue(cx, value) != JSTYPE_FUNCTION) { gjs_throw(cx, "Expected function for callback argument %s, got %s", self->arg_name, JS::InformalValueTypeName(value)); return false; } JS::RootedFunction func(cx, JS_GetObjectFunction(&value.toObject())); GjsAutoCallableInfo callable_info = g_type_info_get_interface(&self->type_info); bool is_object_method = !!state->instance_object; trampoline = gjs_callback_trampoline_new(cx, func, callable_info, self->contents.callback.scope, is_object_method, false); if (!trampoline) return false; if (self->contents.callback.scope == GI_SCOPE_TYPE_NOTIFIED && is_object_method) { auto* priv = ObjectInstance::for_js(cx, state->instance_object); if (!priv) { gjs_throw(cx, "Signal connected to wrong type of object"); return false; } priv->associate_closure(cx, trampoline->js_function); } closure = trampoline->closure; } if (self->has_callback_destroy()) { uint8_t destroy_pos = self->contents.callback.destroy_pos; gjs_arg_set(&state->in_cvalues[destroy_pos], trampoline ? gjs_destroy_notify_callback : nullptr); } if (self->has_callback_closure()) { uint8_t closure_pos = self->contents.callback.closure_pos; gjs_arg_set(&state->in_cvalues[closure_pos], trampoline); } if (trampoline && self->contents.callback.scope != GI_SCOPE_TYPE_CALL) { // Add an extra reference that will be cleared when collecting async // calls, or when GDestroyNotify is called gjs_callback_trampoline_ref(trampoline); } gjs_arg_set(arg, closure); return true; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_marshal_generic_out_in(JSContext*, GjsArgumentCache* self, GjsFunctionCallState* state, GIArgument* arg, JS::HandleValue) { // Default value in case a broken C function doesn't fill in the pointer gjs_arg_unset(&state->out_cvalues[self->arg_pos]); gjs_arg_set(arg, &gjs_arg_member(&state->out_cvalues[self->arg_pos])); return true; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_marshal_caller_allocates_in(JSContext*, GjsArgumentCache* self, GjsFunctionCallState* state, GIArgument* arg, JS::HandleValue) { void* blob = g_slice_alloc0(self->contents.caller_allocates_size); gjs_arg_set(arg, blob); gjs_arg_set(&state->out_cvalues[self->arg_pos], blob); return true; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_marshal_null_in_in(JSContext* cx, GjsArgumentCache* self, GjsFunctionCallState*, GIArgument* arg, JS::HandleValue) { return self->handle_nullable(cx, arg); } GJS_JSAPI_RETURN_CONVENTION static bool gjs_marshal_boolean_in_in(JSContext*, GjsArgumentCache*, GjsFunctionCallState*, GIArgument* arg, JS::HandleValue value) { gjs_arg_set(arg, JS::ToBoolean(value)); return true; } // Type tags are alternated, signed / unsigned static int32_t min_max_ints[5][2] = {{G_MININT8, G_MAXINT8}, {0, G_MAXUINT8}, {G_MININT16, G_MAXINT16}, {0, G_MAXUINT16}, {G_MININT32, G_MAXINT32}}; [[nodiscard]] static inline bool value_in_range(int32_t number, GITypeTag tag) { return (number >= min_max_ints[tag - GI_TYPE_TAG_INT8][0] && number <= min_max_ints[tag - GI_TYPE_TAG_INT8][1]); } GJS_JSAPI_RETURN_CONVENTION static bool gjs_marshal_integer_in_in(JSContext* cx, GjsArgumentCache* self, GjsFunctionCallState*, GIArgument* arg, JS::HandleValue value) { GITypeTag tag = self->contents.number.number_tag; if (self->is_unsigned) { uint32_t number; if (!JS::ToUint32(cx, value, &number)) return false; if (!value_in_range(number, tag)) return report_out_of_range(cx, self->arg_name, tag); gjs_g_argument_set_array_length(tag, arg, number); } else { int32_t number; if (!JS::ToInt32(cx, value, &number)) return false; if (!value_in_range(number, tag)) return report_out_of_range(cx, self->arg_name, tag); gjs_g_argument_set_array_length(tag, arg, number); } return true; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_marshal_number_in_in(JSContext* cx, GjsArgumentCache* self, GjsFunctionCallState*, GIArgument* arg, JS::HandleValue value) { double v; if (!JS::ToNumber(cx, value, &v)) return false; GITypeTag tag = self->contents.number.number_tag; if (tag == GI_TYPE_TAG_DOUBLE) { gjs_arg_set(arg, v); } else if (tag == GI_TYPE_TAG_FLOAT) { if (v < -G_MAXFLOAT || v > G_MAXFLOAT) return report_out_of_range(cx, self->arg_name, GI_TYPE_TAG_FLOAT); gjs_arg_set(arg, v); } else if (tag == GI_TYPE_TAG_INT64) { if (v < G_MININT64 || v > G_MAXINT64) return report_out_of_range(cx, self->arg_name, GI_TYPE_TAG_INT64); gjs_arg_set(arg, v); } else if (tag == GI_TYPE_TAG_UINT64) { if (v < 0 || v > G_MAXUINT64) return report_out_of_range(cx, self->arg_name, GI_TYPE_TAG_UINT64); gjs_arg_set(arg, v); } else if (tag == GI_TYPE_TAG_UINT32) { if (v < 0 || v > G_MAXUINT32) return report_out_of_range(cx, self->arg_name, GI_TYPE_TAG_UINT32); gjs_arg_set(arg, v); } else { g_assert_not_reached(); } return true; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_marshal_unichar_in_in(JSContext* cx, GjsArgumentCache* self, GjsFunctionCallState*, GIArgument* arg, JS::HandleValue value) { if (!value.isString()) return report_typeof_mismatch(cx, self->arg_name, value, ExpectedType::STRING); return gjs_unichar_from_string(cx, value, &gjs_arg_member(arg)); } GJS_JSAPI_RETURN_CONVENTION static bool gjs_marshal_gtype_in_in(JSContext* cx, GjsArgumentCache* self, GjsFunctionCallState*, GIArgument* arg, JS::HandleValue value) { if (value.isNull()) return report_invalid_null(cx, self->arg_name); if (!value.isObject()) return report_typeof_mismatch(cx, self->arg_name, value, ExpectedType::OBJECT); JS::RootedObject gtype_obj(cx, &value.toObject()); return gjs_gtype_get_actual_gtype( cx, gtype_obj, &gjs_arg_member(arg)); } // Common code for most types that are pointers on the C side bool GjsArgumentCache::handle_nullable(JSContext* cx, GIArgument* arg) { if (!nullable) return report_invalid_null(cx, arg_name); gjs_arg_unset(arg); return true; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_marshal_string_in_in(JSContext* cx, GjsArgumentCache* self, GjsFunctionCallState*, GIArgument* arg, JS::HandleValue value) { if (value.isNull()) return self->handle_nullable(cx, arg); if (!value.isString()) return report_typeof_mismatch(cx, self->arg_name, value, ExpectedType::STRING); if (self->contents.string_is_filename) { GjsAutoChar str; if (!gjs_string_to_filename(cx, value, &str)) return false; gjs_arg_set(arg, str.release()); return true; } JS::UniqueChars str = gjs_string_to_utf8(cx, value); if (!str) return false; gjs_arg_set(arg, g_strdup(str.get())); return true; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_marshal_enum_in_in(JSContext* cx, GjsArgumentCache* self, GjsFunctionCallState*, GIArgument* arg, JS::HandleValue value) { int64_t number; if (!JS::ToInt64(cx, value, &number)) return false; // Unpack the values from their uint32_t bitfield. See note in // gjs_arg_cache_build_enum_bounds(). int64_t min, max; if (self->is_unsigned) { min = self->contents.enum_type.enum_min; max = self->contents.enum_type.enum_max; } else { min = static_cast(self->contents.enum_type.enum_min); max = static_cast(self->contents.enum_type.enum_max); } if (number > max || number < min) { gjs_throw(cx, "%" PRId64 " is not a valid value for enum argument %s", number, self->arg_name); return false; } if (self->is_unsigned) gjs_arg_set(arg, number); else gjs_arg_set(arg, number); return true; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_marshal_flags_in_in(JSContext* cx, GjsArgumentCache* self, GjsFunctionCallState*, GIArgument* arg, JS::HandleValue value) { int64_t number; if (!JS::ToInt64(cx, value, &number)) return false; if ((uint64_t(number) & self->contents.flags_mask) != uint64_t(number)) { gjs_throw(cx, "%" PRId64 " is not a valid value for flags argument %s", number, self->arg_name); return false; } // We cast to unsigned because that's what makes sense, but then we // put it in the v_int slot because that's what we use to unmarshal // flags types at the moment. gjs_arg_set(arg, static_cast(number)); return true; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_marshal_foreign_in_in(JSContext* cx, GjsArgumentCache* self, GjsFunctionCallState*, GIArgument* arg, JS::HandleValue value) { GIStructInfo* foreign_info = g_type_info_get_interface(&self->type_info); self->contents.tmp_foreign_info = foreign_info; return gjs_struct_foreign_convert_to_g_argument( cx, value, foreign_info, self->arg_name, GJS_ARGUMENT_ARGUMENT, self->transfer, self->nullable, arg); } GJS_JSAPI_RETURN_CONVENTION static bool gjs_marshal_gvalue_in_in(JSContext* cx, GjsArgumentCache*, GjsFunctionCallState*, GIArgument* arg, JS::HandleValue value) { GValue gvalue = G_VALUE_INIT; if (!gjs_value_to_g_value(cx, value, &gvalue)) return false; gjs_arg_set(arg, g_boxed_copy(G_TYPE_VALUE, &gvalue)); g_value_unset(&gvalue); return true; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_marshal_boxed_in_in(JSContext* cx, GjsArgumentCache* self, GjsFunctionCallState*, GIArgument* arg, JS::HandleValue value) { if (value.isNull()) return self->handle_nullable(cx, arg); GType gtype = self->contents.object.gtype; if (!value.isObject()) return report_gtype_mismatch(cx, self->arg_name, value, gtype); JS::RootedObject object(cx, &value.toObject()); if (gtype == G_TYPE_ERROR) { return ErrorBase::transfer_to_gi_argument( cx, object, arg, GI_DIRECTION_IN, self->transfer); } return BoxedBase::transfer_to_gi_argument(cx, object, arg, GI_DIRECTION_IN, self->transfer, gtype, self->contents.object.info); } // Unions include ClutterEvent and GdkEvent, which occur fairly often in an // interactive application, so they're worth a special case in a different // virtual function. GJS_JSAPI_RETURN_CONVENTION static bool gjs_marshal_union_in_in(JSContext* cx, GjsArgumentCache* self, GjsFunctionCallState*, GIArgument* arg, JS::HandleValue value) { if (value.isNull()) return self->handle_nullable(cx, arg); GType gtype = self->contents.object.gtype; g_assert(gtype != G_TYPE_NONE); if (!value.isObject()) return report_gtype_mismatch(cx, self->arg_name, value, gtype); JS::RootedObject object(cx, &value.toObject()); return UnionBase::transfer_to_gi_argument(cx, object, arg, GI_DIRECTION_IN, self->transfer, gtype, self->contents.object.info); } GJS_JSAPI_RETURN_CONVENTION static bool gjs_marshal_gclosure_in_in(JSContext* cx, GjsArgumentCache* self, GjsFunctionCallState*, GIArgument* arg, JS::HandleValue value) { if (value.isNull()) return self->handle_nullable(cx, arg); if (!(JS_TypeOfValue(cx, value) == JSTYPE_FUNCTION)) return report_typeof_mismatch(cx, self->arg_name, value, ExpectedType::FUNCTION); JS::RootedFunction func(cx, JS_GetObjectFunction(&value.toObject())); GClosure* closure = gjs_closure_new_marshaled(cx, func, "boxed"); gjs_arg_set(arg, closure); g_closure_ref(closure); g_closure_sink(closure); return true; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_marshal_gbytes_in_in(JSContext* cx, GjsArgumentCache* self, GjsFunctionCallState*, GIArgument* arg, JS::HandleValue value) { if (value.isNull()) return self->handle_nullable(cx, arg); if (!value.isObject()) return report_gtype_mismatch(cx, self->arg_name, value, G_TYPE_BYTES); JS::RootedObject object(cx, &value.toObject()); if (JS_IsUint8Array(object)) { gjs_arg_set(arg, gjs_byte_array_get_bytes(object)); return true; } // The bytearray path is taking an extra ref irrespective of transfer // ownership, so we need to do the same here. return BoxedBase::transfer_to_gi_argument( cx, object, arg, GI_DIRECTION_IN, GI_TRANSFER_EVERYTHING, G_TYPE_BYTES, self->contents.object.info); } GJS_JSAPI_RETURN_CONVENTION static bool gjs_marshal_interface_in_in(JSContext* cx, GjsArgumentCache* self, GjsFunctionCallState*, GIArgument* arg, JS::HandleValue value) { if (value.isNull()) return self->handle_nullable(cx, arg); GType gtype = self->contents.object.gtype; g_assert(gtype != G_TYPE_NONE); if (!value.isObject()) return report_gtype_mismatch(cx, self->arg_name, value, gtype); JS::RootedObject object(cx, &value.toObject()); // Could be a GObject interface that's missing a prerequisite, // or could be a fundamental if (ObjectBase::typecheck(cx, object, nullptr, gtype, GjsTypecheckNoThrow())) { return ObjectBase::transfer_to_gi_argument( cx, object, arg, GI_DIRECTION_IN, self->transfer, gtype); } // If this typecheck fails, then it's neither an object nor a // fundamental return FundamentalBase::transfer_to_gi_argument( cx, object, arg, GI_DIRECTION_IN, self->transfer, gtype); } GJS_JSAPI_RETURN_CONVENTION static bool gjs_marshal_object_in_in(JSContext* cx, GjsArgumentCache* self, GjsFunctionCallState*, GIArgument* arg, JS::HandleValue value) { if (value.isNull()) return self->handle_nullable(cx, arg); GType gtype = self->contents.object.gtype; g_assert(gtype != G_TYPE_NONE); if (!value.isObject()) return report_gtype_mismatch(cx, self->arg_name, value, gtype); JS::RootedObject object(cx, &value.toObject()); return ObjectBase::transfer_to_gi_argument(cx, object, arg, GI_DIRECTION_IN, self->transfer, gtype); } GJS_JSAPI_RETURN_CONVENTION static bool gjs_marshal_fundamental_in_in(JSContext* cx, GjsArgumentCache* self, GjsFunctionCallState*, GIArgument* arg, JS::HandleValue value) { if (value.isNull()) return self->handle_nullable(cx, arg); GType gtype = self->contents.object.gtype; g_assert(gtype != G_TYPE_NONE); if (!value.isObject()) return report_gtype_mismatch(cx, self->arg_name, value, gtype); JS::RootedObject object(cx, &value.toObject()); return FundamentalBase::transfer_to_gi_argument( cx, object, arg, GI_DIRECTION_IN, self->transfer, gtype); } GJS_JSAPI_RETURN_CONVENTION static bool gjs_marshal_gtype_struct_instance_in(JSContext* cx, GjsArgumentCache* self, GjsFunctionCallState*, GIArgument* arg, JS::HandleValue value) { // Instance parameter is never nullable if (!value.isObject()) return report_typeof_mismatch(cx, self->arg_name, value, ExpectedType::OBJECT); JS::RootedObject obj(cx, &value.toObject()); GType actual_gtype; if (!gjs_gtype_get_actual_gtype(cx, obj, &actual_gtype)) return false; if (actual_gtype == G_TYPE_NONE) { gjs_throw(cx, "Invalid GType class passed for instance parameter"); return false; } // We use peek here to simplify reference counting (we just ignore transfer // annotation, as GType classes are never really freed.) We know that the // GType class is referenced at least once when the JS constructor is // initialized. if (g_type_is_a(actual_gtype, G_TYPE_INTERFACE)) gjs_arg_set(arg, g_type_default_interface_peek(actual_gtype)); else gjs_arg_set(arg, g_type_class_peek(actual_gtype)); return true; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_marshal_param_instance_in(JSContext* cx, GjsArgumentCache* self, GjsFunctionCallState*, GIArgument* arg, JS::HandleValue value) { // Instance parameter is never nullable if (!value.isObject()) return report_typeof_mismatch(cx, self->arg_name, value, ExpectedType::OBJECT); JS::RootedObject obj(cx, &value.toObject()); if (!gjs_typecheck_param(cx, obj, G_TYPE_PARAM, true)) return false; gjs_arg_set(arg, gjs_g_param_from_param(cx, obj)); if (self->transfer == GI_TRANSFER_EVERYTHING) g_param_spec_ref(gjs_arg_get(arg)); return true; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_marshal_skipped_out(JSContext*, GjsArgumentCache*, GjsFunctionCallState*, GIArgument*, JS::MutableHandleValue) { return true; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_marshal_generic_out_out(JSContext* cx, GjsArgumentCache* self, GjsFunctionCallState*, GIArgument* arg, JS::MutableHandleValue value) { return gjs_value_from_g_argument(cx, value, &self->type_info, arg, true); } GJS_JSAPI_RETURN_CONVENTION static bool gjs_marshal_explicit_array_out_out(JSContext* cx, GjsArgumentCache* self, GjsFunctionCallState* state, GIArgument* arg, JS::MutableHandleValue value) { uint8_t length_pos = self->contents.array.length_pos; GIArgument* length_arg = &(state->out_cvalues[length_pos]); GITypeTag length_tag = self->contents.array.length_tag; size_t length = gjs_g_argument_get_array_length(length_tag, length_arg); return gjs_value_from_explicit_array(cx, value, &self->type_info, arg, length); } GJS_JSAPI_RETURN_CONVENTION static bool gjs_marshal_skipped_release(JSContext*, GjsArgumentCache*, GjsFunctionCallState*, GIArgument* in_arg [[maybe_unused]], GIArgument* out_arg [[maybe_unused]]) { return true; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_marshal_generic_in_release( JSContext* cx, GjsArgumentCache* self, GjsFunctionCallState* state, GIArgument* in_arg, GIArgument* out_arg [[maybe_unused]]) { GITransfer transfer = state->call_completed ? self->transfer : GI_TRANSFER_NOTHING; return gjs_g_argument_release_in_arg(cx, transfer, &self->type_info, in_arg); } GJS_JSAPI_RETURN_CONVENTION static bool gjs_marshal_generic_out_release(JSContext* cx, GjsArgumentCache* self, GjsFunctionCallState*, GIArgument* in_arg [[maybe_unused]], GIArgument* out_arg) { return gjs_g_argument_release(cx, self->transfer, &self->type_info, out_arg); } GJS_JSAPI_RETURN_CONVENTION static bool gjs_marshal_generic_inout_release(JSContext* cx, GjsArgumentCache* self, GjsFunctionCallState* state, GIArgument* in_arg, GIArgument* out_arg) { // For inout, transfer refers to what we get back from the function; for // the temporary C value we allocated, clearly we're responsible for // freeing it. GIArgument* original_out_arg = &(state->inout_original_cvalues[self->arg_pos]); if (!gjs_g_argument_release_in_arg(cx, GI_TRANSFER_NOTHING, &self->type_info, original_out_arg)) return false; return gjs_marshal_generic_out_release(cx, self, state, in_arg, out_arg); } GJS_JSAPI_RETURN_CONVENTION static bool gjs_marshal_explicit_array_out_release( JSContext* cx, GjsArgumentCache* self, GjsFunctionCallState* state, GIArgument* in_arg [[maybe_unused]], GIArgument* out_arg) { uint8_t length_pos = self->contents.array.length_pos; GIArgument* length_arg = &(state->out_cvalues[length_pos]); GITypeTag length_tag = self->contents.array.length_tag; size_t length = gjs_g_argument_get_array_length(length_tag, length_arg); return gjs_g_argument_release_out_array(cx, self->transfer, &self->type_info, length, out_arg); } GJS_JSAPI_RETURN_CONVENTION static bool gjs_marshal_explicit_array_in_release( JSContext* cx, GjsArgumentCache* self, GjsFunctionCallState* state, GIArgument* in_arg, GIArgument* out_arg [[maybe_unused]]) { uint8_t length_pos = self->contents.array.length_pos; GIArgument* length_arg = &(state->in_cvalues[length_pos]); GITypeTag length_tag = self->contents.array.length_tag; size_t length = gjs_g_argument_get_array_length(length_tag, length_arg); GITransfer transfer = state->call_completed ? self->transfer : GI_TRANSFER_NOTHING; return gjs_g_argument_release_in_array(cx, transfer, &self->type_info, length, in_arg); } GJS_JSAPI_RETURN_CONVENTION static bool gjs_marshal_explicit_array_inout_release( JSContext* cx, GjsArgumentCache* self, GjsFunctionCallState* state, GIArgument* in_arg [[maybe_unused]], GIArgument* out_arg) { uint8_t length_pos = self->contents.array.length_pos; GIArgument* length_arg = &(state->in_cvalues[length_pos]); GITypeTag length_tag = self->contents.array.length_tag; size_t length = gjs_g_argument_get_array_length(length_tag, length_arg); // For inout, transfer refers to what we get back from the function; for // the temporary C value we allocated, clearly we're responsible for // freeing it. GIArgument* original_out_arg = &(state->inout_original_cvalues[self->arg_pos]); if (gjs_arg_get(original_out_arg) != gjs_arg_get(out_arg) && !gjs_g_argument_release_in_array(cx, GI_TRANSFER_NOTHING, &self->type_info, length, original_out_arg)) return false; return gjs_g_argument_release_out_array(cx, self->transfer, &self->type_info, length, out_arg); } GJS_JSAPI_RETURN_CONVENTION static bool gjs_marshal_caller_allocates_release( JSContext*, GjsArgumentCache* self, GjsFunctionCallState*, GIArgument* in_arg, GIArgument* out_arg [[maybe_unused]]) { g_slice_free1(self->contents.caller_allocates_size, gjs_arg_get(in_arg)); return true; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_marshal_callback_release(JSContext*, GjsArgumentCache*, GjsFunctionCallState*, GIArgument* in_arg, GIArgument* out_arg [[maybe_unused]]) { auto* closure = gjs_arg_get(in_arg); if (!closure) return true; auto trampoline = static_cast(closure->user_data); // CallbackTrampolines are refcounted because for notified/async closures // it is possible to destroy it while in call, and therefore we cannot // check its scope at this point gjs_callback_trampoline_unref(trampoline); gjs_arg_unset(in_arg); return true; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_marshal_string_in_release(JSContext*, GjsArgumentCache*, GjsFunctionCallState*, GIArgument* in_arg, GIArgument* out_arg [[maybe_unused]]) { g_free(gjs_arg_get(in_arg)); return true; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_marshal_foreign_in_release( JSContext* cx, GjsArgumentCache* self, GjsFunctionCallState* state, GIArgument* in_arg, GIArgument* out_arg [[maybe_unused]]) { bool ok = true; GITransfer transfer = state->call_completed ? self->transfer : GI_TRANSFER_NOTHING; if (transfer == GI_TRANSFER_NOTHING) ok = gjs_struct_foreign_release_g_argument( cx, self->transfer, self->contents.tmp_foreign_info, in_arg); g_base_info_unref(self->contents.tmp_foreign_info); return ok; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_marshal_boxed_in_release(JSContext*, GjsArgumentCache* self, GjsFunctionCallState*, GIArgument* in_arg, GIArgument* out_arg [[maybe_unused]]) { GType gtype = self->contents.object.gtype; g_assert(g_type_is_a(gtype, G_TYPE_BOXED)); if (!gjs_arg_get(in_arg)) return true; g_boxed_free(gtype, gjs_arg_get(in_arg)); return true; } static void gjs_arg_cache_interface_free(GjsArgumentCache* self) { g_clear_pointer(&self->contents.object.info, g_base_info_unref); } static const GjsArgumentMarshallers skip_all_marshallers = { gjs_marshal_skipped_in, // in gjs_marshal_skipped_out, // out gjs_marshal_skipped_release, // release }; // .in is ignored for the return value static const GjsArgumentMarshallers return_value_marshallers = { nullptr, // no in gjs_marshal_generic_out_out, // out gjs_marshal_generic_out_release, // release }; static const GjsArgumentMarshallers return_array_marshallers = { gjs_marshal_generic_out_in, // in gjs_marshal_explicit_array_out_out, // out gjs_marshal_explicit_array_out_release, // release }; static const GjsArgumentMarshallers array_length_out_marshallers = { gjs_marshal_generic_out_in, // in gjs_marshal_skipped_out, // out gjs_marshal_skipped_release, // release }; static const GjsArgumentMarshallers fallback_in_marshallers = { gjs_marshal_generic_in_in, // in gjs_marshal_skipped_out, // out gjs_marshal_generic_in_release, // release }; static const GjsArgumentMarshallers fallback_interface_in_marshallers = { gjs_marshal_generic_in_in, // in gjs_marshal_skipped_out, // out gjs_marshal_generic_in_release, // release gjs_arg_cache_interface_free, // free }; static const GjsArgumentMarshallers fallback_inout_marshallers = { gjs_marshal_generic_inout_in, // in gjs_marshal_generic_out_out, // out gjs_marshal_generic_inout_release, // release }; static const GjsArgumentMarshallers fallback_out_marshallers = { gjs_marshal_generic_out_in, // in gjs_marshal_generic_out_out, // out gjs_marshal_generic_out_release, // release }; static const GjsArgumentMarshallers invalid_in_marshallers = { nullptr, // no in, will cause the function invocation code to throw gjs_marshal_skipped_out, // out gjs_marshal_skipped_release, // release }; static const GjsArgumentMarshallers enum_in_marshallers = { gjs_marshal_enum_in_in, // in gjs_marshal_skipped_out, // out gjs_marshal_skipped_release, // release }; static const GjsArgumentMarshallers flags_in_marshallers = { gjs_marshal_flags_in_in, // in gjs_marshal_skipped_out, // out gjs_marshal_skipped_release, // release }; static const GjsArgumentMarshallers foreign_struct_in_marshallers = { gjs_marshal_foreign_in_in, // in gjs_marshal_skipped_out, // out gjs_marshal_foreign_in_release, // release }; static const GjsArgumentMarshallers foreign_struct_instance_in_marshallers = { gjs_marshal_foreign_in_in, // in gjs_marshal_skipped_out, // out gjs_marshal_skipped_release, // release }; static const GjsArgumentMarshallers gvalue_in_marshallers = { gjs_marshal_gvalue_in_in, // in gjs_marshal_skipped_out, // out gjs_marshal_skipped_release, // release gjs_arg_cache_interface_free, // free }; static const GjsArgumentMarshallers gvalue_in_transfer_none_marshallers = { gjs_marshal_gvalue_in_in, // in gjs_marshal_skipped_out, // out gjs_marshal_boxed_in_release, // release gjs_arg_cache_interface_free, // free }; static const GjsArgumentMarshallers gclosure_in_marshallers = { gjs_marshal_gclosure_in_in, // in gjs_marshal_skipped_out, // out gjs_marshal_skipped_release, // release gjs_arg_cache_interface_free, // free }; static const GjsArgumentMarshallers gclosure_in_transfer_none_marshallers = { gjs_marshal_gclosure_in_in, // in gjs_marshal_skipped_out, // out gjs_marshal_boxed_in_release, // release gjs_arg_cache_interface_free, // free }; static const GjsArgumentMarshallers gbytes_in_marshallers = { gjs_marshal_gbytes_in_in, // in gjs_marshal_skipped_out, // out gjs_marshal_skipped_release, // release gjs_arg_cache_interface_free, // free }; static const GjsArgumentMarshallers gbytes_in_transfer_none_marshallers = { gjs_marshal_gbytes_in_in, // in gjs_marshal_skipped_out, // out gjs_marshal_boxed_in_release, // release gjs_arg_cache_interface_free, // free }; static const GjsArgumentMarshallers object_in_marshallers = { gjs_marshal_object_in_in, // in gjs_marshal_skipped_out, // out // This is a smart marshaller, no release needed gjs_marshal_skipped_release, // release gjs_arg_cache_interface_free, // free }; static const GjsArgumentMarshallers interface_in_marshallers = { gjs_marshal_interface_in_in, // in gjs_marshal_skipped_out, // out // This is a smart marshaller, no release needed gjs_marshal_skipped_release, // release gjs_arg_cache_interface_free, // free }; static const GjsArgumentMarshallers fundamental_in_marshallers = { gjs_marshal_fundamental_in_in, // in gjs_marshal_skipped_out, // out // This is a smart marshaller, no release needed gjs_marshal_skipped_release, // release gjs_arg_cache_interface_free, // free }; static const GjsArgumentMarshallers union_in_marshallers = { gjs_marshal_union_in_in, // in gjs_marshal_skipped_out, // out // This is a smart marshaller, no release needed gjs_marshal_skipped_release, // release gjs_arg_cache_interface_free, // free }; static const GjsArgumentMarshallers boxed_in_marshallers = { gjs_marshal_boxed_in_in, // in gjs_marshal_skipped_out, // out // This is a smart marshaller, no release needed gjs_marshal_skipped_release, // release gjs_arg_cache_interface_free, // free }; static const GjsArgumentMarshallers null_in_marshallers = { gjs_marshal_null_in_in, // in gjs_marshal_skipped_out, // out gjs_marshal_skipped_release, // release }; static const GjsArgumentMarshallers boolean_in_marshallers = { gjs_marshal_boolean_in_in, // in gjs_marshal_skipped_out, // out gjs_marshal_skipped_release, // release }; static const GjsArgumentMarshallers integer_in_marshallers = { gjs_marshal_integer_in_in, // in gjs_marshal_skipped_out, // out gjs_marshal_skipped_release, // release }; static const GjsArgumentMarshallers number_in_marshallers = { gjs_marshal_number_in_in, // in gjs_marshal_skipped_out, // out gjs_marshal_skipped_release, // release }; static const GjsArgumentMarshallers unichar_in_marshallers = { gjs_marshal_unichar_in_in, // in gjs_marshal_skipped_out, // out gjs_marshal_skipped_release, // release }; static const GjsArgumentMarshallers gtype_in_marshallers = { gjs_marshal_gtype_in_in, // in gjs_marshal_skipped_out, // out gjs_marshal_skipped_release, // release }; static const GjsArgumentMarshallers string_in_marshallers = { gjs_marshal_string_in_in, // in gjs_marshal_skipped_out, // out gjs_marshal_skipped_release, // release }; static const GjsArgumentMarshallers string_in_transfer_none_marshallers = { gjs_marshal_string_in_in, // in gjs_marshal_skipped_out, // out gjs_marshal_string_in_release, // release }; // .out is ignored for the instance parameter static const GjsArgumentMarshallers gtype_struct_instance_in_marshallers = { gjs_marshal_gtype_struct_instance_in, // in nullptr, // no out gjs_marshal_skipped_release, // release }; static const GjsArgumentMarshallers param_instance_in_marshallers = { gjs_marshal_param_instance_in, // in nullptr, // no out gjs_marshal_skipped_release, // release }; static const GjsArgumentMarshallers callback_in_marshallers = { gjs_marshal_callback_in, // in gjs_marshal_skipped_out, // out gjs_marshal_callback_release, // release }; static const GjsArgumentMarshallers c_array_in_marshallers = { gjs_marshal_explicit_array_in_in, // in gjs_marshal_skipped_out, // out gjs_marshal_explicit_array_in_release, // release }; static const GjsArgumentMarshallers c_array_inout_marshallers = { gjs_marshal_explicit_array_inout_in, // in gjs_marshal_explicit_array_out_out, // out gjs_marshal_explicit_array_inout_release, // release }; static const GjsArgumentMarshallers c_array_out_marshallers = { gjs_marshal_generic_out_in, // in gjs_marshal_explicit_array_out_out, // out gjs_marshal_explicit_array_out_release, // release }; static const GjsArgumentMarshallers caller_allocates_out_marshallers = { gjs_marshal_caller_allocates_in, // in gjs_marshal_generic_out_out, // out gjs_marshal_caller_allocates_release, // release }; static inline void gjs_arg_cache_set_skip_all(GjsArgumentCache* self) { self->marshallers = &skip_all_marshallers; self->skip_in = self->skip_out = true; } bool gjs_arg_cache_build_return(JSContext*, GjsArgumentCache* self, GjsArgumentCache* arguments, GICallableInfo* callable, bool* inc_counter_out) { g_assert(inc_counter_out && "forgot out parameter"); g_callable_info_load_return_type(callable, &self->type_info); if (g_type_info_get_tag(&self->type_info) == GI_TYPE_TAG_VOID) { *inc_counter_out = false; gjs_arg_cache_set_skip_all(self); return true; } *inc_counter_out = true; self->set_return_value(); self->transfer = g_callable_info_get_caller_owns(callable); if (g_type_info_get_tag(&self->type_info) == GI_TYPE_TAG_ARRAY) { int length_pos = g_type_info_get_array_length(&self->type_info); if (length_pos >= 0) { gjs_arg_cache_set_skip_all(&arguments[length_pos]); // Even if we skip the length argument most of the time, we need to // do some basic initialization here. arguments[length_pos].set_arg_pos(length_pos); arguments[length_pos].marshallers = &array_length_out_marshallers; self->marshallers = &return_array_marshallers; self->set_array_length_pos(length_pos); GIArgInfo length_arg; g_callable_info_load_arg(callable, length_pos, &length_arg); GITypeInfo length_type; g_arg_info_load_type(&length_arg, &length_type); self->contents.array.length_tag = g_type_info_get_tag(&length_type); return true; } } // marshal_in is ignored for the return value, but skip_in is not (it is // used in the failure release path) self->skip_in = true; self->marshallers = &return_value_marshallers; return true; } static void gjs_arg_cache_build_enum_bounds(GjsArgumentCache* self, GIEnumInfo* enum_info) { int64_t min = G_MAXINT64; int64_t max = G_MININT64; int n = g_enum_info_get_n_values(enum_info); for (int i = 0; i < n; i++) { GjsAutoValueInfo value_info = g_enum_info_get_value(enum_info, i); int64_t value = g_value_info_get_value(value_info); if (value > max) max = value; if (value < min) min = value; } // From the docs for g_value_info_get_value(): "This will always be // representable as a 32-bit signed or unsigned value. The use of gint64 as // the return type is to allow both." // We stuff them both into unsigned 32-bit fields, and use a flag to tell // whether we have to compare them as signed. self->contents.enum_type.enum_min = static_cast(min); self->contents.enum_type.enum_max = static_cast(max); self->is_unsigned = min >= 0 && max > G_MAXINT32; } static void gjs_arg_cache_build_flags_mask(GjsArgumentCache* self, GIEnumInfo* enum_info) { uint64_t mask = 0; int n = g_enum_info_get_n_values(enum_info); for (int i = 0; i < n; i++) { GjsAutoValueInfo value_info = g_enum_info_get_value(enum_info, i); int64_t value = g_value_info_get_value(value_info); // From the docs for g_value_info_get_value(): "This will always be // representable as a 32-bit signed or unsigned value. The use of // gint64 as the return type is to allow both." // We stuff both into an unsigned, int-sized field, matching the // internal representation of flags in GLib (which uses guint). mask |= static_cast(value); } self->contents.flags_mask = mask; } [[nodiscard]] static inline bool is_gdk_atom(GIBaseInfo* info) { return strcmp("Atom", g_base_info_get_name(info)) == 0 && strcmp("Gdk", g_base_info_get_namespace(info)) == 0; } static bool gjs_arg_cache_build_interface_in_arg(JSContext* cx, GjsArgumentCache* self, GICallableInfo* callable, GIBaseInfo* interface_info, bool is_instance_param) { GIInfoType interface_type = g_base_info_get_type(interface_info); // We do some transfer magic later, so let's ensure we don't mess up. // Should not happen in practice. if (G_UNLIKELY(self->transfer == GI_TRANSFER_CONTAINER)) return throw_not_introspectable_argument(cx, callable, self->arg_name); switch (interface_type) { case GI_INFO_TYPE_ENUM: gjs_arg_cache_build_enum_bounds(self, interface_info); self->marshallers = &enum_in_marshallers; return true; case GI_INFO_TYPE_FLAGS: gjs_arg_cache_build_flags_mask(self, interface_info); self->marshallers = &flags_in_marshallers; return true; case GI_INFO_TYPE_STRUCT: if (g_struct_info_is_foreign(interface_info)) { if (is_instance_param) self->marshallers = &foreign_struct_instance_in_marshallers; else self->marshallers = &foreign_struct_in_marshallers; return true; } [[fallthrough]]; case GI_INFO_TYPE_BOXED: case GI_INFO_TYPE_OBJECT: case GI_INFO_TYPE_INTERFACE: case GI_INFO_TYPE_UNION: { GType gtype = g_registered_type_info_get_g_type(interface_info); self->contents.object.gtype = gtype; self->contents.object.info = g_base_info_ref(interface_info); // Transfer handling is a bit complex here, because some of our _in // marshallers know not to copy stuff if we don't need to. if (gtype == G_TYPE_VALUE) { if (self->transfer == GI_TRANSFER_NOTHING && !is_instance_param) self->marshallers = &gvalue_in_transfer_none_marshallers; else self->marshallers = &gvalue_in_marshallers; return true; } if (is_gdk_atom(interface_info)) { // Fall back to the generic marshaller self->marshallers = &fallback_interface_in_marshallers; return true; } if (gtype == G_TYPE_CLOSURE) { if (self->transfer == GI_TRANSFER_NOTHING && !is_instance_param) self->marshallers = &gclosure_in_transfer_none_marshallers; else self->marshallers = &gclosure_in_marshallers; return true; } if (gtype == G_TYPE_BYTES) { if (self->transfer == GI_TRANSFER_NOTHING && !is_instance_param) self->marshallers = &gbytes_in_transfer_none_marshallers; else self->marshallers = &gbytes_in_marshallers; return true; } if (g_type_is_a(gtype, G_TYPE_OBJECT)) { self->marshallers = &object_in_marshallers; return true; } if (g_type_is_a(gtype, G_TYPE_PARAM)) { // Fall back to the generic marshaller self->marshallers = &fallback_interface_in_marshallers; return true; } if (interface_type == GI_INFO_TYPE_UNION) { if (gtype == G_TYPE_NONE) { // Can't handle unions without a GType return throw_not_introspectable_unboxed_type( cx, callable, self->arg_name); } self->marshallers = &union_in_marshallers; return true; } if (G_TYPE_IS_INSTANTIATABLE(gtype)) { self->marshallers = &fundamental_in_marshallers; return true; } if (g_type_is_a(gtype, G_TYPE_INTERFACE)) { self->marshallers = &interface_in_marshallers; return true; } // generic boxed type if (gtype == G_TYPE_NONE && self->transfer != GI_TRANSFER_NOTHING) { // Can't transfer ownership of a structure type not // registered as a boxed return throw_not_introspectable_unboxed_type(cx, callable, self->arg_name); } self->marshallers = &boxed_in_marshallers; return true; } break; case GI_INFO_TYPE_INVALID: case GI_INFO_TYPE_FUNCTION: case GI_INFO_TYPE_CALLBACK: case GI_INFO_TYPE_CONSTANT: case GI_INFO_TYPE_INVALID_0: case GI_INFO_TYPE_VALUE: case GI_INFO_TYPE_SIGNAL: case GI_INFO_TYPE_VFUNC: case GI_INFO_TYPE_PROPERTY: case GI_INFO_TYPE_FIELD: case GI_INFO_TYPE_ARG: case GI_INFO_TYPE_TYPE: case GI_INFO_TYPE_UNRESOLVED: default: // Don't know how to handle this interface type (should not happen // in practice, for typelibs emitted by g-ir-compiler) return throw_not_introspectable_argument(cx, callable, self->arg_name); } } GJS_JSAPI_RETURN_CONVENTION static bool gjs_arg_cache_build_normal_in_arg(JSContext* cx, GjsArgumentCache* self, GICallableInfo* callable, GITypeTag tag) { // "Normal" in arguments are those arguments that don't require special // processing, and don't touch other arguments. // Main categories are: // - void* // - small numbers (fit in 32bit) // - big numbers (need a double) // - strings // - enums/flags (different from numbers in the way they're exposed in GI) // - objects (GObjects, boxed, unions, etc.) // - hashes // - sequences (null-terminated arrays, lists, etc.) switch (tag) { case GI_TYPE_TAG_VOID: self->marshallers = &null_in_marshallers; break; case GI_TYPE_TAG_BOOLEAN: self->marshallers = &boolean_in_marshallers; break; case GI_TYPE_TAG_INT8: case GI_TYPE_TAG_INT16: case GI_TYPE_TAG_INT32: self->marshallers = &integer_in_marshallers; self->contents.number.number_tag = tag; self->is_unsigned = false; break; case GI_TYPE_TAG_UINT8: case GI_TYPE_TAG_UINT16: self->marshallers = &integer_in_marshallers; self->contents.number.number_tag = tag; self->is_unsigned = true; break; case GI_TYPE_TAG_UINT32: case GI_TYPE_TAG_INT64: case GI_TYPE_TAG_UINT64: case GI_TYPE_TAG_FLOAT: case GI_TYPE_TAG_DOUBLE: self->marshallers = &number_in_marshallers; self->contents.number.number_tag = tag; break; case GI_TYPE_TAG_UNICHAR: self->marshallers = &unichar_in_marshallers; break; case GI_TYPE_TAG_GTYPE: self->marshallers = >ype_in_marshallers; break; case GI_TYPE_TAG_FILENAME: if (self->transfer == GI_TRANSFER_NOTHING) self->marshallers = &string_in_transfer_none_marshallers; else self->marshallers = &string_in_marshallers; self->contents.string_is_filename = true; break; case GI_TYPE_TAG_UTF8: if (self->transfer == GI_TRANSFER_NOTHING) self->marshallers = &string_in_transfer_none_marshallers; else self->marshallers = &string_in_marshallers; self->contents.string_is_filename = false; break; case GI_TYPE_TAG_INTERFACE: { GjsAutoBaseInfo interface_info = g_type_info_get_interface(&self->type_info); return gjs_arg_cache_build_interface_in_arg( cx, self, callable, interface_info, /* is_instance_param = */ false); } case GI_TYPE_TAG_ARRAY: case GI_TYPE_TAG_GLIST: case GI_TYPE_TAG_GSLIST: case GI_TYPE_TAG_GHASH: case GI_TYPE_TAG_ERROR: default: // FIXME: Falling back to the generic marshaller self->marshallers = &fallback_in_marshallers; } return true; } bool gjs_arg_cache_build_instance(JSContext* cx, GjsArgumentCache* self, GICallableInfo* callable) { GIBaseInfo* interface_info = g_base_info_get_container(callable); // !owned self->set_instance_parameter(); self->transfer = g_callable_info_get_instance_ownership_transfer(callable); // These cases could be covered by the generic marshaller, except that // there's no way to get GITypeInfo for a method's instance parameter. // Instead, special-case the arguments here that would otherwise go through // the generic marshaller. // See: https://gitlab.gnome.org/GNOME/gobject-introspection/-/issues/334 GIInfoType info_type = g_base_info_get_type(interface_info); if (info_type == GI_INFO_TYPE_STRUCT && g_struct_info_is_gtype_struct(interface_info)) { self->marshallers = >ype_struct_instance_in_marshallers; return true; } if (info_type == GI_INFO_TYPE_OBJECT) { GType gtype = g_registered_type_info_get_g_type(interface_info); if (g_type_is_a(gtype, G_TYPE_PARAM)) { self->marshallers = ¶m_instance_in_marshallers; return true; } } return gjs_arg_cache_build_interface_in_arg(cx, self, callable, interface_info, /* is_instance_param = */ true); } bool gjs_arg_cache_build_arg(JSContext* cx, GjsArgumentCache* self, GjsArgumentCache* arguments, uint8_t gi_index, GIDirection direction, GIArgInfo* arg, GICallableInfo* callable, bool* inc_counter_out) { g_assert(inc_counter_out && "forgot out parameter"); self->set_arg_pos(gi_index); self->arg_name = g_base_info_get_name(arg); g_arg_info_load_type(arg, &self->type_info); self->transfer = g_arg_info_get_ownership_transfer(arg); self->nullable = g_arg_info_may_be_null(arg); if (direction == GI_DIRECTION_IN) self->skip_out = true; else if (direction == GI_DIRECTION_OUT) self->skip_in = true; *inc_counter_out = true; GITypeTag type_tag = g_type_info_get_tag(&self->type_info); if (direction == GI_DIRECTION_OUT && g_arg_info_is_caller_allocates(arg)) { if (type_tag != GI_TYPE_TAG_INTERFACE) { gjs_throw(cx, "Unsupported type %s for argument %s with (out " "caller-allocates)", g_type_tag_to_string(type_tag), self->arg_name); return false; } GjsAutoBaseInfo interface_info = g_type_info_get_interface(&self->type_info); g_assert(interface_info); GIInfoType interface_type = g_base_info_get_type(interface_info); size_t size; if (interface_type == GI_INFO_TYPE_STRUCT) { size = g_struct_info_get_size(interface_info); } else if (interface_type == GI_INFO_TYPE_UNION) { size = g_union_info_get_size(interface_info); } else { gjs_throw(cx, "Unsupported type %s for argument %s with (out " "caller-allocates)", g_info_type_to_string(interface_type), self->arg_name); return false; } self->marshallers = &caller_allocates_out_marshallers; self->contents.caller_allocates_size = size; return true; } if (type_tag == GI_TYPE_TAG_INTERFACE) { GjsAutoBaseInfo interface_info = g_type_info_get_interface(&self->type_info); if (interface_info.type() == GI_INFO_TYPE_CALLBACK) { if (direction != GI_DIRECTION_IN) { // Can't do callbacks for out or inout gjs_throw(cx, "Function %s.%s has a callback out-argument %s, not " "supported", g_base_info_get_namespace(callable), g_base_info_get_name(callable), self->arg_name); return false; } if (strcmp(interface_info.name(), "DestroyNotify") == 0 && strcmp(interface_info.ns(), "GLib") == 0) { // We don't know (yet) what to do with GDestroyNotify appearing // before a callback. If the callback comes later in the // argument list, then the invalid marshallers will be // overwritten with the 'skipped' one. If no callback follows, // then this is probably an unsupported function, so the // function invocation code will check this and throw. self->marshallers = &invalid_in_marshallers; *inc_counter_out = false; } else { self->marshallers = &callback_in_marshallers; int destroy_pos = g_arg_info_get_destroy(arg); int closure_pos = g_arg_info_get_closure(arg); if (destroy_pos >= 0) gjs_arg_cache_set_skip_all(&arguments[destroy_pos]); if (closure_pos >= 0) gjs_arg_cache_set_skip_all(&arguments[closure_pos]); if (destroy_pos >= 0 && closure_pos < 0) { gjs_throw(cx, "Function %s.%s has a GDestroyNotify but no " "user_data, not supported", g_base_info_get_namespace(callable), g_base_info_get_name(callable)); return false; } self->contents.callback.scope = g_arg_info_get_scope(arg); self->set_callback_destroy_pos(destroy_pos); self->set_callback_closure_pos(closure_pos); } return true; } } if (type_tag == GI_TYPE_TAG_ARRAY && g_type_info_get_array_type(&self->type_info) == GI_ARRAY_TYPE_C) { int length_pos = g_type_info_get_array_length(&self->type_info); if (length_pos >= 0) { gjs_arg_cache_set_skip_all(&arguments[length_pos]); if (direction == GI_DIRECTION_IN) { self->marshallers = &c_array_in_marshallers; } else if (direction == GI_DIRECTION_INOUT) { self->marshallers = &c_array_inout_marshallers; } else { // Even if we skip the length argument most of time, we need to // do some basic initialization here. arguments[length_pos].set_arg_pos(length_pos); arguments[length_pos].marshallers = &array_length_out_marshallers; self->marshallers = &c_array_out_marshallers; } self->set_array_length_pos(length_pos); GIArgInfo length_arg; g_callable_info_load_arg(callable, length_pos, &length_arg); GITypeInfo length_type; g_arg_info_load_type(&length_arg, &length_type); self->contents.array.length_tag = g_type_info_get_tag(&length_type); if (length_pos < gi_index) { // we already collected length_pos, remove it *inc_counter_out = false; } return true; } } if (direction == GI_DIRECTION_IN) return gjs_arg_cache_build_normal_in_arg(cx, self, callable, type_tag); if (direction == GI_DIRECTION_INOUT) self->marshallers = &fallback_inout_marshallers; else self->marshallers = &fallback_out_marshallers; return true; } cjs-5.2.0/README0000644000175000017500000000206314144444702013320 0ustar jpeisachjpeisachJavaScript bindings for Cinnamon ================================ Based on GJS: https://wiki.gnome.org/action/show/Projects/Gjs Please do the following when reporting CJS crashes: =================================================== If possible, provide a stack trace. Run dmesg and provide the line related to the crash, for instance: [ 4947.459104] cinnamon[2868]: segfault at 7f2611ffffe8 ip 00007f2667dda305 sp 00007fffb416b9d0 error 4 in libcjs.so.0.0.0[7f2667db1000+c1000] Launch the Calculator, choose Advanced Mode and set it to Hexadecimal. Then substract the loading address (first address in brackets: 7f2667db1000) from the ip (00007f2667dda305). In the example above: ip: 00007f2667dda305 loading address: 7f2667db1000 00007f2667dda305 - 7f2667db1000 = 29305 This gives us the offset. Use addr2line to see what's under it in our shared library: addr2line -e /usr/lib/libcjs.so.0.0.0 29305 -fCi gjs_typecheck_boxed When reporting the bug, along with the trace and the dmesg line, please report that function name (in this example gjs_typecheck_boxed). cjs-5.2.0/meson_options.txt0000644000175000017500000000257214144444702016102 0ustar jpeisachjpeisach# Features option('cairo', type: 'feature', value: 'auto', description: 'Build cairo module') option('readline', type: 'feature', value: 'auto', description: 'Use readline for input in interactive shell and debugger') option('profiler', type: 'feature', value: 'auto', description: 'Build profiler (Linux only)') # Flags option('installed_tests', type: 'boolean', value: true, description: 'Install test programs') option('dtrace', type: 'boolean', value: false, description: 'Include dtrace trace support') option('systemtap', type: 'boolean', value: false, description: 'Include systemtap trace support (requires -Ddtrace=true)') option('bsymbolic_functions', type: 'boolean', value: true, description: 'Link with -Bsymbolic-functions linker flag used to avoid intra-library PLT jumps, if supported; not used for Visual Studio and clang-cl builds') option('spidermonkey_rtti', type: 'boolean', value: false, description: 'Needs to match SpiderMonkey\'s config option') option('skip_dbus_tests', type: 'boolean', value: false, description: 'Skip tests that use a DBus session bus') option('skip_gtk_tests', type: 'boolean', value: false, description: 'Skip tests that need a display connection') option('verbose_logs', type: 'boolean', value: false, description: 'Enable extra log messages that may decrease performance (not allowed in release builds)') cjs-5.2.0/examples/0000755000175000017500000000000014144444702014255 5ustar jpeisachjpeisachcjs-5.2.0/examples/http-server.js0000644000175000017500000000214214144444702017075 0ustar jpeisachjpeisach// This is a simple example of a HTTP server in Gjs using libsoup const Soup = imports.gi.Soup; function handler(server, msg, path, query, client) { msg.status_code = 200; msg.response_headers.set_content_type('text/html', {}); msg.response_body.append(` Greetings, visitor from ${client.get_host()}
What is your name?
`); } function helloHandler(server, msg, path, query) { if (!query) { msg.set_redirect(302, '/'); return; } msg.status_code = 200; msg.response_headers.set_content_type('text/html', {charset: 'UTF-8'}); msg.response_body.append(` Hello, ${query.myname}! ☺
Go back `); } function main() { let server = new Soup.Server({port: 1080}); server.add_handler('/', handler); server.add_handler('/hello', helloHandler); server.run(); } main(); cjs-5.2.0/examples/gtk-application.js0000644000175000017500000000703514144444702017706 0ustar jpeisachjpeisach// See the note about Application.run() at the bottom of the script const System = imports.system; // Include this in case both GTK3 and GTK4 installed, otherwise an exception // will be thrown imports.gi.versions.Gtk = '3.0'; const Gio = imports.gi.Gio; const GLib = imports.gi.GLib; const GObject = imports.gi.GObject; const Gtk = imports.gi.Gtk; // An example GtkApplication with a few bells and whistles, see also: // https://wiki.gnome.org/HowDoI/GtkApplication var ExampleApplication = GObject.registerClass({ Properties: { 'exampleprop': GObject.ParamSpec.string( 'exampleprop', // property name 'ExampleProperty', // nickname 'An example read write property', // description GObject.ParamFlags.READWRITE, // read/write/construct... 'a default value' ), }, Signals: {'examplesig': {param_types: [GObject.TYPE_INT]}}, }, class ExampleApplication extends Gtk.Application { _init() { super._init({ application_id: 'org.gnome.gjs.ExampleApplication', flags: Gio.ApplicationFlags.FLAGS_NONE, }); } // Example signal emission emitExamplesig(number) { this.emit('examplesig', number); } vfunc_startup() { super.vfunc_startup(); // An example GAction, see: https://wiki.gnome.org/HowDoI/GAction let exampleAction = new Gio.SimpleAction({ name: 'exampleAction', parameter_type: new GLib.VariantType('s'), }); exampleAction.connect('activate', (action, param) => { param = param.deepUnpack().toString(); if (param === 'exampleParameter') log('Yes!'); }); this.add_action(exampleAction); } vfunc_activate() { super.vfunc_activate(); this.hold(); // Example ApplicationWindow let window = new Gtk.ApplicationWindow({ application: this, title: 'Example Application Window', default_width: 300, default_height: 200, }); let label = new Gtk.Label({label: this.exampleprop}); window.add(label); window.connect('delete-event', () => { this.quit(); }); window.show_all(); // Example GNotification, see: https://developer.gnome.org/GNotification/ let notif = new Gio.Notification(); notif.set_title('Example Notification'); notif.set_body('Example Body'); notif.set_icon( new Gio.ThemedIcon({name: 'dialog-information-symbolic'}) ); // A default action for when the body of the notification is clicked notif.set_default_action("app.exampleAction('exampleParameter')"); // A button for the notification notif.add_button( 'Button Text', "app.exampleAction('exampleParameter')" ); // This won't actually be shown, since an application needs a .desktop // file with a base name matching the application id this.send_notification('example-notification', notif); // Withdraw this.withdraw_notification('example-notification'); } }); // The proper way to run a Gtk.Application or Gio.Application is take ARGV and // prepend the program name to it, and pass that to run() let app = new ExampleApplication(); app.run([System.programInvocationName].concat(ARGV)); // Or a one-liner... // (new ExampleApplication()).run([System.programInvocationName].concat(ARGV)); cjs-5.2.0/examples/README0000644000175000017500000000011514144444702015132 0ustar jpeisachjpeisachIn order to run those example scripts, do: cjs-console script-filename.js cjs-5.2.0/examples/gio-cat.js0000644000175000017500000000117614144444702016143 0ustar jpeisachjpeisach const ByteArray = imports.byteArray; const GLib = imports.gi.GLib; const Gio = imports.gi.Gio; let loop = GLib.MainLoop.new(null, false); function cat(filename) { let f = Gio.file_new_for_path(filename); f.load_contents_async(null, (obj, res) => { let contents; try { contents = obj.load_contents_finish(res)[1]; } catch (e) { logError(e); loop.quit(); return; } print(ByteArray.toString(contents)); loop.quit(); }); loop.run(); } if (ARGV.length !== 1) printerr('Usage: gio-cat.js filename'); else cat(ARGV[0]); cjs-5.2.0/examples/glistmodel.js0000644000175000017500000000715614144444702016767 0ustar jpeisachjpeisach/* exported GjsListStore */ 'use strict'; const GObject = imports.gi.GObject; const Gio = imports.gi.Gio; /** * An example of implementing the GListModel interface in GJS. The only real * requirement here is that the class be derived from some GObject. */ var GjsListStore = GObject.registerClass({ GTypeName: 'GjsListStore', Implements: [Gio.ListModel], }, class MyList extends GObject.Object { _init() { super._init(); /* We'll use a native Array as internal storage for the list model */ this._items = []; } /* Implementing this function amounts to returning a GType. This could be a * more specific GType, but must be a subclass of GObject. */ vfunc_get_item_type() { return GObject.Object.$gtype; } /* Implementing this function just requires returning the GObject at * @position or %null if out-of-range. This must explicitly return %null, * not `undefined`. */ vfunc_get_item(position) { return this._items[position] || null; } /* Implementing this function is as simple as return the length of the * storage object, in this case an Array. */ vfunc_get_n_items() { return this._items.length; } /** * Insert an item in the list. If @position is greater than the number of * items in the list or less than `0` it will be appended to the end of the * list. * * @param {GObject.Object} item - the item to add * @param {number} position - the position to add the item */ insertItem(item, position) { if (!(item instanceof GObject.Object)) throw new TypeError('not a GObject'); if (position < 0 || position > this._items.length) position = this._items.length; this._items.splice(position, 0, item); this.items_changed(position, 0, 1); } /** * Append an item to the list. * * @param {GObject.Object} item - the item to add */ appendItem(item) { if (!(item instanceof GObject.Object)) throw new TypeError('not a GObject'); let position = this._items.length; this._items.push(item); this.items_changed(position, 0, 1); } /** * Prepend an item to the list. * * @param {GObject.Object} item - the item to add */ prependItem(item) { if (!(item instanceof GObject.Object)) throw new TypeError('not a GObject'); this._items.unshift(item); this.items_changed(0, 0, 1); } /** * Remove @item from the list. If @item is not in the list, this function * does nothing. * * @param {GObject.Object} item - the item to remove */ removeItem(item) { if (!(item instanceof GObject.Object)) throw new TypeError('not a GObject'); let position = this._items.indexOf(item); if (position === -1) return; this._items.splice(position, 1); this.items_changed(position, 1, 0); } /** * Remove the item at @position. If @position is outside the length of the * list, this function does nothing. * * @param {number} position - the position of the item to remove */ removePosition(position) { if (position < 0 || position >= this._items.length) return; this._items.splice(position, 1); this.items_changed(position, 1, 0); } /** * Clear the list of all items. */ clear() { let length = this._items.length; if (length === 0) return; this._items = []; this.items_changed(0, length, 0); } }); cjs-5.2.0/examples/webkit.js0000644000175000017500000000060514144444702016101 0ustar jpeisachjpeisachimports.gi.versions.Gtk = '3.0'; imports.gi.versions.WebKit2 = '4.0'; const Gtk = imports.gi.Gtk; const WebKit = imports.gi.WebKit2; Gtk.init(null); let win = new Gtk.Window(); let view = new WebKit.WebView(); view.load_uri('http://www.google.com/'); win.add(view); win.connect('destroy', () => { Gtk.main_quit(); }); win.set_size_request(640, 480); win.show_all(); Gtk.main(); cjs-5.2.0/examples/websocket-client.js0000644000175000017500000000245514144444702020063 0ustar jpeisachjpeisach// This is an example of a WebSocket client in Gjs using libsoup // https://developer.gnome.org/libsoup/stable/libsoup-2.4-WebSockets.html const Soup = imports.gi.Soup; const GLib = imports.gi.GLib; const byteArray = imports.byteArray; const loop = GLib.MainLoop.new(null, false); const session = new Soup.Session(); const message = new Soup.Message({ method: 'GET', uri: Soup.URI.new('wss://echo.websocket.org'), }); session.websocket_connect_async(message, 'origin', [], null, websocket_connect_async_callback); function websocket_connect_async_callback(_session, res) { let connection; try { connection = session.websocket_connect_finish(res); } catch (e) { logError(e); loop.quit(); return; } connection.connect('closed', () => { log('closed'); loop.quit(); }); connection.connect('error', (self, err) => { logError(err); loop.quit(); }); connection.connect('message', (self, type, data) => { if (type !== Soup.WebsocketDataType.TEXT) return; const str = byteArray.toString(byteArray.fromGBytes(data)); log(`message: ${str}`); connection.close(Soup.WebsocketCloseCode.NORMAL, null); }); log('open'); connection.send_text('hello'); } loop.run(); cjs-5.2.0/examples/dbus-client.js0000644000175000017500000001115614144444702017030 0ustar jpeisachjpeisach'use strict'; const GLib = imports.gi.GLib; const Gio = imports.gi.Gio; /* * An XML DBus Interface */ const ifaceXml = ` `; // Pass the XML string to make a re-usable proxy class for an interface proxies. const TestProxy = Gio.DBusProxy.makeProxyWrapper(ifaceXml); let proxy = null; let proxySignalId = 0; let proxyPropId = 0; // Watching a name on DBus. Another option is to create a proxy with the // `Gio.DBusProxyFlags.DO_NOT_AUTO_START` flag and watch the `g-name-owner` // property. function onNameAppeared(connection, name, _owner) { print(`"${name}" appeared on the session bus`); // If creating a proxy synchronously, errors will be thrown as normal try { proxy = new TestProxy( Gio.DBus.session, 'org.gnome.gjs.Test', '/org/gnome/gjs/Test' ); } catch (e) { logError(e); return; } // Proxy wrapper signals use the special functions `connectSignal()` and // `disconnectSignal()` to avoid conflicting with regular GObject signals. proxySignalId = proxy.connectSignal('TestSignal', (proxy_, name_, args) => { print(`TestSignal: ${args[0]}, ${args[1]}`); }); // To watch property changes, you can connect to the `g-properties-changed` // GObject signal with `connect()` proxyPropId = proxy.connect('g-properties-changed', (proxy_, changed, invalidated) => { for (let [prop, value] of Object.entries(changed.deepUnpack())) print(`Property '${prop}' changed to '${value.deepUnpack()}'`); for (let prop of invalidated) print(`Property '${prop}' invalidated`); }); // Reading and writing properties is straight-forward print(`ReadOnlyProperty: ${proxy.ReadOnlyProperty}`); print(`ReadWriteProperty: ${proxy.ReadWriteProperty}`); proxy.ReadWriteProperty = !proxy.ReadWriteProperty; print(`ReadWriteProperty: ${proxy.ReadWriteProperty}`); // Both synchronous and asynchronous functions will be generated try { let value = proxy.SimpleMethodSync(); print(`SimpleMethod: ${value}`); } catch (e) { logError(`SimpleMethod: ${e.message}`); } proxy.ComplexMethodRemote('input string', (value, error, fdList) => { // If @error is not `null`, then an error occurred if (error !== null) { logError(error); return; } print(`ComplexMethod: ${value}`); // Methods that return file descriptors are fairly rare, so you should // know to expect one or not. if (fdList !== null) { // } }); } function onNameVanished(connection, name) { print(`"${name}" vanished from the session bus`); if (proxy !== null) { proxy.disconnectSignal(proxySignalId); proxy.disconnect(proxyPropId); proxy = null; } } let busWatchId = Gio.bus_watch_name( Gio.BusType.SESSION, 'org.gnome.gjs.Test', Gio.BusNameWatcherFlags.NONE, onNameAppeared, onNameVanished ); // Start an event loop let loop = GLib.MainLoop.new(null, false); loop.run(); // Unwatching names works just like disconnecting signal handlers. Gio.bus_unown_name(busWatchId); /* Asynchronous Usage * * Below is the alternative, asynchronous usage of proxy wrappers. If creating * a proxy asynchronously, you should not consider the proxy ready to use until * the callback is invoked without error. */ proxy = null; new TestProxy( Gio.DBus.session, 'org.gnome.gjs.Test', '/org/gnome/gjs/Test', (sourceObj, error) => { // If @error is not `null` it will be an Error object indicating the // failure. @proxy will be `null` in this case. if (error !== null) { logError(error); return; } // At this point the proxy is initialized and you can start calling // functions, using properties and so on. proxy = sourceObj; print(`ReadOnlyProperty: ${proxy.ReadOnlyProperty}`); }, // Optional Gio.Cancellable object. Pass `null` if you need to pass flags. null, // Optional flags passed to the Gio.DBusProxy constructor Gio.DBusProxyFlags.NONE ); cjs-5.2.0/examples/dbus-service.js0000644000175000017500000000647514144444702017222 0ustar jpeisachjpeisach'use strict'; const GLib = imports.gi.GLib; const Gio = imports.gi.Gio; /* * An XML DBus Interface */ const ifaceXml = ` `; // An example of the service-side implementation of the above interface. class Service { constructor() { this.dbus = Gio.DBusExportedObject.wrapJSObject(ifaceXml, this); } // Properties get ReadOnlyProperty() { return 'a string'; } get ReadWriteProperty() { if (this._readWriteProperty === undefined) return false; return this._readWriteProperty; } set ReadWriteProperty(value) { if (this.ReadWriteProperty !== value) { this._readWriteProperty = value; // Emitting property changes over DBus this.dbus.emit_property_changed( 'ReadWriteProperty', new GLib.Variant('b', value) ); } } // Methods SimpleMethod() { print('SimpleMethod() invoked'); } ComplexMethod(input) { print(`ComplexMethod() invoked with "${input}"`); return input.length; } // Signals emitTestSignal() { this.dbus.emit_signal( 'TestSignal', new GLib.Variant('(sb)', ['string', false]) ); } } // Once you've created an instance of your service, you will want to own a name // on the bus so clients can connect to it. let serviceObj = new Service(); let serviceSignalId = 0; function onBusAcquired(connection, _name) { // At this point you have acquired a connection to the bus, and you should // export your interfaces now. serviceObj.dbus.export(connection, '/org/gnome/gjs/Test'); } function onNameAcquired(_connection, _name) { // Clients will typically start connecting and using your interface now. // Emit the TestSignal every few seconds serviceSignalId = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 3, () => { serviceObj.emitTestSignal(); return GLib.SOURCE_CONTINUE; }); } function onNameLost(_connection, _name) { // Clients will know not to call methods on your interface now. Usually this // callback will only be invoked if you try to own a name on DBus that // already has an owner. // Stop emitting the test signal if (serviceSignalId > 0) { GLib.Source.remove(serviceSignalId); serviceSignalId = 0; } } let ownerId = Gio.bus_own_name( Gio.BusType.SESSION, 'org.gnome.gjs.Test', Gio.BusNameOwnerFlags.NONE, onBusAcquired, onNameAcquired, onNameLost ); // Start an event loop let loop = GLib.MainLoop.new(null, false); loop.run(); // Unowning names works just like disconnecting, but note that `onNameLost()` // will not be invoked in this case. Gio.bus_unown_name(ownerId); if (serviceSignalId > 0) { GLib.Source.remove(serviceSignalId); serviceSignalId = 0; } cjs-5.2.0/examples/test.jpg0000644000175000017500000003442014144444702015741 0ustar jpeisachjpeisachJFIFHHC     C    B !1AQa"q2 #B3Rb$CSr46!1A"Qa2qB#3CRS ?;4jOkJiMZqh=T%x hB2>ZdbHb?MO]K_~}~/VyUշR,GjyYKGۼH=7vN3n.)4$gvR1 E!:4̤ls5b}>H*"eٲOdԃ3RQKt@UwD5Ov* RJOR AJH!iBic)R\cJagZkJ!=L92\u<,%Oo[o8 {V~͉hB"*<ϽV[\.(*hѩ8M +je,SXN{SߒHiǵA?(*$sxQrpa=@ "&ExLQ^QPjgLwoNy?XҬ׿|;!ꙣoq_~ <лh&ڪkjfJwcc8895` Mg}_i {ԄD mz(g?E,ʱ ƒj*L3$#I-(!R "TqS  B}9ܛb[+ORڌJ1KjXnu^GVI7ž]IkmR^XrN5ȔV{54}Rs.N(JjGێ,p*%%^$n|=ϕnvύrzaZOz:r5-;*PRxHQV w A#MU9[yzlٯIJuN9% uN)Grs%ɘ ݣÙM\z:Fی-`m4:,io ёsr+t7RjS:1NŭgnohO'KȚ9Pk # WI/DoB)Xkp bEcPi,ET,At$sH(<ԐщME$ )Sx*]ّMi ~ TiH<△U() )*Q)P   *\)zSZtbAHrDOj`#R3P' tCv47/8S `V$mۜe#*W?ʫJZ^|"6fP`C1Og`.Ǡ+|S֖Oq1v7<\-X @=ƗI>C@^dۚGʩжmnU?|6zVGC&7X,۴RGi)~IYܵ-dg'!t`U7{ll ͣi[9I>沤pzihJ_LCX9,l- ,I%\*67]!FH)jQXW-ncctTx^$RJAM6<$8SkNPF%dԻIRF)%HXE=!I::bqUJ?(āQ%JtOM)#iJӣSQ)ѩR;nV@{n,hKWۜK!Jn>6)`'g򾋦[Q#(:MC0Gʕm #8 z&Y7Ȗ&Z puwda#YݳeU~Kk#plMûℽOZFa,|T{!Ê 6AH!*#NHCN[ u2;ޕTN|7}} D94tґ-/B_r1kesb*3 Y@BpxQDp\DeOmd))Hޞ ?Z^t 415_bՈ!bA:HJGMGlFBauy Ax (ʰJ"P\1I1_1NGDI|SJ!=r:j* htNÿ"R14#8I?X}PA۸R@==𛨵B'j ҵ| ih) oy`@nl.:S|ًW}幹+RI VOl`r{ js(4FUgnrq-rmJm,>O$ym ۇgAfIY)8O^j$k5pϨv@⣷]14#n|W 9\@c6Ԗ f@VEs9+5Y 7hC-W "C6HtC3=M$x7{\CmJF28~lUonP㑢>qAZކ|qNJf|d󸸤^rPكr16=).# u=Ч>$OG] Ҳ@|rp79Ț9qCXI+W R+;wQVH8ӴF@=14m<29roc Z ֬ʸsǭ0#-{}? uFF1'{Ի~"M:#N]$"3/Q$g+60.|?K:6w[Z|j)DyA]R7r$m ~|Q&k$icJ!y)!d'p8ϒ{/e<8X(ǯv.4 G(ʎ2qϿ4BMYrolU!*_*{<ȸ8!ۡ3e;!{iNA+I'$KoNưQ긛e̒vC<e%nG ʕ1Uuԭ>dqZ+*#tm)uFIJ\uA{P$r0yCf3o*BTN!{>[J5`OgE|>N g[%60kԽ(l234HQGm߿iahy#>UAQV_PFGZ„/,Kh:<CI'aXo6j΁5s Caeѐ2#5.A BAPsI5yr}vCu,OmjjOM5ݴѮQ -l/,X^kmh .|GK,qj)”>^@vt_Gb`cqTE' @=ϕ]kKY;MfRPQD Q Y{{ä3Ze+< wI'?"# )Gtkjt-^udg$>yX ?Vݹ7=P.HBZCHSM$.1IP$܀qt:26ϑ??}gֽJބ7[r4X!I*.$R{`C5+dzif:Kz|72z;IR-o-mބ(AT8զL׀*Y2@m~^BqR쇶ʘda)@ 8˜ zRe 8RC9- EbH{fx ji̩. qp^@ۼU;Ge$G N0GjcaT9u-6ƂBɞBJtWm6AJV*4~tVŭ:[K|`)4~Fiov&b` RW;;\( MGa͒c&Fɩ𣞨 m@RQ!?%1C[GiɒaqA)B{j$z'ܷoW";V=(b318U`^i}!Lm%@浣 c>BeH*i%JT"tצZYz$^Vv'U<=DXCyZ:)Nt7D\ _D?e R#?Pl](5ڙcNq"3H8ӳIi=G^U^V£FH9'}g>_4Q(qH (dDyjN8“ZmbqVLp a0ac܃SJvvF-1_\2Yi88d^7_M=;MXNa\;BgVЗΨ JV8A#jE'|Ni<^~W1.]F Z JqC~VR6%9+>X4$g~b%w4ܝÂqֳ 4.CytFXS% -e#*x2EV{=nP7jh3VlχDzjz] vjͻtDSuЛ[ڢ>omPu di`ehM*غ݆ʔ`zt>0dT2jO^SW)p!b=CWB )u$ȅ tQXD]ic#C0NDc Ə:{QXAÉ:{ :- m ;9 )SڕR#*BZu R"1W5}BߵWZrnf,Fm606{j?xf:q7+˫[<$y+6}NMͭulf+x N\:Ǭշ,iK<䓵+nx#$qkB _ߚ$p YK2^5n/[600MD3Jr}PŧC6SMy2F>6>~sC NGiyԱW75qݐt^Oz~c,eS$:i8s؟j\Ҽ|:^ 6-޻qBfL' emI U۱Kz-3bèL]cC 1n.&`g )o 품 8m 'j>p9^*R$ V,cWűgcC J| (c!N녊>Uq-\}a6v*8'3} BѺե.+Ks(PRǨUPSZ/\Oj$lyK[&4*gEhڬE"ިUƝ$ét=WhkBJvP(Q⻤t3c(rԥ+$X%caRjPMH l%\62Kc5]PKٟG=46}Gik'P;GEw%͠ CMĠQ$3n65hަvʹ1TAs9Z? ɊVů]eΏq.&Pg˻%Jڡ?js#9P{r)d:Rv{Z eR:ׂ w~Jԭ sP-V@:I) m\B@7K4}6!lZy'''Fjg*p:#b\wPk JNA< nmlG /!(xIeҟH8ڑ1ul.!(v*d\yT`?_jf2[Z1٣ DzjSuHe0$ҐVR=|+?W3ġ:h}ډ|B_5OصkG%拉"״ 䁟L͘u[K s+Z>4)t}[ܢ\ܖ%_7*Q)$sUked6NЁѽx4sDg)G׊sSjk5-)wN\a Tq@8)CP.ݒ);<$ ٺ^47XEYJ %+8N{cmlDW9:nʏD\"%ثT9*d>j@װ8ΖqoUxr(Jvť]K" /N,)06js@A|v,v*?tGr0k1)]e8Rz^_^=nVMg͉v`$}생#i>$~}S{Zŕ2)Pw$[B #$v=5A@JD) .czr}k~Ju'P8(i$xc wֲmW8ulv)PۉI'r'w}1چ3jN=c!}z)<0m?. oq[$ VlgJՍ}qH|IzGHO&5ZM7V\Y8fH\Ҡ0Bv{[RM =G/7G+V /Kܗ}e+[N iAن8h Imv AmA +"N\PZJx%~X]Kp6ą O|PSaL5}$RۈV33pTe{T{xI Y/T!67>9ZZR^Jÿ8AZsֶKv<)G<"*{7=lÁH ϪEYhr+i)2AEE$#Y3\rGjy.9IA,5uʡSU '? [{cr7m#je-unb )*Vx5kWhǼ9PR*bAl$@ǸVuNmpЙ'i',+mU?y=5BkkK:Ng"v݇wBf (r&AQQŽ삐pG>Hƛ)-/-›n/\PwbmNS+jtofVzjY\.q "dYqw.QTD87mVRqGi22'<myNI7L5CTZVw6?0F@ϯߎ1& -ڂFcQxz-突xѱ^#pq~mbFǾ|)b}Jy 20W A5gژݢ)b+˯evyG)JBN}<|}"öxxm7BHT p{J_EVs_lOTV~`3ϵFW S v[3Bb,q%Rv6G9oh[uҰ,Xw+~/)+ZBKkHWIfGM[zu 靲ElyѮPI7; +#}-VmUR$֔qbbZ㎁o 2ʂR YNp7u#^ua)HPOG3y秩BkL8BQVwSwpe:A9؟H(PQ'WէH1JB}0פCYwU]IUS߅.(um=&LͮC#psM*'P|:id\ ^Rɬ!ّ)[]DyX HvaJ0SO6? oifLiR l3ZHPQsAR FЌbo Z $Xx={ 'f8Ef]Pe H Pޭ22{zI iiuYٰ9S B`s-XPm['b r;wjZgu*ctyh+L T3#I \ݧszEc]Ӡ+uil9|\FP[ ##`>=ɫ-mI"Ypa)}+:PmU.Su6 IZharzEҔd#=&B0ֆǒC"X$)drqVYPBtC4y4Ue2Hyr2|{x)&:gB^&У @Unn>]J9_`%n-|U'ԉZ6P\NsڇGNR2ATr)¤Rid%vcIѽPNS+ 4e>E*RBɅ91y/R~>94XmBm2}ojM9r:]niCL7|j0PK.1Q'=YFf[[C%C  _¦k\w;fVŃ;({YҴhhE#wkuࣨVRnm+`x3?ڍqsG~88tTewӒy˴NaM/P fmlǮAjPmEJY94--&78bZۓ>kkQ(BO Y>S3Nvi/ •}[noCۡ '>Ϡ9 P:ok-VRXma$Rk#5 KPBRY=kIwTaT!ݤRH+9|tţ\v.U>Hl]RJ ChkI@?ohKm$0 U>w0B;qԴ9@$-!Yl]mkhhpRsœȸ{6=>ә ϟA6)^Ou?ZHjz~ډjaDF=+GU{lp֊ Dt '>{+9S AJ8wNn^t'Z;Pex|Ǝg%rk|tWP\VR}yU33>*bى7f6~e2A(FFȷR<"rhmmԜbZ *z{ilrܖRQ@s|3$s FwmZNJS!9'<?,P\|@gYϸ+ܥꁺY3|!VM# r~nHXSENR@R @귐ZEtz} hsaLNLer%2*27Df랟.w |0 B) *FrG% hI̓d&m8ФPIlv{VƛPE8Ҷ^X\ڨs*+%);e k8M\VXSe#9²yҳD.iaǘF_H77޴ s&3tCI8䁐2{WMهv'漇O=@]/ee@H`V \~BAܯAɩo 梾ʆUqSlr;rR{ .br@:)+@Hcmg~~>e"ܭ}އ*"ٙ ^TCuzu/>H"as9톽Җw;#џzb`"kyޒ<+q\ܥ%Gi4PDhXHA4" NQ<+)WC% JJ8!(T('9+uH>G&q'Ί†iCeA!pbP55 ]ec:ZqM: )ROP"k#![=?NV؇}rtdc3I_p<40 QV۪x_:mR$ACLj;CYO6/dN)?ai~ pi7ƟMd꾄Աټ0҈{ ړug3-HYgb]ZO9V?CE'k9KfLGXqK4J4S3w I ;Vꛕ SWZqPg"{C|f31hme)@?47̳tj<K7̶C*a:+H2{e}%I$'hMĩ{ZyQXꉠ;EJ8:ڝZ>8hzm'F%T}>8= k?&4e/ha0#sԽNGJjWQ/fK#O;#lK٥)MD?uzD腫S!SF*PsU; $c8D9KڟP;Z5֣[vq$~Iio P: #"S.>p[*?\ۊJAڌ;~QRXOΆHV#P2sP.Fhە/> r0oTJTA㏥1!L9KBrG3Mip%)^)Lz\ڕ (`9!F_cjs-5.2.0/examples/gettext.js0000644000175000017500000000054514144444702016303 0ustar jpeisachjpeisachimports.gi.versions.Gtk = '3.0'; const Gettext = imports.gettext; const Gtk = imports.gi.Gtk; Gettext.bindtextdomain('gnome-panel-3.0', '/usr/share/locale'); Gettext.textdomain('gnome-panel-3.0'); Gtk.init(null); let w = new Gtk.Window({type: Gtk.WindowType.TOPLEVEL}); w.add(new Gtk.Label({label: Gettext.gettext('Panel')})); w.show_all(); Gtk.main(); cjs-5.2.0/examples/gtk.js0000644000175000017500000000467414144444702015413 0ustar jpeisachjpeisach// Include this in case both GTK3 and GTK4 installed, otherwise an exception // will be thrown imports.gi.versions.Gtk = '3.0'; const Gtk = imports.gi.Gtk; // Initialize Gtk before you start calling anything from the import Gtk.init(null); // Construct a top-level window let win = new Gtk.Window({ type: Gtk.WindowType.TOPLEVEL, title: 'A default title', default_width: 300, default_height: 250, // A decent example of how constants are mapped: // 'Gtk' and 'WindowPosition' from the enum name GtkWindowPosition, // 'CENTER' from the enum's constant GTK_WIN_POS_CENTER window_position: Gtk.WindowPosition.CENTER, }); // Object properties can also be set or changed after construction, unless they // are marked construct-only. win.title = 'Hello World!'; // This is a callback function function onDeleteEvent() { log('delete-event emitted'); // If you return false in the "delete_event" signal handler, Gtk will emit // the "destroy" signal. // // Returning true gives you a chance to pop up 'are you sure you want to // quit?' type dialogs. return false; } // When the window is given the "delete_event" signal (this is given by the // window manager, usually by the "close" option, or on the titlebar), we ask // it to call the onDeleteEvent() function as defined above. win.connect('delete-event', onDeleteEvent); // GJS will warn when calling a C function with unexpected arguments... // // window.connect("destroy", Gtk.main_quit); // // ...so use arrow functions for inline callbacks with arguments to adjust win.connect('destroy', () => { Gtk.main_quit(); }); // Create a button to close the window let button = new Gtk.Button({ label: 'Close the Window', // Set visible to 'true' if you don't want to call button.show() later visible: true, // Another example of constant mapping: // 'Gtk' and 'Align' are taken from the GtkAlign enum, // 'CENTER' from the constant GTK_ALIGN_CENTER valign: Gtk.Align.CENTER, halign: Gtk.Align.CENTER, }); // Connect to the 'clicked' signal, using another way to call an arrow function button.connect('clicked', () => win.destroy()); // Add the button to the window win.add(button); // Show the window win.show(); // All gtk applications must have a Gtk.main(). Control will end here and wait // for an event to occur (like a key press or mouse event). The main loop will // run until Gtk.main_quit is called. Gtk.main(); cjs-5.2.0/examples/http-client.js0000644000175000017500000000244214144444702017050 0ustar jpeisachjpeisach// This is a simple example of a HTTP client in Gjs using libsoup // https://developer.gnome.org/libsoup/stable/libsoup-client-howto.html const Soup = imports.gi.Soup; const GLib = imports.gi.GLib; const byteArray = imports.byteArray; const loop = GLib.MainLoop.new(null, false); const session = new Soup.Session(); const message = new Soup.Message({ method: 'GET', uri: Soup.URI.new('http://localhost:1080/hello?myname=gjs'), }); session.send_async(message, null, send_async_callback); function read_bytes_async_callback(inputStream, res) { let data; try { data = inputStream.read_bytes_finish(res); } catch (e) { logError(e); loop.quit(); return; } log(`body:\n${byteArray.toString(byteArray.fromGBytes(data))}`); loop.quit(); } function send_async_callback(self, res) { let inputStream; try { inputStream = session.send_finish(res); } catch (e) { logError(e); loop.quit(); return; } log(`status: ${message.status_code} - ${message.reason_phrase}`); message.response_headers.foreach((name, value) => { log(`${name}: ${value}`); }); inputStream.read_bytes_async(message.response_headers.get('content-length'), null, null, read_bytes_async_callback); } loop.run(); cjs-5.2.0/examples/calc.js0000644000175000017500000000675714144444702015534 0ustar jpeisachjpeisach#!/usr/bin/env gjs imports.gi.versions.Gtk = '3.0'; const {Gtk} = imports.gi; Gtk.init(null); var calcVal = ''; function updateDisplay() { label.set_markup(`${calcVal}`); if (calcVal === '') label.set_markup("0"); } function clear() { calcVal = ''; updateDisplay(); } function backspace() { calcVal = calcVal.substring(0, calcVal.length - 1); updateDisplay(); } function pressedEquals() { calcVal = calcVal.replace('sin', 'Math.sin'); calcVal = calcVal.replace('cos', 'Math.cos'); calcVal = calcVal.replace('tan', 'Math.tan'); calcVal = eval(calcVal); // Avoid ridiculous amounts of precision from toString. if (calcVal === Math.floor(calcVal)) calcVal = Math.floor(calcVal); else // bizarrely gjs loses str.toFixed() somehow?! calcVal = Math.floor(calcVal * 10000) / 10000; label.set_markup(`${calcVal}`); } function pressedOperator(button) { calcVal += button.label; updateDisplay(); } function pressedNumber(button) { calcVal = (calcVal === 0 ? '' : calcVal) + button.label; updateDisplay(); } function swapSign() { calcVal = calcVal[0] === '-' ? calcVal.substring(1) : `-${calcVal}`; updateDisplay(); } function randomNum() { calcVal = `${Math.floor(Math.random() * 1000)}`; updateDisplay(); } function packButtons(buttons, vbox) { var hbox = new Gtk.HBox(); hbox.homogeneous = true; vbox.pack_start(hbox, true, true, 2); for (let i = 0; i <= 4; i++) hbox.pack_start(buttons[i], true, true, 1); } function createButton(str, func) { var btn = new Gtk.Button({label: str}); btn.connect('clicked', func); return btn; } function createButtons() { var vbox = new Gtk.VBox({homogeneous: true}); packButtons([ createButton('(', pressedNumber), createButton('←', backspace), createButton('↻', randomNum), createButton('Clr', clear), createButton('±', swapSign), ], vbox); packButtons([ createButton(')', pressedNumber), createButton('7', pressedNumber), createButton('8', pressedNumber), createButton('9', pressedNumber), createButton('/', pressedOperator), ], vbox); packButtons([ createButton('sin(', pressedNumber), createButton('4', pressedNumber), createButton('5', pressedNumber), createButton('6', pressedNumber), createButton('*', pressedOperator), ], vbox); packButtons([ createButton('cos(', pressedNumber), createButton('1', pressedNumber), createButton('2', pressedNumber), createButton('3', pressedNumber), createButton('-', pressedOperator), ], vbox); packButtons([ createButton('tan(', pressedNumber), createButton('0', pressedNumber), createButton('.', pressedNumber), createButton('=', pressedEquals), createButton('+', pressedOperator), ], vbox); return vbox; } var win = new Gtk.Window({ title: 'Calculator', resizable: false, opacity: 0.6, }); win.resize(250, 250); win.connect('destroy', () => Gtk.main_quit()); var label = new Gtk.Label({label: ''}); label.set_alignment(1, 0); updateDisplay(); var mainvbox = new Gtk.VBox(); mainvbox.pack_start(label, false, true, 1); mainvbox.pack_start(new Gtk.HSeparator(), false, true, 5); mainvbox.pack_start(createButtons(), true, true, 2); win.add(mainvbox); win.show_all(); Gtk.main(); cjs-5.2.0/examples/clutter.js0000644000175000017500000000065514144444702016303 0ustar jpeisachjpeisachconst Clutter = imports.gi.Clutter; Clutter.init(null); const stage = new Clutter.Stage({visible: true}); let texture = new Clutter.Texture({ filename: 'test.jpg', reactive: true, }); texture.connect('button-press-event', () => { log('Clicked!'); return Clutter.EVENT_STOP; }); const [, color] = Clutter.Color.from_string('Black'); stage.background_color = color; stage.add_child(texture); Clutter.main(); cjs-5.2.0/COPYING0000644000175000017500000000237114144444702013475 0ustar jpeisachjpeisachCopyright (c) 2008 litl, LLC This project is dual-licensed as MIT and LGPLv2+. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. The following files contain code from Mozilla which is triple licensed under MPL1.1/LGPLv2+/GPLv2+: The console module (modules/console.c) Stack printer (gjs/stack.c) cjs-5.2.0/.travis.yml0000644000175000017500000000277414144444702014562 0ustar jpeisachjpeisachlanguage: c dist: trusty cache: directories: - ~/docker matrix: include: - env: BASE=ubuntu IMAGE=ubuntu:16.04 CCO=gcc - env: BASE=ubuntu IMAGE=ubuntu:16.04 CCO=clang - env: BASE=ubuntu IMAGE=ubuntu:16.10 CCO=gcc - env: BASE=fedora IMAGE=fedora:25 CCO=gcc before_install: - cd .. - git clone --depth 1 https://github.com/GNOME/jhbuild.git before_script: - export NAME=$(echo $IMAGE | sed "s/:/_/g") # Check if there is a saved base image - if [[ -f ~/docker/$NAME.tar.gz ]]; then cat ~/docker/$NAME.tar.gz | docker import - local/$NAME; fi # Build the base image, if necessary - 'if [[ ! -f ~/docker/$NAME.tar.gz ]]; then docker run -v $(pwd):/cwd -v $(pwd)/gjs/test/travis-ci.sh:/travis-ci.sh -e "BASE=$BASE" -e "OS=$IMAGE" -e "CC=gcc" "$IMAGE" bash -e -c "/travis-ci.sh BUILD_MOZ"; docker run --name $NAME -v $(pwd):/cwd -v $(pwd)/gjs/test/travis-ci.sh:/travis-ci.sh -e "BASE=$BASE" -e "OS=$IMAGE" -e "CC=$CCO" "$IMAGE" bash -e -c "/travis-ci.sh GET_FILES"; docker commit $NAME local/$NAME; mkdir -p ~/docker; docker export $NAME | gzip > ~/docker/$NAME.tar.gz; fi' script: - docker run -v $(pwd):/cwd -v $(pwd)/gjs/test/travis-ci.sh:/travis-ci.sh -e BASE=$BASE -e OS=$IMAGE -e CC=$CCO local/$NAME bash -e -c "/travis-ci.sh GJS" after_failure: - echo '-- FAILURE --' - 'if [[ -f $(pwd)/.cache/jhbuild/build/gjs/test-suite.log ]]; then cat $(pwd)/.cache/jhbuild/build/gjs/test-suite.log; else echo "-- NO LOG FILE FOUND --"; fi' cjs-5.2.0/build/0000755000175000017500000000000014144444702013536 5ustar jpeisachjpeisachcjs-5.2.0/build/choose-tests-locale.sh0000755000175000017500000000147314144444702017757 0ustar jpeisachjpeisach#!/bin/sh if ! which locale > /dev/null; then exit 1 fi locales=$(locale -a | xargs -n1) case $locales in # Prefer C.UTF-8 although it is only available with newer libc *C.UTF-8*) tests_locale=C.UTF-8 ;; # C.utf8 has also been observed in the wild *C.utf8*) tests_locale=C.utf8 ;; # Most systems will probably have this *en_US.UTF-8*) tests_locale=en_US.UTF-8 ;; *en_US.utf8*) tests_locale=en_US.utf8 ;; # If not, fall back to any English UTF-8 locale or any UTF-8 locale at all *en_*.UTF-8*) tests_locale=$(echo $locales | grep -m1 en_.\*\\.UTF-8) ;; *en_*.utf8*) tests_locale=$(echo $locales | grep -m1 en_.\*\\.utf8) ;; *.UTF-8*) tests_locale=$(echo $locales | grep -m1 \\.UTF-8) ;; *.utf8*) tests_locale=$(echo $locales | grep -m1 \\.utf8) ;; *) tests_locale=C ;; esac echo $tests_locale cjs-5.2.0/build/compile-gschemas.py0000644000175000017500000000046214144444702017332 0ustar jpeisachjpeisach#!/usr/bin/env python3 import os import subprocess import sys if len(sys.argv) < 2: sys.exit("usage: compile-gschemas.py ") schemadir = sys.argv[1] if os.environ.get('DESTDIR') is None: print('Compiling GSettings schemas...') subprocess.call(['glib-compile-schemas', schemadir]) cjs-5.2.0/build/symlink-gjs.py0000644000175000017500000000212314144444702016355 0ustar jpeisachjpeisach#!/usr/bin/env python3 import os import shutil import sys import tempfile assert(len(sys.argv) == 2) destdir = os.environ.get('DESTDIR') install_prefix = os.environ.get('MESON_INSTALL_PREFIX') bindir = sys.argv[1] if destdir is not None: # os.path.join() doesn't concat paths if one of them is absolute if install_prefix[0] == '/' and os.name != 'nt': installed_bin_dir = os.path.join(destdir, install_prefix[1:], bindir) else: installed_bin_dir = os.path.join(destdir, install_prefix, bindir) else: installed_bin_dir = os.path.join(install_prefix, bindir) if os.name == 'nt': # Using symlinks on Windows often require administrative privileges, # which is not what we want. Instead, copy gjs-console.exe. shutil.copyfile('cjs-console.exe', os.path.join(installed_bin_dir, 'gjs.exe')) else: try: temp_link = tempfile.mktemp(dir=installed_bin_dir) os.symlink('cjs-console', temp_link) os.replace(temp_link, os.path.join(installed_bin_dir, 'cjs')) finally: if os.path.islink(temp_link): os.remove(temp_link) cjs-5.2.0/.circleci/0000755000175000017500000000000014144444702014272 5ustar jpeisachjpeisachcjs-5.2.0/.circleci/config.yml0000644000175000017500000000304614144444702016265 0ustar jpeisachjpeisachversion: 2.0 shared: &shared steps: - checkout - run: name: Prepare environment command: apt-get update - run: name: Build project command: mint-build -i - run: name: Prepare packages command: | if [ -z $CI_PULL_REQUEST ]; then mkdir /packages mv /root/*.deb /packages/ git log > /packages/git.log cd / tar zcvf packages.tar.gz packages fi - run: name: Deploy packages to Github command: | if [ -z $CI_PULL_REQUEST ]; then wget https://github.com/tcnksm/ghr/releases/download/v0.5.4/ghr_v0.5.4_linux_amd64.zip apt-get install --yes unzip unzip ghr_v0.5.4_linux_amd64.zip TAG="master".$CIRCLE_JOB ./ghr -t $GITHUB_TOKEN -u $CIRCLE_PROJECT_USERNAME -r $CIRCLE_PROJECT_REPONAME -replace $TAG /packages.tar.gz ./ghr -t $GITHUB_TOKEN -u $CIRCLE_PROJECT_USERNAME -r $CIRCLE_PROJECT_REPONAME -recreate -b "Latest unstable packages" $TAG /packages.tar.gz fi jobs: "mint20": <<: *shared docker: - image: linuxmintd/mint20-amd64 "lmde4": <<: *shared docker: - image: linuxmintd/lmde4-amd64 workflows: version: 2 build: jobs: - "mint20" - "lmde4" cjs-5.2.0/CONTRIBUTING.md0000644000175000017500000003427414144444702014702 0ustar jpeisachjpeisach# Contributing to GJS # ## Introduction ## Thank you for considering contributing to GJS! As with any open source project, we can't make it as good as possible without help from you and others. We do have some guidelines for contributing, set out in this file. Following these guidelines helps communicate that you respect the time of the developers who work on GJS. In return, they should reciprocate that respect in addressing your issue, reviewing your work, and helping finalize your merge requests. ### What kinds of contributions we are looking for ### There are many ways to contribute to GJS, not only writing code. We encourage all of them. You can write example programs, tutorials, or blog posts; improve the documentation; [submit bug reports and feature requests][bugtracker]; triage existing bug reports; vote on issues with a thumbs-up or thumbs-down; or write code which is incorporated into [GJS itself][gjs]. ### What kinds of contributions we are not looking for ### Please don't use the [issue tracker][bugtracker] for support questions. Instead, check out the [#javascript][irc] IRC channel on irc.gnome.org. You can also try the [javascript-list][mailinglist] mailing list, or Stack Overflow. If you are writing code, please do not submit merge requests that only fix linter errors in code that you are not otherwise changing (unless you have discussed it in advance with a maintainer on [IRC][irc].) When writing code or submitting a feature request, make sure to first read the section below titled "Roadmap". Contributions that run opposite to the roadmap are not likely to be accepted. ## Ground Rules ## Your responsibilities as a contributor: - Be welcoming and encouraging to newcomers. - Conduct yourself professionally; rude, abusive, harrassing, or discriminatory behaviour is not tolerated. - For any major changes and enhancements you want to make, first create an issue in the [bugtracker], discuss things transparently, and get community feedback. - Ensure all jobs are green on GitLab CI for your merge requests. - Your code must pass the tests. - Your code must pass the linters; code should not introduce any new linting errors. - Your code should not cause any compiler warnings. - Add tests for any new functionality you write, and regression tests for any bugs you fix. ## Your First Contribution ## Unsure where to start? Try looking through the [issues labeled "Newcomers"][newcomers]. We try to have these issues contain some step-by-step explanation on how a newcomer might approach them. If that explanation is missing from an issue marked "Newcomers", feel free to leave a comment on there asking for help on how to get started. [Issues marked "Help Wanted"][helpwanted] may be a bit more involved than the Newcomers issues, but many of them still do not require in-depth familiarity with GJS. ## How to contribute documentation or tutorials ## If you don't have an account on [gitlab.gnome.org], first create one. Some contributions are done in different places than the main GJS repository. To contribute to the documentation, go to the [DevDocs][devdocs] repository. To contribute to tutorials, go to [GJS Guide][gjsguide]. Next, read the [workflow guide to contributing to GNOME][workflow]. (In short, create a fork of the repository, make your changes on a branch, push them to your fork, and create a merge request.) If your contribution fixes an existing issue, please refer to the issue in your commit message with `Closes #123` (for issue 123). Otherwise, creating a separate issue is not required. See the section on "Commit messages" below. When you submit your merge request, make sure to click "Allow commits from contributors with push access". This is so that the maintainers can re-run the GitLab CI jobs, since there is currently a bug in the infrastructure that makes some of the jobs fail unnecessarily. !157 is an example of a small documentation bugfix in a merge request. That's all! ## How to contribute code ## To contribute code, follow the instructions above for contributing documentation. There are further instructions for how to set up a development environment and install the correct tools for GJS development in the [Hacking.md][hacking] file. ## How to report a bug ## If you don't have an account on [gitlab.gnome.org], first create one. Go to the [issue tracker][bugtracker] and click "New issue". Use the "bug" template when reporting a bug. Make sure to answer the questions in the template, as otherwise it might make your bug harder to track down. _If you find a security vulnerability,_ make sure to mark the issue as "confidential"! If in doubt, ask on [IRC][irc] whether you should report a bug about something, but generally it's OK to just go ahead. Bug report #170 is a good example of a bug report with an independently runnable code snippet for testing, and lots of information, although it was written before the templates existed. ## How to suggest a feature or enhancement ## If you find yourself wishing for a feature that doesn't exist in GJS, you are probably not alone. Open an issue on our [issue tracker][bugtracker] which describes the feature you would like to see, why you need it, and how it should work. Use the "feature" template for this. However, for a new feature, the likelihood that it will be implemented goes way up if you or someone else plans to submit a merge request along with it. If the feature is small enough that you won't feel like your time was wasted if we decide not to adopt it, you can just submit a merge request rather than going to the issue tracker. Make sure to explain why you think it's a good feature to have! !213 is an example of a small feature suggestion that was submitted as a merge request. In cases where you've seen something that needs to be fixed or refactored in the code, it's OK not to use a template. It's OK to be less rigorous here, since this type of report is usually used by people who plan to fix the issue themselves later. ## How to triage bugs ## You can help the maintainers by examining the existing bug reports in the bugtracker and adding instructions to reproduce them, or categorizing them with the correct labels. For bugs that cause a crash (segmentation fault, not just a JS exception) use the "1. Crash" label. For other bugs, use the "1. Bug" label. Feature requests should get the "1. Feature" label. Any crashes, or bugs that prevent most or all users from using GJS or GNOME Shell, should also get the "To Do" label. If some information is missing from the bug (for example, you can't reproduce it based on their instructions,) add the "2. Needs information" label. Add any topic labels from the "5" group (e.g. "5. Performance") as you see fit. As for reproducer instructions, a small, self-contained JS program that exhibits the bug, to be run with the command-line `gjs` interpreter, is best. Instructions that provide code to be loaded as a GNOME Shell extension are less helpful, because they are more tedious to test. ## Code review process ## Once you have submitted your merge request, a maintainer will review it. You should get a first response within a few days. Sometimes maintainers are busy; if it's been a week and you've heard nothing, feel free to ping the maintainer and ask for an estimate of when they might be able to review the merge request. You might get a review even if some of the GitLab CI jobs have not yet succeeded. In that case, acceptance of the merge request is contingent on fixing whatever needs to be fixed to get all the jobs to turn green. In general, unless the merge request is very simple, it will not be ready to accept immediately. You should normally expect one to three rounds of code review, depending on the size and complexity of the merge request. Be prepared to accept constructive criticism on your code and to work on improving it before it's merged; code review comments don't mean it's bad. !242 is an example of a bug fix merge request with a few code review comments on it, if you want to get a feel for the process. Contributors with a GNOME developer account have automatic push access to the main GJS repository. However, even if you have this access, you are still expected to submit a merge request and have a GJS maintainer review it. The exception to this is if there is an emergency such as GNOME Continuous being broken. ## Community ## For general questions and support, visit the [#javascript][irc] channel on irc.gnome.org. The maintainers are listed in the [DOAP file][doap] in the root of the repository. ## Roadmap and Philosophy ## This section explains what kinds of changes we do and don't intend to make in GJS in the future and what direction we intend to take the project. Internally, GJS uses Firefox's Javascript engine, called SpiderMonkey. First of all, we will not consider switching GJS to use a different Javascript engine. If you believe that should be done, the best way to make it happen is to start a new project, copy GJS's regression test suite, and make sure all the tests pass and you can run GNOME Shell with it. Every year when a new ESR (extended support release) of Firefox appears, we try to upgrade GJS to use the accompanying version of SpiderMonkey as soon as possible. Sometimes upgrading SpiderMonkey requires breaking backwards compatibility, and in that case we try to make it as easy as possible for existing code to adapt. Other than the above exception, we avoid all changes that break existing code, even if they would be convenient. However, it is OK to break compatibility with GJS's documented behaviour if in practice the behaviour never actually worked as documented. (That happens more often than you might think.) We also try to avoid surprises for people who are used to modern ES standard Javascript, so custom GJS classes should not deviate from the behaviour that people would be used to in the standard. The Node.js ecosystem is quite popular and many Javascript developers are accustomed to it. In theory, we would like to move in the direction of providing all the same facilities as Node.js, but we do not necessarily want to copy the exact way things work in Node.js. The platforms are different and so the implementations sometimes need to be different too. The module system in GJS should be considered legacy. We don't want to make big changes to it or add any features. Instead, we want to enable ES6-style imports for the GJS platform. We do have some overrides for GNOME libraries such as GLib, to make their APIs more Javascript-like. However, we like to keep these to a minimum, so that GNOME code remains straightforward to read if you are used to using the GNOME libraries in other programming languages. GJS was originally written in C, and the current state of the C++ code reflects that. Gradually, we want to move the code to a more idiomatic C++ style, using smart pointer classes such as `GjsAutoChar` to help avoid memory leaks. Even farther in the future, we expect the Rust bindings for SpiderMonkey to mature as Mozilla's Servo browser engine progresses, and we may consider rewriting part or all of GJS in Rust. We believe in automating as much as possible to prevent human error. GJS is a complex program that powers a lot of GNOME, so breakages can be have far-reaching effects in other programs. We intend to move in the direction of having more static code checking in the future. We would also like to have more automated integration testing, for example trying to start a GNOME Shell session with each new change in GJS. Lastly, changes should in principle be compatible with other platforms than only Linux and GNOME. Although we don't have automated testing for other platforms, we will occasionally build and test things there, and gladly accept contributions to fix breakages on other platforms. ## Conventions ## ### Coding style ### We use the [Google style guide][googlestyle] for C++ code, with a few exceptions, 4-space indents being the main one. There is a handy git commit hook that will autoformat your code when you commit it; see the [Hacking.md][hacking] file. For C++ coding style concerns that can't be checked with a linter or an autoformatter, read the [CPP_Style_Guide.md][cppstyle] file. For Javascript code, an [ESLint configuration file][eslint] is included in the root of the GJS repository. This is not integrated with a git commit hook, so you need to manually manually sure that all your new code conforms to the style. Don't rewrite old code with `eslint --fix` unless you are already changing that code for some other reason. ### Commit messages ### The title of the commit should say what you changed, and the body of the commit message should explain why you changed it. We look in the commit history quite often to figure out why code was written a certain way, so it's important to justify each change so that in the future people will realize why it was needed. For further guidelines about line length and commit messages, read [this guide][commitmessages]. If the commit is related to an open issue in the issue tracker, note that on the last line of the commit message. For example, `See #153`, or `Closes #277` if the issue should be automatically closed when the merge request is accepted. ## Thanks ## Thanks to [@nayafia][contributingtemplate] for the inspiration to write this guide! [gitlab.gnome.org]: https://gitlab.gnome.org [bugtracker]: https://gitlab.gnome.org/GNOME/gjs/issues [gjs]: https://gitlab.gnome.org/GNOME/gjs [irc]: https://riot.im/app/#/room/#_gimpnet_#javascript:matrix.org [mailinglist]: https://mail.gnome.org/mailman/listinfo/javascript-list [newcomers]: https://gitlab.gnome.org/GNOME/gjs/issues?label_name%5B%5D=4.+Newcomers [helpwanted]: https://gitlab.gnome.org/GNOME/gjs/issues?label_name%5B%5D=4.+Help+Wanted [devdocs]: https://github.com/ptomato/devdocs [gjsguide]: https://gitlab.gnome.org/rockon999/gjs-guide [workflow]: https://wiki.gnome.org/GitLab#Using_a_fork_-_Non_GNOME_developer [hacking]: https://gitlab.gnome.org/GNOME/gjs/blob/master/doc/Hacking.md [doap]: https://gitlab.gnome.org/GNOME/gjs/blob/master/gjs.doap [googlestyle]: https://google.github.io/styleguide/cppguide.html [cppstyle]: https://gitlab.gnome.org/GNOME/gjs/blob/master/doc/CPP_Style_Guide.md [eslint]: https://eslint.org/ [commitmessages]: https://chris.beams.io/posts/git-commit/ [contributingtemplate]: https://github.com/nayafia/contributing-template cjs-5.2.0/tools/0000755000175000017500000000000014144444702013577 5ustar jpeisachjpeisachcjs-5.2.0/tools/run_cppcheck.sh0000755000175000017500000000026114144444702016601 0ustar jpeisachjpeisach#!/bin/sh ninja -C _build cppcheck --project=_build/compile_commands.json --inline-suppr \ --enable=warning,performance,portability,missingInclude \ --force --quiet $@ cjs-5.2.0/tools/git-pre-commit-format0000755000175000017500000002541214144444702017654 0ustar jpeisachjpeisach#! /bin/bash # # Copyright 2018 Undo Ltd. # # https://github.com/barisione/clang-format-hooks # Force variable declaration before access. set -u # Make any failure in piped commands be reflected in the exit code. set -o pipefail readonly bash_source="${BASH_SOURCE[0]:-$0}" if [ -t 1 ] && hash tput 2> /dev/null; then readonly b=$(tput bold) readonly i=$(tput sitm) readonly n=$(tput sgr0) else readonly b= readonly i= readonly n= fi function error_exit() { for str in "$@"; do echo -n "$b$str$n" >&2 done echo >&2 exit 1 } # realpath is not available everywhere. function realpath() { if [ "${OSTYPE:-}" = "linux-gnu" ]; then readlink -m "$@" else # Python should always be available on macOS. # We use sys.stdout.write instead of print so it's compatible with both Python 2 and 3. python -c "import sys; import os.path; sys.stdout.write(os.path.realpath('''$1''') + '\\n')" fi } # realpath --relative-to is only available on recent Linux distros. # This function behaves identical to Python's os.path.relpath() and doesn't need files to exist. function rel_realpath() { local -r path=$(realpath "$1") local -r rel_to=$(realpath "${2:-$PWD}") # Split the paths into components. IFS='/' read -r -a path_parts <<< "$path" IFS='/' read -r -a rel_to_parts <<< "$rel_to" # Search for the first different component. for ((idx=1; idx<${#path_parts[@]}; idx++)); do if [ "${path_parts[idx]}" != "${rel_to_parts[idx]:-}" ]; then break fi done result=() # Add the required ".." to the $result array. local -r first_different_idx="$idx" for ((idx=first_different_idx; idx<${#rel_to_parts[@]}; idx++)); do result+=("..") done # Add the required components from $path. for ((idx=first_different_idx; idx<${#path_parts[@]}; idx++)); do result+=("${path_parts[idx]}") done if [ "${#result[@]}" -gt 0 ]; then # Join the array with a "/" as separator. echo "$(export IFS='/'; echo "${result[*]}")" else echo . fi } # Find the top-level git directory (taking into account we could be in a submodule). declare git_test_dir=. declare top_dir while true; do top_dir=$(cd "$git_test_dir" && git rev-parse --show-toplevel) || \ error_exit "You need to be in the git repository to run this script." [ -e "$top_dir/.git" ] || \ error_exit "No .git directory in $top_dir." if [ -d "$top_dir/.git" ]; then # We are done! top_dir is the root git directory. break elif [ -f "$top_dir/.git" ]; then # We are in a submodule or git work-tree if .git is a file! if [ -z "$(git rev-parse --show-superproject-working-tree)" ]; then # The --show-superproject-working-tree option is available and we # are in a work tree. gitdir=$(<"$top_dir/.git") gitdir=${gitdir#gitdir: } topdir_basename=${gitdir##*/} git_test_dir=${gitdir%/worktrees/$topdir_basename} break fi # If show-superproject-working-tree returns non-empty string, either: # # 1) --show-superproject-working-tree is not defined for this version of git # # 2) --show-superproject-working-tree is defined and we are in a submodule # # In the first case we will assume it is not a work tree because people # using that advanced technology will be using a recent version of git. # # In second case, we could use the value returned by # --show-superproject-working-tree directly but we do not here because # that would require extra work. # git_test_dir="$git_test_dir/.." fi done readonly top_dir hook_path="$top_dir/.git/hooks/pre-commit" readonly hook_path me=$(realpath "$bash_source") || exit 1 readonly me me_relative_to_hook=$(rel_realpath "$me" "$(dirname "$hook_path")") || exit 1 readonly me_relative_to_hook my_dir=$(dirname "$me") || exit 1 readonly my_dir apply_format="$my_dir/apply-format" readonly apply_format apply_format_relative_to_top_dir=$(rel_realpath "$apply_format" "$top_dir") || exit 1 readonly apply_format_relative_to_top_dir function is_installed() { if [ ! -e "$hook_path" ]; then echo nothing else existing_hook_target=$(realpath "$hook_path") || exit 1 readonly existing_hook_target if [ "$existing_hook_target" = "$me" ]; then # Already installed. echo installed else # There's a hook, but it's not us. echo different fi fi } function install() { if ln -s "$me_relative_to_hook" "$hook_path" 2> /dev/null; then echo "Pre-commit hook installed." else local -r res=$(is_installed) if [ "$res" = installed ]; then error_exit "The hook is already installed." elif [ "$res" = different ]; then error_exit "There's already an existing pre-commit hook, but for something else." elif [ "$res" = nothing ]; then error_exit "There's no pre-commit hook, but we couldn't create a symlink." else error_exit "Unexpected failure." fi fi } function uninstall() { local -r res=$(is_installed) if [ "$res" = installed ]; then rm "$hook_path" || \ error_exit "Couldn't remove the pre-commit hook." elif [ "$res" = different ]; then error_exit "There's a pre-commit hook installed, but for something else. Not removing." elif [ "$res" = nothing ]; then error_exit "There's no pre-commit hook, nothing to uninstall." else error_exit "Unexpected failure detecting the pre-commit hook status." fi } function show_help() { cat << EOF ${b}SYNOPSIS${n} $bash_source [install|uninstall] ${b}DESCRIPTION${n} Git hook to verify and fix formatting before committing. The script is invoked automatically when you commit, so you need to call it directly only to set up the hook or remove it. To setup the hook run this script passing "install" on the command line. To remove the hook run passing "uninstall". ${b}CONFIGURATION${n} You can configure the hook using the "git config" command. ${b}hooks.clangFormatDiffInteractive${n} (default: true) By default, the hook requires user input. If you don't run git from a terminal, you can disable the interactive prompt with: ${i}\$ git config hooks.clangFormatDiffInteractive false${n} ${b}hooks.clangFormatDiffStyle${n} (default: file) Unless a different style is specified, the hook expects a file named .clang-format to exist in the repository. This file should contain the configuration for clang-format. You can specify a different style (in this example, the WebKit one) with: ${i}\$ git config hooks.clangFormatDiffStyle WebKit${n} EOF } if [ $# = 1 ]; then case "$1" in -h | -\? | --help ) show_help exit 0 ;; install ) install exit 0 ;; uninstall ) uninstall exit 0 ;; esac fi [ $# = 0 ] || error_exit "Invalid arguments: $*" # This is a real run of the hook, not a install/uninstall run. if [ -z "${GIT_DIR:-}" ] && [ -z "${GIT_INDEX_FILE:-}" ]; then error_exit \ $'It looks like you invoked this script directly, but it\'s supposed to be used\n' \ $'as a pre-commit git hook.\n' \ $'\n' \ $'To install the hook try:\n' \ $' ' "$bash_source" $' install\n' \ $'\n' \ $'For more details on this script try:\n' \ $' ' "$bash_source" $' --help\n' fi [ -x "$apply_format" ] || \ error_exit \ $'Cannot find the apply-format script.\n' \ $'I expected it here:\n' \ $' ' "$apply_format" readonly style=$(cd "$top_dir" && git config hooks.clangFormatDiffStyle || echo file) readonly patch=$(mktemp) trap '{ rm -f "$patch"; }' EXIT "$apply_format" --style="$style" --cached > "$patch" || \ error_exit $'\nThe apply-format script failed.' if [ "$(wc -l < "$patch")" -eq 0 ]; then echo "The staged content is formatted correctly." exit 0 fi # The code is not formatted correctly. interactive=$(cd "$top_dir" && git config --bool hooks.clangFormatDiffInteractive) if [ "$interactive" != false ]; then # Interactive is the default, so anything that is not false is converted to # true, including possibly invalid values. interactive=true fi readonly interactive if [ "$interactive" = false ]; then echo "${b}The staged content is not formatted correctly.${n}" echo "You can fix the formatting with:" echo " ${i}\$ ./$apply_format_relative_to_top_dir --apply-to-staged${n}" echo echo "You can also make this script interactive (if you use git from a terminal) with:" echo " ${i}\$ git config hooks.clangFormatDiffInteractive true${n}" exit 1 fi if hash colordiff 2> /dev/null; then colordiff < "$patch" else echo "${b}(Install colordiff to see this diff in color!)${n}" echo cat "$patch" fi echo echo "${b}The staged content is not formatted correctly.${n}" echo "The patch shown above can be applied automatically to fix the formatting." echo echo "You can:" echo " [a]: Apply the patch" echo " [f]: Force and commit anyway (not recommended!)" echo " [c]: Cancel the commit" echo " [?]: Show help" echo readonly tty=${PRE_COMMIT_HOOK_TTY:-/dev/tty} while true; do echo -n "What would you like to do? [a/f/c/?] " read -r answer < "$tty" case "$answer" in [aA] ) patch -p0 < "$patch" || \ error_exit \ $'\n' \ $'Cannot apply patch to local files.\n' \ $'Have you modified the file locally after starting the commit?' git apply -p0 --cached < "$patch" || \ error_exit \ $'\n' \ $'Cannot apply patch to git staged changes.\n' \ $'This may happen if you have some overlapping unstaged changes. To solve\n' \ $'you need to stage or reset changes manually.' ;; [fF] ) echo echo "Will commit anyway!" echo "You can always abort by quitting your editor with no commit message." echo echo -n "Press return to continue." read -r < "$tty" exit 0 ;; [cC] ) error_exit "Commit aborted as requested." ;; \? ) echo show_help echo continue ;; * ) echo 'Invalid answer. Type "a", "f" or "c".' echo continue esac break done cjs-5.2.0/tools/gjs-private-iwyu.imp0000644000175000017500000000273414144444702017542 0ustar jpeisachjpeisach# IWYU mapping file for files that are part of libgjs [ {"include": ["", "private", "", "public"]}, {"include": ["", "private", "", "public"]}, {"include": ["@", "private", "", "public"]}, {"include": ["", "private", "", "public"]}, {"include": ["", "private", "", "public"]}, {"include": ["", "private", "", "public"]}, {"include": ["", "private", "", "public"]}, {"include": ["", "private", "", "public"]}, {"include": ["", "private", "", "public"]}, {"include": ["@", "private", "", "public"]}, {"include": ["@\"gio/.*\"", "private", "", "public"]}, {"include": ["@", "private", "", "public"]}, {"include": ["", "private", "", "public"]}, {"include": ["@\"gobject/.*\"", "private", "", "public"]}, {"include": ["@", "private", "", "public"]}, {"include": ["", "private", "", "public"]}, {"include": ["@\"sysprof-capture-.*\"", "private", "", "public"]}, {"include": ["@", "private", "", "public"]}, {"include": ["", "private", "", "public"]}, ] cjs-5.2.0/tools/lcovrc0000644000175000017500000000113714144444702015014 0ustar jpeisachjpeisach# lcov and genhtml configuration # See http://ltp.sourceforge.net/coverage/lcov/lcovrc.5.php # Adapted from GLib's lcovrc # Always enable branch coverage lcov_branch_coverage = 1 # Exclude precondition assertions, as we can never reasonably get full branch # coverage of them, as they should never normally fail. # See https://github.com/linux-test-project/lcov/issues/44 lcov_excl_br_line = LCOV_EXCL_BR_LINE|g_return_if_fail|g_return_val_if_fail|g_assert|g_assert_ # Similarly for unreachable assertions. lcov_excl_line = LCOV_EXCL_LINE|g_return_if_reached|g_return_val_if_reached|g_assert_not_reached cjs-5.2.0/tools/heapgraph.md0000644000175000017500000001546014144444702016066 0ustar jpeisachjpeisach# gjs-heapgraph A heap analyzer for Gjs based on https://github.com/amccreight/heapgraph to aid in debugging and plugging memory leaks. ## Resource Usage Be aware that parsing a heap can take a fair amount of RAM depending on the heap size and time depending on the amount of target objects and path length. Examples of approximate memory and time required to build DOT graphs on an IvyBridge i7: | Heap Size | RAM | Targets | Time | |-----------|-------|---------|-------------| | 5MB | 80MB | 1500 | 1.5 Minutes | | 30MB | 425MB | 7700 | 40 Minutes | ## Basic Usage ### Getting a Heap Dump The more convenient way to dump a heap is to send `SIGUSR1` to a GJS process with the env variable `GJS_DEBUG_HEAP_OUTPUT` set: ```sh $ GJS_DEBUG_HEAP_OUTPUT=myApp.heap gjs myApp.js & $ kill -USR1 ``` It's also possible to dump a heap from within a script via the `System` import: ```js const System = imports.system; // Dumping the heap before the "leak" has happened System.dumpHeap('/home/user/myApp1.heap.'); // Code presumably resulting in a leak... // Running the garbage collector before dumping can avoid some false positives System.gc(); // Dumping the heap after the "leak" has happened System.dumpHeap('/home/user/myApp2.heap.'); ``` ### Output The default output of `./heapgraph.py` is a tiered tree of paths from root to rooted objects. If the output is being sent to a terminal (TTY) some minimal ANSI styling is used to make the output more readable. Additionally, anything that isn't part of the graph will be sent to `stderr` so the output can be directed to a file as plain text. Below is a snippet: ```sh $ ./heapgraph.py myApp2.heap Object > myApp2.tree Parsing file.heap...done Found 343 targets with type "Object" $ cat file.tree ├─[vm_stack[1]]─➤ [Object jsobj@0x7fce60683440] │ ├─[vm_stack[1]]─➤ [Object jsobj@0x7fce606833c0] │ ├─[exact-Object]─➤ [Object jsobj@0x7fce60683380] │ ├─[exact-Object]─➤ [GjsGlobal jsobj@0x7fce60680060] │ ├─[Debugger]─➤ [Function Debugger jsobj@0x7fce606a4540] │ │ ╰─[Object]─➤ [Function Object jsobj@0x7fce606a9cc0] │ │ ╰─[prototype]─➤ [Object (nil) jsobj@0x7fce60681160] │ │ ...and so on ``` `heapgraph.py` can also output DOT graphs that can be a useful way to visualize the heap graph, especially if you don't know exactly what you're looking for. Passing the `--dot-graph` option will output a DOT graph to `.dot` in the current working directory. There are a few choices for viewing dot graphs, and many utilities for converting them to other formats like PDF, Tex or GraphML. For Gnome desktops [`xdot`](https://github.com/jrfonseca/xdot.py) is a nice lightweight Python/Cairo viewer available on PyPi and in most distributions. ```sh $ ./heapgraph.py --dot-graph /home/user/myApp2.heap Object Parsing file.heap...done Found 343 targets with type "Object" $ xdot myApp2.heap.dot ``` ### Excluding Nodes from the Graph The exclusion switch you are most likely to use is `--diff-heap` which will exclude all nodes in the graph common to that heap, allowing you to easily see what's not being collected between two states. ```sh $ ./heapgraph --diff-heap myApp1.heap myApp2.heap GObject ``` You can also exclude Gray Roots, WeakMaps, nodes with a heap address or nodes with labels containing a string. Because GObject addresses are part of the node label, these can be excluded with `--hide-node` as well. By default the global object (GjsGlobal aka `window`), imports (GjsModule, GjsFileImporter), and namespaces (GIRepositoryNamespace) aren't shown in the graph since these are less useful and can't be garbage collected anyways. ```sh $ ./heapgraph.py --hide-addr 0x7f6ef022c060 \ --hide-node 'self-hosting-global' \ --no-gray-roots \ /home/user/myApp2.heap Object $ ./heapgraph.py --hide-node 0x55e93cf5deb0 /home/user/myApp2.heap Object ``` ### Labeling Nodes It can be hard to see what some nodes mean, especially if all the nodes you are interested in are labeled `GObject_Object`. Luckily there is a way to label the nodes in your program so that they are visible in the heap graph. Add a property named `__heapgraph_name` with a simple string value to your object: ```js myObj.__heapgraph_name = 'My object'; ``` Heapgraph will detect this and display the name as part of the node's label, e.g. GObject_Object "My object". ### Command-Line Arguments > **NOTE:** Command line arguments are subject to change; Check > `./heapgraph.py --help` before running. ``` usage: heapgraph.py [-h] [--edge | --function | --string] [--count] [--dot-graph] [--no-addr] [--diff-heap FILE] [--no-gray-roots] [--no-weak-maps] [--show-global] [--show-imports] [--hide-addr ADDR] [--hide-node LABEL] [--hide-edge LABEL] FILE TARGET Find what is rooting or preventing an object from being collected in a GJS heap using a shortest-path breadth-first algorithm. positional arguments: FILE Garbage collector heap from System.dumpHeap() TARGET Heap address (eg. 0x7fa814054d00) or type prefix (eg. Array, Object, GObject, Function...) optional arguments: -h, --help show this help message and exit --edge, -e Treat TARGET as a function name --function, -f Treat TARGET as a function name --string, -s Treat TARGET as a string literal or String() Output Options: --count, -c Only count the matches for TARGET --dot-graph, -d Output a DOT graph to FILE.dot --no-addr, -na Don't show addresses Node/Root Filtering: --diff-heap FILE, -dh FILE Don't show roots common to the heap FILE --no-gray-roots, -ng Don't show gray roots (marked to be collected) --no-weak-maps, -nwm Don't show WeakMaps --show-global, -g Show the global object (eg. window/GjsGlobal) --show-imports, -i Show import and module nodes (eg. imports.foo) --hide-addr ADDR, -ha ADDR Don't show roots with the heap address ADDR --hide-node LABEL, -hn LABEL Don't show nodes with labels containing LABEL --hide-edge LABEL, -he LABEL Don't show edges labelled LABEL ``` ## See Also Below are some links to information relevant to SpiderMonkey garbage collection and heap parsing: * [GC.cpp Comments](https://searchfox.org/mozilla-central/source/js/src/gc/GC.cpp) * [How JavaScript Objects Are Implemented](https://www.infoq.com/presentations/javascript-objects-spidermonkey) * [Tracing garbage collection](https://en.wikipedia.org/wiki/Tracing_garbage_collection#Tri-color_marking) on Wikipedia * [SpiderMonkey Memory](https://gitlab.gnome.org/GNOME/gjs/blob/master/doc/SpiderMonkey_Memory.md) via GJS Repo cjs-5.2.0/tools/run_iwyu.sh0000755000175000017500000001027614144444702016025 0ustar jpeisachjpeisach#!/bin/sh SRCDIR=$(pwd) if [ "$1" == '--help' -o "$1" == '-h' ]; then echo "usage: $0 [ COMMIT ]" echo "Run include-what-you-use on the GJS codebase." echo "If COMMIT is given, analyze only the files changed since that commit," echo "including uncommitted changes." echo "Otherwise, analyze all files." exit 0 fi if [ $# -eq 0 ]; then files=all else # make stat changes not show up as modifications git update-index -q --really-refresh files="$(git diff-tree --name-only -r $1..) $(git diff-index --name-only HEAD)" fi should_analyze () { file=$(realpath --relative-to=$SRCDIR $1) case "$files" in all) return 0 ;; *$file*) return 0 ;; *${file%.cpp}.h*) return 0 ;; *${file%.cpp}-inl.h*) return 0 ;; *${file%.cpp}-private.h*) return 0 ;; *${file%.c}.h*) return 0 ;; *) return 1 ;; esac } if ! meson setup _build; then echo 'Meson failed.' exit 1 fi cd ${BUILDDIR:-_build} echo "files: $files" IWYU="python3 $(which iwyu_tool) -p ." IWYU_RAW="include-what-you-use -xc++ -std=c++17" PRIVATE_MAPPING="-Xiwyu --mapping_file=$SRCDIR/tools/gjs-private-iwyu.imp -Xiwyu --keep=config.h" PUBLIC_MAPPING="-Xiwyu --mapping_file=$SRCDIR/tools/gjs-public-iwyu.imp" POSTPROCESS="python3 $SRCDIR/tools/process_iwyu.py" EXIT=0 # inline-only files with no implementation file don't appear in the compilation # database, so iwyu_tool cannot process them if should_analyze $SRCDIR/gi/utils-inl.h; then if ! $IWYU_RAW $(realpath --relative-to=. $SRCDIR/gi/utils-inl.h) 2>&1 \ | $POSTPROCESS; then EXIT=1 fi fi for FILE in $SRCDIR/gi/*.cpp $SRCDIR/cjs/atoms.cpp $SRCDIR/cjs/byteArray.cpp \ $SRCDIR/cjs/coverage.cpp $SRCDIR/cjs/debugger.cpp \ $SRCDIR/cjs/deprecation.cpp $SRCDIR/cjs/error-types.cpp \ $SRCDIR/cjs/engine.cpp $SRCDIR/cjs/global.cpp $SRCDIR/cjs/importer.cpp \ $SRCDIR/cjs/jsapi-util-error.cpp $SRCDIR/cjs/jsapi-util-string.cpp \ $SRCDIR/cjs/module.cpp $SRCDIR/cjs/native.cpp $SRCDIR/cjs/stack.cpp \ $SRCDIR/modules/cairo-*.cpp $SRCDIR/modules/console.cpp \ $SRCDIR/modules/print.cpp $SRCDIR/modules/system.cpp $SRCDIR/test/*.cpp \ $SRCDIR/util/*.cpp $SRCDIR/libgjs-private/*.c do if should_analyze $FILE; then if ! $IWYU $FILE -- $PRIVATE_MAPPING | $POSTPROCESS; then EXIT=1 fi fi done if should_analyze $SRCDIR/cjs/context.cpp; then if ! $IWYU $SRCDIR/cjs/context.cpp -- $PRIVATE_MAPPING \ -Xiwyu --check_also=*/cjs/context-private.h | $POSTPROCESS; then EXIT=1 fi fi if ( should_analyze $SRCDIR/cjs/jsapi-dynamic-class.cpp || \ should_analyze $SRCDIR/cjs/jsapi-class.h ); then if ! $IWYU $SRCDIR/cjs/jsapi-dynamic-class.cpp -- $PRIVATE_MAPPING \ -Xiwyu --check_also=*/cjs/jsapi-class.h | $POSTPROCESS; then EXIT=1 fi fi if ( should_analyze $SRCDIR/cjs/jsapi-util.cpp || should_analyze $SRCDIR/cjs/jsapi-util-args.h || \ should_analyze $SRCDIR/cjs/jsapi-util-root.h ); then if ! $IWYU $SRCDIR/cjs/jsapi-util.cpp -- $PRIVATE_MAPPING \ -Xiwyu --check_also=*/cjs/jsapi-util-args.h \ -Xiwyu --check_also=*/cjs/jsapi-util-root.h | $POSTPROCESS; then EXIT=1 fi fi if should_analyze $SRCDIR/cjs/mem.cpp; then if ! $IWYU $SRCDIR/cjs/mem.cpp -- $PRIVATE_MAPPING \ -Xiwyu --check_also=*/cjs/mem-private.h | $POSTPROCESS; then EXIT=1 fi fi if should_analyze $SRCDIR/cjs/profiler.cpp; then if ! $IWYU $SRCDIR/cjs/profiler.cpp -- $PRIVATE_MAPPING \ -Xiwyu --check_also=*/cjs/profiler-private.h | $POSTPROCESS; then EXIT=1 fi fi if ( should_analyze $SRCDIR/modules/cairo.cpp || should_analyze $SRCDIR/modules/cairo-module.h ); then if ! $IWYU $SRCDIR/modules/cairo.cpp -- $PRIVATE_MAPPING \ -Xiwyu --check_also=*/modules/cairo-module.h \ -Xiwyu --check_also=*/modules/cairo-private.h | $POSTPROCESS; then EXIT=1 fi fi for FILE in $SRCDIR/cjs/console.cpp $SRCDIR/installed-tests/minijasmine.cpp do if should_analyze $FILE; then if ! $IWYU $FILE -- $PUBLIC_MAPPING | $POSTPROCESS; then EXIT=1 fi fi done if test $EXIT -eq 0; then echo "No changes needed."; fi exit $EXIT cjs-5.2.0/tools/process_iwyu.py0000755000175000017500000001031214144444702016704 0ustar jpeisachjpeisach# coding: utf8 # IWYU is missing the feature to designate a certain header as a "forward-decls # header". In the case of SpiderMonkey, there are certain commonly used forward # declarations that are all gathered in js/TypeDecls.h. # We postprocess the IWYU output to fix this, and also fix the output format # which is quite verbose, making it tedious to scroll through for 60 files. import re import sys class Colors: NORMAL = '\33[0m' RED = '\33[31m' GREEN = '\33[32m' ADD, REMOVE, FULL = range(3) state = None file = None add = {} remove = {} all_includes = {} there_were_errors = False # When encountering one of these lines, move to a different state MATCHERS = { r'\.\./(.*) should add these lines:': ADD, r'\.\./(.*) should remove these lines:': REMOVE, r'The full include-list for \.\./(.*):': FULL, r'\(\.\./(.*) has correct #includes/fwd-decls\)': None } FWD_HEADER = '#include ' FWD_DECLS_IN_HEADER = ( 'class JSAtom;', 'struct JSContext;', 'struct JSClass;', 'class JSFunction;', 'class JSFreeOp;', 'class JSObject;', 'struct JSRuntime;', 'class JSScript;', 'class JSString;', 'namespace js { class TempAllocPolicy; }' 'namespace JS { struct PropertyKey; }', 'namespace JS { class Symbol; }', 'namespace JS { class BigInt; }', 'namespace JS { class Value; }', 'namespace JS { class Compartment; }', 'namespace JS { class Realm; }', 'namespace JS { struct Runtime; }', 'namespace JS { class Zone; }', ) add_fwd_header = False FALSE_POSITIVES = ( # The bodies of these structs already come before their usage, # we don't need to have forward declarations of them as well ('cjs/atoms.h', 'class GjsAtoms;', ''), ('cjs/atoms.h', 'struct GjsSymbolAtom;', ''), # IWYU weird false positive when using std::vector::emplace_back() or # std::vector::push_back() ('gi/private.cpp', '#include ', 'for max'), ('cjs/importer.cpp', '#include ', 'for max'), ('modules/cairo-context.cpp', '#include ', 'for max'), # Weird false positive on some versions of IWYU ('gi/arg.cpp', 'struct _GHashTable;', ''), ('gi/arg.cpp', 'struct _GVariant;', ''), ) def output(): global file, state, add_fwd_header, there_were_errors if add_fwd_header: if FWD_HEADER not in all_includes: if FWD_HEADER in remove: remove.pop(FWD_HEADER, None) else: add[FWD_HEADER] = '' if add or remove: print(f'\n== {file} ==') for line, why in add.items(): if why: why = ' // ' + why print(f'{Colors.GREEN}+{line}{Colors.NORMAL}{why}') for line, why in remove.items(): if why: why = ' // ' + why print(f'{Colors.RED}-{line}{Colors.NORMAL}{why}') there_were_errors = True state = None file = None add.clear() remove.clear() all_includes.clear() add_fwd_header = False for line in sys.stdin: line = line.strip() if not line: continue # filter out errors having to do with compiler arguments unknown to IWYU if line.startswith('error:'): continue if line == '---': output() continue state_changed = False file_changed = False for matcher, newstate in MATCHERS.items(): match = re.match(matcher, line) if match: state = newstate if match.group(1) != file: if file is not None: file_changed = True file = match.group(1) state_changed = True break if file_changed: output() continue if state_changed: continue line, _, why = line.partition(' // ') line = line.strip() if state == ADD: if line in FWD_DECLS_IN_HEADER: add_fwd_header = True continue if (file, line, why) in FALSE_POSITIVES: continue add[line] = why elif state == REMOVE: if line.startswith('- '): line = line[2:] remove[line] = why elif state == FULL: all_includes[line] = why if there_were_errors: sys.exit(1) cjs-5.2.0/tools/gjs-public-iwyu.imp0000644000175000017500000000025014144444702017335 0ustar jpeisachjpeisach# IWYU mapping file for files that use the API of libgjs [ {"ref": "gjs-private-iwyu.imp"}, {"include": ["\"gjs/macros.h\"", "private", "", "public"]} ]cjs-5.2.0/tools/heapgraph.py0000755000175000017500000005507014144444702016122 0ustar jpeisachjpeisach#!/usr/bin/env python3 # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. # # heapgraph.py - Top-level script for interpreting Garbage Collector heaps import argparse import copy from collections import namedtuple from collections import deque import os import re import sys try: from heapdot import (output_dot_file, add_dot_graph_path, add_dot_graph_unreachable) except ImportError: sys.stderr.write('DOT graph output not available\n') NAME_ANNOTATION = '__heapgraph_name' ######################################################## # Command line arguments. ######################################################## parser = argparse.ArgumentParser(description='Find what is rooting or preventing an object from being collected in a GJS heap using a shortest-path breadth-first algorithm.') parser.add_argument('heap_file', metavar='FILE', help='Garbage collector heap from System.dumpHeap()') parser.add_argument('targets', metavar='TARGET', nargs='*', help='Heap address (eg. 0x7fa814054d00) or type prefix (eg. Array, Object, GObject, Function...)') ### Target Options targ_opts = parser.add_argument_group('Target options') targ_opts.add_argument('--edge', '-e', dest='edge_targets', action='append', default=[], help='Add an edge label to the list of targets') targ_opts.add_argument('--function', '-f', dest='func_targets', action='append', default=[], help='Add a function name to the list of targets') targ_opts.add_argument('--string', '-s', dest='string_targets', action='append', default=[], help='Add a string literal or String() to the list of targets') ### Output Options out_opts = parser.add_argument_group('Output Options') out_opts.add_argument('--count', '-c', dest='count', action='store_true', default=False, help='Only count the matches for TARGET') out_opts.add_argument('--dot-graph', '-d', dest='dot_graph', action='store_true', default=False, help='Output a DOT graph to FILE.dot') out_opts.add_argument('--no-addr', '-na', dest='no_addr', action='store_true', default=False, help='Don\'t show addresses') ### Node and Root Filtering filt_opts = parser.add_argument_group('Node/Root Filtering') filt_opts.add_argument('--diff-heap', '-dh', dest='diff_heap', action='store', metavar='FILE', help='Don\'t show roots common to the heap FILE') filt_opts.add_argument('--no-gray-roots', '-ng', dest='no_gray_roots', action='store_true', default=False, help='Don\'t show gray roots (marked to be collected)') filt_opts.add_argument('--show-unreachable', '-u', action='store_true', help="Show objects that have no path to a root but are not collected yet") filt_opts.add_argument('--no-weak-maps', '-nwm', dest='no_weak_maps', action='store_true', default=False, help='Don\'t show WeakMaps') filt_opts.add_argument('--show-global', '-g', dest='show_global', action='store_true', default=False, help='Show the global object (eg. window/GjsGlobal)') filt_opts.add_argument('--show-imports', '-i', dest='show_imports', action='store_true', default=False, help='Show import and module nodes (eg. imports.foo)') filt_opts.add_argument('--hide-addr', '-ha', dest='hide_addrs', action='append', metavar='ADDR', default=[], help='Don\'t show roots with the heap address ADDR') filt_opts.add_argument('--hide-node', '-hn', dest='hide_nodes', action='append', metavar='LABEL', default=['self-hosting-global', 'GIRepositoryNamespace', 'GjsFileImporter', 'GjsGlobal', 'GjsModule'], help='Don\'t show nodes with labels containing LABEL') filt_opts.add_argument('--hide-edge', '-he', dest='hide_edges', action='append', metavar='LABEL', default=[NAME_ANNOTATION], help="Don't show edges labeled LABEL") ############################################################################### # Heap Patterns ############################################################################### GraphAttribs = namedtuple('GraphAttribs', 'edge_labels node_labels roots root_labels weakMapEntries annotations') WeakMapEntry = namedtuple('WeakMapEntry', 'weakMap key keyDelegate value') addr_regex = re.compile('[A-F0-9]+$|0x[a-f0-9]+$') node_regex = re.compile ('((?:0x)?[a-fA-F0-9]+) (?:(B|G|W) )?([^\r\n]*)\r?$') edge_regex = re.compile ('> ((?:0x)?[a-fA-F0-9]+) (?:(B|G|W) )?([^\r\n]*)\r?$') wme_regex = re.compile(r'WeakMapEntry map=((?:0x)?[a-zA-Z0-9]+|\(nil\)) key=((?:0x)?[a-zA-Z0-9]+|\(nil\)) keyDelegate=((?:0x)?[a-zA-Z0-9]+|\(nil\)) value=((?:0x)?[a-zA-Z0-9]+)') func_regex = re.compile('Function(?: ([^/]+)(?:/([<|\w]+))?)?') priv_regex = re.compile(r'([^ ]+) (0x[a-fA-F0-9]+$)') atom_regex = re.compile(r'^string (.*)\r?$') ############################################################################### # Heap Parsing ############################################################################### def parse_roots(fobj): """Parse the roots portion of a garbage collector heap.""" roots = {} root_labels = {} weakMapEntries = [] for line in fobj: node = node_regex.match(line) if node: addr = node.group(1) color = node.group(2) label = node.group(3) # Only overwrite an existing root with a black root. if addr not in roots or color == 'B': roots[addr] = (color == 'B') # It would be classier to save all the root labels, though then # we have to worry about gray vs black. root_labels[addr] = label else: wme = wme_regex.match(line) if wme: weakMapEntries.append(WeakMapEntry(weakMap=wme.group(1), key=wme.group(2), keyDelegate=wme.group(3), value=wme.group(4))) # Skip comments, arenas, realms and zones elif line[0] == '#': continue # Marks the end of the roots section elif line[:10] == '==========': break else: sys.stderr.write('Error: unknown line {}\n'.format(line)) exit(-1) return [roots, root_labels, weakMapEntries] def parse_graph(fobj): """Parse the node and edges of a garbage collector heap.""" edges = {} edge_labels = {} node_labels = {} annotations = {} def addNode (addr, node_label): edges[addr] = {} edge_labels[addr] = {} if node_label != '': node_labels[addr] = node_label def addEdge(source, target, edge_label): edges[source][target] = edges[source].get(target, 0) + 1 if edge_label != '': edge_labels[source].setdefault(target, []).append(edge_label) node_addr = None for line in fobj: e = edge_regex.match(line) if e: target, edge_label = e.group(1, 3) if edge_label == NAME_ANNOTATION: a = atom_regex.match(node_labels[target]) if a: annotations[node_addr] = a.group(1) if (node_addr not in args.hide_addrs and edge_label not in args.hide_edges): addEdge(node_addr, target, edge_label) else: node = node_regex.match(line) if node: node_addr = node.group(1) node_color = node.group(2) node_label = node.group(3) # Use this opportunity to map hide_nodes to addresses for hide_node in args.hide_nodes: if hide_node in node_label: args.hide_addrs.append(node_addr) break else: addNode(node_addr, node_label) # Skip comments, arenas, realms and zones elif line[0] == '#': continue else: sys.stderr.write('Error: Unknown line: {}\n'.format(line[:-1])) # yar, should pass the root crud in and wedge it in here, or somewhere return [edges, edge_labels, node_labels, annotations] def parse_heap(fname): """Parse a garbage collector heap.""" try: fobj = open(fname, 'r') except: sys.stderr.write('Error opening file {}\n'.format(fname)) exit(-1) [roots, root_labels, weakMapEntries] = parse_roots(fobj) [edges, edge_labels, node_labels, annotations] = parse_graph(fobj) fobj.close() graph = GraphAttribs(edge_labels=edge_labels, node_labels=node_labels, roots=roots, root_labels=root_labels, weakMapEntries=weakMapEntries, annotations=annotations) return (edges, graph) def find_nodes(fname): """Parse a garbage collector heap and return a list of node addresses.""" addrs = []; try: fobj = open(fname, 'r') sys.stderr.write('Parsing {0}...'.format(fname)) except: sys.stderr.write('Error opening file {}\n'.format(fname)) exit(-1) # Whizz past the roots for line in fobj: if '==========\n' in line: break for line in fobj: node = node_regex.match(line) if node: addrs.append(node.group(1)) fobj.close() sys.stderr.write('done\n') sys.stderr.flush() return addrs # Some applications may not care about multiple edges. # They can instead use a single graph, which is represented as a map # from a source node to a set of its destinations. def to_single_graph(edges): single_graph = {} for origin, destinations in edges.items(): d = set([]) for destination, distance in destinations.items(): d.add(destination) single_graph[origin] = d return single_graph def load_graph(fname): sys.stderr.write('Parsing {0}...'.format(fname)) (edges, graph) = parse_heap(fname) edges = to_single_graph(edges) sys.stderr.write('done\n') sys.stderr.flush() return (edges, graph) ############################################################################### # Path printing ############################################################################### tree_graph_paths = {} tree_graph_unreachables = set() class style: BOLD = '\033[1m' ITALIC = '\033[3m' UNDERLINE = '\033[4m' PURPLE = '\033[0;36m' END = '\033[0m' def get_edge_label(graph, origin, destination): elabel = lambda l: l[0] if len(l) == 2 else l labels = graph.edge_labels.get(origin, {}).get(destination, []) if len(labels) == 1: label = labels[0] if label == 'signal connection': return 'GSignal' else: return label elif len(labels) > 1: return ', '.join([elabel(l) for l in labels]) else: return '' def get_node_label(graph, addr): label = graph.node_labels[addr] if label.endswith(' '): label = label[:-13] if label.startswith('Function '): fm = func_regex.match(label) if fm.group(2) == '<': return 'Function via {}'.format(fm.group(1)) elif fm.group(2): return 'Function {} in {}'.format(fm.group(2), fm.group(1)) else: return label if label.startswith('script'): label = label[7:].split('/')[-1] elif label.startswith('WeakMap'): label = 'WeakMap' elif label == 'base_shape': label = 'shape' elif label == 'type_object': label = 'type' return label[:50] def get_node_annotation(graph, addr): return graph.annotations.get(addr, None) def format_node(graph, addr, parent=''): node = get_node_label(graph, addr) annotation = get_node_annotation(graph, addr) has_priv = priv_regex.match(node) # Color/Style if os.isatty(1): orig = style.UNDERLINE + 'jsobj@' + addr + style.END if has_priv: node = style.BOLD + has_priv.group(1) + style.END orig += ' ' + style.UNDERLINE + 'priv@' + has_priv.group(2) + style.END else: node = style.BOLD + node + style.END else: orig = 'jsobj@' + addr if has_priv: node = has_priv.group(1) orig += ' priv@' + has_priv.group(2) # Add the annotation if annotation: if os.isatty(1): node += ' "' + style.PURPLE + annotation + style.END + '"' else: node += ' "' + annotation + '"' if args.no_addr: return node return node + ' ' + orig def output_tree_graph(graph, data, base='', parent=''): while data: addr, children = data.popitem() node = format_node(graph, addr, base) # Labels if parent: edge = get_edge_label(graph, parent, addr) else: edge = graph.root_labels[addr] # Color/Style if os.isatty(1): if parent: edge = style.ITALIC + edge + style.END else: edge = style.BOLD + edge + style.END # Print the path segment if data: print('{0}├─[{1}]─➤ [{2}]'.format(base, edge, node)) else: print('{0}╰─[{1}]─➤ [{2}]'.format(base, edge, node)) # Print child segments if children: if data: output_tree_graph(graph, children, base + '│ ', addr) else: output_tree_graph(graph, children, base + ' ', addr) else: if data: print(base + '│ ') else: print(base + ' ') def output_tree_unreachables(graph, data): while data: addr = data.pop() node = format_node(graph, addr) print(' • Unreachable: [{}]'.format(node)) def add_tree_graph_path(owner, path): o = owner.setdefault(path.pop(0), {}) if path: add_tree_graph_path(o, path) def add_path(args, graph, path): if args.dot_graph: add_dot_graph_path(path) else: add_tree_graph_path(tree_graph_paths, path) def add_unreachable(args, node): if args.dot_graph: add_dot_graph_unreachable(node) else: tree_graph_unreachables.add(node) ############################################################################### # Breadth-first shortest path finding. ############################################################################### def find_roots_bfs(args, edges, graph, target): workList = deque() distances = {} def traverseWeakMapEntry(dist, k, m, v, label): if not k in distances or not m in distances: # Haven't found either the key or map yet. return if distances[k][0] > dist or distances[m][0] > dist: # Either the key or the weak map is farther away, so we # must wait for the farther one before processing it. return if v in distances: return distances[v] = (dist + 1, k, m, label) workList.append(v) # For now, ignore keyDelegates. weakData = {} for wme in graph.weakMapEntries: weakData.setdefault(wme.weakMap, set([])).add(wme) weakData.setdefault(wme.key, set([])).add(wme) if wme.keyDelegate != '0x0': weakData.setdefault(wme.keyDelegate, set([])).add(wme) distances[startObject] = (-1, None) workList.append(startObject) # Search the graph. while workList: origin = workList.popleft() dist = distances[origin][0] # Found the target, stop digging if origin == target: continue # origin does not point to any other nodes. if not origin in edges: continue for destination in edges[origin]: if destination not in distances: distances[destination] = (dist + 1, origin) workList.append(destination) if origin in weakData: for wme in weakData[origin]: traverseWeakMapEntry(dist, wme.key, wme.weakMap, wme.value, "value in WeakMap " + wme.weakMap) traverseWeakMapEntry(dist, wme.keyDelegate, wme.weakMap, wme.key, "key delegate in WeakMap " + wme.weakMap) # Print out the paths by unwinding backwards to generate a path, # then print the path. Accumulate any weak maps found during this # process into the printWorkList queue, and print out what keeps # them alive. Only print out why each map is alive once. printWorkList = deque() printWorkList.append(target) printedThings = set([target]) while printWorkList: p = printWorkList.popleft() path = [] while p in distances: path.append(p) dist = distances[p] if len(dist) == 2: [_, p] = dist else: # The weak map key is probably more interesting, # so follow it, and worry about the weak map later. [_, k, m, label] = dist graph.edge_labels[k].setdefault(p, []).append(label) p = k if not m in printedThings and not args.no_weak_maps: printWorkList.append(m) printedThings.add(m) if path: path.pop() path.reverse() add_path(args, graph, path) elif args.show_unreachable: # No path to a root. This object is eligible for collection on the # next GC, but is still in an arena. add_unreachable(args, target) ######################################################## # Target selection ######################################################## def target_edge(graph, target): targets = set() for origin, destinations in graph.edge_labels.items(): for destination in destinations: if target in graph.edge_labels[origin][destination]: targets.add(destination) targets.add(origin) sys.stderr.write('Found {} objects with edge label of {}\n'.format(len(targets), target)) return targets def target_func(graph, target): targets = set() for addr, label in graph.node_labels.items(): if not label[:9] == 'Function ': continue if label[9:] == target: targets.add(addr) sys.stderr.write('Found {} functions named "{}"\n'.format(len(targets), target)) return targets def target_gobject(graph, target): targets = set() for addr, label in graph.node_labels.items(): if label.endswith(target): targets.add(addr) sys.stderr.write('Found GObject with address of {}\n'.format(target)) return targets def target_string(graph, target): targets = set() for addr, label in graph.node_labels.items(): if label[:7] == 'string ' and target in label[7:]: targets.add(addr) elif label[:10] == 'substring ' and target in label[10:]: targets.add(addr) sys.stderr.write('Found {} strings containing "{}"\n'.format(len(targets), target)) return targets def target_type(graph, target): targets = set() for addr in edges.keys(): if graph.node_labels.get(addr, '')[0:len(target)] == target: targets.add(addr) sys.stderr.write('Found {} targets with type "{}"\n'.format(len(targets), target)) return targets def select_targets(args, edges, graph): targets = set() for target in args.targets: # If target seems like an address search for a JS Object, then GObject if addr_regex.match(target): if target in edges: sys.stderr.write('Found object with address "{}"\n'.format(target)) targets.add(target) else: targets.update(target_gobject(graph, target)) else: # Fallback to looking for JavaScript objects by class name targets.update(target_type(graph, target)) for target in args.edge_targets: targets.update(target_edge(graph, target)) for target in args.func_targets: targets.update(target_func(graph, target)) for target in args.string_targets: targets.update(target_string(graph, target)) return list(targets) if __name__ == "__main__": args = parser.parse_args() # Node and Root Filtering if args.show_global: args.hide_nodes.remove('GjsGlobal') if args.show_imports: args.hide_nodes.remove('GjsFileImporter') args.hide_nodes.remove('GjsModule') args.hide_nodes.remove('GIRepositoryNamespace') # Make sure we don't filter an explicit target for target in args.targets: if target in args.hide_nodes: args.hide_nodes.remove(target) # Heap diffing; we do these addrs separately due to the sheer amount diff_addrs = [] if args.diff_heap: diff_addrs = find_nodes(args.diff_heap) # Load the graph (edges, graph) = load_graph(args.heap_file) targets = select_targets(args, edges, graph) if len(targets) == 0: sys.stderr.write('No targets found.\n') sys.exit(-1) elif args.count: sys.exit(-1); # Unlike JavaScript objects, GObjects can be "rooted" by their refcount so # we have to use a fake root (repetitively) rootEdges = set([]) for addr, isBlack in graph.roots.items(): if isBlack or not args.no_gray_roots: rootEdges.add(addr) startObject = 'FAKE START OBJECT' edges[startObject] = rootEdges for addr in targets: if addr in edges and addr not in diff_addrs: find_roots_bfs(args, edges, graph, addr) if args.dot_graph: output_dot_file(args, graph, targets, args.heap_file + ".dot") else: output_tree_graph(graph, tree_graph_paths) output_tree_unreachables(graph, tree_graph_unreachables) cjs-5.2.0/tools/run_coverage.sh0000755000175000017500000000214714144444702016621 0ustar jpeisachjpeisach#!/bin/bash SOURCEDIR=$(pwd) GIDATADIR=$(pkg-config --variable=gidatadir gobject-introspection-1.0) BUILDDIR="$(pwd)/_coverage_build" LCOV_ARGS="--config-file $SOURCEDIR/tools/lcovrc" GENHTML_ARGS='--legend --show-details --branch-coverage' IGNORE="*/cjs/test/* *-resources.c *minijasmine.cpp" rm -rf "$BUILDDIR" meson "$BUILDDIR" -Db_coverage=true VERSION=$(meson introspect "$BUILDDIR" --projectinfo | python -c 'import json, sys; print(json.load(sys.stdin)["version"])') ninja -C "$BUILDDIR" mkdir -p _coverage meson test -C "$BUILDDIR" --num-processes 1 lcov --directory "$BUILDDIR" --capture --output-file _coverage/gjs.lcov.run --no-checksum $LCOV_ARGS lcov --extract _coverage/gjs.lcov.run "$SOURCEDIR/*" "$GIDATADIR/tests/*" $LCOV_ARGS -o _coverage/gjs.lcov.sources lcov --remove _coverage/gjs.lcov.sources $IGNORE $LCOV_ARGS -o _coverage/gjs.lcov genhtml --prefix "$BUILDDIR/lcov/org/gnome/gjs" --prefix "$BUILDDIR" --prefix "$SOURCEDIR" --prefix "$GIDATADIR" \ --output-directory _coverage/html \ --title "gjs-$VERSION Code Coverage" \ $GENHTML_ARGS _coverage/gjs.lcov "$BUILDDIR"/lcov/coverage.lcov cjs-5.2.0/tools/heapdot.py0000644000175000017500000001512114144444702015575 0ustar jpeisachjpeisach#!/usr/bin/env python3 # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. # # heapdot.py - DOT Graph output import re func_regex = re.compile('Function(?: ([^/]+)(?:/([<|\w]+))?)?') priv_regex = re.compile(r'([^ ]+) (\(nil\)|0x[a-fA-F0-9]+$)') ############################################################################### # DOT Graph Output ############################################################################### dot_graph_paths = [] unreachable = set() def add_dot_graph_path(path): dot_graph_paths.append(path) def add_dot_graph_unreachable(node): unreachable.add(node) def output_dot_file(args, graph, targs, fname): # build the set of nodes nodes = set([]) for p in dot_graph_paths: for x in p: nodes.add(x) nodes.update(unreachable) # build the edge map edges = {} for p in dot_graph_paths: prevNode = None for x in p: if prevNode: edges.setdefault(prevNode, set([])).add(x) prevNode = x # Write out the DOT graph outf = open(fname, 'w') outf.write('digraph {\n') # Nodes for addr in nodes: label = graph.node_labels.get(addr, '') color = 'black' style = 'solid' shape = 'rect' priv = '' if label.endswith(''): label = label[:-13] # Lookup the edge label for this node elabel = '' for origin in graph.edge_labels.values(): if addr in origin: elabels = origin[addr] elabel = elabels[0] break # GObject or something else with JS instance private data pm = priv_regex.match(label) if pm: label = pm.group(1) color = 'orange' style = 'bold' if not args.no_addr: priv = pm.group(2) # Some kind of GObject if label.startswith('GObject_'): shape = 'circle' if elabel in ['prototype', 'group_proto']: style += ',dashed' # Another object native to Gjs elif label.startswith('Gjs') or label.startswith('GIR'): shape = 'octagon' elif label.startswith('Function'): fm = func_regex.match(label) if fm.group(2) == '<': label = 'Function via {}()'.format(fm.group(1)) elif fm.group(2): label = 'Function {} in {}'.format(fm.group(2), fm.group(1)) else: if len(label) > 10: label = label[9:] label += '()' color = 'green' style = 'bold,rounded' # A function context elif label == 'Call' or label == 'LexicalEnvironment': color = 'green' style = 'bold,dashed' # A file/script reference elif label.startswith('script'): label = label[7:].split('/')[-1] shape = 'note' color = 'blue' # A WeakMap elif label.startswith('WeakMap'): label = 'WeakMap' style = 'dashed' # Mostly uninteresting objects elif label in ['base_shape', 'object_group', 'type_object']: style = 'dotted' if label == 'base_shape': label = 'shape' elif label == 'type_object': label = 'type' # Only mark the target if it's a single match if addr == targs[0] and len(targs) == 1: color = 'red' style = 'bold' node_label = label if addr in unreachable: style += ',dotted' node_label = 'Unreachable\\n' + node_label if not args.no_addr: node_label += '\\njsobj@' + addr if priv: node_label += '\\npriv@' + priv annotation = graph.annotations.get(addr, None) if annotation: node_label += '\\n\\"{}\\"'.format(annotation) node_text = ' node [label="{0}", color={1}, shape={2}, style="{3}"] q{4};\n'.format(node_label, color, shape, style, addr) outf.write(node_text) # Edges (relationships) for origin, destinations in edges.items(): for destination in destinations: labels = graph.edge_labels.get(origin, {}).get(destination, []) ll = [] for l in labels: if len(l) == 2: l = l[0] if l.startswith('**UNKNOWN SLOT '): continue ll.append(l) label = '' style = 'solid' color = 'black' if len(ll) == 1: label = ll[0] # Object children if label.startswith('objects['): label = label[7:] # Array elements elif label.startswith('objectElements['): label = label[14:] # prototype/constructor function elif label in ['prototype', 'group_proto']: color = 'orange' style = 'bold,dashed' # fun_environment elif label == 'fun_environment': label = '' color = 'green' style = 'bold,dashed' elif label == 'script': label = '' color = 'blue' # Signals # TODO: better heap label via gi/closure.cpp & gi/object.cpp elif label == 'signal connection': color = 'red' style = 'bold,dashed' if len(label) > 18: label = label[:8] + '...' + label[-8:] else: label = ',\\n'.join(ll) outf.write(' q{0} -> q{1} [label="{2}", color={3}, style="{4}"];\n'.format(origin, destination, label, color, style)) # Extra edges, marked as "interesting" via a command line argument if args.edge_targets: for origin, paths in graph.edge_labels.items(): for destination, labels in paths.items(): if destination in edges.get(origin, set()): continue # already printed for label in labels: if label in args.edge_targets: outf.write(' q{0} -> q{1} [label="{2}", color=black, style="solid"];\n'.format(origin, destination, label)) outf.write('}\n') outf.close() cjs-5.2.0/tools/apply-format0000755000175000017500000002263514144444702016150 0ustar jpeisachjpeisach#! /bin/bash # # Copyright 2018 Undo Ltd. # # https://github.com/barisione/clang-format-hooks # Force variable declaration before access. set -u # Make any failure in piped commands be reflected in the exit code. set -o pipefail readonly bash_source="${BASH_SOURCE[0]:-$0}" ################## # Misc functions # ################## function error_exit() { for str in "$@"; do echo -n "$str" >&2 done echo >&2 exit 1 } ######################## # Command line parsing # ######################## function show_help() { if [ -t 1 ] && hash tput 2> /dev/null; then local -r b=$(tput bold) local -r i=$(tput sitm) local -r n=$(tput sgr0) else local -r b= local -r i= local -r n= fi cat << EOF ${b}SYNOPSIS${n} To reformat git diffs: ${i}$bash_source [OPTIONS] [FILES-OR-GIT-DIFF-OPTIONS]${n} To reformat whole files, including unchanged parts: ${i}$bash_source [-f | --whole-file] FILES${n} ${b}DESCRIPTION${n} Reformat C or C++ code to match a specified formatting style. This command can either work on diffs, to reformat only changed parts of the code, or on whole files (if -f or --whole-file is used). ${b}FILES-OR-GIT-DIFF-OPTIONS${n} List of files to consider when applying clang-format to a diff. This is passed to "git diff" as is, so it can also include extra git options or revisions. For example, to apply clang-format on the changes made in the last few revisions you could use: ${i}\$ $bash_source HEAD~3${n} ${b}FILES${n} List of files to completely reformat. ${b}-f, --whole-file${n} Reformat the specified files completely (including parts you didn't change). The patch is printed on stdout by default. Use -i if you want to modify the files on disk. ${b}--staged, --cached${n} Reformat only code which is staged for commit. The patch is printed on stdout by default. Use -i if you want to modify the files on disk. ${b}-i${n} Reformat the code and apply the changes to the files on disk (instead of just printing the patch on stdout). ${b}--apply-to-staged${n} This is like specifying both --staged and -i, but the formatting changes are also staged for commit (so you can just use "git commit" to commit what you planned to, but formatted correctly). ${b}--style STYLE${n} The style to use for reformatting code. If no style is specified, then it's assumed there's a .clang-format file in the current directory or one of its parents. ${b}--help, -h, -?${n} Show this help. EOF } # getopts doesn't support long options. # getopt mangles stuff. # So we parse manually... declare positionals=() declare has_positionals=false declare whole_file=false declare apply_to_staged=false declare staged=false declare in_place=false declare style=file while [ $# -gt 0 ]; do declare arg="$1" shift # Past option. case "$arg" in -h | -\? | --help ) show_help exit 0 ;; -f | --whole-file ) whole_file=true ;; --apply-to-staged ) apply_to_staged=true ;; --cached | --staged ) staged=true ;; -i ) in_place=true ;; --style=* ) style="${arg//--style=/}" ;; --style ) [ $# -gt 0 ] || \ error_exit "No argument for --style option." style="$1" shift ;; -- ) # Stop processing further arguments. if [ $# -gt 0 ]; then positionals+=("$@") has_positionals=true fi break ;; -* ) error_exit "Unknown argument: $arg" ;; *) positionals+=("$arg") ;; esac done # Restore positional arguments, access them from "$@". if [ ${#positionals[@]} -gt 0 ]; then set -- "${positionals[@]}" has_positionals=true fi [ -n "$style" ] || \ error_exit "If you use --style you need to specify a valid style." ####################################### # Detection of clang-format & friends # ####################################### # clang-format. declare format="${CLANG_FORMAT:-}" if [ -z "$format" ]; then format=$(type -p clang-format) fi if [ -z "$format" ]; then error_exit \ $'You need to install clang-format.\n' \ $'\n' \ $'On Ubuntu/Debian this is available in the clang-format package or, in\n' \ $'older distro versions, clang-format-VERSION.\n' \ $'On Fedora it\'s available in the clang package.\n' \ $'You can also specify your own path for clang-format by setting the\n' \ $'$CLANG_FORMAT environment variable.' fi # clang-format-diff. if [ "$whole_file" = false ]; then invalid="/dev/null/invalid/path" if [ "${OSTYPE:-}" = "linux-gnu" ]; then readonly sort_version=-V else # On macOS, sort doesn't have -V. readonly sort_version=-n fi declare paths_to_try=() # .deb packages directly from upstream. # We try these first as they are probably newer than the system ones. while read -r f; do paths_to_try+=("$f") done < <(compgen -G "/usr/share/clang/clang-format-*/clang-format-diff.py" | sort "$sort_version" -r) # LLVM official releases (just untarred in /usr/local). while read -r f; do paths_to_try+=("$f") done < <(compgen -G "/usr/local/clang+llvm*/share/clang/clang-format-diff.py" | sort "$sort_version" -r) # Maybe it's in the $PATH already? This is true for Ubuntu and Debian. paths_to_try+=( \ "$(type -p clang-format-diff 2> /dev/null || echo "$invalid")" \ "$(type -p clang-format-diff.py 2> /dev/null || echo "$invalid")" \ ) # Fedora. paths_to_try+=( \ /usr/share/clang/clang-format-diff.py \ ) # Gentoo. while read -r f; do paths_to_try+=("$f") done < <(compgen -G "/usr/lib/llvm/*/share/clang/clang-format-diff.py" | sort -n -r) # Homebrew. while read -r f; do paths_to_try+=("$f") done < <(compgen -G "/usr/local/Cellar/clang-format/*/share/clang/clang-format-diff.py" | sort -n -r) declare format_diff= # Did the user specify a path? if [ -n "${CLANG_FORMAT_DIFF:-}" ]; then format_diff="$CLANG_FORMAT_DIFF" else for path in "${paths_to_try[@]}"; do if [ -e "$path" ]; then # Found! format_diff="$path" if [ ! -x "$format_diff" ]; then format_diff="python $format_diff" fi break fi done fi if [ -z "$format_diff" ]; then error_exit \ $'Cannot find clang-format-diff which should be shipped as part of the same\n' \ $'package where clang-format is.\n' \ $'\n' \ $'Please find out where clang-format-diff is in your distro and report an issue\n' \ $'at https://github.com/barisione/clang-format-hooks/issues with details about\n' \ $'your operating system and setup.\n' \ $'\n' \ $'You can also specify your own path for clang-format-diff by setting the\n' \ $'$CLANG_FORMAT_DIFF environment variable, for instance:\n' \ $'\n' \ $' CLANG_FORMAT_DIFF="python /.../clang-format-diff.py" \\\n' \ $' ' "$bash_source" fi readonly format_diff fi ############################ # Actually run the command # ############################ if [ "$whole_file" = true ]; then [ "$has_positionals" = true ] || \ error_exit "No files to reformat specified." [ "$staged" = false ] || \ error_exit "--staged/--cached only make sense when applying to a diff." read -r -a format_args <<< "$format" format_args+=("-style=file") [ "$in_place" = true ] && format_args+=("-i") "${format_args[@]}" "$@" else # Diff-only. if [ "$apply_to_staged" = true ]; then [ "$staged" = false ] || \ error_exit "You don't need --staged/--cached with --apply-to-staged." [ "$in_place" = false ] || \ error_exit "You don't need -i with --apply-to-staged." staged=true readonly patch_dest=$(mktemp) trap '{ rm -f "$patch_dest"; }' EXIT else readonly patch_dest=/dev/stdout fi declare git_args=(git diff -U0 --no-color) [ "$staged" = true ] && git_args+=("--staged") # $format_diff may contain a command ("python") and the script to excute, so we # need to split it. read -r -a format_diff_args <<< "$format_diff" [ "$in_place" = true ] && format_diff_args+=("-i") "${git_args[@]}" "$@" \ | "${format_diff_args[@]}" \ -p1 \ -style="$style" \ -iregex='^.*\.(c|cpp|cxx|cc|h|m|mm|js|java)$' \ > "$patch_dest" \ || exit 1 if [ "$apply_to_staged" = true ]; then if [ ! -s "$patch_dest" ]; then echo "No formatting changes to apply." exit 0 fi patch -p0 < "$patch_dest" || \ error_exit "Cannot apply patch to local files." git apply -p0 --cached < "$patch_dest" || \ error_exit "Cannot apply patch to git staged changes." fi fi cjs-5.2.0/NEWS0000644000175000017500000032222714144444702013146 0ustar jpeisachjpeisachVersion 1.66.2 -------------- - Performance improvements and crash fixes backported from the development branch. - Bug fixes enabling use of GTK 4. - Closed bugs and merge requests: * Error in function "_init()" in module "modules/overrides/GObject.js" [#238, !508, Nina Pypchenko] * Revert "arg-cache: Save space by not caching GType" [!512, Jonas Dreßler] * gi/wrapperutils: Move gjs_get_string_id() into resolve() implementations [!513, Jonas Dreßler] * overrides/Gtk: Set BuilderScope in class init [!527, Florian Müllner] * fix readline build on certain systems [!543, Jakub Kulík] Version 1.66.1 -------------- - Closed bugs and merge requests: * Throws on Unsupported caller allocates [!495, Marco Trevisan] * arg: Fix MIN/MAX safe big integer limits [!492, Marco Trevisan] * Fix leak when virtual function is unimplemented [!498, Evan Welsh] * Cannot compile GJS 1.66.0 on macOS with llvm/clang 10.0.1 [#347, !499, Marc-Antoine Perennou] * console: fix typo in command-line option [!500, Andy Holmes] * Prevent passing null pointers when not nullable [!503, Evan Welsh] * Passing fundamentals to functions no longer works [#353, !506, Evan Welsh] - Fixed examples/clutter.js to work with more recent Clutter [Philip Chimento] Version 1.66.0 -------------- - No change from 1.65.92. Version 1.65.92 --------------- - Closed bugs and merge requests: * CI: Make iwyu idempotent [!481, Simon McVittie] * Enum and flags test failing in s390x [#319, !480, Simon McVittie] * Bring back Visual Studio build support for GJS master [!482, Chun-wei Fan] * gjs_dbus_implementation_emit_signal: don't try to unref NULL [!482, Adam Williamson] * doc: add third party applications [!484, Sonny Piers] * boxed: Initialize all the private BoxedInstance members [!487, Marco Trevisan] * object: Fix GjsCallBackTrampoline's leaks [!490, Marco Trevisan] * Various maintenance [!485, Philip Chimento] * Crash using shell's looking glass [#344, !486, Marco Trevisan] Version 1.65.91 --------------- - Closed bugs and merge requests: * Crash in gjs_dbus_implementation_flush() [#332, !471, Andy Holmes] * eslint: Bump ecmaScript version [!473, Florian Müllner] * Documentation: add documentation for ENV variables [!474, Andy Holmes] * Fix build for master on Windows (due to SpiderMonkey-78.x upgrade) [!475, Chun-wei Fan] * Argument cache causes test failure in armhf [#342, !476, Marco Trevisan] * Argument cache causes test regressions in s390x [#341, !477, Simon McVittie] * ByteArray.toString use-after-free [#339, !472, Evan Welsh] * Crash accessing `vfunc_` methods of `Clutter.Actor`s [#313, !478, Evan Welsh] - Various refactors for type safety [Marco Trevisan] Version 1.65.90 --------------- - GJS now has an optional, Linux-only, dependency on libsysprof-capture-4 instead of libsysprof-capture-3 for the profiler functionality. - New API: gjs_coverage_enable() allows the collection of code coverage metrics. If you are using GjsCoverage, it is now required to call gjs_coverage_enable() before you create the first GjsContext. Previously this was not necessary, but due to changes in SpiderMonkey 78 you must now indicate in advance if you want to collect code coverage metrics. - New JavaScript features! This version of GJS is based on SpiderMonkey 78, an upgrade from the previous ESR (Extended Support Release) of SpiderMonkey 68. Here are the highlights of the new JavaScript features. For more information, look them up on MDN or devdocs.io. * New language features + A new regular expression engine, supporting lookbehind and named capture groups, among other things * New syntax + The ?? operator ("nullish coalescing operator") is now supported + The ?. operator ("optional chaining operator") is now supported + Public static class fields are now supported + Separators in numeric literals are now supported: for example, 1_000_000 * New APIs + String.replaceAll() for replacing all instances of a string inside another string + Promise.allSettled() for awaiting until all Promises in an array have either fulfilled or rejected + Intl.Locale + Intl.ListFormat + Intl.RelativeTimeFormat.formatToParts() * New behaviour + There are a lot of minor behaviour changes as SpiderMonkey's JS implementation conforms ever closer to existing ECMAScript standards and adopts new ones. For complete information, read the Firefox developer release notes: https://developer.mozilla.org/en-US/Firefox/Releases/69#JavaScript https://developer.mozilla.org/en-US/Firefox/Releases/70#JavaScript https://developer.mozilla.org/en-US/Firefox/Releases/71#JavaScript https://developer.mozilla.org/en-US/Firefox/Releases/72#JavaScript https://developer.mozilla.org/en-US/Firefox/Releases/73#JavaScript https://developer.mozilla.org/en-US/Firefox/Releases/74#JavaScript https://developer.mozilla.org/en-US/Firefox/Releases/75#JavaScript https://developer.mozilla.org/en-US/Firefox/Releases/76#JavaScript https://developer.mozilla.org/en-US/Firefox/Releases/77#JavaScript https://developer.mozilla.org/en-US/Firefox/Releases/78#JavaScript * Backwards-incompatible changes + The Object.toSource() method has been removed + The uneval() global function has been removed + A leading zero is now never allowed for BigInt literals, making 08n and 09n invalid similar to the existing error when legacy octal numbers like 07n are used + The Function.caller property now has the value of null if the caller is a strict, async, or generator function, instead of throwing a TypeError - Backwards-incompatible change: Paths specified on the command line with the --coverage-prefix argument, are now always interpreted as paths. If they are relative paths, they will be resolved relative to the current working directory. In previous versions, they would be treated as string prefixes, which led to unexpected behaviour when the path of the script was absolute and the coverage prefix relative, or vice versa. - Closed bugs and merge requests: * Port to libsysprof-capture-4.a [!457, Philip Withnall, Philip Chimento] * CI: Switch ASAN jobs to runners tagged so [!461, Bartłomiej Piotrowski] * Rework global code to support multiple global "types". (Part 1) [!453, Evan Welsh] * SpiderMonkey 78 [#329, !462, !458, Evan Welsh, Philip Chimento] * GIArgument inlines [!460, Marco Trevisan, Philip Chimento] * gjs stopped building on 32 bits [#335, !463, Marco Trevisan, Philip Chimento] * Improve performance of argument marshalling [#70, !48, Giovanni Campagna, Philip Chimento] * Build failure on 32-bit [#336, !465, Michael Catanzaro] * Various maintenance [!464, Philip Chimento] * arg-cache.cpp: Fix build on Visual Studio [!466, Chun-wei Fan] * [regression] Super+A crashes gnome-shell [#338, !467, Philip Chimento] * Generating coverage information seems to be broken [#322, !470, Philip Chimento] - Various refactors for type safety [Marco Trevisan] - Various maintenance [Philip Chimento] Version 1.65.4 -------------- - New language features! Public class fields are now supported. See for more information: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Public_class_fields - Closed bugs and merge requests: * arg.cpp: Add required messages for static_assert (fix building on pre-C++17) [!441, Chun-wei Fan] * Add include-what-you-use CI job [!448, !449, Philip Chimento] * Let's enable class fields! [!445, Evan Welsh] * examples: add GListModel implementation [!452, Andy Holmes] * Update ESLint CI image. [!451, Evan Welsh] * function: Only get function name if we actually warn [!454, Jonas Dreßler] * Split print into native library. [!444, Evan Welsh] * Various maintenance [!459, Philip Chimento] - Various refactors for type safety [Marco Trevisan] Version 1.64.4 -------------- - Closed bugs and merge requests: * Fix CI failure caused by GTK4 update [!447, Philip Chimento] Version 1.65.3 -------------- - In GTK 4, Gtk.Widget is now an iterable object which iterates through its child widgets. (`for (let child of widget) { ... }`) - Closed bugs and merge requests: * Installed tests are not in preferred directories [#318, !427, Ross Burton] * Build new test CI images with Buildah [!429, Philip Chimento] * CI fixes for new test images [!433, Philip Chimento] * Various maintenance [!428, Philip Chimento] * Fix dead link [!436, prnsml] * overrides/Gtk: Make GTK4 widgets iteratable [!437, Florian Müllner] * arg.cpp: Fix building on Visual Studio [!439, Chun-wei Fan] * Separate closures and vfuncs [!438, Philip Chimento] * Improvements to IWYU script [!435, Philip Chimento] * Various refactors in preparation for ES modules [!440, Evan Welsh, Philip Chimento] - Various refactors for type safety [Marco Trevisan] Version 1.64.3 -------------- - Closed bugs and merge requests: * arg: Don't sink GClosure ref if it's a return value [!426, Philip Chimento] * overrides/Gtk: Adjust gtk_container_child_set_property() check [!431, Florian Müllner] * 1.63.3: test suite is failing [#298, !430, Philip Chimento] * Simplify private pointers [!434, Philip Chimento] - Various backports: * Use memory GSettings backend in tests [Philip Chimento] * Update debug message from trimLeft/trimRight to trimStart/trimEnd [Philip Chimento] * Various fixes for potential crash and memory issues [Philip Chimento] Version 1.58.8 -------------- - Various backports * 1.63.3: test suite is failing [Philip Chimento] * Various fixes for potential crash and memory issues [Philip Chimento] Version 1.65.2 -------------- - It's now possible to omit the getter and setter for a GObject property on your class, if you only need the default behaviour (reading and writing the property, respecting the default value if not set, and implementing property notifications if the setter changes the value.) This should cut down on boilerplate code and any mistakes made in it. - The log level of exception messages has changed. Previously, some exceptions would be logged as critical-level messages even when they were logged intentionally with logError(). Now, critical-level messages are only logged when an exception goes uncaught (programmer error) and in all other cases a warning-level message is logged. - Closed bugs and merge requests: * build: Use '!=' instead of 'is not' to compare string [Robert Mader, !414] * Various maintenance [Philip Chimento, !413, !425] * doc fixes [Sonny Piers, !415, !416] * jsapi-util: Make log levels of exceptions consistent [Philip Chimento, !418] * Too much recursion error accessing overrided gobject interface property from a subclass [Philip Chimento, #306, !408] * JS: migrate from the global `window` to `globalThis` [Andy Holmes, !423] * doc: Fix a typo [Matthew Leeds, !424] Version 1.64.2 -------------- - Closed bugs and merge requests: * GList of int not correctly demarshalled on 64-bit big-endian [Philip Chimento, Simon McVittie, #309, !417, !419] * Fix template use in GTK4 [Florian Müllner, !420] * Don't crash if a callback doesn't return an expected array of values [Marco Trevisan, !405] * Crash passing integer to strv in constructor [Evan Welsh, #315, !422] * Skip some tests if GTK can't be initialised [Ross Burton, !421] - Various backports: * Fix gjs_log_exception() for InternalError [Philip Chimento] * Fix signal match mechanism [Philip Chimento] Version 1.58.7 -------------- - Various backports: * Don't crash if a callback doesn't return an expected array of values [Marco Trevisan] * GList of int not correctly demarshalled on 64-bit big-endian [Philip Chimento, Simon McVittie] * Crash passing integer to strv in constructor [Evan Welsh] * Ignore format-nonliteral warning [Marco Trevisan] Version 1.65.1 -------------- - Closed bugs and merge requests: * boxed: Implement newEnumerate hook for boxed objects [Ole Jørgen Brønner, !400] * ns: Implement newEnumerate hook for namespaces [Ole Jørgen Brønner, !401] * CI: Tag sanitizer jobs as "privileged" [Philip Chimento, !407] * overrides/Gio: Allow promisifying static methods [Florian Müllner, !410] * overrides/Gio: Guard against repeated _promisify() calls [Florian Müllner, !411] Version 1.64.1 -------------- - The BigInt type is now _actually_ available, as it wasn't enabled in the 1.64.0 release even though it was mentioned in the release notes. - Closed bugs and merge requests: * testCommandLine's Unicode tests failing on Alpine Linux [Philip Chimento, #296, !399] * build: Various clean-ups [Jan Tojnar, !403] * Correctly handle vfunc inout parameters [Marco Trevisan, !404] * Fix failed redirect of output in CommandLine tests [Liban Parker, !409] Version 1.58.6 -------------- - Various backports: * Correctly handle vfunc inout parameters [Marco Trevisan] * Fix failed redirect of output in CommandLine tests [Liban Parker] * Avoid filename conflict when tests run in parallel [Philip Chimento] Version 1.64.0 -------------- - No change from 1.63.92. Version 1.63.92 --------------- - Closed bugs and merge requests: * object: Use g_irepository_get_object_gtype_interfaces [Colin Walters, Philip Chimento, #55, !52] * Add -fno-semantic-interposition to -Bsymbolic-functions [Jan Alexander Steffens (heftig), #303, !397] * examples: add a dbus-client and dbus-service example [Andy Holmes, !398] * Various GNOME Shell crashes during GC, mozjs68 regression [Jan Alexander Steffens (heftig), Philip Chimento, #301, !396] Version 1.63.91 --------------- - Closed bugs and merge requests: * [mozjs68] Reorganize modules for ESM. [Evan Welsh, Philip Chimento, !383] * Various maintenance [Philip Chimento, !388] * Fix building GJS master with Visual Studio and update build instructions [Chun-wei Fan, !389] * Resolve "Gnome Shell crash on GC run with mozjs68" [Philip Chimento, !391] * installed-tests/js: Add missing dep on warnlib_typelib [Jan Alexander Steffens, !393] * object: Cache known unresolvable properties [Daniel van Vugt, Philip Chimento, !394, #302] Version 1.58.5 -------------- - Closed bugs and merge requests: * Fix Visual Studio builds of gnome-3-34 (1.58.x) branch [Chun-wei Fan, !392] * Can not access GObject properties of classes without GI information [Juan Pablo Ugarte, !385, #299] Version 1.63.90 --------------- - New JS API: The GObject module has gained new overrides: GObject.signal_handler_find(), GObject.signal_handlers_block_matched(), GObject.signal_handlers_unblock_matched(), and GObject.signal_handlers_disconnect_matched(). These overrides replace the corresponding C API, which was not idiomatic for JavaScript and was not fully functional because it used bare C pointers for some of its functionality. See modules/overrides/GObject.js for API documentation. - New JavaScript features! This version of GJS is based on SpiderMonkey 68, an upgrade from the previous ESR (Extended Support Release) of SpiderMonkey 60. Here are the highlights of the new JavaScript features. For more information, look them up on MDN or devdocs.io. * New language features + The BigInt type, currently a stage 3 proposal in the ES standard, is now available. * New syntax + `globalThis` is now the ES-standard supported way to get the global object, no matter what kind of JS environment. The old way, `window`, will still work, but is no longer preferred. + BigInt literals are expressed by a number with "n" appended to it: for example, `1n`, `9007199254740992n`. * New APIs + String.prototype.trimStart() and String.prototype.trimEnd() now exist and are preferred instead of trimLeft() and trimRight() which are nonstandard. + String.prototype.matchAll() allows easier access to regex capture groups. + Array.prototype.flat() flattens nested arrays, well-known from lodash and similar libraries. + Array.prototype.flatMap() acts like a reverse filter(), allowing adding elements to an array while iterating functional-style. + Object.fromEntries() creates an object from iterable key-value pairs. + Intl.RelativeTimeFormat is useful for formatting time differences into human-readable strings such as "1 day ago". + BigInt64Array and BigUint64Array are two new typed array types. * New behaviour + There are a lot of minor behaviour changes as SpiderMonkey's JS implementation conforms ever closer to existing ECMAScript standards and adopts new ones. For complete information, read the Firefox developer release notes: https://developer.mozilla.org/en-US/Firefox/Releases/61#JavaScript https://developer.mozilla.org/en-US/Firefox/Releases/62#JavaScript https://developer.mozilla.org/en-US/Firefox/Releases/63#JavaScript https://developer.mozilla.org/en-US/Firefox/Releases/64#JavaScript https://developer.mozilla.org/en-US/Firefox/Releases/65#JavaScript https://developer.mozilla.org/en-US/Firefox/Releases/66#JavaScript https://developer.mozilla.org/en-US/Firefox/Releases/67#JavaScript https://developer.mozilla.org/en-US/Firefox/Releases/68#JavaScript * Backwards-incompatible changes + The nonstandard String generics were removed. These had only ever been implemented by Mozilla and never made it into a standard. (An example of a String generic is calling a string method on something that might not be a string like this: `String.endsWith(foo, 5)`. The proper way is `String.prototype.endsWith.call(foo, 5)` or converting `foo` to a string.) This should not pose much of a problem for existing code, since in the previous version these would already print a deprecation warning whenever they were used. You can use `moz68tool` from mozjs-deprecation-tools (https://gitlab.gnome.org/ptomato/moz60tool) to scan your code for this nonstandard usage. - Closed bugs and merge requests: * invalid import on signal.h [#295, !382, Philip Chimento] * SpiderMonkey 68 [#270, !386, Philip Chimento] * GObject: Add override for GObject.handler_block_by_func [#290, !371, Philip Chimento] Version 1.63.3 -------------- - Closed bugs and merge requests: * JS ERROR: TypeError: this._rooms.get(...) is undefined [Philip Chimento, #289, !367] * Run CI build with --werror [Philip Chimento, #286, !365] * build: Remove Autotools build system [Philip Chimento, !364] * gjs-symlink script is incompatible with distro builds [Michael Catanzaro, Bastien Nocera, #291, !369, !370] * installed-tests: Don't hardcode the path of bash [Ting-Wei Lan, !372] * Update Visual Studio build instructions (after migrating to full Meson-based builds) [Chun-wei Fan, !375] * object: Warn when setting a deprecated property [Florian Müllner, !378] * CI: Create mozjs68 CI images [Philip Chimento, !379] * Various maintenance [Philip Chimento, !374, !380, !381] Version 1.58.4 -------------- - Now prints a warning when constructing an unregistered object inheriting from GObject (i.e. if you forgot to use GObject.registerClass.) In 1.58.2 this would throw an exception, which broke some existing code, so that change was reverted in 1.58.3. In this version the check is reinstated, but we log a warning instead of throwing an exception, so that people know to fix their code, but without breaking things. NOTE: In 1.64 (the next stable release) the warning will be changed back into an exception, because code with this problem can be subtly broken and cause unexpected errors elsewhere. So make sure to fix your code if you get this warning. - Closed bugs and merge requests: * GSettings crash fixes [Andy Holmes, !373] - Memory savings for Cairo objects [Philip Chimento, !374] - Fix for crash in debug functions [Philip Chimento, !374] Version 1.63.2 -------------- - There is an option for changing the generated GType name for GObject classes created in GJS to a new scheme that is less likely to have collisions. This scheme is not yet the default, but you can opt into it by setting `GObject.gtypeNameBasedOnJSPath = true;` as early as possible in your prograṁ. Doing this may require some changes in Glade files if you use composite widget templates. We recommend you make this change in your codebase as soon as possible, to avoid any surprises in the future. - New JS API: GObject.Object has gained a stop_emission_by_name() method which is a bit more idiomatic than calling GObject.signal_stop_emission_by_name(). - It's now supported to use the "object" attribute in a signal connection in a composite widget template in a Glade file. - Closed bugs and merge requests: * CI: Tweak eslint rule for unneeded parentheses [Florian Müllner, !353] * Smarter GType name computation [Marco Trevisan, !337] * Meson CI [Philip Chimento, !354] * Visual Studio builds using Meson [Chun-wei Fan, !355] * Hide internal symbols from ABI [Marco Trevisan, #194, !352] * Allow creating custom tree models [Giovanni Campagna, #71] * build: Fix dist files [Florian Müllner, !357] * GObject: Add convenience wrapper for signal_stop_emission_by_name() [Florian Müllner, !358] * Various maintenance [Philip Chimento, !356] * object_instance_props_to_g_parameters should do more check on argv [Philip Chimento, #63, !359] * Support flat C arrays of structures [Philip Chimento, !361] * Gtk Templates: support connectObj argument [Andy Holmes, !363] - Various build fixes [Philip Chimento] Version 1.58.2 -------------- - Closed bugs and merge requests: * GObject based class initialization checks [Marco Trevisan, Philip Chimento, !336] * Silently leaked return value of callbacks [Xavier Claessens, Philip Chimento, #86, !44] * Crash when calling Gio.Initable.async_init with not vfunc_async_init implementation [Philip Chimento, #287, !362] * [cairo] insufficient checking [Philip Chimento, #49, !360] - Various crash fixes backported from the development branch that didn't close a bug or merge request. Version 1.63.1 -------------- - Note that the 1.59, 1.60, 1.61, and 1.62 releases are hereby skipped, because we are calling the next stable series 1.64 to match gobject-introspection and GLib. - GJS now includes a Meson build system. This is now the preferred way to build it; however, the old Autotools build system is still available for a transitional period. - Closed bugs and merge requests: * GObject: Add convenience wrapper for signal_handler_(un)block() [Florian Müllner, !326] * GObject based class initialization checks [Marco Trevisan, Philip Chimento, !336] * Meson port [Philip Chimento, !338] * add http client example [Sonny Piers, !342] * Smaller CI, phase 2 [Philip Chimento, !343] * add websocket client example [Sonny Piers, !344] * Fix Docker images build [Philip Chimento, !345] * CI: Use new Docker images [Philip Chimento, !346] * docs: Update internal links [Andy Holmes, !348] * Don't pass generic marshaller to g_signal_newv() [Niels De Graef, !349] * tests: Fail debugger tests if command failed [Philip Chimento, !350] * Minor CI image fixes [Philip Chimento, !351] * Various fixes [Marco Trevisan, Philip Chimento] Version 1.58.1 -------------- - Closed bugs and merge requests: * Import wiki documentation [Sonny Piers, !341] * Smaller CI, phase 1 [Philip Chimento, !339] * Crashes after setting child property 'icon-name' on GtkStack then displaying another GtkStack [Florian Müllner, #284, !347] * GLib.strdelimit crashes [Philip Chimento, #283, !340] Version 1.58.0 -------------- - No change from 1.57.92. Version 1.57.92 --------------- - Closed bugs and merge requests: * tests: Enable regression test cases for GPtrArrays and GArrays of structures [Stéphane Seng, !334] * Various maintenance [Philip Chimento, !333, !335] Version 1.57.91 --------------- - GJS no longer links to libgtk-3. This makes it possible to load the Gtk-4.0 typelib in GJS and write programs that use GTK 4. - The heapgraph tool has gained some improvements; it is now possible to print a heap graph of multiple targets. You can also mark an object for better identification in the heap graph by assigning a magic property: for example, myObject.__heapgraph_name = 'Button' will make that object identify itself as "Button" in heap graphs. - Closed bugs and merge requests: * Remove usage of Lang in non legacy code [Sonny Piers, !322] * GTK4 [Florian Müllner, #99, !328, !330] * JS syntax fixes [Marco Trevisan, Philip Chimento, !306, !323] * gi: Avoid infinite recursion when converting GValues [Florian Müllner, !329] * Implement all GObject-introspection test suites [Philip Chimento, !327, !332] * Heapgraph improvements [Philip Chimento, !325] Version 1.57.90 --------------- - New JS API: GLib.Variant has gained a recursiveUnpack() method which transforms the variant entirely into a JS object, discarding all type information. This can be useful for dealing with a{sv} dictionaries, where deepUnpack() will keep the values as GLib.Variant instances in order to preserve the type information. - New JS API: GLib.Variant has gained a deepUnpack() method which is exactly the same as the already existing deep_unpack(), but fits with the other camelCase APIs that GJS adds. - Closed bugs and merge requests: * Marshalling of GPtrArray broken [#9, !311, Stéphane Seng] * Fix locale chooser [!313, Philip Chimento] * dbus-wrapper: Remove interface skeleton flush idle on dispose [!312, Marco Trevisan] * gobject: Use auto-compartment when getting property as well [!316, Florian Müllner] * modules/signals: Use array destructuring in _emit [!317, Jonas Dreßler] * GJS can't call glibtop_init function from libgtop [#259, !319, Philip Chimento] * GLib's VariantDict is missing lookup [#263, !320, Sonny Piers] * toString on an object implementing an interface fails [#252, !299, Marco Trevisan] * Regression in GstPbutils.Discoverer::discovered callback [#262, !318, Philip Chimento] * GLib.Variant.deep_unpack not working properly with a{sv} variants [#225, !321, Fabián Orccón, Philip Chimento] * Various maintenance [!315, Philip Chimento] - Various CI fixes [Philip Chimento] Version 1.57.4 -------------- - Closed bugs and merge requests: * gjs 1.57 requires a recent sysprof version for sysprof-capture-3 [#258, !309, Olivier Fourdan] - Misc documentation changes [Philip Chimento] Version 1.57.3 -------------- - The GJS profiler is now integrated directly into Sysprof 3, via the GJS_TRACE_FD environment variable. Call stack information and garbage collector timing will show up in Sysprof. See also GNOME/Initiatives#10 - New JS API: System.addressOfGObject(obj) will return a string with the hex address of the underlying GObject of `obj` if it is a GObject wrapper, or throw an exception if it is not. This is intended for debugging. - New JS API: It's now possible to pass a value from Gio.DBusProxyFlags to the constructor of a class created by Gio.DBusProxy.makeProxyWrapper(). - Backwards-incompatible change: Trying to read a write-only property on a DBus proxy object, or write a read-only property, will now throw an exception. Previously it would fail silently. It seems unlikely any code is relying on the old behaviour, and if so then it was probably masking a bug. - Closed bugs and merge requests: * Build failure on Continuous [#253, !300, Philip Chimento] * build: Bump glib requirement [!302, Florian Müllner] * profiler: avoid clearing 512 bytes of stack [!304, Christian Hergert] * system: add addressOfGObject method [!296, Marco Trevisan] * Add support for GJS_TRACE_FD [!295, Christian Hergert] * Gio: Make possible to pass DBusProxyFlags to proxy wrapper [!297, Marco Trevisan] * Various maintenance [!301, Philip Chimento] * Marshalling of GPtrArray broken [#9, !307, Stéphane Seng] * Build fix [!308, Philip Chimento] * Gio: sync dbus wrapper properties flags [!298, Marco Trevisan] * GjsMaybeOwned: Reduce allocation when used as Object member [!303, Marco Trevisan] Version 1.57.2 -------------- - There are now overrides for Gio.SettingsSchema and Gio.Settings which avoid aborting the whole process when trying to access a nonexistent key or child schema. The original API from GLib was intended for apps, since apps should have complete control over which settings keys they are allowed to access. However, it is not a good fit for shell extensions, which may need to access different settings keys depending on the version of GNOME shell they're running on. This feature is based on code from Cinnamon which the copyright holders have kindly agreed to relicense to GJS's license. - New JS API: It is now possible to pass GObject.TypeFlags to GObject.registerClass(). For example, passing `GTypeFlags: GObject.TypeFlags.ABSTRACT` in the class info object, will create a class that cannot be instantiated. This functionality was present in Lang.Class but has been missing from GObject.registerClass(). - Closed bugs and merge requests: * Document logging features [#230, !288, Andy Holmes] * Support optional GTypeFlags value in GObject subclasses [!290, Florian Müllner] * Ensure const-correctness in C++ objects [#105, !291, Onur Şahin] * Programmer errors with GSettings cause segfaults [#205, !284, Philip Chimento] * Various maintenance [!292, Philip Chimento] * debugger: Fix summary help [!293, Florian Müllner] * context: Use Heap pointers for GC objects stored in vectors [!294, Philip Chimento] Version 1.56.2 -------------- - Closed bugs and merge requests: * Crash in BoxedInstance when struct could not be allocated directly [#240, !285, Philip Chimento] * Cairo conversion bugs [!286, Philip Chimento] * Gjs crashes when binding inherited property to js added gobject-property [#246, !289, Marco Trevisan] * console: Don't accept --profile after the script name [!287, Philip Chimento] Version 1.57.1 -------------- - Closed bugs and merge requests: * Various maintenance [!279, Philip Chimento] * mainloop: Assign null to property instead of deleting [!280, Jason Hicks] * Added -d version note README.md [!282, Nauman Umer] * Extra help for debugger commands [#236, !283, Nauman Umer] * Crash in BoxedInstance when struct could not be allocated directly [#240, !285, Philip Chimento] * Cairo conversion bugs [!286, Philip Chimento] Version 1.56.1 -------------- - Closed bugs and merge requests: * Calling dumpHeap() on non-existent directory causes crash [#134, !277, Philip Chimento] * Using Gio.MemoryInputStream.new_from_data ("string") causes segfault [#221, !278, Philip Chimento] * Fix gjs_context_eval() for non-zero-terminated strings [!281, Philip Chimento] Version 1.56.0 -------------- - No change from 1.55.92. Version 1.55.92 --------------- - Closed bugs and merge requests: * Fix CI failures [!269, Philip Chimento] * Possible memory allocation/deallocation bug (possibly in js_free() in GJS) [!270, Chun-wei Fan, Philip Chimento] * cairo-context: Special-case 0-sized vector [!271, Florian Müllner] * Add some more eslint rules [!272, Florian Müllner] * win32/NMake: Fix introspection builds [!274, Chun-wei Fan] * NMake/libgjs-private: Export all the public symbols there [!275, Chun-wei Fan] Version 1.55.91 --------------- - The problem of freezing while running the tests using GCC's sanitizers was determined to be a bug in GCC, which was fixed in GCC 9.0.1. - Closed bugs and merge requests: * gnome-sound-recorder crashes deep inside libgjs [#223, !266, Philip Chimento] * Various maintenance [!267, Philip Chimento] * wrapperutils: Define $gtype property as non-enumerable [!268, Philip Chimento] Version 1.55.90 --------------- - New JS API: It's now possible to call and implement DBus methods whose parameters or return types include file descriptor lists (type signature 'h'.) This involves passing or receiving a Gio.UnixFDList instance along with the parameters or return values. To call a method with a file descriptor list, pass the Gio.UnixFDList along with the rest of the parameters, in any order, the same way you would pass a Gio.Cancellable or async callback. For return values, things are a little more complicated, in order to avoid breaking existing code. Previously, synchronously called DBus proxy methods would return an unpacked GVariant. Now, but only if called with a Gio.UnixFDList, they will return [unpacked GVariant, Gio.UnixFDList]. This does not break existing code because it was not possible to call a method with a Gio.UnixFDList before, and the return value is unchanged if not calling with a Gio.UnixFDList. This does mean, unfortunately, that if you have a method with an 'h' in its return signature but not in its argument signatures, you will have to call it with an empty FDList in order to receive an FDList with the return value, when calling synchronously. On the DBus service side, when receiving a method call, we now pass the Gio.UnixFDList received from DBus to the called method. Previously, sync methods were passed the parameters, and async methods were passed the parameters plus the Gio.DBusInvocation object. Appending the Gio.UnixFDList to those parameters also should not break existing code. See the new tests in installed-tests/js/testGDBus.js for examples of calling methods with FD lists. - We have observed on the CI server that GJS 1.55.90 will hang forever while running the test suite compiled with GCC 9.0.0 and configured with the --enable-asan and --enable-ubsan arguments. This should be addressed in one of the following 1.55.x releases. - Closed bugs and merge requests: * GDBus proxy overrides should support Gio.DBusProxy.call_with_unix_fd_list() [#204, !263, Philip Chimento] * Add regression tests for GObject vfuncs [!259, Jason Hicks] * GjsPrivate: Sources should be C files [!262, Philip Chimento] * build: Vendor last-good version of AX_CODE_COVERAGE [!264, Philip Chimento] Version 1.55.4 -------------- - Closed bugs and merge requests: * Various maintenance [!258, Philip Chimento] * Boxed copy constructor should not be called, split Boxed into prototype and instance structs [#215, !260, Philip Chimento] Version 1.55.3 -------------- - Closed bugs and merge requests: * Manually constructed ByteArray toString segfaults [#219, !254, Philip Chimento] * signals: Add _signalHandlerIsConnected method [!255, Jason Hicks] * Various maintenance [!257, Philip Chimento] Version 1.52.5 -------------- - This was a release consisting only of backports from the GNOME 3.30 branch to the GNOME 3.28 branch. - This release includes the "Big Hammer" patch from GNOME 3.30 to reduce memory usage. For more information, read the blog post at https://feaneron.com/2018/04/20/the-infamous-gnome-shell-memory-leak/ It was not originally intended to be backported to GNOME 3.28, but in practice several Linux distributions already backported it, and it has been working well to reduce memory usage, and the bugs have been ironed out of it. It does decrease performance somewhat, so if you don't want that then don't install this update. - Closed bugs and merge requests: * Ensure not to miss the force_gc flag [#150, !132, Carlos Garnacho] * Make GC much more aggressive [#62, !50, Giovanni Campagna, Georges Basile Stavracas Neto, Philip Chimento] * Queue GC when a GObject reference is toggled down [#140, !114, !127, Georges Basile Stavracas Neto] * Reduce memory overhead of g_object_weak_ref() [#144, !122, Carlos Garnacho, Philip Chimento] * context: Defer and therefore batch forced GC runs [performance] [!236, Daniel van Vugt] * context: use timeout with seconds to schedule a gc trigger [!239, Marco Trevisan] * Use compacting GC on RSS size growth [!133, #151, Carlos Garnacho] * GType memleak fixes [!244, Marco Trevisan] Version 1.55.2 -------------- - Closed bugs and merge requests: * Gnome-shell crashes on destroying cached param specs [#213, !240, Marco Trevisan] * Various maintenance [!235, !250, Philip Chimento] * Auto pointers builder [!243, Marco Trevisan] * configure.ac: Update bug link [!245, Andrea Azzarone] * SIGSEGV when exiting gnome-shell [#212, !247, Andrea Azzarone, Philip Chimento] * Fix build with --enable-dtrace and create CI job to ensure it doesn't break in the future [#196, !237, !253, Philip Chimento] * Delay JSString-to-UTF8 conversion [!249, Philip Chimento] * Annotate return values [!251, Philip Chimento] * Fix a regression with GError toString() [!252, Philip Chimento] * GType memleak fixes [!244, Marco Trevisan] * Atoms refactor [!233, Philip Chimento, Marco Trevisan] * Write a "Code Hospitable" README file [#17, !248, Philip Chimento, Andy Holmes, Avi Zajac] * object: Method lookup repeatedly traverses introspection [#54, !53, Colin Walters, Philip Chimento] * Handler of GtkEditable::insert-text signal is not run [#147, !143, Tomasz Miąsko, Philip Chimento] Version 1.54.3 -------------- - Closed bugs and merge requests: * object: Fix write-only properties [!246, Philip Chimento] * SIGSEGV when exiting gnome-shell [#212, !247, Andrea Azzarone] * SelectionData.get_targets crashes with "Unable to resize vector" [#201, !241, Philip Chimento] * Gnome-shell crashes on destroying cached param specs [#213, !240, Marco Trevisan] * GType memleak fixes [!244, Marco Trevisan] * Fix build with --enable-dtrace and create CI job to ensure it doesn't break in the future [#196, !253, Philip Chimento] Version 1.54.2 -------------- - Closed bugs and merge requests: * context: Defer and therefore batch forced GC runs [performance] [!236, Daniel van Vugt] * context: use timeout with seconds to schedule a gc trigger [!239, Marco Trevisan] * fundamental: Check if gtype is valid before using it [!242, Georges Basile Stavracas Neto] - Backported a fix for a crash in the interactive interpreter when executing something like `throw "foo"` [Philip Chimento] - Backported various maintenance from 3.31 [Philip Chimento] Version 1.55.1 -------------- - New API for programs that embed GJS: gjs_memory_report(). This was already an internal API, but now it is exported. - Closed bugs and merge requests: * object: Implement newEnumerate hook for GObject [!155, Ole Jørgen Brønner] * Various maintenance [!228, Philip Chimento] * ByteArray.toString should stop at null bytes [#195, !232, Philip Chimento] * Byte arrays that represent encoded strings should be 0-terminated [#203, !232, Philip Chimento] * context: Defer and therefore batch forced GC runs [performance] [!236, Daniel van Vugt] * context: use timeout with seconds to schedule a gc trigger [!239, Marco Trevisan] * arg: Add special-case for byte arrays going to C [#67, !49, Jasper St. Pierre, Philip Chimento] Version 1.52.4 -------------- - This was a release consisting only of backports from the GNOME 3.30 branch to the GNOME 3.28 branch. - Closed bugs and merge requests: * `ARGV` encoding issues [#22, !108, Evan Welsh] * Segfault on enumeration of GjSFileImporter properties when a searchpath entry contains a symlink [#154, !144, Ole Jørgen Brønner] * Possible refcounting bug around GtkListbox signal handlers [#24, !154, Philip Chimento] * Fix up GJS_DISABLE_JIT flag now the JIT is enabled by default in SpiderMonkey [!159, Christopher Wheeldon] * Expose GObject static property symbols. [!197, Evan Welsh] * Do not run linters on tagged commits [!181, Claudio André] * gjs-1.52.0 fails to compile against x86_64 musl systems [#132, !214, Philip Chimento] * gjs no longer builds after recent autoconf-archive updates [#149, !217, Philip Chimento] Version 1.54.1 -------------- - Closed bugs and merge requests: * legacy: Ensure generated GType names are valid [!229, Florian Müllner] * Fix GJS profiler with MozJS 60 [!230, Georges Basile Stavracas Neto] * Regression with DBus proxies [#202, !231, Philip Chimento] Version 1.54.0 -------------- - Compatibility fix for byte arrays: the legacy toString() behaviour of byte arrays returned from GObject-introspected functions is now restored. If you use the functionality, a warning will be logged asking you to upgrade your code. - Closed bugs and merge requests: * byteArray: Add compatibility toString property [Philip Chimento, !227] Version 1.53.92 --------------- - Technology preview of a GNOME 3.32 feature: native Promises for GIO-style asynchronous operations. This is the result of Avi Zajac's summer internship. To use it, you can opt in once for each specific asynchronous method, by including code such as the following: Gio._promisify(Gio.InputStream.prototype, 'read_bytes_async', 'read_bytes_finish'); After executing this, you will be able to use native Promises with the Gio.InputStream.prototype.read_async() method, simply by not passing a callback to it: try { let bytes = await stream.read_bytes_async(count, priority, cancel); } catch (e) { logError(e, 'Failed to read bytes'); } Note that any "success" boolean return values are deleted from the array of return values from the async method. That is, let [contents, etag] = file.load_contents_async(cancel); whereas the callback version still returns a useless [ok, contents, etag] that can never be false, since on false an exception would be thrown. In the callback version, we must keep this for compatibility reasons. Note that due to a bug in GJS (https://gitlab.gnome.org/GNOME/gjs/issues/189), promisifying methods on Gio.File.prototype and other interface prototypes will not work. We provide the API Gio._LocalFilePrototype on which you can promisify methods that will work on Gio.File instances on the local disk only: Gio._promisify(Gio._LocalFilePrototype, 'load_contents_async', 'load_contents_finish'); We estimate this will cover many common use cases. Since this is a technology preview, we do not guarantee API stability with the version coming in GNOME 3.32. These APIs are marked with underscores to emphasize that they are not stable yet. Use them at your own risk. - Closed bugs and merge requests: * Added promisify to GJS GIO overrides [!225, Avi Zajac] * Temporary fix for Gio.File.prototype [!226, Avi Zajac] Version 1.53.91 --------------- - Closed bugs and merge requests: * CI: add webkit and gtk-app tests [!222, Claudio André] * Fix example eslint errors [!207, Claudio André, Philip Chimento] * Fix more "lost" GInterface properties [!223, Florian Müllner] * Fix --enable-installed-tests when built from a tarball [!224, Simon McVittie] Version 1.53.90 --------------- - GJS now depends on SpiderMonkey 60 and requires a compiler capable of C++14. - GJS includes a simple debugger now. It has basic stepping, breaking, and printing commands, that work like GDB. Activate it by running the GJS console interpreter with the -d or --debugger flag before the name of the JS program on the command line. - New API for programs that embed GJS: gjs_context_setup_debugger_console(). To integrate the debugger into programs that embed the GJS interpreter, call this before executing the JS program. - New JavaScript features! This version of GJS is based on SpiderMonkey 60, an upgrade from the previous ESR (Extended Support Release) of SpiderMonkey 52. Here are the highlights of the new JavaScript features. For more information, look them up on MDN or devdocs.io. * New syntax + `for await (... of ...)` syntax is used for async iteration. + The rest operator is now supported in object destructuring: e.g. `({a, b, ...cd} = {a: 1, b: 2, c: 3, d: 4});` + The spread operator is now supported in object literals: e.g. `mergedObject = {...obj1, ...obj2};` + Generator methods can now be async, using the `async function*` syntax, or `async* f() {...}` method shorthand. + It's now allowed to omit the variable binding from a catch statement, if you don't need to access the thrown exception: `try {...} catch {}` * New APIs + Promise.prototype.finally(), popular in many third-party Promise libraries, is now available natively. + String.prototype.toLocaleLowerCase() and String.prototype.toLocaleUpperCase() now take an optional locale or array of locales. + Intl.PluralRules is now available. + Intl.NumberFormat.protoype.formatToParts() is now available. + Intl.Collator now has a caseFirst option. + Intl.DateTimeFormat now has an hourCycle option. * New behaviour + There are a lot of minor behaviour changes as SpiderMonkey's JS implementation conforms ever closer to ECMAScript standards. For complete information, read the Firefox developer release notes: https://developer.mozilla.org/en-US/Firefox/Releases/53#JavaScript https://developer.mozilla.org/en-US/Firefox/Releases/54#JavaScript https://developer.mozilla.org/en-US/Firefox/Releases/55#JavaScript https://developer.mozilla.org/en-US/Firefox/Releases/56#JavaScript https://developer.mozilla.org/en-US/Firefox/Releases/57#JavaScript https://developer.mozilla.org/en-US/Firefox/Releases/58#JavaScript https://developer.mozilla.org/en-US/Firefox/Releases/59#JavaScript https://developer.mozilla.org/en-US/Firefox/Releases/60#JavaScript * Backwards-incompatible changes + Conditional catch clauses have been removed, as they were a Mozilla extension which will not be standardized. This requires some attention in GJS programs, as previously we condoned code like `catch (e if e.matches(Gio.IOError, Gio.IOError.EXISTS))` with a comment in overrides/GLib.js, so it's likely this is used in several places. + The nonstandard `for each (... in ...)` loop was removed. + The nonstandard legacy lambda syntax (`function(x) x*x`) was removed. + The nonstandard Mozilla iteration protocol was removed, as well as nonstandard Mozilla generators, including the Iterator and StopIteration objects, and the Function.prototype.isGenerator() method. + Array comprehensions and generator comprehensions have been removed. + Several nonstandard methods were removed: ArrayBuffer.slice() (but not the standard version, ArrayBuffer.prototype.slice()), Date.prototype.toLocaleFormat(), Function.prototype.isGenerator(), Object.prototype.watch(), and Object.prototype.unwatch(). - Many of the above backwards-incompatible changes can be caught by scanning your source code using https://gitlab.gnome.org/ptomato/moz60tool, or https://extensions.gnome.org/extension/1455/spidermonkey-60-migration-validator/ - Deprecation: the custom ByteArray is now discouraged. Instead of ByteArray, use Javascript's native Uint8Array. The ByteArray module still contains functions for converting between byte arrays, strings, and GLib.Bytes instances. The old ByteArray will continue to work as before, except that Uint8Array will now be returned from introspected functions that previously returned a ByteArray. To keep your old code working, change this: let byteArray = functionThatReturnsByteArray(); to this: let byteArray = new ByteArray.ByteArray(functionThatReturnsByteArray()); To port to the new code: * ByteArray.ByteArray -> Uint8Array * ByteArray.fromArray() -> Uint8Array.from() * ByteArray.ByteArray.prototype.toString() -> ByteArray.toString() * ByteArray.ByteArray.prototype.toGBytes() -> ByteArray.toGBytes() * ByteArray.fromString(), ByteArray.fromGBytes() remain the same * Unlike ByteArray, Uint8Array's length is fixed. Assigning an element past the end of a ByteArray would lengthen the array. Now, it is ignored. Instead use Uint8Array.of(), for example, this code: let a = ByteArray.fromArray([97, 98, 99, 100]); a[4] = 101; should be replaced by this code: let a = Uint8Array.from([97, 98, 99, 100]); a = Uint8Array.of(...a, 101); The length of the byte array must be set at creation time. This code will not work anymore: let a = new ByteArray.ByteArray(); a[0] = 255; Instead, use "new Uint8Array(1)" to reserve the correct length. - Closed bugs and merge requests: * Run tests using real software [#178, !192, Claudio André] * Script tests are missing some errors [#179, !192, Claudio André] * Create a '--disable-readline' option and use it [!196, Claudio André] * CI: stop using Fedora for clang builds [!198, Claudio André] * Expose GObject static property symbols. [!197, Evan Welsh] * CI fixes [!200, Claudio André] * Docker images creation [!201, Claudio André] * Get Docker images built and stored in GJS registry [#185, !203, !208, Claudio André, Philip Chimento] * Clear the static analysis image a bit more [!205, Claudio André] * Rename the packaging job to flatpak [!210, Claudio André] * Create SpiderMonkey 60 docker images [!202, Claudio André] * Debugger [#110, !204, Philip Chimento] * Add convenience g_object_set() replacement [!213, Florian Müllner] * Add dependencies of the real tests (examples) [!215, Claudio André] * CWE-126 [#174, !218, Philip Chimento] * gjs no longer builds after recent autoconf-archive updates [#149, !217, Philip Chimento] * gjs-1.52.0 fails to compile against x86_64 musl systems [#132, !214, Philip Chimento] * Run the GTK real tests (recently added) [!212, Claudio André] * Fix thorough tests failures [!220, Philip Chimento] * Port to SpiderMonkey 60 [#161, !199, Philip Chimento] * Replace ByteArray with native ES6 TypedArray [#5, !199, Philip Chimento] * Overriding GInterface properties broke [#186, !216, Florian Müllner, Philip Chimento] * Avoid segfault when checking for GByteArray [!221, Florian Müllner] - Various build fixes [Philip Chimento] Version 1.53.4 -------------- - Refactored the way GObject properties are accessed. This should be a bit more efficient, as property info (GParamSpec) is now cached for every object type. There may still be some regressions from this; please be on the lookout so we can fix them in the next release. - The memory usage for each object instance has been reduced, resulting in several dozens of megabytes less memory usage in GNOME Shell. - The CI pipeline was refactored, now runs a lot faster, detects more failure situations, builds on different architectures, uses the GitLab Docker registry, and publishes code coverage statistics to https://gnome.pages.gitlab.gnome.org/gjs/ - For contributors, the C++ style guide has been updated, and there is now a script in the tools/ directory that will install a Git hook to automatically format your code when you commit it. The configuration may not be perfect yet, so bear with us while we get it right. - Closed bugs and merge requests: * Define GObject properties and fields as JS properties [#160, !151, Philip Chimento] * Possible refcounting bug around GtkListbox signal handlers [#24, !154, Philip Chimento] * Fix up GJS_DISABLE_JIT flag now the JIT is enabled by default in SpiderMonkey [!159, Christopher Wheeldon] * Various CI maintenance [!160, !161, !162, !169, !172, !180, !191, !193, Claudio André] * Update GJS wiki URL [!157, Seth Woodworth] * Build failure in GNOME Continuous [#104, !156, Philip Chimento] * Refactor ObjectInstance into C++ class [!158, !164, Philip Chimento] * Use Ubuntu in the coverage job [!163, Claudio André] * Remove unused files [!165, Claudio André] * Add a ARMv8 build test [!166, Claudio André] * Make CI faster [!167, Claudio André] * Add a PPC4LE build test [!168, Claudio André] * gdbus: Fix deprecated API [!170, Philip Chimento] * Change Docker images names pattern [#173, !174, Claudio André] * Assert failure on a GC_ZEAL run [#165, !173, Philip Chimento] * Do not run linters on tagged commits [!181, Claudio André] * Fail on compiler warnings [!182, Claudio André] * Save code statistics in GitLab Pages [!183, Claudio André] * Build static analysis Docker image in GitLab [!184, !185, !187, !189, Claudio André] * GNOME Shell always crashes with SIGBUS [#171, !188, Georges Basile Stavracas Neto] * Coverage badge is no longer able to show its value [#177, !186, Claudio André] * Move the Docker images creation to GitLab [!190, Claudio André] * Cut the Gordian knot of coding style [#172, !171, Philip Chimento] * Some GObect/GInterface properties broke [#182, !195, Philip Chimento] Version 1.53.3 -------------- - This release was made from an earlier state of master, before a bug was introduced, while we sort out how to fix it. As a result, we don't have too many changes this round. - Closed bugs and merge requests: * Adding multiple ESLint rules for spacing [!152, Avi Zajac] * Various maintenance [!153, Philip Chimento] Version 1.53.2 -------------- - The `Template` parameter passed to `GObject.registerClass()` now accepts file:/// URIs as well as resource:/// URIs and byte arrays. - New API: `gjs_get_js_version()` returns a string identifying the version of the underlying SpiderMonkey JS engine. The interpreter executable has also gained a `--jsversion` argument which will print this string. - Several fixes for memory efficiency and performance. - Once again we welcomed contributions from a number of first-time contributors! - Closed bugs and merge requests: * Add support for file:/// uri to glade template [#108, !41, Jesus Bermudez, Philip Chimento] * Reduce memory overhead of g_object_weak_ref() [#144, !122, Carlos Garnacho, Philip Chimento] * gjs: JS_GetContextPrivate(): gjs-console killed by SIGSEGV [#148, !121, Philip Chimento] * Use compacting GC on RSS size growth [#151, !133, Carlos Garnacho] * Segfault on enumeration of GjSFileImporter properties when a searchpath entry contains a symlink [#154, !144, Ole Jørgen Brønner] * Compare linter jobs to correct base [#156, !140, Claudio André] * Various maintenance [!141, Philip Chimento] * Support interface signal handlers [!142, Tomasz Miąsko] * Remove unnecessary inline [!145, Emmanuele Bassi] * Add badges to the readme [!146, !147, Claudio André] * Fix debug logging [!148, Philip Chimento] * CI: add a GC zeal test [!149, Claudio André] Version 1.53.1 -------------- - Improvements to garbage collection performance. Read for more information: https://feaneron.com/2018/04/20/the-infamous-gnome-shell-memory-leak/ - Now, when building a class from a UI template file (using the `Template` parameter passed to `GObject.registerClass()`, for example) signals defined in the UI template file will be automatically connected. - As an experimental feature, we now offer a flatpak built with each GJS commit, including branches. You can use this to test your apps with a particular GJS branch before it is merged. Look for it in the "Artifacts" section of the CI pipeline. - Closed bugs and merge requests: * Tweener: Add min/max properties [!67, Jason Hicks] * `ARGV` encoding issues [#22, !108, Evan Welsh] * Make GC much more aggressive [#62, !50, Giovanni Campagna, Georges Basile Stavracas Neto, Philip Chimento] * Queue GC when a GObject reference is toggled down [#140, !114, !127, Georges Basile Stavracas Neto] * overrides: support Gtk template callbacks [!124, Andy Holmes] * Ensure not to miss the force_gc flag [#150, !132, Carlos Garnacho] * Create a flatpak on CI [#153, !135, Claudio André] * Readme update [!138, Claudio André] Version 1.52.3 -------------- - Closed bugs and merge requests: * Include calc.js example from Seed [!130, William Barath, Philip Chimento] * CI: Un-pin the Fedora Docker image [#141, !131, Claudio André] * Reduce overhead of wrapped objects [#142, !121, Carlos Garnacho, Philip Chimento] * Various CI changes [!134, !136, Claudio André] Version 1.52.2 -------------- - This is an unscheuled release in order to revert a commit that causes a crash on exit, with some Cairo versions. - Closed bugs and merge requests: * CI: pinned Fedora to old tag [!119, Claudio André] * heapgraph.py: adjust terminal output style [!120, Andy Holmes] * CI: small tweaks [!123, Claudio André] * Warn about compilation warnings [!125, Claudio André] * Miscellaneous commits [Philip Chimento, Jason Hicks] Version 1.52.1 -------------- - This version has more changes than would normally be expected from a stable version. The intention of 1.52.1 is to deliver a version that runs cleaner under performance tools, in time for the upcoming GNOME Shell performance hackfest. We also wanted to deliver a stable CI pipeline before branching GNOME 3.28 off of master. - Claudio André's work on the CI pipeline deserves a spotlight. We now have test jobs that run linters, sanitizers, Valgrind, and more; the tests are run on up-to-date Docker images; and the reliability errors that were plaguing the test runs are solved. - In addition to System.dumpHeap(), you can now dump a heap from a running Javascript program by starting it with the environment variable GJS_DEBUG_HEAP_OUTPUT=some_name, and sending it SIGUSR1. - heapgraph.py is a tool in the repository (not installed in distributions) for analyzing and graphing heap dumps, to aid with tracking down memory leaks. - The linter CI jobs will compare your branch against GNOME/gjs@master, and fail if your branch added any new linter errors. There may be false positives, and the rules configuration is not perfect. If that's the case on your merge request, you can skip the appropriate linter job by adding the text "[skip (linter)]" in your commit message: e.g., "[skip cpplint]". - We welcomed first merge requests from several new contributors for this release. - Closed bugs and merge requests: * Crash when resolving promises if exception is pending [#18, !95, Philip Chimento] * gjs_byte_array_get_proto(JSContext*): assertion failed: (((void) "gjs_" "byte_array" "_define_proto() must be called before " "gjs_" "byte_array" "_get_proto()", !v_proto.isUndefined())) [#39, !92, Philip Chimento] * Tools for examining heap graph [#116, !61, !118, Andy Holmes, Tommi Komulainen, Philip Chimento] * Run analysis tools to prepare for release [#120, !88, Philip Chimento] * Add support for passing flags to Gio.DBusProxy in makeProxyWrapper [#122, !81, Florian Müllner] * Cannot instantiate Cairo.Context [#126, !91, Philip Chimento] * GISCAN GjsPrivate-1.0.gir fails [#128, !90, Philip Chimento] * Invalid read of g_object_finalized flag [#129, !117, Philip Chimento] * Fix race condition in coverage file test [#130, !99, Philip Chimento] * Linter jobs should only fail if new lint errors were added [#133, !94, Philip Chimento] * Disable all tests that depends on X if there is no XServer [#135, !109, Claudio André] * Pick a different C++ linter [#137, !102, Philip Chimento] * Create a CI test that builds using autotools only [!74, Claudio André] * CI: enable ASAN [!89, Claudio André] * CI: disable static analysis jobs using the commit message [!93, Claudio André] * profiler: Don't assume layout of struct sigaction [!96, James Cowgill] * Valgrind [!98, Claudio André] * Robustness of CI [!103, Claudio André] * CI: make a separate job for installed tests [!106, Claudio André] * Corrected Markdown format and added links to JHBuild in setup guide for GJS [!111, Avi Zajac] * Update tweener.js -- 48 eslint errors fixed [!112, Karen Medina] * Various maintenance [!100, !104, !105, !107, !110, !113, !116, Claudio André, Philip Chimento] Version 1.52.0 -------------- - No changes from 1.51.92 except for the continuous integration configuration. - Closed bugs and merge requests: * Various CI improvements [!84, !85, !86, !87, Claudio André] Version 1.51.92 --------------- - Closed bugs and merge requests: * abort if we are called back in a non-main thread [#75, !72, Philip Chimento] * 3.27.91 build failure on debian/Ubuntu [#122, !73, Tim Lunn] * Analyze project code quality with Code Climate inside CI [#10, !77, Claudio André] * Various CI improvements [!75, !76, !79, !80, !82, !83, Claudio André] Version 1.51.91 --------------- - Promises now resolve with a higher priority, so asynchronous code should be faster. [Jason Hicks] - Closed bugs and merge requests: * New build 'warnings' [#117, !62, !63, Claudio André, Philip Chimento] * Various CI maintenance [!64, !65, !66, Claudio André, Philip Chimento] * profiler: Don't include alloca.h when disabled [!69, Ting-Wei Lan] * GNOME crash with fatal error "Finalizing proxy for an object that's scheduled to be unrooted: Gio.Subprocess" in gjs [#26, !70, Philip Chimento] Version 1.51.90 --------------- - Note that all the old Bugzilla bug reports have been migrated over to GitLab. - GJS now, once again, includes a profiler, which outputs files that can be read with sysprof. To use it, simply run your program with the environment variable GJS_ENABLE_PROFILER=1 set. If your program is a JS script that is executed with the interpreter, you can also pass --profile to the interpreter. See "gjs --help" for more info. - New API: For programs that want more control over when to start and stop profiling, there is new API for GjsContext. When you create your GjsContext there are two construct-only properties available, "profiler-enabled" and "profiler-sigusr2". If you set profiler-sigusr2 to TRUE, then the profiler can be started and stopped while the program is running by sending SIGUSR2 to the process. You can also use gjs_context_get_profiler(), gjs_profiler_set_filename(), gjs_profiler_start(), and gjs_profiler_stop() for more explicit control. - New API: GObject.signal_connect(), GObject.signal_disconnect(), and GObject.signal_emit_by_name() are now available in case a GObject-derived class has conflicting connect(), disconnect() or emit() methods. - Closed bugs and merge requests: * Handle 0-valued GType gracefully [#11, !10, Philip Chimento] * Profiler [#31, !37, Christian Hergert, Philip Chimento] * Various maintenance [!40, !59, Philip Chimento, Giovanni Campagna] * Rename GObject.Object.connect/disconnect? [#65, !47, Giovanni Campagna] * Better debugging output for uncatchable exceptions [!39, Simon McVittie] * Update Docker images and various CI maintenance [!54, !56, !57, !58, Claudio André] * Install GJS suppression file for Valgrind [#2, !55, Philip Chimento] Version 1.50.4 -------------- - Closed bugs and merge requests: * Gnome Shell crash with places-status extension when you plug an USB device [#33, !38, Philip Chimento] Version 1.50.3 -------------- - GJS will now log a warning when a GObject is accessed in Javascript code after the underlying object has been freed in C. (This used to work most of the time, but crash unpredictably.) We now prevent this situation which, is usually caused by a memory management bug in the underlying C library. - Closed bugs and merge requests: * Add checks for GObjects that have been finalized [#21, #23, !25, !28, !33, Marco Trevisan] * Test "Cairo context has methods when created from a C function" fails [#27, !35, Valentín Barros] * Various fixes from the master branch for rare crashes [Philip Chimento] Version 1.51.4 -------------- - We welcomed code and documentation from several new contributors in this release! - GJS will now log a warning when a GObject is accessed in Javascript code after the underlying object has been freed in C. (This used to work most of the time, but crash unpredictably.) We now prevent this situation which, is usually caused by a memory management bug in the underlying C library. - APIs exposed through GObject Introspection that use the GdkAtom type are now usable from Javascript. Previously these did not work. On the Javascript side, a GdkAtom translates to a string, so there is no Gdk.Atom type that you can access. The special atom GDK_NONE translates to null in Javascript, and there is also no Gdk.NONE constant. - The GitLab CI tasks have continued to gradually become more and more sophisticated. - Closed bugs and merge requests: * Add checks for GObjects that have been finalized [#21, #23, !22, !27, Marco Trevisan] * Fail static analyzer if new warnings are found [!24, Claudio André] * Run code coverage on GitLab [!20, Claudio André] * Amend gtk.js and add gtk-application.js with suggestion [!32, Andy Holmes] * Improve GdkAtom support that is blocking clipboard APIs [#14, !29, makepost] * Test "Cairo context has methods when created from a C function" fails [#27, !35, Valentín Barros] * Various CI improvements [#6, !26, !34, Claudio André] * Various maintenance [!23, !36, Philip Chimento] Version 1.51.3 -------------- - This release was made from an earlier state of master, before a breaking change was merged, while we decide whether to revert that change or not. - Closed bugs and merge requests: * CI improvements on GitLab [!14, !15, !19, Claudio André] * Fix CI build on Ubuntu [#16, !18, !21, Claudio André, Philip Chimento] Version 1.51.2 -------------- - Version 1.51.1 was skipped. - The home of GJS is now at GNOME's GitLab instance: https://gitlab.gnome.org/GNOME/gjs From now on we'll be taking GitLab merge requests instead of Bugzilla patches. If you want to report a bug, please report it at GitLab. - Closed bugs and merge requests: * Allow throwing GErrors from JS virtual functions [#682701, Giovanni Campagna] * [RFC] bootstrap system [#777724, Jasper St. Pierre, Philip Chimento] * Fix code coverage (and refactor it to take advantage of mozjs52 features) [#788166, !1, !3, Philip Chimento] * Various maintenance [!2, Philip Chimento] * Get GitLab CI working and various improvements [#6, !7, !9, !11, !13, Claudio André] * Add build status badge to README [!8, Claudio André] * Use Docker images for CI [!12, Claudio André] - Some changes in progress to improve garbage collection when signals are disconnected. See bug #679688 for more information [Giovanni Campagna] Version 1.50.2 -------------- - Closed bugs and merge requests: * tweener: Fix a couple of warnings [!5, Florian Müllner] * legacy: Allow ES6 classes to inherit from abstract Lang.Class class [!6, Florian Müllner] - Minor bugfixes [Philip Chimento] Version 1.50.1 -------------- - As a debugging aid, gjs_dumpstack() now works even during garbage collection. - Code coverage tools did not work so well in the last few 1.49 releases. The worst problems are now fixed, although even more improvements will be released in the next unstable version. Fixes include: * Specifing prefixes for code coverage files now works again * Code coverage now works on lines inside ES6 class definitions * The detection of which lines are executable has been improved a bit Version 1.50.0 -------------- - Closed bugs: * Relicense coverage.cpp and coverage.h to the same license as the rest of GJS [#787263, Philip Chimento; thanks to Dominique Leuenberger for pointing out the mistake] Version 1.49.92 --------------- - It's now possible to build GJS with sanitizers (ASan and UBSan) enabled; add "--enable-asan" and "--enable-ubsan" to your configure flags. This has already caught some memory leaks. - There's also a "make check-valgrind" target which will run GJS's test suite under Valgrind to catch memory leaks and threading races. - Many of the crashes in GNOME 3.24 were caused by GJS's closure invalidation code which had to change from the known-working state in 1.46 because of changes to SpiderMonkey's garbage collector. This code has been refactored to be less complicated, which will hopefully improve stability and debuggability. - Closed bugs: * Clean up the idle closure invalidation mess [#786668, Philip Chimento] * Add ASan and UBSan to GJS [#783220, Claudio André] * Run analysis tools on GJS to prepare for release [#786995, Philip Chimento] * Fix testLegacyGObject importing the GTK overrides [#787113, Philip Chimento] - Docs tweak [Philip Chimento] 1.49.91 ------- - Deprecation: The private "__name__" property on Lang.Class instances is now discouraged. Code should not have been using this anyway, but if it did then it should use the "name" property on the class (this.__name__ should become this.constructor.name), which is compatible with ES6 classes. - Closed bugs: * Use ES6 classes [#785652, Philip Chimento] * A few fixes for stack traces and error reporting [#786183, Philip Chimento] * /proc/self/stat is read for every frame if GC was not needed [#786017, Benjamin Berg] - Build fix [Philip Chimento] Version 1.49.90 --------------- - New API: GObject.registerClass(), intended for use with ES6 classes. When defining a GObject class using ES6 syntax, you must call GObject.registerClass() on the class object, with an optional metadata object as the first argument. (The metadata object works exactly like the meta properties from Lang.Class, except that Name and Extends are not present.) Old: var MyClass = new Lang.Class({ Name: 'MyClass', Extends: GObject.Object, Signals: { 'event': {} }, _init(props={}) { this._private = []; this.parent(props); }, }); New: var MyClass = GObject.registerClass({ Signals: { 'event': {} }, }, class MyClass extends GObject.Object { _init(props={}) { this._private = []; super._init(props); } }); It is forward compatible with the following syntax requiring decorators and class fields, which are not in the JS standard yet: @GObject.registerClass class MyClass extends GObject.Object { static [GObject.signals] = { 'event': {} } _init(props={}) { this._private = []; super._init(props); } } One limitation is that GObject ES6 classes can't have constructor() methods, they must do any setup in an _init() method. This may be able to be fixed in the future. - Closed bugs: * Misc 1.49 and mozjs52 enhancements [#785040, Philip Chimento] * Switch to native promises [#784713, Philip Chimento] * Can't call exports using top-level variable toString [#781623, Philip Chimento] * Properties no longer recognized when shadowed by a method [#785091, Philip Chimento, Rico Tzschichholz] * Patch: backport of changes required for use with mozjs-55 [#785424, Luke Jones] Version 1.48.6 -------------- - Closed bugs: * GJS crash in needsPostBarrier, possible access from wrong thread [#783935, Philip Chimento] (again) Version 1.49.4 -------------- - New JavaScript features! This version of GJS is based on SpiderMonkey 52, an upgrade from the previous ESR (Extended Support Release) of SpiderMonkey 38. GJS now uses the latest ESR in its engine and the plan is to upgrade again when SpiderMonkey 59 is released in March 2018, pending maintainer availability. Here are the highlights of the new JavaScript features. For more information, look them up on MDN or devdocs.io. * New language features + ES6 classes + Async functions and await operator + Reflect - built-in object with methods for interceptable operations * New syntax + Exponentiation operator: `**` + Variable-length Unicode code point escapes: `"\u{1f369}"` + Destructured default arguments: `function f([x, y]=[1, 2], {z: z}={z: 3})` + Destructured rest parameters: `function f(...[a, b, c])` + `new.target` allows a constructor access to the original constructor that was invoked + Unicode (u) flag for regular expressions, and corresponding RegExp.unicode property + Trailing comma in function parameter lists now allowed * New APIs + New Array, String, and TypedArray method: includes() + TypedArray sort(), toLocaleString(), and toString() methods, to correspond with regular arrays + New Object.getOwnPropertyDescriptors() and Object.values() methods + New Proxy traps: getPrototypeOf() and setPrototypeOf() + [Symbol.toPrimitive] property specifying how to convert an object to a primitive value + [Symbol.species] property allowing to override the default constructor for objects + [Symbol.match], [Symbol.replace], [Symbol.search], and [Symbol.split] properties allowing to customize matching behaviour in RegExp subclasses + [Symbol.hasInstance] property allowing to customize the behaviour of the instanceof operator for objects + [Symbol.toStringTag] property allowing to customize the message printed by Object.toString() without overriding it + [Symbol.isConcatSpreadable] property allowing to control the behaviour of an array subclass in an argument list to Array.concat() + [Symbol.unscopables] property allowing to control which object properties are lifted into the scope of a with statement + New Intl.getCanonicalLocales() method + Date.toString() and RegExp.toString() generic methods + Typed arrays can now be constructed from any iterable object + Array.toLocaleString() gained optional locales and options arguments, to correspond with other toLocaleString() methods * New behaviour + The "arguments" object is now iterable + Date.prototype, WeakMap.prototype, and WeakSet.prototype are now ordinary objects, not instances + Full ES6-compliant implementation of let keyword + RegExp.sticky ('y' flag) behaviour is ES6 standard, it used to be subject to a long-standing bug in Firefox + RegExp constructor with RegExp first argument and flags no longer throws an exception (`new RegExp(/ab+c/, 'i')` works now) + Generators are no longer constructible, as per ES6 (`function* f {}` followed by `new f` will not work) + It is now required to construct ArrayBuffer, TypedArray, Map, Set, and WeakMap with the new operator + Block-level functions (e.g. `{ function foo() {} }`) are now allowed in strict mode; they are scoped to their block + The options.timeZone argument to Date.toLocaleDateString(), Date.toLocaleString(), Date.toLocaleTimeString(), and the constructor of Intl.DateTimeFormat now understands IANA time zone names (such as "America/Vancouver") * Backwards-incompatible changes + Non-standard "let expressions" and "let blocks" (e.g., `let (x = 5) { use(x) }`) are not supported any longer + Non-standard flags argument to String.match(), String.replace(), and String.search() (e.g. `str.replace('foo', 'bar', 'g')`) is now ignored + Non-standard WeakSet.clear() method has been removed + Variables declared with let and const are now 'global lexical bindings', as per the ES6 standard, meaning that they will not be exported in modules. We are maintaining the old behaviour for the time being as a compatibility workaround, but please change "let" or "const" to "var" inside your module file. A warning will remind you of this. For more information, read: https://blog.mozilla.org/addons/2015/10/14/breaking-changes-let-const-firefox-nightly-44/ * Experimental features (may change in future versions) + String.padEnd(), String.padStart() methods (proposed in ES2017) + Intl.DateTimeFormat.formatToParts() method (proposed in ES2017) + Object.entries() method (proposed in ES2017) + Atomics, SharedArrayBuffer, and WebAssembly are disabled by default, but can be enabled if you compile mozjs yourself - Closed bugs: * Prepare for SpiderMonkey 45 and 52 [#781429, Philip Chimento] * Add a static analysis tool as a make target [#783214, Claudio André] * Fix the build with debug logs enabled [#784469, Tomas Popela] * Switch to SpiderMonkey 52 [#784196, Philip Chimento, Chun-wei Fan] * Test suite fails when run with JIT enabled [#616193, Philip Chimento] Version 1.48.5 -------------- - Closed bugs: * GJS crash in needsPostBarrier, possible access from wrong thread [#783935, Philip Chimento] - Fix format string, caught by static analysis [Claudio André] - Fixes for regression in 1.48.4 [Philip Chimento] Version 1.49.3 -------------- - This will be the last release using SpiderMonkey 38. - Fixes in preparation for SpiderMonkey 52 [Philip Chimento] - Use the Centricular fork of libffi to build on Windows [Chun-wei Fan] - Closed bugs: * [RFC] Use a C++ auto pointer instead of g_autofree [#777597, Chun-wei Fan, Daniel Boles, Philip Chimento] * Build failure in GNOME Continuous [#783031, Chun-wei Fan] Version 1.48.4 -------------- - Closed bugs: * gnome-shell 3.24.1 crash on wayland [#781799, Philip Chimento]; thanks to everyone who contributed clues Version 1.49.2 -------------- - New feature: When building an app with the Package module, using the Meson build system, you can now run the app with "ninja run" and all the paths will be set up correctly. - New feature: Gio.ListStore is now iterable. - New API: Package.requireSymbol(), a companion for the already existing Package.require(), that not only checks for a GIR library but also for a symbol defined in that library. - New API: Package.checkSymbol(), similar to Package.requireSymbol() but does not exit if the symbol was not found. Use this to support older versions of a GIR library with fallback functionality. - New API: System.dumpHeap(), for debugging only. Prints the state of the JS engine's heap to standard output. Takes an optional filename parameter which will dump to a file instead if given. - Closed bugs: * Make gjs build on Windows/Visual Studio [#775868, Chun-wei Fan] * Bring back fancy error reporter in gjs-console [#781882, Philip Chimento] * Add Meson running from source support to package.js [#781882, Patrick Griffis] * package: Fix initSubmodule() when running from source in Meson [#782065, Patrick Griffis] * package: Set GSETTINGS_SCHEMA_DIR when ran from source [#782069, Patrick Griffis] * Add imports.gi.has() to check for symbol availability [#779593, Florian Müllner] * overrides: Implement Gio.ListStore[Symbol.iterator] [#782310, Patrick Griffis] * tweener: Explicitly check for undefined properties [#781219, Debarshi Ray, Philip Chimento] * Add a way to dump the heap [#780106, Juan Pablo Ugarte] - Fixes in preparation for SpiderMonkey 52 [Philip Chimento] - Misc fixes [Philip Chimento] Version 1.48.3 -------------- - Closed bugs: * arg: don't crash when asked to convert a null strv to an array [#775679, Cosimo Cecchi, Sam Spilsbury] * gjs 1.48.0: does not compile on macOS with clang [#780350, Tom Schoonjans, Philip Chimento] * Modernize shell scripts [#781806, Claudio André] Version 1.49.1 -------------- - Closed bugs: * test GObject Class failure [#693676, Stef Walter] * Enable incremental GCs [#724797, Giovanni Campagna] * Don't silently accept extra arguments to C functions [#680215, Jasper St. Pierre, Philip Chimento] * Special case GValues in signals and properties [#688128, Giovanni Campagna, Philip Chimento] * [cairo] Instantiate wrappers properly [#614413, Philip Chimento, Johan Dahlin] * Warn if we're importing an unversioned namespace [#689654, Colin Walters, Philip Chimento] - Fixes in preparation for SpiderMonkey 45 [Philip Chimento] - Misc fixes [Philip Chimento, Chun-wei Fan, Dan Winship] Version 1.48.2 -------------- - Closed bugs: * Intermittent crash in gnome-shell, probably in weak pointer updating code [#781194, Georges Basile Stavracas Neto] * Add contributor's guide [#781297, Philip Chimento] - Misc fixes [Debarshi Ray, Philip Chimento] Version 1.48.1 -------------- - Closed bugs: * gjs crashed with SIGSEGV in gjs_object_from_g_object [#779918, Philip Chimento] - Misc bug fixes [Florian Müllner, Philip Chimento, Emmanuele Bassi] Version 1.48.0 -------------- - Closed bugs: * Memory leak in object_instance_resolve() [#780171, Philip Chimento]; thanks to Luke Jones and Hussam Al-Tayeb Version 1.47.92 --------------- - Closed bugs: * gjs 1.47.91 configure fails with Fedora's mozjs38 [#779412, Philip Chimento] * tests: Don't fail when Gtk+-4.0 is available [#779594, Florian Müllner] * gjs 1.47.91 test failures on non-amd64 [#779399, Philip Chimento] * gjs_eval_thread should always be set [#779693, Philip Chimento] * System.exit() should exit even across main loop iterations [#779692, Philip Chimento] * Fix a typo in testCommandLine.sh [#779772, Claudio André] * arg: Fix accidental fallthrough [#779838, Florian Müllner] * jsUnit: Explicitly check if tempTop.parent is defined [#779871, Iain Lane] - Misc bug fixes [Philip Chimento] Version 1.47.91 --------------- - Closed bugs: * overrides/Gio: Provide an empty array on error, rather than null [#677513, Jasper St. Pierre, Philip Chimento] * WithSignals parameter for Lang.Class [#664897, Philip Chimento] * add API to better support asynchronous code [#608450, Philip Chimento] * 1.47.90 tests are failing [#778780, Philip Chimento] * boxed: Plug a memory leak [#779036, Florian Müllner] * Don't crash when marshalling an unsafe integer from introspection [#778705, Philip Chimento] * Lang.Class should include symbol properties [#778718, Philip Chimento] * Console output of arrays should be UTF-8 aware [#778729, Philip Chimento] * Various fixes for 1.47.91 [#779293, Philip Chimento] - Progress towards a Visual Studio build of GJS on Windows [Chun-wei Fan] - Misc bug fixes [Chun-wei Fan, Philip Chimento] Version 1.47.90 --------------- - New JavaScript features! This version of GJS is based on SpiderMonkey 38, an upgrade from the previous ESR (Extended Support Release) of SpiderMonkey 31. Our plans are to continue upgrading to subsequent ESRs as maintainer availability allows. Here are the highlights of the new JavaScript features. For more information, look them up on MDN or devdocs.io. * New syntax + Shorthand syntax for method definitions: { foo() { return 5; } } + Shorthand syntax for object literals: let b = 42; let o = {b}; o.b === 42 + Computed property names for the above, as well as in getter and setter expressions and destructuring assignment: { ['b' + 'ar']() { return 6; } } + Spread operator in destructuring assignment: let [a, ...b] = [1, 2, 3]; + Template literals: `Hello, ${name}` with optional tags: tag`string` * New APIs + Symbol, a new fundamental type + WeakSet, a Set which does not prevent its members from being garbage-collected + [Symbol.iterator] properties for Array, Map, Set, String, TypedArray, and the arguments object + New Array and TypedArray functionality: Array.copyWithin(), Array.from() + New return() method for generators + New Number.isSafeInteger() method + New Object.assign() method which can replace Lang.copyProperties() in many cases + New Object.getOwnPropertySymbols() method + New RegExp flags, global, ignoreCase, multiline, sticky properties that give access to the flags that the regular expression was created with + String.raw, a tag for template strings similar to Python's r"" + New TypedArray methods for correspondence with Array: entries(), every(), fill(), filter(), find(), findIndex(), forEach(), indexOf(), join(), keys(), lastIndexOf(), map(), of(), reduce(), reduceRight(), reverse(), slice(), some(), values() * New behaviour + Temporal dead zone: print(x); let x = 5; no longer allowed + Full ES6-compliant implementation of const keyword + The Set, Map, and WeakMap constructors can now take null as their argument + The WeakMap constructor can now take an iterable as its argument + The Function.name and Function.length properties are configurable + When subclassing Map, WeakMap, and Set or using the constructors on generic objects, they will look for custom set() and add() methods. + RegExp.source and RegExp.toString() now deal with empty regexes, and escape their output. + Non-object arguments to Object.preventExtensions() now do not throw an exception, simply return the original object * Backwards-incompatible changes + It is now a syntax error to declare the same variable twice with "let" or "const" in the same scope. Existing code may need to be fixed, but the fix is trivial. + SpiderMonkey is now extra vocal about warning when you access an undefined property, and this causes some false positives. You can turn this warning off by setting GJS_DISABLE_EXTRA_WARNINGS=1. If it is overly annoying, let me know and I will consider making it disabled by default in a future release. + When enumerating the importer object (i.e., "for (let i in imports) {...}") you will now get the names of any built-in modules that have previously been imported. (But don't do this, anyway.) - Closed bugs: * SpiderMonkey 38 prep [#776966, Philip Chimento] * Misc fixes [#777205, Philip Chimento] * missing class name in error message [#642506, Philip Chimento] * Add continuous integration to GJS [#776549, Claudio André] * Switch to SpiderMonkey 38 [#777962, Philip Chimento] - Progress towards a build of GJS on Windows [Chun-wei Fan] - Progress towards increasing test coverage [Claudio André] - Misc bug fixes [Philip Chimento] Version 1.47.4 -------------- - New JavaScript feature: ES6 Promises. This release includes Lie [1], a small, self-contained Promise implementation, which is imported automatically to form the Promise global object [2]. In the future, Promises will be built into the SpiderMonkey engine and Lie will be removed, but any code using Promises will continue to work as before. [1] https://github.com/calvinmetcalf/lie [2] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise - News for GJS embedders such as gnome-shell: * New API: The GjsCoverage type and its methods are now exposed. Use this if you are embedding GJS and need to output code coverage statistics. - Closed bugs: * Add GjsCoverage to gjs-1.0 public API [#775776, Philip Chimento] * Should use uint32_t instead of u_int32_t in coverage.cpp [#776193, Shawn Walker, Alan Coopersmith] * Port tests to use an embedded copy of Jasmine [#775444, Philip Chimento] * support fields in GObject [#563391, Havoc Pennington, Philip Chimento] * Javascript errors in property getters and setter not always reported [#730101, Matt Watson, Philip Chimento] * Exception swallowed while importing Gom [#737607, Philip Chimento] * log a warning if addSignalMethods() replaces existing methods [#619710, Joe Shaw, Philip Chimento] * Provide a useful toString for importer and module objects [#636283, Jasper St. Pierre, Philip Chimento] * Fails to marshal out arrays [#697020, Paolo Borelli] * coverage: Don't warn about executing odd lines by default anymore [#751146, Sam Spilsbury, Philip Chimento] * coverage: Crash in EnterBaseline on SpiderMonkey when Ion is enabled during coverage mode. [#742852, Sam Spilsbury, Philip Chimento] * installed tests cannot load libregress.so [#776938, Philip Chimento] * Crash with subclassed fundamental with no introspection [#760057, Lionel Landwerlin] - Misc bug fixes [Philip Chimento, Claudio André] Version 1.47.3 -------------- - New JavaScript features! This version of GJS is based on SpiderMonkey 31, an upgrade from the previous ESR (Extended Support Release) of SpiderMonkey 24. Our plans are to continue upgrading to subsequent ESRs as maintainer availability allows. Here are the highlights of the new JavaScript features. For more information, look them up on MDN or devdocs.io. * New syntax + Spread operator in function calls: someFunction(arg1, arg2, ...iterableObj) + Generator functions: yield, function*, yield* + Binary and octal numeric literals: 0b10011100, 0o377 + Function arguments without defaults can now come after those with defaults: function f(x=1, y) {} * New standard library module + Intl - Locale-sensitive formatting and string comparison * New APIs + Iterator protocol - any object implementing certain methods is an "iterator" + New Array functionality: fill(), find(), findIndex(), of() + New String functionality for working with Unicode: codePointAt(), fromCodePoint(), normalize() + New Array methods for correspondence with Object: entries(), keys() + ES6 Generator methods to replace the old Firefox-specific generator API: next(), throw() + forEach() methods for Map and Set, for correspondence with Array + A bunch of new Math functions: acosh(), asinh(), atanh(), cbrt(), clz32(), cosh(), expm1(), fround(), hypot(), log10(), log1p(), log2(), sign(), sinh(), tanh(), trunc() + Some constants to tell information about float support on the platform: Number.EPSILON, Number.MAX_SAFE_INTEGER, Number.MIN_SAFE_INTEGER + New Number.parseInt() and Number.parseFloat() which are now preferred over those in the global namespace + New Object.setPrototypeOf() which now is preferred over setting obj.prototype.__proto__ + New locales and options extra arguments to all toLocaleString() and related methods + Misc new functionality: ArrayBuffer.isView(), Proxy.handler.isExtensible, Proxy.revocable() * New behaviour + -0 and +0 are now considered equal as Map keys and Set values + On typed arrays, numerical indexed properties ignore the prototype object: Int8Array.prototype[20] = 'foo'; (new Int8Array(32))[20] == 0 * New non-standard Mozilla extensions + Array comprehensions + Generator comprehensions; both were originally proposed for ES6 but removed - Backwards-incompatible change: we have changed the way certain JavaScript values are marshalled into GObject introspection 32 or 64-bit signed integer values, to match the ECMA standard. Here is the relevant section of the standard: http://www.ecma-international.org/ecma-262/7.0/index.html#sec-toint32 Notable differences between the old and new behaviour are: * Floating-point numbers ending in 0.5 are rounded differently when marshalled into 32 or 64-bit signed integers. Previously they were rounded to floor(x + 0.5), now they are rounded to signum(x) * floor(abs(x)) as per the ECMA standard: http://www.ecma-international.org/ecma-262/7.0/index.html#sec-toint32 Note that previously, GJS rounded according to the standard when converting to *unsigned* integers! * Objects whose number value is NaN (e.g, arrays of strings), would previously fail to convert, throwing a TypeError. Now they convert to 0 when marshalled into 32 or 64-bit signed integers. Note that the new behaviour is the behaviour you got all along when using pure JavaScript, without any GObject introspection: gjs> let a = new Int32Array(2); gjs> a[0] = 10.5; 10.5 gjs> a[1] = ['this', 'is', 'fine']; this,is,fine gjs> a[0] 10 gjs> a[1] 0 - News for GJS embedders such as gnome-shell: * New API: gjs_error_quark() is now exposed, and the error domain GJS_ERROR and codes GJS_ERROR_FAILED and GJS_ERROR_SYSTEM_EXIT. * Backwards-incompatible change: Calling System.exit() from JS code will now not abort the program immediately, but instead will return immediately from gjs_context_eval() so that you can unref your GjsContext before internal resources are released on exit. If gjs_context_eval() or gjs_context_eval_file() returns an error with code GJS_ERROR_SYSTEM_EXIT, it means that the JS code called System.exit(). The exit code will be found in the 'exit_status_p' out parameter to gjs_context_eval() or gjs_context_eval_file(). If you receive this error, you should do any cleanup needed and exit your program with the given exit code. - Closed bugs: * spidermonkey 31 prep [Philip Chimento, Tim Lunn, #742249] * Please use dbus-run-session to run dbus related tests [Philip Chimento, #771745] * don't abort gdb'd tests [Philip Chimento, Havoc Pennington, #605972] * Use Automake test suite runner [Philip Chimento, #775205] * please add doc/ directory to make dist tar file [Philip Chimento, #595439] * support new mozjs 31.5 [Philip Chimento, #751252] * SEGFAULT in js::NewObjectWithClassProtoCommon when instantiating a dynamic type defined in JS [Philip Chimento, Juan Pablo Ugarte, #770244] - Misc bug fixes [Philip Chimento, Alexander Larsson] Version 1.47.0 -------------- - New API: GLib.log_structured() is a convenience wrapper for the C function g_log_variant(), allowing you to do structured logging without creating GVariants by hand. For example: GLib.log_structured('test', GLib.LogLevelFlags.LEVEL_WARNING, { 'MESSAGE': 'Just a test', 'A_FIELD': 'A value', }); - Backwards-incompatible change: we have changed the way gjs-console interprets command-line arguments. Previously there was a heuristic to try to decide whether "--help" given on the command line was meant for GJS itself or for a script being launched. This did not work in some cases, for example: $ gjs -c 'if (ARGV.indexOf("--help") == -1) throw "ugh"' --help would print the GJS help. Now, all arguments meant for GJS itself must come _before_ the name of the script to execute or any script given with a "-c" argument. Any arguments _after_ the filename or script are passed on to the script. This is the way that Python handles its command line arguments as well. If you previously relied on any -I arguments after your script being added to the search path, then you should either reorder those arguments, use GJS_PATH, or handle -I inside your script, adding paths to imports.searchPath manually. In order to ease the pain of migration, GJS will continue to take those arguments into account for the time being, while still passing them on to the script. A warning will be logged if you are using the deprecated behaviour. - News for GJS embedders such as gnome-shell: * Removed API: The gjs-internals-1.0 API is now discontinued. Since gnome-shell was the only known user of this API, its pkg-config file has been removed completely and gnome-shell has been patched not to use it. - Closed bugs: * make check fails with --enable-debug / -DDEBUG spidermonkey [Philip Chimento, #573335] * Modernize autotools configuration [Philip Chimento, #772027] * overrides/GLib: Add log_structured() - wrapper for g_log_variant() [Jonh Wendell, #771598] * Remove gjs-internals-1.0 API [Philip Chimento, #772386] * Add support for boolean, gunichar, and 64-bit int arrays [Philip Chimento, #772790] * add a --version flag [Philip Chimento, #772033] * Switch to AX_COMPILER_FLAGS and compile warning-free [Philip Chimento, #773297] * Reinstate importer test [Philip Chimento, #773335] Version 1.46.0 -------------- - Be future proof against Format fixes in SpiderMonkey [Giovanni Campagna, #770111] Version 1.45.4 -------------- - Release out args before freeing caller-allocated structs [Florian Müllner, #768413] - Marshal variable array-typed signal arguments [Philip Chimento, Florian Müllner, #761659] - Marshal all structs in out arrays correctly [Philip Chimento, #761658] - Call setlocale() before processing arguments [Ting-Wei Lan, #760424] - Build fixes and improvements [Philip Chimento, #737702, #761072] [Hashem Nasarat, #761366] [Carlos Garnacho, #765905] [Simon McVittie, #767368] [Emmanuele Bassi] [Michael Catanzaro] [Matt Watson] Version 1.45.3 -------------- - Support external construction of gjs-defined GObjects [Florian Müllner, #681254] - Add new format.printf() API [Colin Walters, #689664] - Add new API to get the name of a repository [Jasper St. Pierre, #685413] - Add C to JS support for arrays of flat structures [Giovanni Campagna, #704842] - Add API to specify CSS node name [Florian Müllner, #758349] - Return value of default signal handler for "on_signal_name" methods [Philip Chimento, #729288] - Fix multiple emissions of onOverwrite in Tweener [Tommi Komulainen, #597927] - Misc bug fixes [Philip Chimento, #727370] [Owen Taylor, #623330] [Juan RP, #667908] [Ben Iofel, #757763] Version 1.44.0 -------------- - Add Lang.Interface and GObject.Interface [Philip Chimento, Roberto Goizueta, #751343, #752984] - Support callbacks with (transfer full) return types [Debarshi Ray, #750286] - Add binding for setlocale() [Philip Chimento, #753072] - Improve support to generate code coverage reports [Sam Spilsbury, #743009, #743007, #742362, #742535, #742797, #742466, #751732] - Report errors from JS property getters/setters [Matt Watson, #730101] - Fix crash when garbage collection triggers while inside an init function [Sam Spilsbury, #742517] - Port to CallReceiver/CallArgs [Tim Lunn, #742249] - Misc bug fixes [Ting-Wei Lan, #736979, #753072] [Iain Lane, #750688] [Jasper St. Pierre] [Philip Chimento] [Colin Walters] Version 1.43.3 -------------- - GTypeClass and GTypeInterface methods, such as g_object_class_list_properties(), are now available [#700347] - Added full automatic support for GTK widget templates [#700347, #737661] [Jonas Danielsson, #739739] - Added control of JS Date caches to system module [#739790] - Misc bug fixes and memory leak fixes [Owen Taylor, #738122] [Philip Chimento, #740696, #737701] Version 1.42.0 -------------- - Fix a regression caused by PPC fixes in 1.41.91 Version 1.41.91 --------------- - Added the ability to disable JS language warnings [Lionel Landwerlin, #734569] - Fixed crashes in PPC (and probably other arches) due to invalid callback signatures [Michel Danzer, #729554] - Fixed regressions with dbus 1.8.6 [Tim Lunn, #735358] - Readded file system paths to the default module search, to allow custom GI overrides for third party libraries [Jasper St. Pierre] Version 1.41.4 -------------- - Fixed memory management of GObject methods that unref their instance [#729545] - Added a package module implementing the https://wiki.gnome.org/Projects/Gjs/Package application conventions [#690136] - Misc bug fixes Version 1.41.3 -------------- - Fixed GObject and Gtk overrides [Mattias Bengtsson, #727781] [#727394] - Fixed crashes caused by reentrancy during finalization [#725024] - Added a wrapper type for cairo regions [Jasper St. Pierre, #682303] - Several cleanups to GC [#725024] - Thread-safe structures are now finalized in the background, for greater responsiveness [#725024] [#730030] - A full GC is now scheduled if after executing a piece of JS we see that the RSS has grown by over 150% [Cosimo Cecchi, #725099] [#728048] - ParamSpecs now support methods and static methods implemented by glib and exposed by gobject-introspection, in addition to the manually bound fields [#725282] - Protototypes no longer include static properties or constructors [#725282] - Misc cleanups and bugfixes [Mattias Bengtsson, #727786] [#725282] [Lionel Landwerlin, #728004] [Lionel Landwerlin, #727824] Version 1.40.0 -------------- - No changes Version 1.39.90 --------------- - Implemented fundamental object support [Lionel Landwerlin, #621716, #725061] - Fixed GIRepositoryGType prototype [#724925] - Moved GObject.prototype.disconnect() to a JS implementation [#698283] - Added support for enumeration methods [#725143] - Added pseudo-classes for fundamental types [#722554] - Build fixes [Ting-Wei Lan, #724853] Version 0.3 (03-Jul-2009) ------------------------- Changes: - DBus support At a high level there are three pieces. First, the "gjs-dbus" library is a C support library for DBus. Second, the modules/dbus*.[ch] implement parts of the JavaScript API in C. Third, the modules/dbus.js file fills out the rest of the JavaScript API and implementation. - Support simple fields for boxed types - Support "copy construction" of boxed types - Support simple structures not registered as boxed - Allow access to nested structures - Allow direct assignment to nested structure fields - Allow enum and flag structure fields - Allow creating boxed wrapper without copy - Support for non-default constructor (i.e. constructors like GdkPixbuf.Pixbuf.new_from_file(file)) - Add a Lang.bind function which binds the meaning of 'this' - Add an interactive console gjs-console - Allow code in directory modules (i.e. the code should reside in __init__.js files) - Fix handling of enum/flags return values - Handle non-gobject-registered flags - Add Tweener.registerSpecialProperty to tweener module - Add profiler for javascript code - Add gjs_context_get_all and gjs_dumpstack - useful to invoke from a debugger such as gdb - Support GHashTable - GHashTable return values/out parameters - Support GHashTable 'in' parameters - Convert JSON-style object to a GHashTable - Add support for UNIX shebang (i.e. #!/usr/bin/gjs-console) - Support new introspection short/ushort type tags - Support GI_TYPE_TAG_FILENAME - Improve support for machine-dependent integer types and arrays of integers - Fix several memory leaks Contributors: Colin Walters, C. Scott Ananian, Dan Winship, David Zeuthen, Havoc Pennington, Johan Bilien, Johan Dahlin, Lucas Rocha, Marco Pesenti Gritti, Marina Zhurakhinskaya, Owen Taylor, Tommi Komulainen Bugs fixed: Bug 560506 - linking problem Bug 560670 - Turn on compilation warnings Bug 560808 - Simple field supported for boxed types Bug 561514 - memory leak when skipping deprecated methods Bug 561516 - skipping deprecated methods shouldn't signal error case Bug 561849 - Alternate way of connecting signals. Bug 562892 - valgrind errors on get_obj_key Bug 564424 - Add an interactive console Bug 564664 - Expose JS_SetDebugErrorHook Bug 566185 - Allow code in directory modules Bug 567675 - Handle non-gobject-registered flags Bug 569178 - Add readline support to the console module Bug 570775 - array of parameters leaked on each new GObject Bug 570964 - Race when shutting down a context, r=hp Bug 580948 - Add DBus support Bug 584560 - Add support for UNIX shebang Bug 584850 - Clean up some __BIG definitions. Bug 584858 - Fix errors (and leftover debugging) from dbus merge. Bug 584858 - Move gjsdbus to gjs-dbus to match installed location. Bug 585386 - Add a flush() method to DBus bus object. Bug 585460 - Fix segfault when a non-existing node is introspected. Bug 586665 - Fix seg fault when attempting to free callback type. Bug 586760 - Support converting JavaScript doubles to DBus int64/uint64. Bug 561203 - Fix priv_from_js_with_typecheck() for dynamic types Bug 561573 - Add non-default constructors and upcoming static methods to "class" Bug 561585 - allow using self-built spidermonkey Bug 561664 - Closure support is broken Bug 561686 - Don't free prov->gboxed for "has_parent" Boxed Bug 561812 - Support time_t Bug 562575 - Set correct GC parameters and max memory usage Bug 565029 - Style guide - change recommendation for inheritance Bug 567078 - Don't keep an extra reference to closures Bug 569374 - Logging exceptions from tweener callbacks could be better Bug 572113 - add profiler Bug 572121 - Invalid read of size 1 Bug 572130 - memory leaks Bug 572258 - profiler hooks should be installed only when needed Bug 580865 - Call JS_SetLocaleCallbacks() Bug 580947 - Validate UTF-8 strings in gjs_string_to_utf8() Bug 580957 - Change the way we trigger a warning in testDebugger Bug 581277 - Call JS_SetScriptStackQuota on our contexts Bug 581384 - Propagate exceptions from load context Bug 581385 - Return false when gjs_g_arg_release_internal fails Bug 581389 - Fix arg.c to use 'interface' instead of 'symbol' Bug 582686 - Don't g_error() when failing to release an argument Bug 582704 - Don't depend on hash table order in test cases Bug 582707 - Fix problems with memory management for in parameters Bug 584849 - Kill warnings (uninitialized variables, strict aliasing) Bug 560808 - Structure support Version 0.2 (12-Nov-2008) ------------------------- Changes: - Compatible with gobject-introspection 0.6.0 - New modules: mainloop, signals, tweener - Support passing string arrays to gobject-introspection methods - Added jsUnit based unit testing framework - Improved error handling and error reporting Contributors: Colin Walters, Havoc Pennington, Johan Bilien, Johan Dahlin, Lucas Rocha, Owen Taylor, Tommi Komulainen Bugs fixed: Bug 557398 – Allow requiring namespace version Bug 557448 – Enum and Flags members should be uppercase Bug 557451 – Add search paths from environment variables Bug 557451 – Add search paths from environment variables Bug 557466 – Module name mangling considered harmful Bug 557579 – Remove use of GJS_USE_UNINSTALLED_FILES in favor of GI_TYPELIB_PATH Bug 557772 - gjs_invoke_c_function should work with union and boxed as well Bug 558114 – assertRaises should print return value Bug 558115 – Add test for basic types Bug 558148 – 'const char*' in arguments are leaked Bug 558227 – Memory leak if invoked function returns an error Bug 558741 – Mutual imports cause great confusion Bug 558882 – Bad error if you omit 'new' Bug 559075 – enumerating importer should skip hidden files Bug 559079 – generate code coverage report Bug 559194 - Fix wrong length buffer in get_obj_key() Bug 559612 - Ignore deprecated methods definitions. Bug 560244 - support for strv parameters Version 0.1 ----------- Initial version! Ha! cjs-5.2.0/doc/0000755000175000017500000000000014144444702013204 5ustar jpeisachjpeisachcjs-5.2.0/doc/Understanding-SpiderMonkey-code.md0000644000175000017500000000567114144444702021663 0ustar jpeisachjpeisachBasics ------ - SpiderMonkey is the Javascript engine from Mozilla Firefox. It's also known as "mozjs" in most Linux distributions, and sometimes as "JSAPI" in code. - Like most browsers' JS engines, SpiderMonkey works standalone, which is what allows GJS to work. In Mozilla terminology, this is known as "embedding", and GJS is an "embedder." - Functions that start with `JS_` or `JS::`, or types that start with `JS`, are part of the SpiderMonkey API. - Functions that start with `js_` or `js::` are part of the "JS Friend" API, which is a section of the SpiderMonkey API which is supposedly less stable. (Although SpiderMonkey isn't API stable in the first place.) - We use the SpiderMonkey from the ESR (Extended Support Release) of Firefox. These ESRs are released approximately once a year. - Since ESR 24, the SpiderMonkey team has gotten sloppy about making official releases of standalone SpiderMonkey. (Arguably, that was because nobody, including us, was using them.) We had high hopes for an official release of ESR 52, but there were some problems that couldn't be fixed on the ESR branch. The SpiderMonkey team will likely make an official release of ESR 60, but they may need some reminders when the time comes. - When reading GJS code, to quickly find out what a SpiderMonkey API function does, you can go to https://searchfox.org/ and search for it. This is literally faster than opening `jsapi.h` in your editor, and you can click through to other functions, and find everywhere a function is used. - Don't trust the wiki on MDN as documentation for SpiderMonkey, as it is mostly out of date and can be quite misleading. Coding conventions ------------------ - Most API functions take a `JSContext *` as their first parameter. This context contains the state of the JS engine. - `cx` stands for "context." - Many API functions return a `bool`. As in many other APIs, these should return `true` for success and `false` for failure. - Specific to SpiderMonkey is the convention that if an API function returns `false`, an exception should have been thrown (a JS exception, not a C++ exception, which would terminate the program!) This is also described as "an exception should be _pending_ on `cx`". Likewise, if the function returns `true`, an exception should not be pending. - There are two ways to violate that condition: - Returning `false` with no exception pending. This is interpreted as an "uncatchable" exception, and it's used for out-of-memory and killing scripts within Firefox, for example. In GJS we use it to implement `System.exit()`. - Returning `true` while an exception is pending. This can easily happen by forgetting to check the return value of a SpiderMonkey function, and is a programmer error but not too serious. It will probably cause some warnings. - Likewise if an API function returns a pointer such as `JSObject*` (this is less common), the convention is that it should return `nullptr` on failure, in which case an exception should be pending.cjs-5.2.0/doc/Home.md0000644000175000017500000000636214144444702014425 0ustar jpeisachjpeisach# GJS: Javascript Bindings for GNOME The current stable series is built on Mozilla's SpiderMonkey 78 featuring **ECMAScript 2019** and GObjectIntrospection making most of the **GNOME API library** available. To find out when a language feature was implemented in GJS, review [NEWS][gjs-news] in the GitLab repository. In many cases older versions of GJS can be supported using [polyfills][mdn-polyfills] and [legacy-style GJS classes](Modules.md#lang). GJS includes some built-in modules like Cairo and Gettext, as well as helpers for some core APIs like DBus and GVariants. See the [Modules](Modules.md) page for an overview of the built-in modules and their usage. [gjs-news]: https://gitlab.gnome.org/GNOME/gjs/raw/master/NEWS [mdn-polyfills]: https://developer.mozilla.org/docs/Glossary/Polyfill ## GNOME API Documentation There is now official [GNOME API Documentation][gjs-docs] for GJS, including everything from GLib and Gtk to Soup and WebKit2. The [Mapping](Mapping.md) page has an overview of GNOME API usage in GJS such as subclassing, constants and flags, functions with multiple return values, and more. There are also a growing number of [examples][gjs-examples] and thorough tests of language features in the [test suite][gjs-tests]. [gjs-docs]: https://gjs-docs.gnome.org/ [gjs-examples]: https://gitlab.gnome.org/GNOME/gjs/tree/master/examples [gjs-tests]: https://gitlab.gnome.org/GNOME/gjs/blob/master/installed-tests/js ## Standalone Applications It's possible to write standalone applications with GJS for the GNOME Desktop, and infrastructure for Gettext, GSettings and GResources via the `package` import. There is a package specification, template repository available and plans for an in depth tutorial. * [GJS Package Specification](https://wiki.gnome.org/Projects/Gjs/Package/Specification.md) * [GJS Package Template](https://github.com/gcampax/gtk-js-app) GNOME Applications written in GJS: * [GNOME Characters](https://gitlab.gnome.org/GNOME/gnome-characters) * [GNOME Documents](https://gitlab.gnome.org/GNOME/gnome-documents) * [GNOME Maps](https://gitlab.gnome.org/GNOME/gnome-maps) * [GNOME Sound Recorder](https://gitlab.gnome.org/GNOME/gnome-sound-recorder) * [GNOME Weather](https://gitlab.gnome.org/GNOME/gnome-weather) * [GNOME Books](https://gitlab.gnome.org/GNOME/gnome-books) * [Polari](https://gitlab.gnome.org/GNOME/polari) IRC Client Third party applications written in GJS: * [Tangram](https://github.com/sonnyp/Tangram) * [Quick Lookup](https://github.com/johnfactotum/quick-lookup) * [Foliate](https://github.com/johnfactotum/foliate) * [Marker](https://github.com/fabiocolacio/Marker) * [Gnomit](https://github.com/small-tech/gnomit) ## Getting Help * Mailing List: http://mail.gnome.org/mailman/listinfo/javascript-list * IRC: irc://irc.gnome.org/#javascript * Issue/Bug Tracker: https://gitlab.gnome.org/GNOME/gjs/issues * StackOverflow: https://stackoverflow.com/questions/tagged/gjs ## External Links * [GObjectIntrospection](https://wiki.gnome.org/action/show/Projects/GObjectIntrospection) * [GNOME Developer Platform Demo](https://developer.gnome.org/gnome-devel-demos/stable/js.html) (Some older examples that still might be informative) * [Writing GNOME Shell Extensions](https://wiki.gnome.org/Projects/GNOMEShell/Extensions/Writing)cjs-5.2.0/doc/cairo.md0000644000175000017500000000424614144444702014631 0ustar jpeisachjpeisachThe cairo bindings follows the C API pretty closely. ## Naming ## The module name is called 'cairo' and usually imported into the namespace as 'Cairo'. ```js const Cairo = imports.cairo; ``` Methods are studlyCaps, similar to other JavaScript apis, eg `cairo_move_to` is wrapped to `Cairo.Context.moveTo()` `cairo_surface_write_to_png` to `Cairo.Context.writeToPNG()`. Abbrevations such as RGB, RGBA, PNG, PDF, SVG are always upper-case. Enums are set in the cairo namespace, the enum names are capitalized: `CAIRO_FORMAT_ARGB32` is mapped to `Cairo.Format.ARGB32` etc. ## Surfaces (`cairo_surface_t`) ## Prototype hierarchy * `Surface` (abstract) * `ImageSurface` * `PDFSurface` * `SVGSurface` * `PostScriptSurface` The native surfaces (win32, quartz, xlib) are not supported at this point. Methods manipulating a surface are present in the surface class. Creating an ImageSurface from a PNG is done by calling a static method: ```js let surface = Cairo.ImageSurface.createFromPNG("filename.png"); ``` ## Context (`cairo_t`) ## `cairo_t` is mapped as `Cairo.Context`. You will either get a context from a third-party library such as Clutter/Gtk/Poppler or by calling the `Cairo.Context` constructor. ```js let cr = new Cairo.Context(surface); let cr = Gdk.cairo_create(...); ``` All introspection methods taking or returning a `cairo_t` will automatically create a `Cairo.Context`. ## Patterns (`cairo_pattern_t`) ## Prototype hierarchy * `Pattern` * `Gradient` * `LinearGradient` * `RadialGradient` * `SurfacePattern` * `SolidPattern` You can create a linear gradient by calling the constructor: Constructors: ```js let pattern = new Cairo.LinearGradient(0, 0, 100, 100); let pattern = new Cairo.RadialGradient(0, 0, 10, 100, 100, 10); let pattern = new Cairo.SurfacePattern(surface); let pattern = new Cairo.SolidPattern.createRGB(0, 0, 0); let pattern = new Cairo.SolidPattern.createRGBA(0, 0, 0, 0); ``` TODO: * context: wrap the remaning methods * surface methods * image surface methods * matrix * version * iterating over `cairo_path_t` Fonts & Glyphs are not wrapped, use PangoCairo instead. * glyphs * text cluster * font face * scaled font * font options cjs-5.2.0/doc/Modules.md0000644000175000017500000002561114144444702015143 0ustar jpeisachjpeisachGJS includes some built-in modules, as well as helpers for some core APIs like DBus like Variants. The headings below are links to the JavaScript source, which are decently documented and informative of usage. ## [Gio](https://gitlab.gnome.org/GNOME/gjs/blob/master/modules/core/overrides/Gio.js) **Import with `const Gio = imports.gi.Gio;`** The `Gio` override includes a number of utilities for DBus that will be documented further at a later date. Below is a reasonable overview. * `Gio.DBus.session`, `Gio.DBus.system` Convenience properties that wrap `Gio.bus_get_sync()` to return a DBus connection * `Gio.DBusNodeInfo.new_for_xml(xmlString)` Return a new `Gio.DBusNodeInfo` for xmlString * `Gio.DBusInterfaceInfo.new_for_xml(xmlString)` Return a new `Gio.DBusInterfaceInfo` for the first interface node of xmlString * `Gio.DBusProxy.makeProxyWrapper(xmlString)` Returns a `function(busConnection, busName, objectPath, asyncCallback, cancellable)` which can be called to return a new `Gio.DBusProxy` for the first interface node of `xmlString`. See [here][old-dbus-example] for the original example. * `Gio.DBusExportedObject.wrapJSObject(Gio.DbusInterfaceInfo, jsObj)` Takes `jsObj`, an object instance implementing the interface described by `Gio.DbusInterfaceInfo`, and returns an implementation object with these methods: * `export(busConnection, objectPath)` * `unexport()` * `flush()` * `emit_signal(name, variant)` * `emit_property_changed(name, variant)` [old-dbus-example]: https://wiki.gnome.org/Gjs/Examples/DBusClient ## [GLib](https://gitlab.gnome.org/GNOME/gjs/blob/master/modules/core/overrides/GLib.js) **Import with `const GLib = imports.gi.GLib;`** Mostly GVariant and GBytes compatibility. * `GLib.log_structured()`: Wrapper for g_log_variant() * `GLib.Bytes.toArray()`: Convert a GBytes object to a ByteArray object * `GLib.Variant.unpack()`: Unpack a variant to a native type * `GLib.Variant.deep_unpack()`: Deep unpack a variant. ## [GObject](https://gitlab.gnome.org/GNOME/gjs/blob/master/modules/core/overrides/GObject.js) **Import with `const GObject = imports.gi.GObject;`** Mostly GObject implementation (properties, signals, GType mapping). May be useful as a reference. ## [Gtk](https://gitlab.gnome.org/GNOME/gjs/blob/master/modules/core/overrides/Gtk.js) **Import with `const Gtk = imports.gi.Gtk;`** Mostly GtkBuilder/composite template implementation. May be useful as a reference. >>> **REMINDER:** You should specify a version prior to importing a library with multiple versions: ```js imports.gi.versions.Gtk = "3.0"; const Gtk = imports.gi.Gtk; ``` >>> ## Cairo **Import with `const Cairo = imports.cairo;`** Mostly API compatible with [cairo](https://www.cairographics.org/documentation/), but using camelCase function names. There is list of constants in [cairo.js][cairo-const] and functions for each object in its corresponding C++ file (eg. [cairo-context.cpp][cairo-func]). A simple example drawing a 32x32 red circle: ```js imports.gi.versions.Gtk = "3.0"; const Gtk = imports.gi.Gtk; const Cairo = imports.cairo; let drawingArea = new Gtk.DrawingArea({ height_request: 32, width_request: 32 }); drawingArea.connect("draw", (widget, cr) => { // Cairo in GJS uses camelCase function names cr.setSourceRGB(1.0, 0.0, 0.0); cr.setOperator(Cairo.Operator.DEST_OVER); cr.arc(16, 16, 16, 0, 2*Math.PI); cr.fill(); // currently when you connect to a draw signal you have to call // cr.$dispose() on the Cairo context or the memory will be leaked. cr.$dispose(); return false; }); ``` [cairo-const]: https://gitlab.gnome.org/GNOME/gjs/blob/master/modules/script/cairo.js [cairo-func]: https://gitlab.gnome.org/GNOME/gjs/blob/master/modules/cairo-context.cpp#L825 ## [Format](https://gitlab.gnome.org/GNOME/gjs/blob/master/modules/script/format.js) **Import with `const Format = imports.format;`** The format import is mostly obsolete, providing `vprintf()`, `printf()` and `format()`. Native [template literals][template-literals] should be preferred now, except in few situations like Gettext (See [Bug #50920][bug-50920]). ```js let foo = "Pi"; let bar = 1; let baz = Math.PI; // Using native template literals (Output: Pi to 2 decimal points: 3.14) `${foo} to ${bar*2} decimal points: ${baz.toFixed(bar*2)}` // Applying format() to the string prototype const Format = imports.format; String.prototype.format = Format.format; // Using format() (Output: Pi to 2 decimal points: 3.14) "%s to %d decimal points: %.2f".format(foo, bar*2, baz); // Using format() with Gettext _("%d:%d").format(11, 59); Gettext.ngettext("I have %d apple", "I have %d apples", num).format(num); ``` [template-literals]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals [bug-50920]: https://savannah.gnu.org/bugs/?50920 ## [Gettext](https://gitlab.gnome.org/GNOME/gjs/blob/master/modules/script/gettext.js) **Import with `const Gettext = imports.gettext;`** Helper functions for gettext. See also [examples/gettext.js][example-gettext] for usage. [example-gettext]: https://gitlab.gnome.org/GNOME/gjs/blob/master/examples/gettext.js ## [jsUnit](https://gitlab.gnome.org/GNOME/gjs/blob/master/modules/script/jsUnit.js) **DEPRECATED** Deprecated unit test functions. [Jasmine][jasmine-gjs] for GJS should now be preferred, as demonstrated in the GJS [test suite][gjs-tests]. [jasmine-gjs]: https://github.com/ptomato/jasmine-gjs [gjs-tests]: https://gitlab.gnome.org/GNOME/gjs/blob/master/installed-tests/js ## [`Lang`](https://gitlab.gnome.org/GNOME/gjs/blob/master/modules/script/lang.js) **DEPRECATED** Lang is a mostly obsolete library, that should only be used in cases where older versions of GJS must be supported. For example, `Lang.bind()` was necessary to bind `this` to the function context before the availability of arrow functions: ```js const Lang = imports.lang; const FnorbLib = imports.fborbLib; const MyLegacyClass = new Lang.Class({ _init: function() { let fnorb = new FnorbLib.Fnorb(); fnorb.connect('frobate', Lang.bind(this, this._onFnorbFrobate)); }, _onFnorbFrobate: function(fnorb) { this._updateFnorb(); } }); var MyNewClass = class { constructor() { let fnorb = new FnorbLib.Fnorb(); fnorb.connect('frobate', fnorb => this._onFnorbFrobate); } _onFnorbFrobate(fnorb) { this._updateFnorb(); } } ``` ## [Mainloop](https://gitlab.gnome.org/GNOME/gjs/blob/master/modules/script/mainloop.js) **DEPRECATED** Mainloop is simply a layer of convenience and backwards-compatibility over some GLib functions (such as [`GLib.timeout_add()`][gjs-timeoutadd] which in GJS is mapped to [`g_timeout_add_full()`][c-timeoutaddfull]). It's use is not generally recommended anymore. [c-timeoutaddfull]: https://developer.gnome.org/glib/stable/glib-The-Main-Event-Loop.html#g-timeout-add-full [gjs-timeoutadd]: http://devdocs.baznga.org/glib20~2.50.0/glib.timeout_add ## [Package](https://gitlab.gnome.org/GNOME/gjs/blob/master/modules/script/package.js) Infrastructure and utilities for [standalone applications](Home#standalone-applications). ## [Signals](https://gitlab.gnome.org/GNOME/gjs/blob/master/modules/script/signals.js) **Import with `const Signals = imports.signals;`** A GObject-like signal framework for native Javascript objects. **NOTE:** Unlike [GObject signals](Mapping#signals), `this` within a signal callback will refer to the global object (ie. `window`). ```js const Signals = imports.signals; var MyJSClass = class { testSignalEmission () { this.emit("exampleSignal", "stringArg", 42); } } Signals.addSignalMethods(MyJSClass.prototype); let obj = new MyJSObject(); // Connect and disconnect like standard GObject signals let handlerId = obj.connect("exampleSignal", (obj, stringArg, intArg) => { // Remember 'this' === 'window' }); obj.disconnect(handlerId); // A convenience function not in GObject obj.disconnectAll(); ``` ## [System](https://gitlab.gnome.org/GNOME/gjs/blob/master/modules/system.cpp) **Import with `const System = imports.system;`** The System module offers a number of useful functions and properties for debugging and shell interaction (eg. ARGV): * `addressOf(object)` Return the memory address of any object as a string in hexadecimal, e.g. `0xb4f170f0`. Caution, don't use this as a unique identifier! JavaScript's garbage collector can move objects around in memory, or deduplicate identical objects, so this value may change during the execution of a program. * `refcount(gobject)` Return the reference count of any GObject-derived type (almost any class from GTK, Clutter, GLib, Gio, etc.). When an object's reference count is zero, it is cleaned up and erased from memory. * `breakpoint()` This is the real gem of the System module! It allows just the tiniest amount of decent debugging practice in GJS. Put `System.breakpoint()` in your code and run it under GDB like so: ``` gdb --args gjs my_program.js ``` When GJS reaches the breakpoint, it will stop executing and return you to the GDB prompt, where you can examine the stack or other things, or type `cont` to continue running. Note that if you run the program outside of GDB, it will abort at the breakpoint, so make sure to remove the breakpoint when you're done debugging. * `gc()` Run the garbage collector. * `exit(error_code)` This works the same as C's `exit()` function; exits the program, passing a certain error code to the shell. The shell expects the error code to be zero if there was no error, or non-zero (any value you please) to indicate an error. This value is used by other tools such as `make`; if `make` calls a program that returns a non-zero error code, then `make` aborts the build. * `version` This property contains version information about GJS. * `programInvocationName` This property contains the name of the script as it was invoked from the command line. In C and other languages, this information is contained in the first element of the platform's equivalent of `argv`, but GJS's `ARGV` only contains the subsequent command-line arguments, so `ARGV[0]` in GJS is the same as `argv[1]` in C. For example, passing ARGV to a `Gio.Application`/`Gtk.Application` (See also: [examples/gtk-application.js][example-application]): ```js imports.gi.versions.Gtk = "3.0"; const Gtk = imports.gi.Gtk; const System = imports.system; let myApp = new Gtk.Application(); myApp.connect("activate", () => log("activated")); myApp.run([System.programInvocationName].concat(ARGV)); ``` [example-application]: https://gitlab.gnome.org/GNOME/gjs/blob/master/examples/gtk-application.js ## [Tweener](https://gitlab.gnome.org/GNOME/gjs/blob/master/modules/script/tweener/) **Import with `const Tweener = imports.tweener.tweener;`** Built-in version of the well-known [Tweener][tweener-www] animation/property transition library. [tweener-www]: http://hosted.zeh.com.br/tweener/docs/cjs-5.2.0/doc/Hacking.md0000644000175000017500000001317214144444702015076 0ustar jpeisachjpeisach# Hacking on GJS # ## Setting up ## First of all, if you are contributing C++ code, install the handy git commit hook that will autoformat your code when you commit it. In your GJS checkout directory, run `tools/git-pre-commit-format install`. For more information, see . (You can skip this step if it doesn't work for you, but in that case you'll need to manually format your code before it gets merged. You can also skip this step if you are not writing any C++ code.) GJS requires four other libraries to be installed: GLib, libffi, gobject-introspection, and SpiderMonkey (also called "mozjs78" on some systems.) The readline library is not required, but strongly recommended. We recommend installing your system's development packages for GLib, libffi, gobject-introspection, and readline. (For example, on Ubuntu you would run `sudo apt-get install libglib2.0-dev libffi-dev libreadline-dev libgirepository1.0-dev libreadline-dev`.) But, if your system's versions of these packages aren't new enough, then the build process will download and build sufficient versions. SpiderMonkey cannot be auto-installed, so you will need to install it either through your system's package manager, or building it yourself. Even if your system includes a development package for SpiderMonkey, we still recommend building it if you are going to do any development on GJS so that you can enable the debugging features. These debugging features reduce performance by quite a lot, but they will help catch mistakes in the API that could otherwise go unnoticed and cause crashes in gnome-shell later on. To build SpiderMonkey, follow the instructions on [this page](https://github.com/mozilla-spidermonkey/spidermonkey-embedding-examples/blob/esr78/docs/Building%20SpiderMonkey.md) to download the source code and build the library. If you are using `-Dprefix` to build GJS into a different path, then make sure to use the same build prefix for SpiderMonkey with `--prefix`. ## First build ## To build GJS, change to your checkout directory, and run: ```sh meson _build ninja -C _build ``` Add any options with `-D` arguments to the `meson _build` command. For a list of available options, run `meson configure`. To install GJS into the path you chose with `-Dprefix`, (or into `/usr/local` if you didn't choose a path), run `ninja -C _build install`, adding `sudo` if necessary. ## Making Sure Your Stuff Doesn't Break Anything Else ## Make your changes in your GJS checkout directory, then run `ninja -C _build` to build a modified copy of GJS. Each changeset should ensure that the test suite still passes. In fact, each commit should ensure that the test suite still passes, though there are some exceptions to this rule. You can run the test suite with `meson test -C _build`. For some contributions, it's a good idea to test your modified version of GJS with GNOME Shell. For this, you might want to use JHBuild to build GJS instead, and run it with `jhbuild run gnome-shell --replace`. You need to be logged into an Xorg session, not Wayland, for this to work. ## Debugging ## Mozilla has some pretty-printers that make debugging JSAPI code easier. Unfortunately they're not included in most packaged distributions of mozjs, but you can grab them from your built copy of mozjs. After reaching a breakpoint in your program, type this to activate the pretty-printers: ```sh source /path/to/spidermonkey/js/src/_build/js/src/shell/js-gdb.py ``` (replace `/path/to/spidermonkey` with the path to your SpiderMonkey sources) ## Checking Things More Thoroughly Before A Release ## ### GC Zeal ### Run the test suite with "GC zeal" to make non-deterministic GC errors more likely to show up. To see which GC zeal options are available: ```sh JS_GC_ZEAL=-1 js78 ``` We include three test setups, `extra_gc`, `pre_verify`, and `post_verify`, for the most useful modes: `2,1`, `4`, and `11` respectively. Run them like this (replace `extra_gc` with any of the other names): ```sh meson test -C _build --setup=extra_gc ``` Failures in mode `pre_verify` usually point to a GC thing not being traced when it should have been. Failures in mode `post_verify` usually point to a weak pointer's location not being updated after GC moved it. ### Valgrind ### Valgrind catches memory leak errors in the C++ code. It's a good idea to run the test suite under Valgrind before each release. To run the test suite under Valgrind's memcheck tool: ```sh meson test -C _build --setup=valgrind ``` The logs from each run will be in `_build/meson-logs/testlog-valgrind.txt`. Note that LeakSanitizer, part of ASan (see below) can catch many, but not all, errors that Valgrind can catch. LSan executes faster than Valgrind, however. ### Static Code Analysis ### To execute cppcheck, a static code analysis tool for the C and C++, run: ```sh tools/run_cppcheck.sh ``` It is a versatile tool that can check non-standard code, including: variable checking, bounds checking, leaks, etc. It can detect the types of bugs that the compilers normally fail to detect. ### Sanitizers ### To build GJS with support for the ASan and UBSan sanitizers, configure meson like this: ```sh meson _build -Db_sanitize=address,undefined ``` and then run the tests as normal. ### Test Coverage ### To generate a test coverage report, run this script: ```sh tools/run_coverage.sh gio open _coverage/html/index.html ``` This will build GJS into a separate build directory with code coverage instrumentation enabled, run the test suite to collect the coverage data, and open the generated HTML report. [embedder](https://github.com/spidermonkey-embedders/spidermonkey-embedding-examples/blob/esr78/docs/Building%20SpiderMonkey.md)cjs-5.2.0/doc/Style_Guide.md0000644000175000017500000001016014144444702015741 0ustar jpeisachjpeisach# Coding style # Our goal is to have all JavaScript code in GNOME follow a consistent style. In a dynamic language like JavaScript, it is essential to be rigorous about style (and unit tests), or you rapidly end up with a spaghetti-code mess. ## Linter ## GJS includes an eslint configuration file, `.eslintrc.yml`. There is an additional one that applies to test code in `installed-tests/js/.eslintrc.yml`. We recommend using this for your project, with any modifications you need that are particular to your project. In most editors you can set up eslint to run on your code as you type. Or you can set it up as a git commit hook. In any case if you contribute code to GJS, eslint will check the code in your merge request. The style guide for JS code in GJS is, by definition, the eslint config file. This file only contains conventions that the linter can't catch. ## Imports ## Use CamelCase when importing modules to distinguish them from ordinary variables, e.g. ```js const Big = imports.big; const {GLib} = imports.gi; ``` ## Variable declaration ## Always use `const` or `let` when block scope is intended. In almost all cases `const` is correct if you don't reassign the variable, and otherwise `let`. In general `var` is only needed for variables that you are exporting from a module. ```js // Iterating over an array for (let i = 0; i < 10; ++i) { let foo = bar(i); } // Iterating over an object's properties for (let prop in someobj) { ... } ``` If you don't use `let` or `const` then the variable is added to function scope, not the for loop block scope. See [What's new in JavaScript 1.7][1] A common case where this matters is when you have a closure inside a loop: ```js for (let i = 0; i < 10; ++i) { GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, function () { log(`number is: ${i}`); }); } ``` If you used `var` instead of `let` it would print "10" a bunch of times. ## `this` in closures ## `this` will not be captured in a closure; `this` is relative to how the closure is invoked, not to the value of this where the closure is created, because `this` is a keyword with a value passed in at function invocation time, it is not a variable that can be captured in closures. To solve this, use `Function.bind()`, or arrow functions, e.g.: ```js const closure = () => { this._fnorbate(); }; // or const closure = function() { this._fnorbate() }.bind(this); ``` A more realistic example would be connecting to a signal on a method of a prototype: ```js const MyPrototype = { _init() { fnorb.connect('frobate', this._onFnorbFrobate.bind(this)); }, _onFnorbFrobate(fnorb) { this._updateFnorb(); }, }; ``` ## Object literal syntax ## JavaScript allows equivalently: ```js const foo = {'bar': 42}; const foo = {bar: 42}; ``` and ```js const b = foo['bar']; const b = foo.bar; ``` If your usage of an object is like an object, then you're defining "member variables." For member variables, use the no-quotes no-brackets syntax, that is, `{bar: 42}` and `foo.bar`. If your usage of an object is like a hash table (and thus conceptually the keys can have special chars in them), don't use quotes, but use brackets, `{bar: 42}`, `foo['bar']`. ## Variable naming ## - We use javaStyle variable names, with CamelCase for type names and lowerCamelCase for variable and method names. However, when calling a C method with underscore-based names via introspection, we just keep them looking as they do in C for simplicity. - Private variables, whether object member variables or module-scoped variables, should begin with `_`. - True global variables (in the global or 'window' object) should be avoided whenever possible. If you do create them, the variable name should have a namespace in it, like `BigFoo` - When you assign a module to an alias to avoid typing `imports.foo.bar` all the time, the alias should be `const TitleCase` so `const Bar = imports.foo.bar;` - If you need to name a variable something weird to avoid a namespace collision, add a trailing `_` (not leading, leading `_` means private). [1] http://developer.mozilla.org/en/docs/index.php?title=New_in_JavaScript_1.7&printable=yes#Block_scope_with_let cjs-5.2.0/doc/Logging.md0000644000175000017500000000720514144444702015120 0ustar jpeisachjpeisachGJS includes a number of built-in funtions for logging and aiding debugging, in addition to those available as a part of the GNOME APIs. # Built-in Functions GJS includes four built-in logging functions: `log()`, `logError()`, `print()` and `printerr()`. These functions are available globally (ie. without import) and the source for these is found in [global.cpp][global-cpp]. ### log() `log()` is the most basic function available, taking a single argument as a `String` or other object which can be coerced to a `String`. The string, or object coerced to a string, is logged with `g_message()` from GLib. ```js // expected output: JS LOG: Some message log('Some message'); // expected output: JS LOG: [object Object] log(new Object()); ``` ### logError() `logError()` is a more useful function for debugging that logs the stack trace of a JavaScript `Error()` object, with an optional prefix. It is commonly used in conjunction with `try...catch` blocks to log errors while still trapping the exception. An example in `gjs-console` with a backtrace: ```js $ gjs gjs> try { .... throw new Error('Some error occured'); .... } catch (e) { .... logError(e, 'FooError'); .... } (gjs:28115): Gjs-WARNING **: 19:28:13.334: JS ERROR: FooError: Error: Some error occured @typein:2:16 @:1:34 ``` ### print() & printerr() `print()` takes any number of string (or coercable) arguments, joins them with a space and appends a newline (`\n`). The resulting message will be printed directly to `stdout` of the current process using `g_print()`. `printerr()` is exactly like `print()`, except the resulting message is printed to `stderr` with `g_printerr()`. These functions are generally less useful for debugging code in programs that embed GJS like GNOME Shell, where it is less convenient to access the `stdout` and `stderr` pipes. ```js $ gjs gjs> print('some', 'string', 42); some string 42$ gjs> printerr('some text 42'); some text ``` # GLib Functions Aside from the built-in functions in GJS, many functions from GLib can be used to log messages at different severity levels and assist in debugging. Below is a common pattern for defining a series of logging functions as used in [Polari][polari] and some other GJS applications: ```js const GLib = imports.gi.GLib; var LOG_DOMAIN = 'Polari'; function _makeLogFunction(level) { return message => { let stack = (new Error()).stack; let caller = stack.split('\n')[1]; // Map from resource- to source location caller = caller.replace('resource:///org/gnome/Polari/js', 'src'); let [code, line] = caller.split(':'); let [func, file] = code.split(/\W*@/); GLib.log_structured(LOG_DOMAIN, level, { 'MESSAGE': `${message}`, 'SYSLOG_IDENTIFIER': 'org.gnome.Polari', 'CODE_FILE': file, 'CODE_FUNC': func, 'CODE_LINE': line }); }; } // `window` is the global object in GJS, for historical reasons window.log = _makeLogFunction(GLib.LogLevelFlags.LEVEL_MESSAGE); window.debug = _makeLogFunction(GLib.LogLevelFlags.LEVEL_DEBUG); window.info = _makeLogFunction(GLib.LogLevelFlags.LEVEL_INFO); window.warning = _makeLogFunction(GLib.LogLevelFlags.LEVEL_WARNING); window.critical = _makeLogFunction(GLib.LogLevelFlags.LEVEL_CRITICAL); window.error = _makeLogFunction(GLib.LogLevelFlags.LEVEL_ERROR); // Log all messages when connected to the journal if (GLib.log_writer_is_journald(2)) GLib.setenv('G_MESSAGES_DEBUG', LOG_DOMAIN, false); ``` [global-cpp]: https://gitlab.gnome.org/GNOME/gjs/blob/master/gjs/global.cpp [polari]: https://gitlab.gnome.org/GNOME/polari/blob/master/src/main.js cjs-5.2.0/doc/Package/0000755000175000017500000000000014144444702014537 5ustar jpeisachjpeisachcjs-5.2.0/doc/Package/Specification.md0000644000175000017500000002011414144444702017637 0ustar jpeisachjpeisachThis document aims to build a set of conventions for JS applications using GJS and GObjectIntrospection. ## Rationale It is believed that the current deployment facilities for GJS apps, ie autotools, [bash wrapper scripts](https://git.gnome.org/browse/gnome-documents/tree/src/gnome-documents.in) and [sed invocations](https://git.gnome.org/browse/gnome-documents/tree/src/Makefile.am#n26) represent a huge obstacle in making the GJS application platform palatable for newcomers. Additionally, the lack of standardization on the build system hinders the diffusion of pure JS utility / convenience modules. The goal is to create a standard packaging method for GJS, similar to Python's . The choice of keeping the autotools stems from the desire of integration with GNOME submodules such as libgd and egg-list-box. While those are temporary and will enter GTK in due time, it is still worthy for free software applications to be able to share submodules easily. Moreover, so far the autotools have the best support for generating GObjectIntrospection information, and it is sometimes necessary for JS apps to use a private helper library in a compiled language. ## Requirements * Implementation details, whenever exposed to the app developers because of limitations of the underlying tools, must be copy-pastable between packages. * The application must be fully functional when run uninstalled. In particular, it must not fail because it lacks GtkBuilder files, images, CSS or GSettings. * The application must honor `--prefix` and `--libdir` (which must be a subdirectory of `--prefix`) at configure time. * The application must not require more than `--prefix` and `--libdir` to work. * The application must be installable by a regular user, provided he has write permission in `--prefix` * The format must allow the application to be comprised of one or more JS entry points, and one or more introspection based libraries ## Prior Art * [setuptools](https://pypi.python.org/pypi/setuptools) and [distutils-extra](https://launchpad.net/python-distutils-extra) (for Python) * [Ubuntu Quickly](https://wiki.ubuntu.com/Quickly|Ubuntu Quickly) (again, for Python) * [CommonJS package format](http://wiki.commonjs.org/wiki/Packages) (only describes the package layout, and does not provide runtime services) * https://live.gnome.org/BuilDj (build system only) ## Specification The following meta variable are used throughout this document: * **${package-name}**: the fully qualified ID of the package, in DBus name format. Example: org.gnome.Weather. * **${entry-point-name}**: the fully qualified ID of an entry point, in DBus name format. Example: org.gnome.Weather.Application. This must be a sub ID of **${package-name}** * **${entry-point-path}**: the entry point ID, converted to a DBus path in the same way GApplication does it (prepend /, replace . with /) * **${package-tarname}**: the short, but unambiguous, short name of the package, such as gnome-weather * **${package-version}**: the version of the package This specification is an addition to the Gjs style guide, and it inherits all requirements. ## Package layout * The application package is expected to use autotools, or a compatible build system. In particular, it must optionally support recursive configure and recursive make. * The following directories and files in the toplevel package must exist: * **src/**: contains JS modules * **src/${entry-point-name}.src.gresource.xml**: the GResource XML for JS files for the named entry point (see below) * **src/${entry-point-name}.src.gresource**: the compiled GResource for JS files * **data/**: contains misc application data (CSS, GtkBuilder definitions, images...) * **data/${entry-point-name}.desktop**: contains the primary desktop file for the application * *(OPTIONAL)* **data/${entry-point-name}.data.gresource**: contains the primary application resource * *(OPTIONAL)* **data/${entry-point-name}.gschema.xml**: contains the primary GSettings schema * *(OPTIONAL)* **data/gschemas.compiled**: compiled version of GSettings schemas in data/, for uninstalled use * *(OPTIONAL)* **lib/**: contains sources and .la files of private shared libraries * *(OPTIONAL)* **lib/.libs**: contains the compiled (.so) version of private libraries * *(OPTIONAL)* another toplevel directory such as libgd or egg-list-box: same as lib/, but for shared submodules * **po/**: contains intltool PO files and templates; the translation domain must be ${package-name} * The package must be installed as following: * **${datadir}** must be configured as **${prefix}/share** * Arch-independent private data (CSS, GtkBuilder, GResource) must be installed in **${datadir}/${package-name}**, aka **${pkgdatadir}** * Source files must be compiled in a GResource with path **${entry-point-path}/js**, in a bundle called **${entry-point-name}.src.gresource** installed in **${pkgdatadir}** * Private libraries must be **${libdir}/${package-name}**, aka ${pkglibdir} * Typelib for private libraries must be in **${pkglibdir}/girepository-1.0** * Translations must be in **${datadir}/locale/** * Other files (launches, GSettings schemas, icons, etc) must be in their specified locations, relative to **${prefix}** and **${datadir}** ## Usage Applications complying with this specification will have one application script, installed in **${prefix}/share/${package-name}** (aka **${pkgdatadir}**), and named as **${entry-point-name}**, without any extension or mangling. Optionally, one or more symlinks will be placed in ${bindir}, pointing to the appropriate script in ${pkgdatadir} and named in a fashion more suitable for command line usage (usually ${package-tarname}). Alternatively, a script that calls "gapplication launch ${package-name}" can be used. The application itself will be DBus activated from a script called **src/${entry-point-name}**, generated from configure substitution of the following **${entry-point-name}.in**: ```sh #!@GJS@ imports.package.init({ name: "${package-name}", version: "@PACKAGE_VERSION@", prefix: "@prefix@" }); imports.package.run(${main-module}) ``` Where **${main-module}** is a module containing the `main()` function that will be invoked to start the process. This function should accept a single argument, an array of command line args. The first element in the array will be the full resolved path to the entry point itself (unlike the global ARGV variable for gjs). Also unlike ARGV, it is safe to modify this array. This `main()` function should initialize a GApplication whose id is **${entry-point-name}**, and do all the work inside the GApplication `vfunc_*` handlers. > **`[!]`** Users should refer to https://github.com/gcampax/gtk-js-app for a full example of the build environment. ## Runtime support The following API will be available to applications, through the [`package.js`](https://gitlab.gnome.org/GNOME/gjs/blob/master/modules/script/package.js) module. * `window.pkg` (ie `pkg` on the global object) will provide access to the package module * `pkg.name` and `pkg.version` will return the package name and version, as passed to `pkg.init()` * `pkg.prefix`, `pkg.datadir`, `pkg.libdir` will return the installed locations of those folders * `pkg.pkgdatadir`, `pkg.moduledir`, `pkg.pkglibdir`, `pkg.localedir` will return the respective directories, or the appropriate subdirectory of the current directory if running uninstalled * `pkg.initGettext()` will initialize gettext. After calling `window._`, `window.C_` and `window.N_` will be available * `pkg.initFormat()` will initialize the format module. After calling, String.prototype.format will be available * `pkg.initSubmodule(name)` will initialize a submodule named @name. It must be called before accessing the typelibs installed by that submodule * `pkg.loadResource(name)` will load and register a GResource named @name. @name is optional and defaults to ${package-name} * `pkg.require(deps)` will mark a set of dependencies on GI and standard JS modules. **@deps** is a object whose keys are repository names and whose values are API versions. If the dependencies are not satisfied, `pkg.require()` will print an error message and quit.cjs-5.2.0/doc/Environment.md0000644000175000017500000000534114144444702016035 0ustar jpeisachjpeisach## Environment GJS allows runtime configuration with a number of environment variables. ### General * `GJS_PATH` Set this variable to a list of colon-separated (`:`) paths (just like `PATH`), to add them to the search path for the importer. Use of the `--include-path` command-line option is preferred over this variable. * `GJS_ABORT_ON_OOM` > NOTE: This feature is not well tested. Setting this variable to any value causes GJS to exit when an out-of-memory condition is encountered, instead of just printing a warning. ### JavaScript Engine * `JS_GC_ZEAL` Enable GC zeal, a testing and debugging feature that helps find GC-related bugs in JSAPI applications. See the [Hacking][hacking-gczeal] and the [JSAPI Documentation][mdn-gczeal] for more information about this variable. * `GJS_DISABLE_JIT` Setting this variable to any value will disable JIT compiling in the JavaScript engine. ### Debugging * `GJS_DEBUG_HEAP_OUTPUT` In addition to `System.dumpHeap()`, you can dump a heap from a running program by starting it with this environment variable set to a path and sending it the `SIGUSR1` signal. * `GJS_DEBUG_OUTPUT` Set this to "stderr" to log to `stderr` or a file path to save to. * `GJS_DEBUG_TOPICS` Set this to a semi-colon delimited (`;`) list of prefixes to allow to be logged. Prefixes include: * "JS GI USE" * "JS MEMORY" * "JS CTX" * "JS IMPORT" * "JS NATIVE" * "JS KP ALV" * "JS G REPO" * "JS G NS" * "JS G OBJ" * "JS G FUNC" * "JS G FNDMTL" * "JS G CLSR" * "JS G BXD" * "JS G ENUM" * "JS G PRM" * "JS G ERR" * "JS G IFACE" * `GJS_DEBUG_THREAD` Set this variable to print the thread number when logging. * `GJS_DEBUG_TIMESTAMP` Set this variable to print a timestamp when logging. ### Testing * `GJS_COVERAGE_OUTPUT` Set this variable to define an output path for code coverage information. Use of the `--coverage-output` command-line option is preferred over this variable. * `GJS_COVERAGE_PREFIXES` Set this variable to define a colon-separated (`:`) list of prefixes to output code coverage information for. Use of the `--coverage-prefix` command-line option is preferred over this variable. * `GJS_ENABLE_PROFILER` Set this variable to `1` to enable or `0` to disable the profiler. Use of the `--profile` command-line option is preferred over this variable. * `GJS_TRACE_FD` The GJS profiler is integrated directly into Sysprof via this variable. It not typically useful to set this manually. [hacking-gczeal]: https://gitlab.gnome.org/GNOME/gjs/blob/master/doc/Hacking.md#gc-zeal [mdn-gczeal]: https://developer.mozilla.org/docs/Mozilla/Projects/SpiderMonkey/JSAPI_reference/JS_SetGCZeal cjs-5.2.0/doc/Mapping.md0000644000175000017500000002173214144444702015126 0ustar jpeisachjpeisach## GObject Construction, Subclassing, Templates and GType ### Constructing GObjects GObjects can be constructed with the `new` operator, just like JavaScript objects, and usually take an Object map of properties. The object that you pass to `new` (e.g. `Gtk.Label` in `let label = new Gtk.Label()`) is the **constructor object**, that contains constructor methods and static methods such as `Gio.File.new_for_path()`. It's different from the **prototype object** containing instance methods. For more information on JavaScript's prototypal inheritance, this [blog post][understanding-javascript-prototypes] is a good resource. ```js let label = new Gtk.Label({ label: 'gnome.org', halign: Gtk.Align.CENTER, hexpand: true, use_markup: true, visible: true }); let file = Gio.File.new_for_path('/proc/cpuinfo'); ``` [understanding-javascript-prototypes]: https://javascriptweblog.wordpress.com/2010/06/07/understanding-javascript-prototypes/ ### Subclassing GObjects GObjects have facilities for defining properties, signals and implemented interfaces. Additionally, Gtk objects support defining a CSS name and composite template. The **constructor object** is also passed to the `extends` keyword in class declarations when subclassing GObjects. ```js var MyLabel = GObject.registerClass({ // GObject GTypeName: 'Gjs_MyLabel', // GType name (see below) Implements: [ Gtk.Orientable ], // Interfaces the subclass implements Properties: {}, // More below on custom properties Signals: {}, // More below on custom signals // Gtk CssName: '', // CSS name Template: 'resource:///path/example.ui', // Builder template Children: [ 'label-child' ], // Template children InternalChildren: [ 'internal-box' ] // Template internal (private) children }, class MyLabel extends Gtk.Label { // Currently GObjects use _init, not construct _init(params) { // Chaining up super._init(params); } }); ``` ### GType Objects This is the object that represents a type in the GObject type system. Internally a GType is an integer, but you can't access that integer in GJS. The `$gtype` property gives the GType object for the given type. This is the proper way to find the GType given an object or a class. For a class, `GObject.type_from_name('GtkLabel')` would work too if you know the GType name, but only if you had previously constructed a Gtk.Label object. ```js log(Gtk.Label.$gtype); log(labelInstance.constructor.$gtype); // expected output: [object GType for 'GtkLabel'] ``` The `name` property of GType objects gives the GType name as a string ('GtkLabel'). This is the proper way to find the type name given an object or a class. User defined subclasses' GType name will be the class name prefixed with `Gjs_`by default. If you want to specify your own name, you can pass it as the value for the `GTypeName` property to `GObject.registerClass()`. This will be relevant in situations such as defining a composite template for a GtkWidget subclass. ```js log(Gtk.Label.$gtype.name); log(labelInstance.constructor.$gtype.name); // expected output: GtkLabel log(MyLabel.$gtype.name); // expected output: Gjs_MyLabel ``` [`instanceof`][mdn-instanceof] can be used to compare an object instance to a **constructor object**. ```js log(typeof labelInstance); // expected output: object log(labelInstance instanceof Gtk.Label); // expected output: true ``` [mdn-instanceof]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Operators/instanceof ## Properties GObject properties may be retrieved and set using native property style access or GObject get/set methods. Note that variables in JavaScript can't contain hyphens (-) so when a property name is *unquoted* use an underscore (_). ```js if (!label.use_markup) { label.connect('notify::use-markup', () => { ... }); label.use_markup = true; label['use-markup'] = true; label.set_use_markup(true); } ``` GObject subclasses can register properties, which is necessary if you want to use `GObject.notify()` or `GObject.bind_property()`. **NOTE:** Never use underscores in property names in the ParamSpec, because of the conversion between underscores and hyphens mentioned above. ```js var MyLabel = GObject.registerClass({ Properties: { 'example-prop': GObject.ParamSpec.string( 'example-prop', // property name 'ExampleProperty', // nickname 'An example read write property', // description GObject.ParamFlags.READWRITE, // READABLE/READWRITE/CONSTRUCT/etc 'A default' // default value if omitting getter/setter ) } }, class MyLabel extends Gtk.Label { get example_prop() { if (!('_example_prop' in this) return 'A default'; return this._example_prop; } set example_prop(value) { if (this._example_prop !== value) { this._example_prop = value; this.notify('example-prop'); } } }); ``` If you just want a simple property that you can get change notifications from, you can leave out the getter and setter and GJS will attempt to do the right thing. However, if you define one, you have to define both (unless the property is read-only or write-only). The 'default value' parameter passed to `GObject.ParamSpec` will be taken into account if you omit the getter and setter. If you write your own getter and setter, you have to implement the default value yourself, as in the above example. ## Signals Every object inherited from GObject has `connect()`, `connect_after()`, `disconnect()` and `emit()` methods. ```js let handlerId = label.connect('activate-link', (label, uri) => { Gtk.show_uri_on_window( label.get_toplevel(), uri, Gdk.get_current_time() ); return true; }); label.emit('activate-link', 'https://www.gnome.org'); label.disconnect(handlerId); ``` GObject subclasses can also register their own signals. ```js var MyLabel = GObject.registerClass({ Signals: { 'my-signal': { flags: GObject.SignalFlags.RUN_FIRST, param_types: [ GObject.TYPE_STRING ] } } }, class ExampleApplication extends GObject.Object { _init() { super._init(); this.emit('my-signal', 'a string parameter'); } }); ``` **NOTE:** GJS also includes a built-in [`signals`](Modules#signals) module for applying signals to native JavaScript classes. ## Enumerations and Flags Both enumerations and flags appear as entries under the namespace, with associated member properties. These are available in the official GJS [GNOME API documentation][gjs-docs]. ```js // enum GtkAlign, member GTK_ALIGN_CENTER Gtk.Align.CENTER; // enum GtkWindowType, member GTK_WINDOW_TOPLEVEL Gtk.WindowType.TOPLEVEL; // enum GApplicationFlags, member G_APPLICATION_FLAGS_NONE Gio.ApplicationFlags.FLAGS_NONE ``` Flags can be manipulated using native [bitwise operators][mdn-bitwise]. ```js let myApp = new Gio.Application({ flags: Gio.ApplicationFlags.HANDLES_OPEN | Gio.ApplicationFlags.HANDLES_COMMAND_LINE }); if (myApp.flags & Gio.ApplicationFlags.HANDLES_OPEN) { myApp.flags &= ~Gio.ApplicationFlags.HANDLES_OPEN; } ``` [gjs-docs]: http://devdocs.baznga.org/ [mdn-bitwise]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators ## Structs and Unions C structures and unions are documented in the [GNOME API documentation][gjs-docs] (e.g. [Gdk.Event][gdk-event]) and generally have either JavaScript properties or getter methods for each member. Results may vary when trying to modify structs or unions. ```js widget.connect("key-press-event", (widget, event) => { log(event); // expected output: [union instance proxy GIName:Gdk.Event jsobj@0x7f19a00b6400 native@0x5620c6a7c6e0] log(event.get_event_type() === Gdk.EventType.KEY_PRESS); // expected output: true let [ok, keyval] = event.get_keyval(); log(keyval); // example output: 65507 }); ``` [gdk-event]: http://devdocs.baznga.org/gdk30~3.22p/gdk.event ## Multiple return values (caller-allocates) In GJS caller-allocates (variables passed into a function) and functions with multiple out parameters are returned as an array of return values. If the function has a return value, it will be the first element of that array. ```js let [minimumSize, naturalSize] = label.get_preferred_size(); // Functions with boolean 'success' returns often still throw an Error on failure try { let file = new Gio.File({ path: '/proc/cpuinfo' }); let [ok, contents, etag_out] = file.load_contents(null); // "ok" is actually useless in this scenario, since if it is false, // an exception will have been thrown. You can skip return values // you don't want with array elision: let [, contents2] = file.load_contents(null); } catch(e) { log('Failed to read file: ' + e.message); } ```cjs-5.2.0/doc/CPP_Style_Guide.md0000644000175000017500000010700314144444702016446 0ustar jpeisachjpeisach# C++ Coding Standards # ## Introduction ## This guide attempts to describe a few coding standards that are being used in GJS. For formatting we follow the [Google C++ Style Guide][google]. This guide won't repeat all the rules that you can read there. Instead, it covers rules that can't be checked "mechanically" with an automated style checker. It is not meant to be exhaustive. This guide is based on the [LLVM coding standards][llvm] (source code [here][llvm-source].) No coding standards should be regarded as absolute requirements to be followed in all instances, but they are important to keep a large complicated codebase readable. Many of these rules are not uniformly followed in the code base. This is because most of GJS was written before they were put in place. Our long term goal is for the entire codebase to follow the conventions, but we explicitly *do not* want patches that do large-scale reformatting of existing code. On the other hand, it is reasonable to rename the methods of a class if you're about to change it in some other way. Just do the reformatting as a separate commit from the functionality change. The ultimate goal of these guidelines is to increase the readability and maintainability of our code base. If you have suggestions for topics to be included, please open an issue at . [google]: https://google.github.io/styleguide/cppguide.html [llvm]: https://llvm.org/docs/CodingStandards.html [llvm-source]: https://raw.githubusercontent.com/llvm-mirror/llvm/master/docs/CodingStandards.rst ## Languages, Libraries, and Standards ## Most source code in GJS using these coding standards is C++ code. There are some places where C code is used due to environment restrictions or historical reasons. Generally, our preference is for standards conforming, modern, and portable C++ code as the implementation language of choice. ### C++ Standard Versions ### GJS is currently written using C++17 conforming code, although we restrict ourselves to features which are available in the major toolchains. Regardless of the supported features, code is expected to (when reasonable) be standard, portable, and modern C++17 code. We avoid unnecessary vendor-specific extensions, etc., including `g_autoptr()` and friends. ### C++ Standard Library ### Use the C++ standard library facilities whenever they are available for a particular task. In particular, use STL containers rather than `GList*` and `GHashTable*` and friends, for their type safety and memory management. There are some exceptions such as the standard I/O streams library which is avoided, and use in space-constrained situations. ### Supported C++17 Language and Library Features ### While GJS and SpiderMonkey use C++17, not all features are available in all of the toolchains which we support. A good rule of thumb is to check whether SpiderMonkey uses the feature. If so, it's okay to use in GJS. ### Other Languages ### Any code written in JavaScript is not subject to the formatting rules below. Instead, we adopt the formatting rules enforced by the [`eslint`][eslint] tool. [eslint]: https://eslint.org/ ## Mechanical Source Issues ## All source code formatting should follow the [Google C++ Style Guide][google] with a few exceptions: * We use four-space indentation, to match the previous GJS coding style so that the auto-formatter doesn't make a huge mess. * Likewise we keep short return statements on separate lines instead of allowing them on single lines. Our tools (clang-format and cpplint) have the last word on acceptable formatting. It may happen that the tools are not configured correctly, or contradict each other. In that case we accept merge requests to fix that, rather than code that the tools reject. [google]: https://google.github.io/styleguide/cppguide.html ### Source Code Formatting ### #### Commenting #### Comments are one critical part of readability and maintainability. Everyone knows they should comment their code, and so should you. When writing comments, write them as English prose, which means they should use proper capitalization, punctuation, etc. Aim to describe what the code is trying to do and why, not *how* it does it at a micro level. Here are a few critical things to document: ##### File Headers ###### Every source file should have a header on it that describes the basic purpose of the file. If a file does not have a header, it should not be checked into the tree. The standard header looks like this: ```c++ /* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* * Copyright (c) YEARS NAME * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #include /* gi/private.cpp - private "imports._gi" module with operations that we need * to use from JS in order to create GObject classes, but should not be exposed * to client code. */ ``` A few things to note about this particular format: The "`-*-`" string on the first line is there to tell editors that the source file is a C++ file, not a C file (since C++ and C headers both share the `.h` extension.) This is originally an Emacs convention, but other editors use it too. The next section in the file is a concise note that describes the file's copyright and the license that the file is released under. This makes it perfectly clear what terms the source code can be distributed under and should not be modified. Names can be added to the copyright when making a substantial contribution to the file, not just a function or two. After the header includes comes a paragraph or two about what code the file contains. If an algorithm is being implemented or something tricky is going on, this should be explained here, as well as any notes or *gotchas* in the code to watch out for. ##### Class overviews ###### Classes are one fundamental part of a good object oriented design. As such, a class definition should have a comment block that explains what the class is used for and how it works. Every non-trivial class is expected to have such a comment block. ##### Method information ###### Methods defined in a class (as well as any global functions) should also be documented properly. A quick note about what it does and a description of the borderline behaviour is all that is necessary here (unless something particularly tricky or insidious is going on). The hope is that people can figure out how to use your interfaces without reading the code itself. #### Comment Formatting #### Either C++ style comments (`//`) or C style (`/* */`) comments are acceptable. However, when documenting a method or function, use [gtk-doc style] comments which are based on C style (`/** */`). When C style comments take more than one line, put an asterisk (`*`) at the beginning of each line: ```c++ /* a list of all GClosures installed on this object (from * signals, trampolines and explicit GClosures), used when tracing */ ``` Commenting out large blocks of code is discouraged, but if you really have to do this (for documentation purposes or as a suggestion for debug printing), use `#if 0` and `#endif`. These nest properly and are better behaved in general than C style comments. [gtk-doc style]: https://developer.gnome.org/gtk-doc-manual/unstable/documenting.html.en ### Language and Compiler Issues ### #### Treat Compiler Warnings Like Errors #### If your code has compiler warnings in it, something is wrong — you aren't casting values correctly, you have questionable constructs in your code, or you are doing something legitimately wrong. Compiler warnings can cover up legitimate errors in output and make dealing with a translation unit difficult. It is not possible to prevent all warnings from all compilers, nor is it desirable. Instead, pick a standard compiler (like GCC) that provides a good thorough set of warnings, and stick to it. Currently we use GCC and the set of warnings defined by the [`ax_compiler_flags`][ax-compiler-flags] macro. In the future, we will use Meson's highest `warning_level` setting as the arbiter. [ax-compiler-flags]: https://www.gnu.org/software/autoconf-archive/ax_compiler_flags.html#ax_compiler_flags #### Write Portable Code #### In almost all cases, it is possible and within reason to write completely portable code. If there are cases where it isn't possible to write portable code, isolate it behind a well defined (and well documented) interface. In practice, this means that you shouldn't assume much about the host compiler (and Visual Studio tends to be the lowest common denominator). #### Use of `class` and `struct` Keywords #### In C++, the `class` and `struct` keywords can be used almost interchangeably. The only difference is when they are used to declare a class: `class` makes all members private by default while `struct` makes all members public by default. Unfortunately, not all compilers follow the rules and some will generate different symbols based on whether `class` or `struct` was used to declare the symbol (e.g., MSVC). This can lead to problems at link time. * All declarations and definitions of a given `class` or `struct` must use the same keyword. For example: ```c++ class Foo; // Breaks mangling in MSVC. struct Foo { int data; }; ``` * As a rule of thumb, `struct` should be kept to structures where *all* members are declared public. ```c++ // Foo feels like a class... this is strange. struct Foo { private: int m_data; public: Foo() : m_data(0) {} int getData() const { return m_data; } void setData(int d) { m_data = d; } }; // Bar isn't POD, but it does look like a struct. struct Bar { int m_data; Bar() : m_data(0) {} }; ``` #### Use `auto` Type Deduction to Make Code More Readable #### Some are advocating a policy of "almost always `auto`" in C++11 and later, but GJS uses a more moderate stance. Use `auto` only if it makes the code more readable or easier to maintain. Don't "almost always" use `auto`, but do use `auto` with initializers like `cast(...)` or other places where the type is already obvious from the context. Another time when `auto` works well for these purposes is when the type would have been abstracted away anyway, often behind a container's typedef such as `std::vector::iterator`. #### Beware unnecessary copies with ``auto`` #### The convenience of `auto` makes it easy to forget that its default behaviour is a copy. Particularly in range-based `for` loops, careless copies are expensive. As a rule of thumb, use `auto&` unless you need to copy the result, and use `auto*` when copying pointers. ```c++ // Typically there's no reason to copy. for (const auto& val : container) observe(val); for (auto& val : container) val.change(); // Remove the reference if you really want a new copy. for (auto val : container) { val.change(); save_somewhere(val); } // Copy pointers, but make it clear that they're pointers. for (const auto* ptr : container) observe(*ptr); for (auto* ptr : container) ptr->change(); ``` #### Beware of non-determinism due to ordering of pointers #### In general, there is no relative ordering among pointers. As a result, when unordered containers like sets and maps are used with pointer keys the iteration order is undefined. Hence, iterating such containers may result in non-deterministic code generation. While the generated code might not necessarily be "wrong code", this non-determinism might result in unexpected runtime crashes or simply hard to reproduce bugs on the customer side making it harder to debug and fix. As a rule of thumb, in case an ordered result is expected, remember to sort an unordered container before iteration. Or use ordered containers like `std::vector` if you want to iterate pointer keys. #### Beware of non-deterministic sorting order of equal elements #### `std::sort` uses a non-stable sorting algorithm in which the order of equal elements is not guaranteed to be preserved. Thus using `std::sort` for a container having equal elements may result in non-determinstic behaviour. ## Style Issues ## ### The High-Level Issues ### #### Self-contained Headers #### Header files should be self-contained (compile on their own) and end in `.h`. Non-header files that are meant for inclusion should end in `.inc` and be used sparingly. All header files should be self-contained. Users and refactoring tools should not have to adhere to special conditions to include the header. Specifically, a header should have header guards and include all other headers it needs. There are rare cases where a file designed to be included is not self-contained. These are typically intended to be included at unusual locations, such as the middle of another file. They might not use header guards, and might not include their prerequisites. Name such files with the `.inc` extension. Use sparingly, and prefer self-contained headers when possible. #### `#include` as Little as Possible #### `#include` hurts compile time performance. Don't do it unless you have to, especially in header files. But wait! Sometimes you need to have the definition of a class to use it, or to inherit from it. In these cases go ahead and `#include` that header file. Be aware however that there are many cases where you don't need to have the full definition of a class. If you are using a pointer or reference to a class, you don't need the header file. If you are simply returning a class instance from a prototyped function or method, you don't need it. In fact, for most cases, you simply don't need the definition of a class. And not `#include`ing speeds up compilation. It is easy to try to go too overboard on this recommendation, however. You **must** include all of the header files that you are using — you can include them either directly or indirectly through another header file. To make sure that you don't accidentally forget to include a header file in your module header, make sure to include your module header **first** in the implementation file (as mentioned above). This way there won't be any hidden dependencies that you'll find out about later. The tool [IWYU][iwyu] can help with this, but it generates a lot of false positives, so we don't automate it. In many cases, header files with SpiderMonkey types will only need to include one SpiderMonkey header, ``, unless they have inline functions or SpiderMonkey member types. This header file contains a number of forward declarations and nothing else. [iwyu]: https://include-what-you-use.org/ #### Header inclusion order #### Headers should be included in the following order: - `` - C system headers - C++ system headers - GNOME library headers - SpiderMonkey library headers - GJS headers Each of these groups must be separated by blank lines. Within each group, all the headers should be alphabetized. The first five groups should use angle brackets for the includes. Note that the header `` must be included before any SpiderMonkey headers. GJS headers should use quotes, _except_ in public header files (any header file included from ``.) If you need to include headers conditionally, add the conditional after the group that it belongs to, separated by a blank line. If it is not obvious, you may add a comment after the include, explaining what this header is included for. This makes it easier to figure out whether to remove a header later if its functionality is no longer used in the file. Here is an example of all of the above rules together: ```c++ #include // for ENABLE_CAIRO #include // for strlen #ifdef _WIN32 # define WIN32_LEAN_AND_MEAN # include #endif #include // for codecvt_utf8_utf16 #include // for wstring_convert #include #include #include #include // for GCHashMap #include // for JS_New, JSAutoRealm, JS_GetProperty #include #include "gjs/atoms.h" #include "gjs/context-private.h" #include "gjs/jsapi-util.h" ``` #### Keep "Internal" Headers Private #### Many modules have a complex implementation that causes them to use more than one implementation (`.cpp`) file. It is often tempting to put the internal communication interface (helper classes, extra functions, etc.) in the public module header file. Don't do this! If you really need to do something like this, put a private header file in the same directory as the source files, and include it locally. This ensures that your private interface remains private and undisturbed by outsiders. It's okay to put extra implementation methods in a public class itself. Just make them private (or protected) and all is well. #### Use Early Exits and `continue` to Simplify Code #### When reading code, keep in mind how much state and how many previous decisions have to be remembered by the reader to understand a block of code. Aim to reduce indentation where possible when it doesn't make it more difficult to understand the code. One great way to do this is by making use of early exits and the `continue` keyword in long loops. As an example of using an early exit from a function, consider this "bad" code: ```c++ Value* do_something(Instruction* in) { if (!is_a(in) && in->has_one_use() && do_other_thing(in)) { ... some long code.... } return nullptr; } ``` This code has several problems if the body of the `if` is large. When you're looking at the top of the function, it isn't immediately clear that this *only* does interesting things with non-terminator instructions, and only applies to things with the other predicates. Second, it is relatively difficult to describe (in comments) why these predicates are important because the `if` statement makes it difficult to lay out the comments. Third, when you're deep within the body of the code, it is indented an extra level. Finally, when reading the top of the function, it isn't clear what the result is if the predicate isn't true; you have to read to the end of the function to know that it returns null. It is much preferred to format the code like this: ```c++ Value* do_something(Instruction* in) { // Terminators never need 'something' done to them because ... if (is_a(in)) return nullptr; // We conservatively avoid transforming instructions with multiple uses // because goats like cheese. if (!in->has_one_use()) return nullptr; // This is really just here for example. if (!do_other_thing(in)) return nullptr; ... some long code.... } ``` This fixes these problems. A similar problem frequently happens in `for` loops. A silly example is something like this: ```c++ for (Instruction& in : bb) { if (auto* bo = dyn_cast(&in)) { Value* lhs = bo->get_operand(0); Value* rhs = bo->get_operand(1); if (lhs != rhs) { ... } } } ``` When you have very small loops, this sort of structure is fine. But if it exceeds more than 10-15 lines, it becomes difficult for people to read and understand at a glance. The problem with this sort of code is that it gets very nested very quickly, meaning that the reader of the code has to keep a lot of context in their brain to remember what is going immediately on in the loop, because they don't know if/when the `if` conditions will have `else`s etc. It is strongly preferred to structure the loop like this: ```c++ for (Instruction& in : bb) { auto* bo = dyn_cast(&in); if (!bo) continue; Value* lhs = bo->get_operand(0); Value* rhs = bo->get_operand(1); if (lhs == rhs) continue; ... } ``` This has all the benefits of using early exits for functions: it reduces nesting of the loop, it makes it easier to describe why the conditions are true, and it makes it obvious to the reader that there is no `else` coming up that they have to push context into their brain for. If a loop is large, this can be a big understandability win. #### Don't use `else` after a `return` #### For similar reasons above (reduction of indentation and easier reading), please do not use `else` or `else if` after something that interrupts control flow — like `return`, `break`, `continue`, `goto`, etc. For example, this is *bad*: ```c++ case 'J': { if (is_signed) { type = cx.getsigjmp_buf_type(); if (type.is_null()) { error = ASTContext::ge_missing_sigjmp_buf; return QualType(); } else { break; } } else { type = cx.getjmp_buf_type(); if (type.is_null()) { error = ASTContext::ge_missing_jmp_buf; return QualType(); } else { break; } } } ``` It is better to write it like this: ```c++ case 'J': if (is_signed) { type = cx.getsigjmp_buf_type(); if (type.is_null()) { error = ASTContext::ge_missing_sigjmp_buf; return QualType(); } } else { type = cx.getjmp_buf_type(); if (type.is_null()) { error = ASTContext::ge_missing_jmp_buf; return QualType(); } } break; ``` Or better yet (in this case) as: ```c++ case 'J': if (is_signed) type = cx.getsigjmp_buf_type(); else type = cx.getjmp_buf_type(); if (type.is_null()) { error = is_signed ? ASTContext::ge_missing_sigjmp_buf : ASTContext::ge_missing_jmp_buf; return QualType(); } break; ``` The idea is to reduce indentation and the amount of code you have to keep track of when reading the code. #### Turn Predicate Loops into Predicate Functions ##### It is very common to write small loops that just compute a boolean value. There are a number of ways that people commonly write these, but an example of this sort of thing is: ```c++ bool found_foo = false; for (unsigned ix = 0, len = bar_list.size(); ix != len; ++ix) if (bar_list[ix]->is_foo()) { found_foo = true; break; } if (found_foo) { ... } ``` This sort of code is awkward to write, and is almost always a bad sign. Instead of this sort of loop, we strongly prefer to use a predicate function (which may be `static`) that uses early exits to compute the predicate. We prefer the code to be structured like this: ```c++ /* Helper function: returns true if the specified list has an element that is * a foo. */ static bool contains_foo(const std::vector &list) { for (unsigned ix = 0, len = list.size(); ix != len; ++ix) if (list[ix]->is_foo()) return true; return false; } ... if (contains_foo(bar_list)) { ... } ``` There are many reasons for doing this: it reduces indentation and factors out code which can often be shared by other code that checks for the same predicate. More importantly, it *forces you to pick a name* for the function, and forces you to write a comment for it. In this silly example, this doesn't add much value. However, if the condition is complex, this can make it a lot easier for the reader to understand the code that queries for this predicate. Instead of being faced with the in-line details of how we check to see if the `bar_list` contains a foo, we can trust the function name and continue reading with better locality. ### The Low-Level Issues ### #### Name Types, Functions, Variables, and Enumerators Properly #### Poorly-chosen names can mislead the reader and cause bugs. We cannot stress enough how important it is to use *descriptive* names. Pick names that match the semantics and role of the underlying entities, within reason. Avoid abbreviations unless they are well known. After picking a good name, make sure to use consistent capitalization for the name, as inconsistency requires clients to either memorize the APIs or to look it up to find the exact spelling. Different kinds of declarations have different rules: * **Type names** (including classes, structs, enums, typedefs, etc.) should be nouns and should be named in camel case, starting with an upper-case letter (e.g. `ObjectInstance`). * **Variable names** should be nouns (as they represent state). The name should be snake case (e.g. `count` or `new_param`). Private member variables should start with `m_` to distinguish them from local variables representing the same thing. * **Function names** should be verb phrases (as they represent actions), and command-like function should be imperative. The name should be snake case (e.g. `open_file()` or `is_foo()`). * **Enum declarations** (e.g. `enum Foo {...}`) are types, so they should follow the naming conventions for types. A common use for enums is as a discriminator for a union, or an indicator of a subclass. When an enum is used for something like this, it should have a `Kind` suffix (e.g. `ValueKind`). * **Enumerators** (e.g. `enum { Foo, Bar }`) and **public member variables** should start with an upper-case letter, just like types. Unless the enumerators are defined in their own small namespace or inside a class, enumerators should have a prefix corresponding to the enum declaration name. For example, `enum ValueKind { ... };` may contain enumerators like `VK_Argument`, `VK_BasicBlock`, etc. Enumerators that are just convenience constants are exempt from the requirement for a prefix. For instance: ```c++ enum { MaxSize = 42, Density = 12 }; ``` Here are some examples of good and bad names: ```c++ class VehicleMaker { ... Factory m_f; // Bad -- abbreviation and non-descriptive. Factory m_factory; // Better. Factory m_tire_factory; // Even better -- if VehicleMaker has more // than one kind of factories. }; Vehicle make_vehicle(VehicleType Type) { VehicleMaker m; // Might be OK if having a short life-span. Tire tmp1 = m.make_tire(); // Bad -- 'Tmp1' provides no information. Light headlight = m.make_light("head"); // Good -- descriptive. ... } ``` #### Assert Liberally #### Use the `g_assert()` macro to its fullest. Check all of your preconditions and assumptions, you never know when a bug (not necessarily even yours) might be caught early by an assertion, which reduces debugging time dramatically. To further assist with debugging, usually you should put some kind of error message in the assertion statement, which is printed if the assertion is tripped. This helps the poor debugger make sense of why an assertion is being made and enforced, and hopefully what to do about it. Here is one complete example: ```c++ inline Value* get_operand(unsigned ix) { g_assert(ix < operands.size() && "get_operand() out of range!"); return operands[ix]; } ``` To indicate a piece of code that should not be reached, use `g_assert_not_reached()`. When assertions are enabled, this will print the message if it's ever reached and then exit the program. When assertions are disabled (i.e. in release builds), `g_assert_not_reached()` becomes a hint to compilers to skip generating code for this branch. If the compiler does not support this, it will fall back to the `abort()` implementation. Neither assertions or `g_assert_not_reached()` will abort the program on a release build. If the error condition can be triggered by user input then the recoverable error mechanism of `GError*` should be used instead. In cases where this is not practical, either use `g_critical()` and continue execution as best as possible, or use `g_error()` to abort with a fatal error. Another issue is that values used only by assertions will produce an "unused value" warning when assertions are disabled. For example, this code will warn: ```c++ unsigned size = v.size(); g_assert(size > 42 && "Vector smaller than it should be"); bool new_to_set = my_set.insert(value); g_assert(new_to_set && "The value shouldn't be in the set yet"); ``` These are two interesting different cases. In the first case, the call to `v.size()` is only useful for the assert, and we don't want it executed when assertions are disabled. Code like this should move the call into the assert itself. In the second case, the side effects of the call must happen whether the assert is enabled or not. In this case, the value should be cast to void to disable the warning. To be specific, it is preferred to write the code like this: ```c++ g_assert(v.size() > 42 && "Vector smaller than it should be"); bool new_to_set = my_set.insert(value); (void)new_to_set; g_assert(new_to_set && "The value shouldn't be in the set yet"); ``` #### Do Not Use `using namespace std` #### In GJS, we prefer to explicitly prefix all identifiers from the standard namespace with an `std::` prefix, rather than rely on `using namespace std;`. In header files, adding a `using namespace XXX` directive pollutes the namespace of any source file that `#include`s the header. This is clearly a bad thing. In implementation files (e.g. `.cpp` files), the rule is more of a stylistic rule, but is still important. Basically, using explicit namespace prefixes makes the code **clearer**, because it is immediately obvious what facilities are being used and where they are coming from. And **more portable**, because namespace clashes cannot occur between LLVM code and other namespaces. The portability rule is important because different standard library implementations expose different symbols (potentially ones they shouldn't), and future revisions to the C++ standard will add more symbols to the `std` namespace. As such, we never use `using namespace std;` in GJS. The exception to the general rule (i.e. it's not an exception for the `std` namespace) is for implementation files. For example, in the future we might decide to put GJS code inside a `Gjs` namespace. In that case, it is OK, and actually clearer, for the `.cpp` files to have a `using namespace Gjs;` directive at the top, after the `#include`s. This reduces indentation in the body of the file for source editors that indent based on braces, and keeps the conceptual context cleaner. The general form of this rule is that any `.cpp` file that implements code in any namespace may use that namespace (and its parents'), but should not use any others. #### Provide a Virtual Method Anchor for Classes in Headers #### If a class is defined in a header file and has a vtable (either it has virtual methods or it derives from classes with virtual methods), it must always have at least one out-of-line virtual method in the class. Without this, the compiler will copy the vtable and RTTI into every `.o` file that `#include`s the header, bloating `.o` file sizes and increasing link times. #### Don't use default labels in fully covered switches over enumerations #### `-Wswitch` warns if a switch, without a default label, over an enumeration, does not cover every enumeration value. If you write a default label on a fully covered switch over an enumeration then the `-Wswitch` warning won't fire when new elements are added to that enumeration. To help avoid adding these kinds of defaults, Clang has the warning `-Wcovered-switch-default`. A knock-on effect of this stylistic requirement is that when building GJS with GCC you may get warnings related to "control may reach end of non-void function" if you return from each case of a covered switch-over-enum because GCC assumes that the enum expression may take any representable value, not just those of individual enumerators. To suppress this warning, use `g_assert_not_reached()` after the switch. #### Use range-based `for` loops wherever possible #### The introduction of range-based `for` loops in C++11 means that explicit manipulation of iterators is rarely necessary. We use range-based `for` loops wherever possible for all newly added code. For example: ```c++ for (GClosure* closure : m_closures) ... use closure ...; ``` #### Don't evaluate `end()` every time through a loop #### In cases where range-based `for` loops can't be used and it is necessary to write an explicit iterator-based loop, pay close attention to whether `end()` is re-evaluted on each loop iteration. One common mistake is to write a loop in this style: ```c++ for (auto* closure = m_closures->begin(); closure != m_closures->end(); ++closure) ... use closure ... ``` The problem with this construct is that it evaluates `m_closures->end()` every time through the loop. Instead of writing the loop like this, we strongly prefer loops to be written so that they evaluate it once before the loop starts. A convenient way to do this is like so: ```c++ for (auto* closure = m_closures->begin(), end = m_closures->end(); closure != end; ++closure) ... use closure ... ``` The observant may quickly point out that these two loops may have different semantics: if the container is being mutated, then `m_closures->end()` may change its value every time through the loop and the second loop may not in fact be correct. If you actually do depend on this behavior, please write the loop in the first form and add a comment indicating that you did it intentionally. Why do we prefer the second form (when correct)? Writing the loop in the first form has two problems. First it may be less efficient than evaluating it at the start of the loop. In this case, the cost is probably minor — a few extra loads every time through the loop. However, if the base expression is more complex, then the cost can rise quickly. If the end expression was actually something like `some_map[x]->end()`, map lookups really aren't cheap. By writing it in the second form consistently, you eliminate the issue entirely and don't even have to think about it. The second (even bigger) issue is that writing the loop in the first form hints to the reader that the loop is mutating the container (which a comment would handily confirm!) If you write the loop in the second form, it is immediately obvious without even looking at the body of the loop that the container isn't being modified, which makes it easier to read the code and understand what it does. While the second form of the loop is a few extra keystrokes, we do strongly prefer it. #### Avoid `std::endl` #### The `std::endl` modifier, when used with `iostreams`, outputs a newline to the output stream specified. In addition to doing this, however, it also flushes the output stream. In other words, these are equivalent: ```c++ std::cout << std::endl; std::cout << '\n' << std::flush; ``` Most of the time, you probably have no reason to flush the output stream, so it's better to use a literal `'\n'`. #### Don't use `inline` when defining a function in a class definition #### A member function defined in a class definition is implicitly inline, so don't put the `inline` keyword in this case. Don't: ```c++ class Foo { public: inline void bar() { // ... } }; ``` Do: ```c++ class Foo { public: void bar() { // ... } }; ``` cjs-5.2.0/doc/SpiderMonkey_Memory.md0000644000175000017500000001542314144444702017474 0ustar jpeisachjpeisach# Memory management in SpiderMonkey # When writing JavaScript extensions in C++, we have to understand and be careful about memory management. This document only applies to C++ code using the jsapi.h API. If you simply write a GObject-style library and describe it via gobject-introspection typelib, there is no need to understand garbage collection details. ## Mark-and-sweep collector ## As background, SpiderMonkey uses mark-and-sweep garbage collection. (see [this page][1] for one explanation, if not familiar with this.) This is a good approach for "embeddable" interpreters, because unlike say the Boehm GC, it doesn't rely on any weird hacks like scanning the entire memory or stack of the process. The collector only has to know about stuff that the language runtime created itself. Also, mark-and-sweep is simple to understand when working with the embedding API. ## Representation of objects ## An object has two forms. * `JS::Value` is a type-tagged version, think of `GValue` (though it is much more efficient) * inside a `JS::Value` can be one of: a 32-bit integer, a boolean, a double, a `JSString*`, a `JS::Symbol*`, or a `JSObject*`. `JS::Value` is a 64 bits-wide union. Some of the bits are a type tag. However, don't rely on the layout of `JS::Value`, as it may change between API versions. You check the type tag with the methods `val.isObject()`, `val.isInt32()`, `val.isDouble()`, `val.isString()`, `val.isBoolean()`, `val.isSymbol()`. Use `val.isNull()` and `val.isUndefined()` rather than comparing `val == JSVAL_NULL` and `val == JSVAL_VOID` to avoid an extra memory access. null does not count as an object, so `val.isObject()` does not return true for null. This contrasts with the behavior of `JSVAL_IS_OBJECT(val)`, which was the previous API, but this was changed because the object-or-null behavior was a source of bugs. If you still want this behaviour use `val.isObjectOrNull()`. The methods `val.toObject()`, `val.toInt32()`, etc. are just accessing the appropriate members of the union. The jsapi.h header is pretty readable, if you want to learn more. Types you see in there not mentioned above, such as `JSFunction*`, would show up as an object - `val.isObject()` would return true. From a `JS::Value` perspective, everything is one of object, string, symbol, double, int, boolean, null, or undefined. ## Value types vs. allocated types; "gcthing" ## For integers, booleans, doubles, null, and undefined there is no pointer. The value is just part of the `JS::Value` union. So there is no way to "free" these, and no way for them to be finalized or become dangling. The importance is: these types just get ignored by the garbage collector. However, strings, symbols, and objects are all allocated pointers that get finalized eventually. These are what garbage collection applies to. The API refers to these allocated types as "GC things." The macro `val.toGCThing()` returns the value part of the union as a pointer. `val.isGCThing()` returns true for string, object, symbol, null; and false for void, boolean, double, integer. ## Tracing ## The general rule is that SpiderMonkey has a set of GC roots. To do the garbage collection, it finds all objects accessible from those roots, and finalizes all objects that are not. So if you have a `JS::Value` or `JSObject*`/`JSString*`/`JSFunction*`/`JS::Symbol*` somewhere that is not reachable from one of SpiderMonkey's GC roots - say, declared on the stack or in the private data of an object - that will not be found. SpiderMonkey may try to finalize this object even though you have a reference to it. If you reference JavaScript objects from your custom object, you have to use `JS::Heap` and set the `JSCLASS_MARK_IS_TRACE` flag in your JSClass, and define a trace function in the class struct. A trace function just invokes `JS::TraceEdge()` to tell SpiderMonkey about any objects you reference. See [JSTraceOp docs][2]. Tracing doesn't add a GC thing to the GC root set! It just notifies the interpreter that a thing is reachable from another thing. ## Global roots ## The GC roots include anything you have declared with `JS::Rooted` and the global object set on each `JSContext*`. You can also manually add roots with [`JS::PersistentRooted()`][3]. Anything reachable from any of these root objects will not be collected. `JS::PersistentRooted` pins an object in memory forever until it is destructed, so be careful of leaks. Basically `JS::PersistentRooted` changes memory management of an object to manual mode. Note that the wrapped T in `JS::PersistentRooted` is the location of your value, not the value itself. That is, a `JSObject**` or `JS::Value*`. Some implications are: * the location can't go away (don't use a stack address that will vanish before the `JS::PersistentRooted` is destructed, for example) * the root is keeping "whatever is at the location" from being collected, not "whatever was originally at the location" ## Local roots ## Here is the trickier part. If you create an object, say: ```c++ JSObject *obj = JS_New(cx, whatever, ...); ``` `obj` is NOT now referenced by any other object. If the GC ran right away, `obj` would be collected. This is what `JS::Rooted` is for, and its specializations `JS::RootedValue`, `JS::RootedObject`, etc. `JS::Rooted` adds its wrapped `T` to the GC root set, and removes it when the `JS::Rooted` goes out of scope. Note that `JS::Rooted` can only be used on the stack. For optimization reasons, roots that are added with `JS::Rooted` must be removed in LIFO order, and the stack scoping ensures that. Any SpiderMonkey APIs that can cause a garbage collection will force you to use `JS:Rooted` by taking a `JS::Handle` instead of a bare GC thing. `JS::Handle` can only be created from `JS::Rooted. So instead of the above code, you would write ```c++ JS::RootedObject obj(cx, JS_New(cx, whatever, ...)); ``` ### JSFunctionSpec and extra local roots ### When SpiderMonkey is calling a native function, it will pass in an argv of `JS::Value`. It already has to add all the argv values as GC roots. The "extra local roots" feature tells SpiderMonkey to stick some extra slots on the end of argv that are also GC roots. You can then assign to `argv[MAX(min_args, actual_argc)]` and whatever you put in there won't get garbage collected. This is kind of a confusing and unreadable hack IMO, though it is probably efficient and thus justified in certain cases. I don't know really. ## More tips ## For another attempt to explain all this, see [Rooting Guide from Mozilla.org][4]. [1] http://www.brpreiss.com/books/opus5/html/page424.html [2] http://developer.mozilla.org/en/docs/JSTraceOp [3] https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/JSAPI_reference/JS::PersistentRooted [4] https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/GC_Rooting_Guide GC cjs-5.2.0/doc/ByteArray.md0000644000175000017500000000337014144444702015433 0ustar jpeisachjpeisachThe `imports.byteArray` module was originally based on an ECMAScript 4 proposal that was never adopted. Now that ES6 has typed arrays, we use `Uint8Array` to represent byte arrays in GJS and add some extra functions for conversion to and from strings and `GLib.Bytes`. Unlike the old custom `ByteArray`, `Uint8Array` is not resizable. The main goal for most gjs users will be to shovel bytes between various C APIs, for example reading from an IO stream and then pushing the bytes into a parser. Actually manipulating bytes in JS is likely to be pretty rare, and slow ... an advantage of the gjs/gobject-introspection setup is that stuff best done in C, like messing with bytes, can be done in C. --- ## ByteArray Functions ## The ByteArray module has the following functions: ### `fromString(s:String, encoding:String):Uint8Array` ### Convert a String into a newly constructed `Uint8Array`; this creates a new `Uint8Array` of the same length as the String, then assigns each `Uint8Array` entry the corresponding byte value of the String encoded according to the given encoding (or UTF-8 if not given). ### `toString(a:Uint8Array, encoding:String):String` ### Converts the `Uint8Array` into a literal string. The bytes are interpreted according to the given encoding (or UTF-8 if not given). The resulting string is guaranteed to round-trip back into an identical ByteArray by passing the result to `ByteArray.fromString()`, i.e., `b === ByteArray.fromString(ByteArray.toString(b, encoding), encoding)`. ### `fromGBytes(b:GLib.Bytes):Uint8Array` ### Convert a `GLib.Bytes` instance into a newly constructed `Uint8Array`. The contents are copied. ### `toGBytes(a:Uint8Array):GLib.Bytes` ### Converts the `Uint8Array` into a `GLib.Bytes` instance. The contents are copied. cjs-5.2.0/test/0000755000175000017500000000000014144444702013416 5ustar jpeisachjpeisachcjs-5.2.0/test/gjs-test-coverage/0000755000175000017500000000000014144444702016747 5ustar jpeisachjpeisachcjs-5.2.0/test/gjs-test-coverage/loadedJSFromResource.js0000644000175000017500000000006714144444702023331 0ustar jpeisachjpeisach/* exported mockFunction */ function mockFunction() {} cjs-5.2.0/test/gjs-tests.cpp0000644000175000017500000004057114144444702016054 0ustar jpeisachjpeisach/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* * Copyright (c) 2008 litl, LLC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #include #include #include // for size_t, strlen #include // for u16string, u32string #include #include #include // for g_unlink #include #include #include #include #include // for UniqueChars #include #include #include #include // for JSProto_Number #include "gi/arg-inl.h" #include "cjs/context.h" #include "cjs/error-types.h" #include "cjs/jsapi-util.h" #include "cjs/profiler.h" #include "test/gjs-test-no-introspection-object.h" #include "test/gjs-test-utils.h" #include "util/misc.h" // COMPAT: https://gitlab.gnome.org/GNOME/glib/-/merge_requests/1553 #ifdef __clang_analyzer__ void g_assertion_message(const char*, const char*, int, const char*, const char*) __attribute__((analyzer_noreturn)); #endif #define VALID_UTF8_STRING "\303\211\303\226 foobar \343\203\237" static void gjstest_test_func_gjs_context_construct_destroy(void) { GjsContext *context; /* Construct twice just to possibly a case where global state from * the first leaks. */ context = gjs_context_new (); g_object_unref (context); context = gjs_context_new (); g_object_unref (context); } static void gjstest_test_func_gjs_context_construct_eval(void) { GjsContext *context; int estatus; GError *error = NULL; context = gjs_context_new (); if (!gjs_context_eval (context, "1+1", -1, "", &estatus, &error)) g_error ("%s", error->message); g_object_unref (context); } static void gjstest_test_func_gjs_context_eval_non_zero_terminated(void) { GjsAutoUnref gjs = gjs_context_new(); GError* error = NULL; int status; // This string is invalid JS if it is treated as zero-terminated bool ok = gjs_context_eval(gjs, "77!", 2, "", &status, &error); g_assert_true(ok); g_assert_no_error(error); g_assert_cmpint(status, ==, 77); } static void gjstest_test_func_gjs_context_exit(void) { GjsContext *context = gjs_context_new(); GError *error = NULL; int status; bool ok = gjs_context_eval(context, "imports.system.exit(0);", -1, "", &status, &error); g_assert_false(ok); g_assert_error(error, GJS_ERROR, GJS_ERROR_SYSTEM_EXIT); g_assert_cmpuint(status, ==, 0); g_clear_error(&error); ok = gjs_context_eval(context, "imports.system.exit(42);", -1, "", &status, &error); g_assert_false(ok); g_assert_error(error, GJS_ERROR, GJS_ERROR_SYSTEM_EXIT); g_assert_cmpuint(status, ==, 42); g_clear_error(&error); g_object_unref(context); } #define JS_CLASS "\ const GObject = imports.gi.GObject; \ const FooBar = GObject.registerClass(class FooBar extends GObject.Object {}); \ " static void gjstest_test_func_gjs_gobject_js_defined_type(void) { GjsContext *context = gjs_context_new(); GError *error = NULL; int status; bool ok = gjs_context_eval(context, JS_CLASS, -1, "", &status, &error); g_assert_no_error(error); g_assert_true(ok); GType foo_type = g_type_from_name("Gjs_FooBar"); g_assert_cmpuint(foo_type, !=, G_TYPE_INVALID); gpointer foo = g_object_new(foo_type, NULL); g_assert_true(G_IS_OBJECT(foo)); g_object_unref(foo); g_object_unref(context); } static void gjstest_test_func_gjs_gobject_without_introspection(void) { GjsAutoUnref context = gjs_context_new(); GError* error = nullptr; int status; /* Ensure class */ g_type_class_ref(GJSTEST_TYPE_NO_INTROSPECTION_OBJECT); #define TESTJS \ "const {GObject} = imports.gi;" \ "var obj = GObject.Object.newv(" \ " GObject.type_from_name('GjsTestNoIntrospectionObject'), []);" \ "obj.a_int = 1234;" bool ok = gjs_context_eval(context, TESTJS, -1, "", &status, &error); g_assert_true(ok); g_assert_no_error(error); GjsTestNoIntrospectionObject* obj = gjstest_no_introspection_object_peek(); g_assert_nonnull(obj); int val = 0; g_object_get(obj, "a-int", &val, NULL); g_assert_cmpint(val, ==, 1234); #undef TESTJS } static void gjstest_test_func_gjs_jsapi_util_string_js_string_utf8( GjsUnitTestFixture* fx, const void*) { JS::RootedValue js_string(fx->cx); g_assert_true(gjs_string_from_utf8(fx->cx, VALID_UTF8_STRING, &js_string)); g_assert_true(js_string.isString()); JS::UniqueChars utf8_result = gjs_string_to_utf8(fx->cx, js_string); g_assert_nonnull(utf8_result); g_assert_cmpstr(VALID_UTF8_STRING, ==, utf8_result.get()); } static void gjstest_test_func_gjs_jsapi_util_error_throw(GjsUnitTestFixture* fx, const void*) { JS::RootedValue exc(fx->cx), value(fx->cx); /* Test that we can throw */ gjs_throw(fx->cx, "This is an exception %d", 42); g_assert_true(JS_IsExceptionPending(fx->cx)); JS_GetPendingException(fx->cx, &exc); g_assert_false(exc.isUndefined()); JS::RootedObject exc_obj(fx->cx, &exc.toObject()); JS_GetProperty(fx->cx, exc_obj, "message", &value); g_assert_true(value.isString()); JS::UniqueChars s = gjs_string_to_utf8(fx->cx, value); g_assert_nonnull(s); g_assert_cmpstr(s.get(), ==, "This is an exception 42"); /* keep this around before we clear it */ JS::RootedValue previous(fx->cx, exc); JS_ClearPendingException(fx->cx); g_assert_false(JS_IsExceptionPending(fx->cx)); /* Check that we don't overwrite a pending exception */ JS_SetPendingException(fx->cx, previous); g_assert_true(JS_IsExceptionPending(fx->cx)); gjs_throw(fx->cx, "Second different exception %s", "foo"); g_assert_true(JS_IsExceptionPending(fx->cx)); exc = JS::UndefinedValue(); JS_GetPendingException(fx->cx, &exc); g_assert_false(exc.isUndefined()); g_assert_true(&exc.toObject() == &previous.toObject()); } static void test_jsapi_util_string_utf8_nchars_to_js(GjsUnitTestFixture* fx, const void*) { JS::RootedValue v_out(fx->cx); bool ok = gjs_string_from_utf8_n(fx->cx, VALID_UTF8_STRING, strlen(VALID_UTF8_STRING), &v_out); g_assert_true(ok); g_assert_true(v_out.isString()); } static void test_jsapi_util_string_char16_data(GjsUnitTestFixture* fx, const void*) { char16_t *chars; size_t len; JS::ConstUTF8CharsZ jschars(VALID_UTF8_STRING, strlen(VALID_UTF8_STRING)); JS::RootedString str(fx->cx, JS_NewStringCopyUTF8Z(fx->cx, jschars)); g_assert_true(gjs_string_get_char16_data(fx->cx, str, &chars, &len)); std::u16string result(chars, len); g_assert_true(result == u"\xc9\xd6 foobar \u30df"); g_free(chars); /* Try with a string that is likely to be stored as Latin-1 */ str = JS_NewStringCopyZ(fx->cx, "abcd"); g_assert_true(gjs_string_get_char16_data(fx->cx, str, &chars, &len)); result.assign(chars, len); g_assert_true(result == u"abcd"); g_free(chars); } static void test_jsapi_util_string_to_ucs4(GjsUnitTestFixture* fx, const void*) { gunichar *chars; size_t len; JS::ConstUTF8CharsZ jschars(VALID_UTF8_STRING, strlen(VALID_UTF8_STRING)); JS::RootedString str(fx->cx, JS_NewStringCopyUTF8Z(fx->cx, jschars)); g_assert_true(gjs_string_to_ucs4(fx->cx, str, &chars, &len)); std::u32string result(chars, chars + len); g_assert_true(result == U"\xc9\xd6 foobar \u30df"); g_free(chars); /* Try with a string that is likely to be stored as Latin-1 */ str = JS_NewStringCopyZ(fx->cx, "abcd"); g_assert_true(gjs_string_to_ucs4(fx->cx, str, &chars, &len)); result.assign(chars, chars + len); g_assert_true(result == U"abcd"); g_free(chars); } static void test_jsapi_util_debug_string_valid_utf8(GjsUnitTestFixture* fx, const void*) { JS::RootedValue v_string(fx->cx); g_assert_true(gjs_string_from_utf8(fx->cx, VALID_UTF8_STRING, &v_string)); char *debug_output = gjs_value_debug_string(fx->cx, v_string); g_assert_nonnull(debug_output); g_assert_cmpstr("\"" VALID_UTF8_STRING "\"", ==, debug_output); g_free(debug_output); } static void test_jsapi_util_debug_string_invalid_utf8(GjsUnitTestFixture* fx, const void*) { g_test_skip("SpiderMonkey doesn't validate UTF-8 after encoding it"); JS::RootedValue v_string(fx->cx); const char16_t invalid_unicode[] = { 0xffff, 0xffff }; v_string.setString(JS_NewUCStringCopyN(fx->cx, invalid_unicode, 2)); char *debug_output = gjs_value_debug_string(fx->cx, v_string); g_assert_nonnull(debug_output); /* g_assert_cmpstr("\"\\xff\\xff\\xff\\xff\"", ==, debug_output); */ g_free(debug_output); } static void test_jsapi_util_debug_string_object_with_complicated_to_string( GjsUnitTestFixture* fx, const void*) { const char16_t desserts[] = { 0xd83c, 0xdf6a, /* cookie */ 0xd83c, 0xdf69, /* doughnut */ }; JS::RootedValueArray<2> contents(fx->cx); contents[0].setString(JS_NewUCStringCopyN(fx->cx, desserts, 2)); contents[1].setString(JS_NewUCStringCopyN(fx->cx, desserts + 2, 2)); JS::RootedObject array(fx->cx, JS::NewArrayObject(fx->cx, contents)); JS::RootedValue v_array(fx->cx, JS::ObjectValue(*array)); char *debug_output = gjs_value_debug_string(fx->cx, v_array); g_assert_nonnull(debug_output); g_assert_cmpstr(u8"🍪,🍩", ==, debug_output); g_free(debug_output); } static void gjstest_test_func_util_misc_strv_concat_null(void) { char **ret; ret = gjs_g_strv_concat(NULL, 0); g_assert_nonnull(ret); g_assert_null(ret[0]); g_strfreev(ret); } static void gjstest_test_func_util_misc_strv_concat_pointers(void) { char *strv0[2] = {(char*)"foo", NULL}; char *strv1[1] = {NULL}; char **strv2 = NULL; char *strv3[2] = {(char*)"bar", NULL}; char **stuff[4]; char **ret; stuff[0] = strv0; stuff[1] = strv1; stuff[2] = strv2; stuff[3] = strv3; ret = gjs_g_strv_concat(stuff, 4); g_assert_nonnull(ret); g_assert_cmpstr(ret[0], ==, strv0[0]); /* same string */ g_assert_true(ret[0] != strv0[0]); // different pointer g_assert_cmpstr(ret[1], ==, strv3[0]); g_assert_true(ret[1] != strv3[0]); g_assert_null(ret[2]); g_strfreev(ret); } static void gjstest_test_profiler_start_stop(void) { GjsAutoUnref context = static_cast(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"); gjs_profiler_start(profiler); for (size_t ix = 0; ix < 100; ix++) { GError *error = nullptr; int estatus; #define TESTJS "[1,5,7,1,2,3,67,8].sort()" if (!gjs_context_eval(context, TESTJS, -1, "", &estatus, &error)) g_printerr("ERROR: %s", error->message); #undef TESTJS } gjs_profiler_stop(profiler); if (g_unlink("dont-conflict-with-other-test.syscap") != 0) g_message("Temp profiler file not deleted"); } static void gjstest_test_safe_integer_max(GjsUnitTestFixture* fx, const void*) { JS::RootedObject number_class_object(fx->cx); JS::RootedValue safe_value(fx->cx); g_assert_true( JS_GetClassObject(fx->cx, JSProto_Number, &number_class_object)); g_assert_true(JS_GetProperty(fx->cx, number_class_object, "MAX_SAFE_INTEGER", &safe_value)); g_assert_cmpint(safe_value.toNumber(), ==, max_safe_big_number()); } static void gjstest_test_safe_integer_min(GjsUnitTestFixture* fx, const void*) { JS::RootedObject number_class_object(fx->cx); JS::RootedValue safe_value(fx->cx); g_assert_true( JS_GetClassObject(fx->cx, JSProto_Number, &number_class_object)); g_assert_true(JS_GetProperty(fx->cx, number_class_object, "MIN_SAFE_INTEGER", &safe_value)); g_assert_cmpint(safe_value.toNumber(), ==, min_safe_big_number()); } int main(int argc, char **argv) { /* Avoid interference in the tests from stray environment variable */ g_unsetenv("GJS_ENABLE_PROFILER"); g_unsetenv("GJS_TRACE_FD"); g_test_init(&argc, &argv, NULL); g_test_add_func("/gjs/context/construct/destroy", gjstest_test_func_gjs_context_construct_destroy); g_test_add_func("/gjs/context/construct/eval", gjstest_test_func_gjs_context_construct_eval); g_test_add_func("/gjs/context/eval/non-zero-terminated", gjstest_test_func_gjs_context_eval_non_zero_terminated); g_test_add_func("/gjs/context/exit", gjstest_test_func_gjs_context_exit); g_test_add_func("/gjs/gobject/js_defined_type", gjstest_test_func_gjs_gobject_js_defined_type); g_test_add_func("/gjs/gobject/without_introspection", gjstest_test_func_gjs_gobject_without_introspection); g_test_add_func("/gjs/profiler/start_stop", gjstest_test_profiler_start_stop); g_test_add_func("/util/misc/strv/concat/null", gjstest_test_func_util_misc_strv_concat_null); g_test_add_func("/util/misc/strv/concat/pointers", gjstest_test_func_util_misc_strv_concat_pointers); #define ADD_JSAPI_UTIL_TEST(path, func) \ g_test_add("/gjs/jsapi/util/" path, GjsUnitTestFixture, NULL, \ gjs_unit_test_fixture_setup, func, \ gjs_unit_test_fixture_teardown) ADD_JSAPI_UTIL_TEST("error/throw", gjstest_test_func_gjs_jsapi_util_error_throw); ADD_JSAPI_UTIL_TEST("string/js/string/utf8", gjstest_test_func_gjs_jsapi_util_string_js_string_utf8); ADD_JSAPI_UTIL_TEST("string/utf8-nchars-to-js", test_jsapi_util_string_utf8_nchars_to_js); ADD_JSAPI_UTIL_TEST("string/char16_data", test_jsapi_util_string_char16_data); ADD_JSAPI_UTIL_TEST("string/to_ucs4", test_jsapi_util_string_to_ucs4); ADD_JSAPI_UTIL_TEST("debug_string/valid-utf8", test_jsapi_util_debug_string_valid_utf8); ADD_JSAPI_UTIL_TEST("debug_string/invalid-utf8", test_jsapi_util_debug_string_invalid_utf8); ADD_JSAPI_UTIL_TEST("debug_string/object-with-complicated-to-string", test_jsapi_util_debug_string_object_with_complicated_to_string); ADD_JSAPI_UTIL_TEST("gi/args/safe-integer/max", gjstest_test_safe_integer_max); ADD_JSAPI_UTIL_TEST("gi/args/safe-integer/min", gjstest_test_safe_integer_min); #undef ADD_JSAPI_UTIL_TEST gjs_test_add_tests_for_coverage (); gjs_test_add_tests_for_parse_call_args(); gjs_test_add_tests_for_rooting(); g_test_run(); return 0; } cjs-5.2.0/test/gjs-test-no-introspection-object.cpp0000644000175000017500000000663514144444702022450 0ustar jpeisachjpeisach/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* * Copyright © 2020 Endless Mobile Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #include "test/gjs-test-no-introspection-object.h" struct _GjsTestNoIntrospectionObject { GObject parent_instance; int a_int; }; G_DEFINE_TYPE(GjsTestNoIntrospectionObject, gjstest_no_introspection_object, G_TYPE_OBJECT) static GjsTestNoIntrospectionObject* last_object = NULL; static void gjstest_no_introspection_object_init( GjsTestNoIntrospectionObject* self) { self->a_int = 0; last_object = self; } static void gjstest_no_introspection_object_set_property(GObject* object, unsigned prop_id, const GValue* value, GParamSpec* pspec) { GjsTestNoIntrospectionObject* self = GJSTEST_NO_INTROSPECTION_OBJECT(object); switch (prop_id) { case 1: self->a_int = g_value_get_int(value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void gjstest_no_introspection_object_get_property(GObject* object, unsigned prop_id, GValue* value, GParamSpec* pspec) { GjsTestNoIntrospectionObject* self = GJSTEST_NO_INTROSPECTION_OBJECT(object); switch (prop_id) { case 1: g_value_set_int(value, self->a_int); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void gjstest_no_introspection_object_class_init( GjsTestNoIntrospectionObjectClass* klass) { GObjectClass* object_class = G_OBJECT_CLASS(klass); object_class->set_property = gjstest_no_introspection_object_set_property; object_class->get_property = gjstest_no_introspection_object_get_property; g_object_class_install_property( object_class, 1, g_param_spec_int("a-int", "An integer", "An integer property", 0, 100000000, 0, G_PARAM_READWRITE)); } GjsTestNoIntrospectionObject* gjstest_no_introspection_object_peek() { return last_object; } cjs-5.2.0/test/gjs-test-call-args.cpp0000644000175000017500000004352114144444702017532 0ustar jpeisachjpeisach#include #include #include // for strlen, strstr #include #include #include #include #include #include #include #include #include // for UniqueChars #include // for JS_DefineFunctions #include "cjs/jsapi-util-args.h" #include "cjs/jsapi-util.h" #include "test/gjs-test-common.h" #include "test/gjs-test-utils.h" namespace mozilla { union Utf8Unit; } // COMPAT: https://gitlab.gnome.org/GNOME/glib/-/merge_requests/1553 #ifdef __clang_analyzer__ void g_assertion_message(const char*, const char*, int, const char*, const char*) __attribute__((analyzer_noreturn)); #endif #define assert_match(str, pattern) \ G_STMT_START { \ const char *__s1 = (str), *__s2 = (pattern); \ if (!g_pattern_match_simple(__s2, __s1)) { \ g_assertion_message(G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \ "assertion failed (\"" #str \ "\" matches \"" #pattern "\")"); \ } \ } \ G_STMT_END typedef enum _test_enum { ZERO, ONE, TWO, THREE } test_enum_t; typedef enum _test_signed_enum { MINUS_THREE = -3, MINUS_TWO, MINUS_ONE } test_signed_enum_t; #define JSNATIVE_TEST_FUNC_BEGIN(name) \ static bool \ name(JSContext *cx, \ unsigned argc, \ JS::Value *vp) \ { \ JS::CallArgs args = JS::CallArgsFromVp(argc, vp); \ bool retval; #define JSNATIVE_TEST_FUNC_END \ if (retval) \ args.rval().setUndefined(); \ return retval; \ } JSNATIVE_TEST_FUNC_BEGIN(no_args) retval = gjs_parse_call_args(cx, "noArgs", args, ""); JSNATIVE_TEST_FUNC_END JSNATIVE_TEST_FUNC_BEGIN(no_args_ignore_trailing) retval = gjs_parse_call_args(cx, "noArgsIgnoreTrailing", args, "!"); JSNATIVE_TEST_FUNC_END #define JSNATIVE_NO_ASSERT_TYPE_TEST_FUNC(type, fmt) \ JSNATIVE_TEST_FUNC_BEGIN(type##_arg_no_assert) \ type val; \ retval = gjs_parse_call_args(cx, #type "ArgNoAssert", args, fmt, \ "val", &val); \ JSNATIVE_TEST_FUNC_END JSNATIVE_NO_ASSERT_TYPE_TEST_FUNC(bool, "b"); JSNATIVE_NO_ASSERT_TYPE_TEST_FUNC(int, "i"); #undef JSNATIVE_NO_ASSERT_TYPE_TEST_FUNC JSNATIVE_TEST_FUNC_BEGIN(object_arg_no_assert) JS::RootedObject val(cx); retval = gjs_parse_call_args(cx, "objectArgNoAssert", args, "o", "val", &val); JSNATIVE_TEST_FUNC_END JSNATIVE_TEST_FUNC_BEGIN(optional_int_args_no_assert) int val1, val2; retval = gjs_parse_call_args(cx, "optionalIntArgsNoAssert", args, "i|i", "val1", &val1, "val2", &val2); JSNATIVE_TEST_FUNC_END JSNATIVE_TEST_FUNC_BEGIN(args_ignore_trailing) int val; retval = gjs_parse_call_args(cx, "argsIgnoreTrailing", args, "!i", "val", &val); JSNATIVE_TEST_FUNC_END JSNATIVE_TEST_FUNC_BEGIN(one_of_each_type) bool boolval; JS::UniqueChars strval; GjsAutoChar fileval; int intval; unsigned uintval; int64_t int64val; double dblval; JS::RootedObject objval(cx); retval = gjs_parse_call_args(cx, "oneOfEachType", args, "bsFiutfo", "bool", &boolval, "str", &strval, "file", &fileval, "int", &intval, "uint", &uintval, "int64", &int64val, "dbl", &dblval, "obj", &objval); g_assert_cmpint(boolval, ==, true); g_assert_cmpstr(strval.get(), ==, "foo"); g_assert_cmpstr(fileval, ==, "foo"); g_assert_cmpint(intval, ==, 1); g_assert_cmpint(uintval, ==, 1); g_assert_cmpint(int64val, ==, 1); g_assert_cmpfloat(dblval, ==, 1.0); g_assert_true(objval); JSNATIVE_TEST_FUNC_END JSNATIVE_TEST_FUNC_BEGIN(optional_args_all) bool val1, val2, val3; retval = gjs_parse_call_args(cx, "optionalArgsAll", args, "b|bb", "val1", &val1, "val2", &val2, "val3", &val3); g_assert_cmpint(val1, ==, true); g_assert_cmpint(val2, ==, true); g_assert_cmpint(val3, ==, true); JSNATIVE_TEST_FUNC_END JSNATIVE_TEST_FUNC_BEGIN(optional_args_only_required) bool val1 = false, val2 = false, val3 = false; retval = gjs_parse_call_args(cx, "optionalArgsOnlyRequired", args, "b|bb", "val1", &val1, "val2", &val2, "val3", &val3); g_assert_cmpint(val1, ==, true); g_assert_cmpint(val2, ==, false); g_assert_cmpint(val3, ==, false); JSNATIVE_TEST_FUNC_END JSNATIVE_TEST_FUNC_BEGIN(only_optional_args) int val1, val2; retval = gjs_parse_call_args(cx, "onlyOptionalArgs", args, "|ii", "val1", &val1, "val2", &val2); JSNATIVE_TEST_FUNC_END JSNATIVE_TEST_FUNC_BEGIN(unsigned_enum_arg) test_enum_t val; retval = gjs_parse_call_args(cx, "unsignedEnumArg", args, "i", "enum_param", &val); g_assert_cmpint(val, ==, ONE); JSNATIVE_TEST_FUNC_END JSNATIVE_TEST_FUNC_BEGIN(signed_enum_arg) test_signed_enum_t val; retval = gjs_parse_call_args(cx, "signedEnumArg", args, "i", "enum_param", &val); g_assert_cmpint(val, ==, MINUS_ONE); JSNATIVE_TEST_FUNC_END JSNATIVE_TEST_FUNC_BEGIN(one_of_each_nullable_type) JS::UniqueChars strval; GjsAutoChar fileval; JS::RootedObject objval(cx); retval = gjs_parse_call_args(cx, "oneOfEachNullableType", args, "?s?F?o", "strval", &strval, "fileval", &fileval, "objval", &objval); g_assert_null(strval); g_assert_null(fileval); g_assert_false(objval); JSNATIVE_TEST_FUNC_END JSNATIVE_TEST_FUNC_BEGIN(unwind_free_test) int intval; unsigned uval; JS::RootedObject objval(cx); retval = gjs_parse_call_args(cx, "unwindFreeTest", args, "oiu", "objval", &objval, "intval", &intval, "error", &uval); g_assert_false(objval); JSNATIVE_TEST_FUNC_END #define JSNATIVE_BAD_NULLABLE_TEST_FUNC(type, fmt) \ JSNATIVE_TEST_FUNC_BEGIN(type##_invalid_nullable) \ type val; \ retval = gjs_parse_call_args(cx, #type "InvalidNullable", \ args, "?" fmt, \ "val", &val); \ JSNATIVE_TEST_FUNC_END JSNATIVE_BAD_NULLABLE_TEST_FUNC(bool, "b"); JSNATIVE_BAD_NULLABLE_TEST_FUNC(int, "i"); JSNATIVE_BAD_NULLABLE_TEST_FUNC(unsigned, "u"); JSNATIVE_BAD_NULLABLE_TEST_FUNC(int64_t, "t"); JSNATIVE_BAD_NULLABLE_TEST_FUNC(double, "f"); #undef JSNATIVE_BAD_NULLABLE_TEST_FUNC #define JSNATIVE_BAD_TYPE_TEST_FUNC(type, ch) \ JSNATIVE_TEST_FUNC_BEGIN(type##_invalid_type) \ type val; \ retval = gjs_parse_call_args(cx, #type "InvalidType", args, ch, \ "val", &val); \ JSNATIVE_TEST_FUNC_END JSNATIVE_BAD_TYPE_TEST_FUNC(bool, "i"); JSNATIVE_BAD_TYPE_TEST_FUNC(int, "u"); JSNATIVE_BAD_TYPE_TEST_FUNC(unsigned, "t"); JSNATIVE_BAD_TYPE_TEST_FUNC(int64_t, "f"); JSNATIVE_BAD_TYPE_TEST_FUNC(double, "b"); JSNATIVE_BAD_TYPE_TEST_FUNC(GjsAutoChar, "i"); #undef JSNATIVE_BAD_TYPE_TEST_FUNC JSNATIVE_TEST_FUNC_BEGIN(UniqueChars_invalid_type) JS::UniqueChars value; retval = gjs_parse_call_args(cx, "UniqueCharsInvalidType", args, "i", "value", &value); JSNATIVE_TEST_FUNC_END JSNATIVE_TEST_FUNC_BEGIN(object_invalid_type) JS::RootedObject val(cx); retval = gjs_parse_call_args(cx, "objectInvalidType", args, "i", "val", &val); JSNATIVE_TEST_FUNC_END static JSFunctionSpec native_test_funcs[] = { JS_FN("noArgs", no_args, 0, 0), JS_FN("noArgsIgnoreTrailing", no_args_ignore_trailing, 0, 0), JS_FN("boolArgNoAssert", bool_arg_no_assert, 0, 0), JS_FN("intArgNoAssert", int_arg_no_assert, 0, 0), JS_FN("objectArgNoAssert", object_arg_no_assert, 0, 0), JS_FN("optionalIntArgsNoAssert", optional_int_args_no_assert, 0, 0), JS_FN("argsIgnoreTrailing", args_ignore_trailing, 0, 0), JS_FN("oneOfEachType", one_of_each_type, 0, 0), JS_FN("optionalArgsAll", optional_args_all, 0, 0), JS_FN("optionalArgsOnlyRequired", optional_args_only_required, 0, 0), JS_FN("onlyOptionalArgs", only_optional_args, 0, 0), JS_FN("unsignedEnumArg", unsigned_enum_arg, 0, 0), JS_FN("signedEnumArg", signed_enum_arg, 0, 0), JS_FN("oneOfEachNullableType", one_of_each_nullable_type, 0, 0), JS_FN("unwindFreeTest", unwind_free_test, 0, 0), JS_FN("boolInvalidNullable", bool_invalid_nullable, 0, 0), JS_FN("intInvalidNullable", int_invalid_nullable, 0, 0), JS_FN("unsignedInvalidNullable", unsigned_invalid_nullable, 0, 0), JS_FN("int64_tInvalidNullable", int64_t_invalid_nullable, 0, 0), JS_FN("doubleInvalidNullable", double_invalid_nullable, 0, 0), JS_FN("boolInvalidType", bool_invalid_type, 0, 0), JS_FN("intInvalidType", int_invalid_type, 0, 0), JS_FN("unsignedInvalidType", unsigned_invalid_type, 0, 0), JS_FN("int64_tInvalidType", int64_t_invalid_type, 0, 0), JS_FN("doubleInvalidType", double_invalid_type, 0, 0), JS_FN("GjsAutoCharInvalidType", GjsAutoChar_invalid_type, 0, 0), JS_FN("UniqueCharsInvalidType", UniqueChars_invalid_type, 0, 0), JS_FN("objectInvalidType", object_invalid_type, 0, 0), JS_FS_END}; static void setup(GjsUnitTestFixture *fx, gconstpointer unused) { gjs_unit_test_fixture_setup(fx, unused); JS::RootedObject global(fx->cx, gjs_get_import_global(fx->cx)); bool success = JS_DefineFunctions(fx->cx, global, native_test_funcs); g_assert_true(success); } static void run_code(GjsUnitTestFixture *fx, gconstpointer code) { const char *script = (const char *) code; JS::SourceText source; bool ok = source.init(fx->cx, script, strlen(script), JS::SourceOwnership::Borrowed); g_assert_true(ok); JS::CompileOptions options(fx->cx); options.setFileAndLine("unit test", 1); JS::RootedValue ignored(fx->cx); ok = JS::Evaluate(fx->cx, options, source, &ignored); g_assert_null(gjs_test_get_exception_message(fx->cx)); g_assert_true(ok); } static void run_code_expect_exception(GjsUnitTestFixture *fx, gconstpointer code) { const char *script = (const char *) code; JS::SourceText source; bool ok = source.init(fx->cx, script, strlen(script), JS::SourceOwnership::Borrowed); g_assert_true(ok); JS::CompileOptions options(fx->cx); options.setFileAndLine("unit test", 1); JS::RootedValue ignored(fx->cx); ok = JS::Evaluate(fx->cx, options, source, &ignored); g_assert_false(ok); GjsAutoChar message = gjs_test_get_exception_message(fx->cx); g_assert_nonnull(message); /* Cheap way to shove an expected exception message into the data argument */ const char *expected_msg = strstr((const char *) code, "//"); if (expected_msg != NULL) { expected_msg += 2; assert_match(message, expected_msg); } } void gjs_test_add_tests_for_parse_call_args(void) { #define ADD_CALL_ARGS_TEST_BASE(path, code, f) \ g_test_add("/callargs/" path, GjsUnitTestFixture, code, setup, f, \ gjs_unit_test_fixture_teardown) #define ADD_CALL_ARGS_TEST(path, code) \ ADD_CALL_ARGS_TEST_BASE(path, code, run_code) #define ADD_CALL_ARGS_TEST_XFAIL(path, code) \ ADD_CALL_ARGS_TEST_BASE(path, code, run_code_expect_exception) ADD_CALL_ARGS_TEST("no-args-works", "noArgs()"); ADD_CALL_ARGS_TEST_XFAIL("no-args-fails-on-extra-args", "noArgs(1, 2, 3)//*Expected 0 arguments, got 3"); ADD_CALL_ARGS_TEST("no-args-ignores-trailing", "noArgsIgnoreTrailing(1, 2, 3)"); ADD_CALL_ARGS_TEST_XFAIL("too-many-args-fails", "intArgNoAssert(1, 2)" "//*Expected 1 arguments, got 2"); ADD_CALL_ARGS_TEST_XFAIL("too-many-args-fails-when-more-than-optional", "optionalIntArgsNoAssert(1, 2, 3)" "//*Expected minimum 1 arguments (and 1 optional), got 3"); ADD_CALL_ARGS_TEST_XFAIL("too-few-args-fails", "intArgNoAssert()//*At least 1 argument required, " "but only 0 passed"); ADD_CALL_ARGS_TEST_XFAIL("too-few-args-fails-with-optional", "optionalIntArgsNoAssert()//*At least 1 argument " "required, but only 0 passed"); ADD_CALL_ARGS_TEST("args-ignores-trailing", "argsIgnoreTrailing(1, 2, 3)"); ADD_CALL_ARGS_TEST("one-of-each-type-works", "oneOfEachType(true, 'foo', 'foo', 1, 1, 1, 1, {})"); ADD_CALL_ARGS_TEST("optional-args-work-when-passing-all-args", "optionalArgsAll(true, true, true)"); ADD_CALL_ARGS_TEST("optional-args-work-when-passing-only-required-args", "optionalArgsOnlyRequired(true)"); ADD_CALL_ARGS_TEST("enum-types-work", "unsignedEnumArg(1)"); ADD_CALL_ARGS_TEST("signed-enum-types-work", "signedEnumArg(-1)"); ADD_CALL_ARGS_TEST("one-of-each-nullable-type-works", "oneOfEachNullableType(null, null, null)"); ADD_CALL_ARGS_TEST("passing-no-arguments-when-all-optional", "onlyOptionalArgs()"); ADD_CALL_ARGS_TEST("passing-some-arguments-when-all-optional", "onlyOptionalArgs(1)"); ADD_CALL_ARGS_TEST("passing-all-arguments-when-all-optional", "onlyOptionalArgs(1, 1)"); ADD_CALL_ARGS_TEST_XFAIL("allocated-args-are-freed-on-error", "unwindFreeTest({}, 1, -1)" "//*Value * is out of range"); ADD_CALL_ARGS_TEST_XFAIL("nullable-bool-is-invalid", "boolInvalidNullable(true)" "//*Invalid format string combination ?b"); ADD_CALL_ARGS_TEST_XFAIL("nullable-int-is-invalid", "intInvalidNullable(1)" "//*Invalid format string combination ?i"); ADD_CALL_ARGS_TEST_XFAIL("nullable-unsigned-is-invalid", "unsignedInvalidNullable(1)" "//*Invalid format string combination ?u"); ADD_CALL_ARGS_TEST_XFAIL("nullable-int64-is-invalid", "int64_tInvalidNullable(1)" "//*Invalid format string combination ?t"); ADD_CALL_ARGS_TEST_XFAIL("nullable-double-is-invalid", "doubleInvalidNullable(1)" "//*Invalid format string combination ?f"); ADD_CALL_ARGS_TEST_XFAIL("invalid-bool-type", "boolInvalidType(1)" "//*Wrong type for i, got bool?"); ADD_CALL_ARGS_TEST_XFAIL("invalid-int-type", "intInvalidType(1)" "//*Wrong type for u, got int32_t?"); ADD_CALL_ARGS_TEST_XFAIL("invalid-unsigned-type", "unsignedInvalidType(1)" "//*Wrong type for t, got uint32_t?"); ADD_CALL_ARGS_TEST_XFAIL("invalid-int64-type", "int64_tInvalidType(1)" "//*Wrong type for f, got int64_t?"); ADD_CALL_ARGS_TEST_XFAIL("invalid-double-type", "doubleInvalidType(false)" "//*Wrong type for b, got double?"); ADD_CALL_ARGS_TEST_XFAIL("invalid-autochar-type", "GjsAutoCharInvalidType(1)" "//*Wrong type for i, got GjsAutoChar?"); ADD_CALL_ARGS_TEST_XFAIL("invalid-autojschar-type", "UniqueCharsInvalidType(1)" "//*Wrong type for i, got JS::UniqueChars?"); ADD_CALL_ARGS_TEST_XFAIL("invalid-object-type", "objectInvalidType(1)" "//*Wrong type for i, got JS::MutableHandleObject"); ADD_CALL_ARGS_TEST_XFAIL("invalid-boolean", "boolArgNoAssert({})//*Not a boolean"); ADD_CALL_ARGS_TEST_XFAIL("invalid-object", "objectArgNoAssert(3)//*Not an object"); #undef ADD_CALL_ARGS_TEST_XFAIL #undef ADD_CALL_ARGS_TEST #undef ADD_CALL_ARGS_TEST_BASE } cjs-5.2.0/test/test-ci.sh0000755000175000017500000001126214144444702015327 0ustar jpeisachjpeisach#!/bin/sh -e do_Set_Env () { #Save cache on $pwd (required by artifacts) mkdir -p "$(pwd)"/.cache XDG_CACHE_HOME="$(pwd)"/.cache export XDG_CACHE_HOME #SpiderMonkey and libgjs export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib64:/usr/local/lib #Macros export ACLOCAL_PATH=$ACLOCAL_PATH:/usr/local/share/aclocal export JHBUILD_RUN_AS_ROOT=1 export SHELL=/bin/bash PATH=$PATH:~/.local/bin export DISPLAY="${DISPLAY:-:0}" } do_Get_Upstream_Master () { if test "$CI_PROJECT_PATH_SLUG" = "gnome-gjs"; then if test "$CI_BUILD_REF_SLUG" = "master" -o \ "$CI_BUILD_REF_SLUG" = "gnome-"* -o \ -n "$CI_COMMIT_TAG"; then echo '-----------------------------------------' echo 'Running against upstream' echo "=> $1 Nothing to do" do_Done exit 0 fi fi echo '-----------------------------------------' echo 'Cloning upstream master' mkdir -p ~/tmp-upstream; cd ~/tmp-upstream || exit 1 git clone --depth 1 https://gitlab.gnome.org/GNOME/gjs.git; cd gjs || exit 1 echo '-----------------------------------------' } do_Compare_With_Upstream_Master () { echo '-----------------------------------------' echo 'Compare the working code with upstream master' sort < /cwd/master-report.txt > /cwd/master-report-sorted.txt sort < /cwd/current-report.txt > /cwd/current-report-sorted.txt NEW_WARNINGS=$(comm -13 /cwd/master-report-sorted.txt /cwd/current-report-sorted.txt | wc -l) REMOVED_WARNINGS=$(comm -23 /cwd/master-report-sorted.txt /cwd/current-report-sorted.txt | wc -l) if test "$NEW_WARNINGS" -ne 0; then echo '-----------------------------------------' echo "### $NEW_WARNINGS new warning(s) found by $1 ###" echo '-----------------------------------------' diff -U0 /cwd/master-report.txt /cwd/current-report.txt || true echo '-----------------------------------------' exit 1 else echo "$REMOVED_WARNINGS warning(s) were fixed." echo "=> $1 Ok" fi } do_Create_Artifacts_Folder () { # Create the artifacts folders save_dir="$(pwd)" mkdir -p "$save_dir"/analysis; touch "$save_dir"/analysis/doing-"$1" } do_Check_Script_Errors () { local total=0 total=$(cat scripts.log | grep 'not ok ' | wc -l) if test "$total" -gt 0; then echo '-----------------------------------------' echo "### Found $total errors on scripts.log ###" echo '-----------------------------------------' exit 1 fi } # ----------- Run the Tests ----------- if test -n "$TEST"; then extra_opts="($TEST)" fi . test/extra/do_environment.sh # Show some environment info do_Print_Labels 'ENVIRONMENT' echo "Running on: $BASE $OS" echo "Job: $TASK_ID" echo "Configure options: ${CONFIG_OPTS-$BUILD_OPTS}" echo "Doing: $1 $extra_opts" do_Create_Artifacts_Folder "$1" if test "$1" = "SETUP"; then do_Show_Info do_Print_Labels 'Show GJS git information' git log --pretty=format:"%h %cd %s" -1 elif test "$1" = "BUILD"; then do_Set_Env DEFAULT_CONFIG_OPTS="-Dcairo=enabled -Dreadline=enabled -Dprofiler=enabled \ -Ddtrace=false -Dsystemtap=false -Dverbose_logs=false --werror" meson _build $DEFAULT_CONFIG_OPTS $CONFIG_OPTS ninja -C _build if test "$TEST" != "skip"; then xvfb-run -a meson test -C _build $TEST_OPTS fi elif test "$1" = "SH_CHECKS"; then # It doesn't (re)build, just run the 'Tests' do_Print_Labels 'Shell Scripts Check' do_Set_Env export LC_ALL=C.UTF-8 export LANG=C.UTF-8 export LANGUAGE=C.UTF-8 export NO_AT_BRIDGE=1 sudo ninja -C _build install installed-tests/scripts/testExamples.sh > scripts.log do_Check_Script_Errors elif test "$1" = "CPPLINT"; then do_Print_Labels 'C/C++ Linter report ' cpplint --quiet $(find . -name \*.cpp -or -name \*.c -or -name \*.h | sort) 2>&1 >/dev/null | \ tee "$save_dir"/analysis/current-report.txt | \ sed -E -e 's/:[0-9]+:/:LINE:/' -e 's/ +/ /g' \ > /cwd/current-report.txt cat "$save_dir"/analysis/current-report.txt echo # Get the code committed at upstream master do_Get_Upstream_Master "cppLint" cpplint --quiet $(find . -name \*.cpp -or -name \*.c -or -name \*.h | sort) 2>&1 >/dev/null | \ tee "$save_dir"/analysis/master-report.txt | \ sed -E -e 's/:[0-9]+:/:LINE:/' -e 's/ +/ /g' \ > /cwd/master-report.txt echo # Compare the report with master and fail if new warnings are found do_Compare_With_Upstream_Master "cppLint" fi # Releases stuff and finishes do_Done cjs-5.2.0/test/mock-js-resources.gresource.xml0000644000175000017500000000027414144444702021513 0ustar jpeisachjpeisach test/gjs-test-coverage/loadedJSFromResource.js cjs-5.2.0/test/gjs-test-rooting.cpp0000644000175000017500000002156114144444702017346 0ustar jpeisachjpeisach#include #include #include #include // for JS_GC, JS_SetGCCallback, JSGCStatus #include #include #include #include // for JS_GetPrivate, JS_NewObject, JS_Set... #include "cjs/jsapi-util-root.h" #include "test/gjs-test-utils.h" // COMPAT: https://gitlab.gnome.org/GNOME/glib/-/merge_requests/1553 #ifdef __clang_analyzer__ void g_assertion_message(const char*, const char*, int, const char*, const char*) __attribute__((analyzer_noreturn)); #endif static GMutex gc_lock; static GCond gc_finished; static volatile int gc_counter; #define PARENT(fx) ((GjsUnitTestFixture *)fx) struct GjsRootingFixture { GjsUnitTestFixture parent; bool finalized; bool notify_called; GjsMaybeOwned *obj; /* only used in callback test cases */ }; static void test_obj_finalize(JSFreeOp*, JSObject* obj) { bool *finalized_p = static_cast(JS_GetPrivate(obj)); g_assert_false(*finalized_p); *finalized_p = true; } static const JSClassOps test_obj_class_ops = { nullptr, // addProperty nullptr, // deleteProperty nullptr, // enumerate nullptr, // newEnumerate nullptr, // resolve nullptr, // mayResolve test_obj_finalize}; static JSClass test_obj_class = { "TestObj", JSCLASS_HAS_PRIVATE | JSCLASS_FOREGROUND_FINALIZE, &test_obj_class_ops }; static JSObject * test_obj_new(GjsRootingFixture *fx) { JSObject *retval = JS_NewObject(PARENT(fx)->cx, &test_obj_class); JS_SetPrivate(retval, &fx->finalized); return retval; } static void on_gc(JSContext*, JSGCStatus status, JS::GCReason, void*) { if (status != JSGC_END) return; g_mutex_lock(&gc_lock); g_atomic_int_inc(&gc_counter); g_cond_broadcast(&gc_finished); g_mutex_unlock(&gc_lock); } static void setup(GjsRootingFixture *fx, gconstpointer unused) { gjs_unit_test_fixture_setup(PARENT(fx), unused); JS_SetGCCallback(PARENT(fx)->cx, on_gc, fx); } static void teardown(GjsRootingFixture *fx, gconstpointer unused) { gjs_unit_test_fixture_teardown(PARENT(fx), unused); } static void wait_for_gc(GjsRootingFixture *fx) { int count = g_atomic_int_get(&gc_counter); JS_GC(PARENT(fx)->cx); g_mutex_lock(&gc_lock); while (count == g_atomic_int_get(&gc_counter)) { g_cond_wait(&gc_finished, &gc_lock); } g_mutex_unlock(&gc_lock); } static void test_maybe_owned_rooted_flag_set_when_rooted(GjsRootingFixture* fx, const void*) { auto obj = new GjsMaybeOwned(); obj->root(PARENT(fx)->cx, JS::TrueValue()); g_assert_true(obj->rooted()); delete obj; } static void test_maybe_owned_rooted_flag_not_set_when_not_rooted( GjsRootingFixture*, const void*) { auto obj = new GjsMaybeOwned(); *obj = JS::TrueValue(); g_assert_false(obj->rooted()); delete obj; } static void test_maybe_owned_rooted_keeps_alive_across_gc(GjsRootingFixture* fx, const void*) { auto obj = new GjsMaybeOwned(); obj->root(PARENT(fx)->cx, test_obj_new(fx)); wait_for_gc(fx); g_assert_false(fx->finalized); delete obj; wait_for_gc(fx); g_assert_true(fx->finalized); } static void test_maybe_owned_rooted_is_collected_after_reset( GjsRootingFixture* fx, const void*) { auto obj = new GjsMaybeOwned(); obj->root(PARENT(fx)->cx, test_obj_new(fx)); obj->reset(); wait_for_gc(fx); g_assert_true(fx->finalized); delete obj; } static void update_weak_pointer(JSContext*, JS::Compartment*, void* data) { auto* obj = static_cast*>(data); if (*obj) obj->update_after_gc(); } static void test_maybe_owned_weak_pointer_is_collected_by_gc( GjsRootingFixture* fx, const void*) { auto obj = new GjsMaybeOwned(); *obj = test_obj_new(fx); JS_AddWeakPointerCompartmentCallback(PARENT(fx)->cx, &update_weak_pointer, obj); wait_for_gc(fx); g_assert_true(fx->finalized); JS_RemoveWeakPointerCompartmentCallback(PARENT(fx)->cx, &update_weak_pointer); delete obj; } static void test_maybe_owned_heap_rooted_keeps_alive_across_gc( GjsRootingFixture* fx, const void*) { auto obj = new GjsMaybeOwned(); obj->root(PARENT(fx)->cx, test_obj_new(fx)); wait_for_gc(fx); g_assert_false(fx->finalized); delete obj; wait_for_gc(fx); g_assert_true(fx->finalized); } static void test_maybe_owned_switching_mode_keeps_same_value( GjsRootingFixture* fx, const void*) { JSObject *test_obj = test_obj_new(fx); auto obj = new GjsMaybeOwned(); *obj = test_obj; g_assert_true(*obj == test_obj); obj->switch_to_rooted(PARENT(fx)->cx); g_assert_true(obj->rooted()); g_assert_true(*obj == test_obj); obj->switch_to_unrooted(PARENT(fx)->cx); g_assert_false(obj->rooted()); g_assert_true(*obj == test_obj); delete obj; } static void test_maybe_owned_switch_to_rooted_prevents_collection( GjsRootingFixture* fx, const void*) { auto obj = new GjsMaybeOwned(); *obj = test_obj_new(fx); obj->switch_to_rooted(PARENT(fx)->cx); wait_for_gc(fx); g_assert_false(fx->finalized); delete obj; } static void test_maybe_owned_switch_to_unrooted_allows_collection( GjsRootingFixture* fx, const void*) { auto obj = new GjsMaybeOwned(); obj->root(PARENT(fx)->cx, test_obj_new(fx)); obj->switch_to_unrooted(PARENT(fx)->cx); JS_AddWeakPointerCompartmentCallback(PARENT(fx)->cx, &update_weak_pointer, obj); wait_for_gc(fx); g_assert_true(fx->finalized); JS_RemoveWeakPointerCompartmentCallback(PARENT(fx)->cx, &update_weak_pointer); delete obj; } static void context_destroyed(JS::HandleObject, void* data) { auto fx = static_cast(data); g_assert_false(fx->notify_called); g_assert_false(fx->finalized); fx->notify_called = true; fx->obj->reset(); } static void test_maybe_owned_notify_callback_called_on_context_destroy( GjsRootingFixture* fx, const void*) { fx->obj = new GjsMaybeOwned(); fx->obj->root(PARENT(fx)->cx, test_obj_new(fx), context_destroyed, fx); gjs_unit_test_destroy_context(PARENT(fx)); g_assert_true(fx->notify_called); delete fx->obj; } static void test_maybe_owned_object_destroyed_after_notify( GjsRootingFixture* fx, const void*) { fx->obj = new GjsMaybeOwned(); fx->obj->root(PARENT(fx)->cx, test_obj_new(fx), context_destroyed, fx); gjs_unit_test_destroy_context(PARENT(fx)); g_assert_true(fx->finalized); delete fx->obj; } void gjs_test_add_tests_for_rooting(void) { #define ADD_ROOTING_TEST(path, f) \ g_test_add("/rooting/" path, GjsRootingFixture, nullptr, setup, f, \ teardown); ADD_ROOTING_TEST("maybe-owned/rooted-flag-set-when-rooted", test_maybe_owned_rooted_flag_set_when_rooted); ADD_ROOTING_TEST("maybe-owned/rooted-flag-not-set-when-not-rooted", test_maybe_owned_rooted_flag_not_set_when_not_rooted); ADD_ROOTING_TEST("maybe-owned/rooted-keeps-alive-across-gc", test_maybe_owned_rooted_keeps_alive_across_gc); ADD_ROOTING_TEST("maybe-owned/rooted-is-collected-after-reset", test_maybe_owned_rooted_is_collected_after_reset); ADD_ROOTING_TEST("maybe-owned/weak-pointer-is-collected-by-gc", test_maybe_owned_weak_pointer_is_collected_by_gc); ADD_ROOTING_TEST("maybe-owned/heap-rooted-keeps-alive-across-gc", test_maybe_owned_heap_rooted_keeps_alive_across_gc); ADD_ROOTING_TEST("maybe-owned/switching-mode-keeps-same-value", test_maybe_owned_switching_mode_keeps_same_value); ADD_ROOTING_TEST("maybe-owned/switch-to-rooted-prevents-collection", test_maybe_owned_switch_to_rooted_prevents_collection); ADD_ROOTING_TEST("maybe-owned/switch-to-unrooted-allows-collection", test_maybe_owned_switch_to_unrooted_allows_collection); #undef ADD_ROOTING_TEST #define ADD_CONTEXT_DESTROY_TEST(path, f) \ g_test_add("/rooting/" path, GjsRootingFixture, nullptr, setup, f, nullptr); ADD_CONTEXT_DESTROY_TEST("maybe-owned/notify-callback-called-on-context-destroy", test_maybe_owned_notify_callback_called_on_context_destroy); ADD_CONTEXT_DESTROY_TEST("maybe-owned/object-destroyed-after-notify", test_maybe_owned_object_destroyed_after_notify); #undef ADD_CONTEXT_DESTROY_TEST } cjs-5.2.0/test/extra/0000755000175000017500000000000014144444702014541 5ustar jpeisachjpeisachcjs-5.2.0/test/extra/do_environment.sh0000755000175000017500000000217514144444702020133 0ustar jpeisachjpeisach#!/bin/sh -e do_Print_Labels () { if test -n "$1"; then label_len=${#1} span=$(((54 - $label_len) / 2)) echo echo "= ======================================================== =" printf "%s %${span}s %s %${span}s %s\n" "=" "" "$1" "" "=" echo "= ======================================================== =" else echo "= ========================= Done ========================= =" echo fi } do_Done () { # Done. De-initializes whatever is needed do_Print_Labels 'FINISHED' } do_Show_Info () { local compiler="${CC:-gcc}" echo '-----------------------------------------' echo 'Build system information' echo -n "Processors: "; grep -c ^processor /proc/cpuinfo grep ^MemTotal /proc/meminfo id; uname -a printenv echo '-----------------------------------------' cat /etc/*-release echo '-----------------------------------------' echo 'Compiler version' $compiler --version echo '-----------------------------------------' $compiler -dM -E -x c /dev/null echo '-----------------------------------------' } cjs-5.2.0/test/extra/Dockerfile.alpine.cpplint0000644000175000017500000000070014144444702021447 0ustar jpeisachjpeisachFROM alpine:latest MAINTAINER Claudio André (c) 2018 V1.0 LABEL architecture="x86_64" LABEL version="1.0" LABEL description="Docker image to run CI for GNOME GJS (JavaScript bindings for GNOME)." RUN apk add --no-cache python3 git RUN python3 -m ensurepip && \ rm -r /usr/lib/python*/ensurepip && \ pip3 install --no-cache --upgrade pip setuptools wheel RUN pip3 install --no-cache --upgrade cpplint RUN mkdir -p /cwd CMD ["/bin/bash"] cjs-5.2.0/test/extra/Dockerfile.debug0000644000175000017500000000564614144444702017633 0ustar jpeisachjpeisach# === Build stage === FROM fedora:32 AS build ARG MOZJS_BRANCH=mozjs68 ARG BUILD_OPTS="--enable-posix-nspr-emulation --enable-unaligned-private-values" ENV SHELL=/bin/bash RUN dnf -y install 'dnf-command(builddep)' clang-devel cmake git llvm-devel \ make ninja-build which RUN dnf -y builddep mozjs68 sysprof WORKDIR /root ADD https://include-what-you-use.org/downloads/include-what-you-use-0.14.src.tar.gz /root/ RUN tar xzf include-what-you-use-0.14.src.tar.gz RUN mkdir -p include-what-you-use/_build WORKDIR /root/include-what-you-use/_build RUN cmake -G Ninja -DCMAKE_INSTALL_PREFIX=/usr .. RUN ninja RUN DESTDIR=/root/iwyu-install ninja install WORKDIR /root RUN git clone --depth 1 https://gitlab.gnome.org/GNOME/sysprof.git WORKDIR /root/sysprof RUN meson _build -Dprefix=/usr -Dlibdir=lib64 -Denable_examples=false \ -Denable_gtk=false -Denable_tests=false -Denable_tools=false -Dhelp=false \ -Dlibsysprof=false -Dwith_sysprofd=none RUN ninja -C _build RUN DESTDIR=/root/sysprof-install ninja -C _build install WORKDIR /root RUN git clone --depth 1 https://github.com/ptomato/mozjs.git -b ${MOZJS_BRANCH} RUN mkdir -p mozjs/_build WORKDIR /root/mozjs/_build RUN ../js/src/configure --prefix=/usr --libdir=/usr/lib64 --disable-jemalloc \ --with-system-zlib --with-intl-api --enable-debug AUTOCONF=autoconf \ ${BUILD_OPTS} RUN make -j$(nproc) RUN DESTDIR=/root/mozjs-install make install RUN rm -f /root/mozjs-install/usr/lib64/libjs_static.ajs # === Actual Docker image === FROM fedora:32 ENV SHELL=/bin/bash # List is comprised of base dependencies for CI scripts, gjs, and debug packages # needed for informative stack traces, e.g. in Valgrind. # # Do everything in one RUN command so that the dnf cache is not cached in the # final Docker image. RUN dnf -y install --enablerepo=fedora-debuginfo,updates-debuginfo \ binutils cairo-debuginfo cairo-debugsource cairo-gobject-devel clang \ compiler-rt dbus-daemon diffutils fontconfig-debuginfo \ fontconfig-debugsource gcc-c++ git glib2-debuginfo glib2-debugsource \ glib2-devel glibc-debuginfo glibc-debuginfo-common gnome-desktop-testing \ gobject-introspection-debuginfo gobject-introspection-debugsource \ gobject-introspection-devel gtk3-debuginfo gtk3-debugsource gtk3-devel \ gtk4-debuginfo gtk4-debugsource gtk4-devel lcov libasan libubsan libtsan \ meson-0.54.0-1.fc32 ninja-build pkgconf readline-devel systemtap-sdt-devel \ valgrind which Xvfb xz && \ dnf clean all && rm -rf /var/cache/dnf COPY --from=build /root/mozjs-install/usr /usr COPY --from=build /root/sysprof-install/usr /usr COPY --from=build /root/iwyu-install/usr /usr RUN ln -s /usr/bin/iwyu_tool.py /usr/bin/iwyu_tool # Enable sudo for wheel users RUN sed -i -e 's/# %wheel/%wheel/' -e '0,/%wheel/{s/%wheel/# %wheel/}' \ /etc/sudoers ENV HOST_USER_ID 5555 RUN useradd -u $HOST_USER_ID -G wheel -ms /bin/bash user USER user WORKDIR /home/user ENV LANG C.UTF-8 cjs-5.2.0/test/extra/Dockerfile0000644000175000017500000000402514144444702016534 0ustar jpeisachjpeisach# === Build Spidermonkey stage === FROM fedora:32 AS mozjs-build ARG MOZJS_BRANCH=mozjs68 ARG BUILD_OPTS="--enable-posix-nspr-emulation --enable-unaligned-private-values" ENV SHELL=/bin/bash RUN dnf -y install 'dnf-command(builddep)' git make which llvm-devel RUN dnf -y builddep mozjs68 WORKDIR /root RUN git clone --depth 1 https://github.com/ptomato/mozjs.git -b ${MOZJS_BRANCH} RUN mkdir -p mozjs/_build WORKDIR /root/mozjs/_build RUN ../js/src/configure --prefix=/usr --libdir=/usr/lib64 --disable-jemalloc \ --with-system-zlib --with-intl-api AUTOCONF=autoconf ${BUILD_OPTS} RUN make -j$(nproc) RUN DESTDIR=/root/mozjs-install make install RUN rm -f /root/mozjs-install/usr/lib64/libjs_static.ajs # === Actual Docker image === FROM fedora:32 ENV SHELL=/bin/bash # List is comprised of base dependencies for CI scripts, gjs, and debug packages # needed for informative stack traces, e.g. in Valgrind. # # Do everything in one RUN command so that the dnf cache is not cached in the # final Docker image. RUN dnf -y install --enablerepo=fedora-debuginfo,updates-debuginfo \ binutils cairo-debuginfo cairo-debugsource cairo-gobject-devel clang \ compiler-rt dbus-daemon diffutils fontconfig-debuginfo \ fontconfig-debugsource gcc-c++ git glib2-debuginfo glib2-debugsource \ glib2-devel glibc-debuginfo glibc-debuginfo-common gnome-desktop-testing \ gobject-introspection-debuginfo gobject-introspection-debugsource \ gobject-introspection-devel gtk3-debuginfo gtk3-debugsource gtk3-devel \ gtk4-debuginfo gtk4-debugsource gtk4-devel lcov libasan libubsan libtsan \ meson-0.54.0-1.fc32 ninja-build pkgconf readline-devel systemtap-sdt-devel \ valgrind which Xvfb xz && \ dnf clean all && rm -rf /var/cache/dnf COPY --from=mozjs-build /root/mozjs-install/usr /usr # Enable sudo for wheel users RUN sed -i -e 's/# %wheel/%wheel/' -e '0,/%wheel/{s/%wheel/# %wheel/}' \ /etc/sudoers ENV HOST_USER_ID 5555 RUN useradd -u $HOST_USER_ID -G wheel -ms /bin/bash user USER user WORKDIR /home/user ENV LANG C.UTF-8 cjs-5.2.0/test/gjs-test-utils.h0000644000175000017500000000352514144444702016472 0ustar jpeisachjpeisach/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* * Copyright © 2013 Endless Mobile, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. * * Authored By: Sam Spilsbury */ #ifndef TEST_GJS_TEST_UTILS_H_ #define TEST_GJS_TEST_UTILS_H_ #include #include "cjs/context.h" #include struct GjsUnitTestFixture { GjsContext *gjs_context; JSContext *cx; JS::Realm* realm; }; void gjs_unit_test_fixture_setup(GjsUnitTestFixture* fx, const void* unused); void gjs_unit_test_destroy_context(GjsUnitTestFixture *fx); void gjs_unit_test_fixture_teardown(GjsUnitTestFixture* fx, const void* unused); void gjs_test_add_tests_for_coverage (); void gjs_test_add_tests_for_parse_call_args(void); void gjs_test_add_tests_for_rooting(void); #endif // TEST_GJS_TEST_UTILS_H_ cjs-5.2.0/test/gjs-test-coverage.cpp0000644000175000017500000015135114144444702017461 0ustar jpeisachjpeisach/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* * Copyright © 2014 Endless Mobile, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. * * Authored By: Sam Spilsbury */ #include // for errno #include // for sscanf, size_t #include // for strtol, atoi, mkdtemp #include // for strlen, strstr, strcmp, strncmp, strcspn #include #include #include #include "cjs/context.h" #include "cjs/coverage.h" #include "cjs/jsapi-util.h" // COMPAT: https://gitlab.gnome.org/GNOME/glib/-/merge_requests/1553 #ifdef __clang_analyzer__ void g_assertion_message(const char*, const char*, int, const char*, const char*) __attribute__((analyzer_noreturn)); #endif typedef struct _GjsCoverageFixture { GjsContext *context; GjsCoverage *coverage; GFile *tmp_output_dir; GFile *tmp_js_script; GFile *lcov_output_dir; GFile *lcov_output; } GjsCoverageFixture; // SpiderMonkey has a bug where collected coverage data is erased during a // garbage collection. If we're running the tests with JS_GC_ZEAL, then we get // a GC after every script execution, so we definitely won't have coverage. static bool skip_if_gc_zeal_mode(void) { const char *gc_zeal = g_getenv("JS_GC_ZEAL"); if (gc_zeal && (strcmp(gc_zeal, "1") == 0 || strcmp(gc_zeal, "2") == 0 || g_str_has_prefix(gc_zeal, "2,"))) { g_test_skip("https://bugzilla.mozilla.org/show_bug.cgi?id=1447906"); return true; } return false; } static void replace_file(GFile *file, const char *contents) { GError *error = NULL; g_file_replace_contents(file, contents, strlen(contents), NULL /* etag */, FALSE /* make backup */, G_FILE_CREATE_NONE, NULL /* etag out */, NULL /* cancellable */, &error); g_assert_no_error(error); } static void recursive_delete_dir(GFile *dir) { GFileEnumerator *files = g_file_enumerate_children(dir, G_FILE_ATTRIBUTE_STANDARD_TYPE, G_FILE_QUERY_INFO_NONE, NULL, NULL); while (TRUE) { GFile *file; GFileInfo *info; if (!g_file_enumerator_iterate(files, &info, &file, NULL, NULL) || !file || !info) break; if (g_file_info_get_file_type(info) == G_FILE_TYPE_DIRECTORY) { recursive_delete_dir(file); continue; } g_file_delete(file, NULL, NULL); } g_file_delete(dir, NULL, NULL); g_object_unref(files); } static void gjs_coverage_fixture_set_up(void* fixture_data, const void*) { GjsCoverageFixture *fixture = (GjsCoverageFixture *) fixture_data; const char* js_script = "var f = function () { return 1; }\n"; char *tmp_output_dir_name = g_strdup("/tmp/gjs_coverage_tmp.XXXXXX"); tmp_output_dir_name = mkdtemp(tmp_output_dir_name); if (!tmp_output_dir_name) g_error("Failed to create temporary directory for test files: %s\n", strerror(errno)); fixture->tmp_output_dir = g_file_new_for_path(tmp_output_dir_name); fixture->tmp_js_script = g_file_get_child(fixture->tmp_output_dir, "gjs_coverage_script.js"); fixture->lcov_output_dir = g_file_get_child(fixture->tmp_output_dir, "gjs_coverage_test_coverage"); fixture->lcov_output = g_file_get_child(fixture->lcov_output_dir, "coverage.lcov"); g_file_make_directory_with_parents(fixture->lcov_output_dir, NULL, NULL); char *tmp_js_script_filename = g_file_get_path(fixture->tmp_js_script); /* Allocate a strv that we can pass over to gjs_coverage_new */ char *coverage_paths[] = { tmp_js_script_filename, NULL }; char *search_paths[] = { tmp_output_dir_name, NULL }; gjs_coverage_enable(); fixture->context = gjs_context_new_with_search_path((char **) search_paths); fixture->coverage = gjs_coverage_new(coverage_paths, fixture->context, fixture->lcov_output_dir); replace_file(fixture->tmp_js_script, js_script); g_free(tmp_output_dir_name); g_free(tmp_js_script_filename); } static void gjs_coverage_fixture_tear_down(void* fixture_data, const void*) { GjsCoverageFixture *fixture = (GjsCoverageFixture *) fixture_data; recursive_delete_dir(fixture->tmp_output_dir); g_object_unref(fixture->tmp_js_script); g_object_unref(fixture->tmp_output_dir); g_object_unref(fixture->lcov_output_dir); g_object_unref(fixture->lcov_output); g_object_unref(fixture->coverage); g_object_unref(fixture->context); } static const char * line_starting_with(const char *data, const char *needle) { const gsize needle_length = strlen(needle); const char *iter = data; while (iter) { if (strncmp(iter, needle, needle_length) == 0) return iter; iter = strstr(iter, "\n"); if (iter) iter += 1; } return NULL; } static char * write_statistics_and_get_coverage_data(GjsCoverage *coverage, GFile *lcov_output) { gjs_coverage_write_statistics(coverage); char *coverage_data_contents; g_file_load_contents(lcov_output, NULL /* cancellable */, &coverage_data_contents, nullptr, /* length out */ NULL /* etag */, NULL /* error */); return coverage_data_contents; } static char * get_script_identifier(GFile *script) { char *filename = g_file_get_path(script); if (!filename) filename = g_file_get_uri(script); return filename; } static bool eval_script(GjsContext *cx, GFile *script) { char *filename = get_script_identifier(script); bool retval = gjs_context_eval_file(cx, filename, NULL, NULL); g_free(filename); return retval; } static char * eval_script_and_get_coverage_data(GjsContext *context, GjsCoverage *coverage, GFile *script, GFile *lcov_output) { eval_script(context, script); return write_statistics_and_get_coverage_data(coverage, lcov_output); } static void assert_coverage_data_contains_value_for_key(const char *data, const char *key, const char *value) { const char *sf_line = line_starting_with(data, key); g_assert_nonnull(sf_line); GjsAutoChar actual = g_strndup(&sf_line[strlen(key)], strlen(value)); g_assert_cmpstr(value, ==, actual); } using CoverageDataMatchFunc = void (*)(const char *value, const void *user_data); static void assert_coverage_data_matches_value_for_key(const char *data, const char *key, CoverageDataMatchFunc match, const void *user_data) { const char *line = line_starting_with(data, key); g_assert_nonnull(line); (*match)(line, user_data); } static void assert_coverage_data_matches_values_for_key(const char *data, const char *key, size_t n, CoverageDataMatchFunc match, const void *user_data, size_t data_size) { const char *line = line_starting_with (data, key); /* Keep matching. If we fail to match one of them then * bail out */ char *data_iterator = (char *) user_data; while (line && n > 0) { (*match)(line, data_iterator); line = line_starting_with(line + 1, key); --n; data_iterator += data_size; } /* If n is zero then we've found all available matches */ g_assert_cmpuint(n, ==, 0); } static void test_covered_file_is_duplicated_into_output_if_resource( void* fixture_data, const void*) { if (skip_if_gc_zeal_mode()) return; GjsCoverageFixture *fixture = (GjsCoverageFixture *) fixture_data; const char *mock_resource_filename = "resource:///org/gnome/gjs/mock/test/gjs-test-coverage/loadedJSFromResource.js"; const char *coverage_scripts[] = { mock_resource_filename, NULL }; g_object_unref(fixture->context); g_object_unref(fixture->coverage); char *js_script_dirname = g_file_get_path(fixture->tmp_output_dir); char *search_paths[] = { js_script_dirname, NULL }; fixture->context = gjs_context_new_with_search_path(search_paths); fixture->coverage = gjs_coverage_new(coverage_scripts, fixture->context, fixture->lcov_output_dir); bool ok = gjs_context_eval_file(fixture->context, mock_resource_filename, nullptr, nullptr); g_assert_true(ok); gjs_coverage_write_statistics(fixture->coverage); GFile *expected_temporary_js_script = g_file_resolve_relative_path(fixture->lcov_output_dir, "org/gnome/gjs/mock/test/gjs-test-coverage/loadedJSFromResource.js"); g_assert_true(g_file_query_exists(expected_temporary_js_script, NULL)); g_object_unref(expected_temporary_js_script); g_free(js_script_dirname); } static GFile * get_output_file_for_script_on_disk(GFile *script, GFile *output_dir) { char *base = g_file_get_basename(script); GFile *output = g_file_get_child(output_dir, base); g_free(base); return output; } static char * get_output_path_for_script_on_disk(GFile *script, GFile *output_dir) { GFile *output = get_output_file_for_script_on_disk(script, output_dir); char *output_path = g_file_get_path(output); g_object_unref(output); return output_path; } static void test_covered_file_is_duplicated_into_output_if_path( void* fixture_data, const void*) { if (skip_if_gc_zeal_mode()) return; GjsCoverageFixture *fixture = (GjsCoverageFixture *) fixture_data; eval_script(fixture->context, fixture->tmp_js_script); gjs_coverage_write_statistics(fixture->coverage); GFile *expected_temporary_js_script = get_output_file_for_script_on_disk(fixture->tmp_js_script, fixture->lcov_output_dir); g_assert_true(g_file_query_exists(expected_temporary_js_script, NULL)); g_object_unref(expected_temporary_js_script); } static void test_previous_contents_preserved(void* fixture_data, const void*) { GjsCoverageFixture *fixture = (GjsCoverageFixture *) fixture_data; const char *existing_contents = "existing_contents\n"; replace_file(fixture->lcov_output, existing_contents); char *coverage_data_contents = eval_script_and_get_coverage_data(fixture->context, fixture->coverage, fixture->tmp_js_script, fixture->lcov_output); g_assert_nonnull(strstr(coverage_data_contents, existing_contents)); g_free(coverage_data_contents); } static void test_new_contents_written(void* fixture_data, const void*) { if (skip_if_gc_zeal_mode()) return; GjsCoverageFixture *fixture = (GjsCoverageFixture *) fixture_data; const char *existing_contents = "existing_contents\n"; replace_file(fixture->lcov_output, existing_contents); char *coverage_data_contents = eval_script_and_get_coverage_data(fixture->context, fixture->coverage, fixture->tmp_js_script, fixture->lcov_output); /* We have new content in the coverage data */ g_assert_cmpstr(existing_contents, !=, coverage_data_contents); g_free(coverage_data_contents); } static void test_expected_source_file_name_written_to_coverage_data( void* fixture_data, const void*) { if (skip_if_gc_zeal_mode()) return; GjsCoverageFixture *fixture = (GjsCoverageFixture *) fixture_data; char *coverage_data_contents = eval_script_and_get_coverage_data(fixture->context, fixture->coverage, fixture->tmp_js_script, fixture->lcov_output); char *expected_source_filename = get_output_path_for_script_on_disk(fixture->tmp_js_script, fixture->lcov_output_dir); assert_coverage_data_contains_value_for_key(coverage_data_contents, "SF:", expected_source_filename); g_free(expected_source_filename); g_free(coverage_data_contents); } static void test_expected_entry_not_written_for_nonexistent_file( void* fixture_data, const void*) { GjsCoverageFixture *fixture = (GjsCoverageFixture *) fixture_data; const char *coverage_paths[] = { "doesnotexist", NULL }; g_object_unref(fixture->coverage); fixture->coverage = gjs_coverage_new(coverage_paths, fixture->context, fixture->lcov_output_dir); GFile *doesnotexist = g_file_new_for_path("doesnotexist"); char *coverage_data_contents = eval_script_and_get_coverage_data(fixture->context, fixture->coverage, doesnotexist, fixture->lcov_output); const char *sf_line = line_starting_with(coverage_data_contents, "SF:"); g_assert_null(sf_line); g_free(coverage_data_contents); g_object_unref(doesnotexist); } typedef enum _BranchTaken { NOT_EXECUTED, NOT_TAKEN, TAKEN } BranchTaken; typedef struct _BranchLineData { int expected_branch_line; int expected_id; BranchTaken taken; } BranchLineData; static void branch_at_line_should_be_taken(const char *line, const void *user_data) { auto branch_data = static_cast(user_data); int line_no, branch_id, block_no, hit_count_num, nmatches; char hit_count[20]; /* can hold maxint64 (19 digits) + nul terminator */ /* Advance past "BRDA:" */ line += 5; nmatches = sscanf(line, "%i,%i,%i,%19s", &line_no, &block_no, &branch_id, hit_count); g_assert_cmpint(nmatches, ==, 4); /* Determine the branch hit count. It will be either: * > -1 if the line containing the branch was never executed, or * > N times the branch was taken. * * The value of -1 is represented by a single "-" character, so * we should detect this case and set the value based on that */ if (strlen(hit_count) == 1 && *hit_count == '-') hit_count_num = -1; else hit_count_num = atoi(hit_count); g_assert_cmpint(line_no, ==, branch_data->expected_branch_line); g_assert_cmpint(branch_id, ==, branch_data->expected_id); switch (branch_data->taken) { case NOT_EXECUTED: g_assert_cmpint(hit_count_num, ==, -1); break; case NOT_TAKEN: g_assert_cmpint(hit_count_num, ==, 0); break; case TAKEN: g_assert_cmpint(hit_count_num, >, 0); break; default: g_assert_true(false && "Invalid branch state"); }; } static void test_single_branch_coverage_written_to_coverage_data( void* fixture_data, const void*) { if (skip_if_gc_zeal_mode()) return; GjsCoverageFixture *fixture = (GjsCoverageFixture *) fixture_data; const char *script_with_basic_branch = "let x = 0;\n" "if (x > 0)\n" " x++;\n" "else\n" " x++;\n"; replace_file(fixture->tmp_js_script, script_with_basic_branch); char *coverage_data_contents = eval_script_and_get_coverage_data(fixture->context, fixture->coverage, fixture->tmp_js_script, fixture->lcov_output); const BranchLineData expected_branches[] = {{2, 0, TAKEN}, {2, 1, NOT_TAKEN}}; const gsize expected_branches_len = G_N_ELEMENTS(expected_branches); /* There are two possible branches here, the second should be taken * and the first should not have been */ assert_coverage_data_matches_values_for_key(coverage_data_contents, "BRDA:", expected_branches_len, branch_at_line_should_be_taken, expected_branches, sizeof(BranchLineData)); assert_coverage_data_contains_value_for_key(coverage_data_contents, "BRF:", "2"); assert_coverage_data_contains_value_for_key(coverage_data_contents, "BRH:", "1"); g_free(coverage_data_contents); } static void test_multiple_branch_coverage_written_to_coverage_data( void* fixture_data, const void*) { if (skip_if_gc_zeal_mode()) return; GjsCoverageFixture *fixture = (GjsCoverageFixture *) fixture_data; const char *script_with_case_statements_branch = "let y;\n" "for (let x = 0; x < 3; x++) {\n" " switch (x) {\n" " case 0:\n" " y = x + 1;\n" " break;\n" " case 1:\n" " y = x + 1;\n" " break;\n" " case 2:\n" " y = x + 1;\n" " break;\n" " }\n" "}\n"; replace_file(fixture->tmp_js_script, script_with_case_statements_branch); char *coverage_data_contents = eval_script_and_get_coverage_data(fixture->context, fixture->coverage, fixture->tmp_js_script, fixture->lcov_output); const BranchLineData expected_branches[] = { {2, 0, TAKEN}, {2, 1, TAKEN}, {3, 0, TAKEN}, {3, 1, TAKEN}, {3, 2, TAKEN}, {3, 3, NOT_TAKEN}, }; const gsize expected_branches_len = G_N_ELEMENTS(expected_branches); /* There are two possible branches here, the second should be taken * and the first should not have been */ assert_coverage_data_matches_values_for_key(coverage_data_contents, "BRDA:", expected_branches_len, branch_at_line_should_be_taken, expected_branches, sizeof(BranchLineData)); g_free(coverage_data_contents); } static void test_branches_for_multiple_case_statements_fallthrough( void* fixture_data, const void*) { if (skip_if_gc_zeal_mode()) return; GjsCoverageFixture *fixture = (GjsCoverageFixture *) fixture_data; const char *script_with_case_statements_branch = "let y;\n" "for (let x = 0; x < 3; x++) {\n" " switch (x) {\n" " case 0:\n" " case 1:\n" " y = x + 1;\n" " break;\n" " case 2:\n" " y = x + 1;\n" " break;\n" " case 3:\n" " y = x +1;\n" " break;\n" " }\n" "}\n"; replace_file(fixture->tmp_js_script, script_with_case_statements_branch); char *coverage_data_contents = eval_script_and_get_coverage_data(fixture->context, fixture->coverage, fixture->tmp_js_script, fixture->lcov_output); const BranchLineData expected_branches[] = { {2, 0, TAKEN}, {2, 1, TAKEN}, {3, 0, TAKEN}, {3, 1, TAKEN}, {3, 2, NOT_TAKEN}, {3, 3, NOT_TAKEN}, }; const gsize expected_branches_len = G_N_ELEMENTS(expected_branches); /* There are two possible branches here, the second should be taken * and the first should not have been */ assert_coverage_data_matches_values_for_key(coverage_data_contents, "BRDA:", expected_branches_len, branch_at_line_should_be_taken, expected_branches, sizeof(BranchLineData)); g_free(coverage_data_contents); } static void any_line_matches_not_executed_branch(const char *data) { const char *line = line_starting_with(data, "BRDA:"); while (line) { int line_no, branch_id, block_no; char hit_count; /* Advance past "BRDA:" */ line += 5; int nmatches = sscanf(line, "%i,%i,%i,%c", &line_no, &block_no, &branch_id, &hit_count); g_assert_cmpint(nmatches, ==, 4); if (line_no == 3 && branch_id == 0 && hit_count == '-') return; line = line_starting_with(line + 1, "BRDA:"); } g_assert_true(false && "BRDA line with line 3 not found"); } static void test_branch_not_hit_written_to_coverage_data(void* fixture_data, const void*) { if (skip_if_gc_zeal_mode()) return; GjsCoverageFixture *fixture = (GjsCoverageFixture *) fixture_data; const char *script_with_never_executed_branch = "let x = 0;\n" "if (x > 0) {\n" " if (x > 0)\n" " x++;\n" "} else {\n" " x++;\n" "}\n"; replace_file(fixture->tmp_js_script, script_with_never_executed_branch); char *coverage_data_contents = eval_script_and_get_coverage_data(fixture->context, fixture->coverage, fixture->tmp_js_script, fixture->lcov_output); any_line_matches_not_executed_branch(coverage_data_contents); g_free(coverage_data_contents); } static void has_function_name(const char *line, const void *user_data) { const char *expected_function_name = *(static_cast(user_data)); /* Advance past "FN:" */ line += 3; /* Advance past the first comma */ while (*(line - 1) != ',') ++line; GjsAutoChar actual = g_strndup(line, strlen(expected_function_name)); g_assert_cmpstr(actual, ==, expected_function_name); } static void test_function_names_written_to_coverage_data(void* fixture_data, const void*) { if (skip_if_gc_zeal_mode()) return; GjsCoverageFixture *fixture = (GjsCoverageFixture *) fixture_data; const char *script_with_named_and_unnamed_functions = "function f(){}\n" "let b = function(){}\n"; replace_file(fixture->tmp_js_script, script_with_named_and_unnamed_functions); char *coverage_data_contents = eval_script_and_get_coverage_data(fixture->context, fixture->coverage, fixture->tmp_js_script, fixture->lcov_output); const char* expected_function_names[] = { "top-level", "f", "b", }; const gsize expected_function_names_len = G_N_ELEMENTS(expected_function_names); /* Just expect that we've got an FN matching out expected function names */ assert_coverage_data_matches_values_for_key(coverage_data_contents, "FN:", expected_function_names_len, has_function_name, expected_function_names, sizeof(const char *)); g_free(coverage_data_contents); } static void has_function_line(const char *line, const void *user_data) { const char *expected_function_line = *(static_cast(user_data)); /* Advance past "FN:" */ line += 3; GjsAutoChar actual = g_strndup(line, strlen(expected_function_line)); g_assert_cmpstr(actual, ==, expected_function_line); } static void test_function_lines_written_to_coverage_data(void* fixture_data, const void*) { if (skip_if_gc_zeal_mode()) return; GjsCoverageFixture *fixture = (GjsCoverageFixture *) fixture_data; const char *script_with_functions = "function f(){}\n" "\n" "function g(){}\n"; replace_file(fixture->tmp_js_script, script_with_functions); char *coverage_data_contents = eval_script_and_get_coverage_data(fixture->context, fixture->coverage, fixture->tmp_js_script, fixture->lcov_output); const char* const expected_function_lines[] = { "1", "1", "3", }; const gsize expected_function_lines_len = G_N_ELEMENTS(expected_function_lines); assert_coverage_data_matches_values_for_key(coverage_data_contents, "FN:", expected_function_lines_len, has_function_line, expected_function_lines, sizeof(const char *)); g_free(coverage_data_contents); } typedef struct _FunctionHitCountData { const char *function; unsigned int hit_count_minimum; } FunctionHitCountData; static void hit_count_is_more_than_for_function(const char *line, const void *user_data) { auto data = static_cast(user_data); char *detected_function = NULL; unsigned int hit_count; size_t max_buf_size; int nmatches; /* Advance past "FNDA:" */ line += 5; max_buf_size = strcspn(line, "\n"); detected_function = g_new(char, max_buf_size + 1); GjsAutoChar format_string = g_strdup_printf("%%5u,%%%zus", max_buf_size); // clang-format off #if defined(__clang__) || __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6) _Pragma("GCC diagnostic push") _Pragma("GCC diagnostic ignored \"-Wformat-nonliteral\"") #endif nmatches = sscanf(line, format_string, &hit_count, detected_function); #if defined(__clang__) || __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6) _Pragma("GCC diagnostic pop") #endif // clang-format on g_assert_cmpint(nmatches, ==, 2); g_assert_cmpstr(data->function, ==, detected_function); g_assert_cmpuint(hit_count, >=, data->hit_count_minimum); g_free(detected_function); } /* For functions with whitespace between their definition and * first executable line, its possible that the JS engine might * enter their frame a little later in the script than where their * definition starts. We need to handle that case */ static void test_function_hit_counts_for_big_functions_written_to_coverage_data( void* fixture_data, const void*) { if (skip_if_gc_zeal_mode()) return; GjsCoverageFixture *fixture = (GjsCoverageFixture *) fixture_data; const char *script_with_executed_functions = "function f(){\n" "\n" "\n" "var x = 1;\n" "}\n" "let b = function(){}\n" "f();\n" "b();\n"; replace_file(fixture->tmp_js_script, script_with_executed_functions); char *coverage_data_contents = eval_script_and_get_coverage_data(fixture->context, fixture->coverage, fixture->tmp_js_script, fixture->lcov_output); const FunctionHitCountData expected_hit_counts[] = { {"top-level", 1}, {"f", 1}, {"b", 1}, }; const gsize expected_hit_count_len = G_N_ELEMENTS(expected_hit_counts); /* There are two possible branches here, the second should be taken * and the first should not have been */ assert_coverage_data_matches_values_for_key(coverage_data_contents, "FNDA:", expected_hit_count_len, hit_count_is_more_than_for_function, expected_hit_counts, sizeof(FunctionHitCountData)); g_free(coverage_data_contents); } /* For functions which start executing at a function declaration * we also need to make sure that we roll back to the real function, */ static void test_function_hit_counts_for_little_functions_written_to_coverage_data( void* fixture_data, const void*) { if (skip_if_gc_zeal_mode()) return; GjsCoverageFixture *fixture = (GjsCoverageFixture *) fixture_data; const char *script_with_executed_functions = "function f(){\n" "var x = function(){};\n" "}\n" "let b = function(){}\n" "f();\n" "b();\n"; replace_file(fixture->tmp_js_script, script_with_executed_functions); char *coverage_data_contents = eval_script_and_get_coverage_data(fixture->context, fixture->coverage, fixture->tmp_js_script, fixture->lcov_output); const FunctionHitCountData expected_hit_counts[] = { {"top-level", 1}, {"f", 1}, {"b", 1}, }; const gsize expected_hit_count_len = G_N_ELEMENTS(expected_hit_counts); /* There are two possible branches here, the second should be taken * and the first should not have been */ assert_coverage_data_matches_values_for_key(coverage_data_contents, "FNDA:", expected_hit_count_len, hit_count_is_more_than_for_function, expected_hit_counts, sizeof(FunctionHitCountData)); g_free(coverage_data_contents); } static void test_function_hit_counts_written_to_coverage_data( void* fixture_data, const void*) { if (skip_if_gc_zeal_mode()) return; GjsCoverageFixture *fixture = (GjsCoverageFixture *) fixture_data; const char *script_with_executed_functions = "function f(){}\n" "let b = function(){}\n" "f();\n" "b();\n"; replace_file(fixture->tmp_js_script, script_with_executed_functions); char *coverage_data_contents = eval_script_and_get_coverage_data(fixture->context, fixture->coverage, fixture->tmp_js_script, fixture->lcov_output); const FunctionHitCountData expected_hit_counts[] = { {"top-level", 1}, {"f", 1}, {"b", 1}, }; const gsize expected_hit_count_len = G_N_ELEMENTS(expected_hit_counts); /* There are two possible branches here, the second should be taken * and the first should not have been */ assert_coverage_data_matches_values_for_key(coverage_data_contents, "FNDA:", expected_hit_count_len, hit_count_is_more_than_for_function, expected_hit_counts, sizeof(FunctionHitCountData)); g_free(coverage_data_contents); } static void test_total_function_coverage_written_to_coverage_data( void* fixture_data, const void*) { if (skip_if_gc_zeal_mode()) return; GjsCoverageFixture *fixture = (GjsCoverageFixture *) fixture_data; const char *script_with_some_executed_functions = "function f(){}\n" "let b = function(){}\n" "f();\n"; replace_file(fixture->tmp_js_script, script_with_some_executed_functions); char *coverage_data_contents = eval_script_and_get_coverage_data(fixture->context, fixture->coverage, fixture->tmp_js_script, fixture->lcov_output); /* More than one assert per test is bad, but we are testing interlinked concepts */ assert_coverage_data_contains_value_for_key(coverage_data_contents, "FNF:", "3"); assert_coverage_data_contains_value_for_key(coverage_data_contents, "FNH:", "2"); g_free(coverage_data_contents); } typedef struct _LineCountIsMoreThanData { unsigned int expected_lineno; unsigned int expected_to_be_more_than; } LineCountIsMoreThanData; static void line_hit_count_is_more_than(const char *line, const void *user_data) { auto data = static_cast(user_data); const char *coverage_line = &line[3]; char *comma_ptr = NULL; unsigned int lineno = strtol(coverage_line, &comma_ptr, 10); g_assert_cmpint(comma_ptr[0], ==, ','); char *end_ptr = NULL; unsigned int value = strtol(&comma_ptr[1], &end_ptr, 10); g_assert_true(end_ptr[0] == '\0' || end_ptr[0] == '\n'); g_assert_cmpuint(lineno, ==, data->expected_lineno); g_assert_cmpuint(value, >, data->expected_to_be_more_than); } static void test_single_line_hit_written_to_coverage_data(void* fixture_data, const void*) { if (skip_if_gc_zeal_mode()) return; GjsCoverageFixture *fixture = (GjsCoverageFixture *) fixture_data; char *coverage_data_contents = eval_script_and_get_coverage_data(fixture->context, fixture->coverage, fixture->tmp_js_script, fixture->lcov_output); const LineCountIsMoreThanData data = {1, 0}; assert_coverage_data_matches_value_for_key(coverage_data_contents, "DA:", line_hit_count_is_more_than, &data); g_free(coverage_data_contents); } static void test_hits_on_multiline_if_cond(void* fixture_data, const void*) { if (skip_if_gc_zeal_mode()) return; GjsCoverageFixture *fixture = (GjsCoverageFixture *) fixture_data; const char *script_with_multine_if_cond = "let a = 1;\n" "let b = 1;\n" "if (a &&\n" " b) {\n" "}\n"; replace_file(fixture->tmp_js_script, script_with_multine_if_cond); char *coverage_data_contents = eval_script_and_get_coverage_data(fixture->context, fixture->coverage, fixture->tmp_js_script, fixture->lcov_output); /* Hits on all lines, including both lines with a condition (3 and 4) */ const LineCountIsMoreThanData data[] = {{1, 0}, {2, 0}, {3, 0}, {4, 0}}; assert_coverage_data_matches_value_for_key(coverage_data_contents, "DA:", line_hit_count_is_more_than, data); g_free(coverage_data_contents); } static void test_full_line_tally_written_to_coverage_data(void* fixture_data, const void*) { if (skip_if_gc_zeal_mode()) return; GjsCoverageFixture *fixture = (GjsCoverageFixture *) fixture_data; char *coverage_data_contents = eval_script_and_get_coverage_data(fixture->context, fixture->coverage, fixture->tmp_js_script, fixture->lcov_output); /* More than one assert per test is bad, but we are testing interlinked concepts */ assert_coverage_data_contains_value_for_key(coverage_data_contents, "LF:", "1"); assert_coverage_data_contains_value_for_key(coverage_data_contents, "LH:", "1"); g_free(coverage_data_contents); } static void test_no_hits_to_coverage_data_for_unexecuted(void* fixture_data, const void*) { if (skip_if_gc_zeal_mode()) return; GjsCoverageFixture *fixture = (GjsCoverageFixture *) fixture_data; char *coverage_data_contents = write_statistics_and_get_coverage_data(fixture->coverage, fixture->lcov_output); /* No files were executed, so the coverage data is empty. */ g_assert_cmpstr(coverage_data_contents, ==, "\n"); g_free(coverage_data_contents); } static void test_end_of_record_section_written_to_coverage_data( void* fixture_data, const void*) { if (skip_if_gc_zeal_mode()) return; GjsCoverageFixture *fixture = (GjsCoverageFixture *) fixture_data; char *coverage_data_contents = eval_script_and_get_coverage_data(fixture->context, fixture->coverage, fixture->tmp_js_script, fixture->lcov_output); g_assert_nonnull(strstr(coverage_data_contents, "end_of_record")); g_free(coverage_data_contents); } typedef struct _GjsCoverageMultipleSourcesFixture { GjsCoverageFixture base_fixture; GFile *second_js_source_file; } GjsCoverageMultpleSourcesFixutre; static void gjs_coverage_multiple_source_files_to_single_output_fixture_set_up(gpointer fixture_data, gconstpointer user_data) { gjs_coverage_fixture_set_up(fixture_data, user_data); GjsCoverageMultpleSourcesFixutre *fixture = (GjsCoverageMultpleSourcesFixutre *) fixture_data; fixture->second_js_source_file = g_file_get_child(fixture->base_fixture.tmp_output_dir, "gjs_coverage_second_source_file.js"); /* Because GjsCoverage searches the coverage paths at object-creation time, * we need to destroy the previously constructed one and construct it again */ char *first_js_script_path = g_file_get_path(fixture->base_fixture.tmp_js_script); char *second_js_script_path = g_file_get_path(fixture->second_js_source_file); char *coverage_paths[] = { first_js_script_path, second_js_script_path, NULL }; g_object_unref(fixture->base_fixture.context); g_object_unref(fixture->base_fixture.coverage); char *output_path = g_file_get_path(fixture->base_fixture.tmp_output_dir); char *search_paths[] = { output_path, NULL }; fixture->base_fixture.context = gjs_context_new_with_search_path(search_paths); fixture->base_fixture.coverage = gjs_coverage_new(coverage_paths, fixture->base_fixture.context, fixture->base_fixture.lcov_output_dir); g_free(output_path); g_free(first_js_script_path); g_free(second_js_script_path); char *base_name = g_file_get_basename(fixture->base_fixture.tmp_js_script); char *base_name_without_extension = g_strndup(base_name, strlen(base_name) - 3); char* mock_script = g_strconcat("const FirstScript = imports.", base_name_without_extension, ";\n", "let a = FirstScript.f;\n" "\n", NULL); replace_file(fixture->second_js_source_file, mock_script); g_free(mock_script); g_free(base_name_without_extension); g_free(base_name); } static void gjs_coverage_multiple_source_files_to_single_output_fixture_tear_down(gpointer fixture_data, gconstpointer user_data) { GjsCoverageMultpleSourcesFixutre *fixture = (GjsCoverageMultpleSourcesFixutre *) fixture_data; g_object_unref(fixture->second_js_source_file); gjs_coverage_fixture_tear_down(fixture_data, user_data); } static void test_multiple_source_file_records_written_to_coverage_data( void* fixture_data, const void*) { if (skip_if_gc_zeal_mode()) return; GjsCoverageMultpleSourcesFixutre *fixture = (GjsCoverageMultpleSourcesFixutre *) fixture_data; char *coverage_data_contents = eval_script_and_get_coverage_data(fixture->base_fixture.context, fixture->base_fixture.coverage, fixture->second_js_source_file, fixture->base_fixture.lcov_output); const char *first_sf_record = line_starting_with(coverage_data_contents, "SF:"); g_assert_nonnull(first_sf_record); const char *second_sf_record = line_starting_with(first_sf_record + 1, "SF:"); g_assert_nonnull(second_sf_record); g_free(coverage_data_contents); } typedef struct _ExpectedSourceFileCoverageData { const char *source_file_path; LineCountIsMoreThanData *more_than; unsigned int n_more_than_matchers; const char expected_lines_hit_character; const char expected_lines_found_character; } ExpectedSourceFileCoverageData; static void assert_coverage_data_for_source_file(ExpectedSourceFileCoverageData *expected, const size_t expected_size, const char *section_start) { gsize i; for (i = 0; i < expected_size; ++i) { if (strncmp(§ion_start[3], expected[i].source_file_path, strlen (expected[i].source_file_path)) == 0) { assert_coverage_data_matches_values_for_key(section_start, "DA:", expected[i].n_more_than_matchers, line_hit_count_is_more_than, expected[i].more_than, sizeof (LineCountIsMoreThanData)); const char *total_hits_record = line_starting_with(section_start, "LH:"); g_assert_cmpint(total_hits_record[3], ==, expected[i].expected_lines_hit_character); const char *total_found_record = line_starting_with(section_start, "LF:"); g_assert_cmpint(total_found_record[3], ==, expected[i].expected_lines_found_character); return; } } g_assert_true(false && "Expected source file path to be found in section"); } static void test_correct_line_coverage_data_written_for_both_source_file_sections( void* fixture_data, const void*) { if (skip_if_gc_zeal_mode()) return; GjsCoverageMultpleSourcesFixutre *fixture = (GjsCoverageMultpleSourcesFixutre *) fixture_data; char *coverage_data_contents = eval_script_and_get_coverage_data(fixture->base_fixture.context, fixture->base_fixture.coverage, fixture->second_js_source_file, fixture->base_fixture.lcov_output); LineCountIsMoreThanData first_script_matcher = {1, 0}; LineCountIsMoreThanData second_script_matchers[] = {{1, 0}, {2, 0}}; char *first_script_output_path = get_output_path_for_script_on_disk(fixture->base_fixture.tmp_js_script, fixture->base_fixture.lcov_output_dir); char *second_script_output_path = get_output_path_for_script_on_disk(fixture->second_js_source_file, fixture->base_fixture.lcov_output_dir); ExpectedSourceFileCoverageData expected[] = { { first_script_output_path, &first_script_matcher, 1, '1', '1' }, { second_script_output_path, second_script_matchers, 2, '2', '2' } }; const gsize expected_len = G_N_ELEMENTS(expected); const char *first_sf_record = line_starting_with(coverage_data_contents, "SF:"); assert_coverage_data_for_source_file(expected, expected_len, first_sf_record); const char *second_sf_record = line_starting_with(first_sf_record + 3, "SF:"); assert_coverage_data_for_source_file(expected, expected_len, second_sf_record); g_free(first_script_output_path); g_free(second_script_output_path); g_free(coverage_data_contents); } typedef struct _FixturedTest { gsize fixture_size; GTestFixtureFunc set_up; GTestFixtureFunc tear_down; } FixturedTest; static void add_test_for_fixture(const char *name, FixturedTest *fixture, GTestFixtureFunc test_func, gconstpointer user_data) { g_test_add_vtable(name, fixture->fixture_size, user_data, fixture->set_up, test_func, fixture->tear_down); } void gjs_test_add_tests_for_coverage() { FixturedTest coverage_fixture = { sizeof(GjsCoverageFixture), gjs_coverage_fixture_set_up, gjs_coverage_fixture_tear_down }; add_test_for_fixture("/gjs/coverage/file_duplicated_into_output_path", &coverage_fixture, test_covered_file_is_duplicated_into_output_if_path, NULL); add_test_for_fixture("/gjs/coverage/file_duplicated_full_resource_path", &coverage_fixture, test_covered_file_is_duplicated_into_output_if_resource, NULL); add_test_for_fixture("/gjs/coverage/contents_preserved_accumulate_mode", &coverage_fixture, test_previous_contents_preserved, NULL); add_test_for_fixture("/gjs/coverage/new_contents_appended_accumulate_mode", &coverage_fixture, test_new_contents_written, NULL); add_test_for_fixture("/gjs/coverage/expected_source_file_name_written_to_coverage_data", &coverage_fixture, test_expected_source_file_name_written_to_coverage_data, NULL); add_test_for_fixture("/gjs/coverage/entry_not_written_for_nonexistent_file", &coverage_fixture, test_expected_entry_not_written_for_nonexistent_file, NULL); add_test_for_fixture("/gjs/coverage/single_branch_coverage_written_to_coverage_data", &coverage_fixture, test_single_branch_coverage_written_to_coverage_data, NULL); add_test_for_fixture("/gjs/coverage/multiple_branch_coverage_written_to_coverage_data", &coverage_fixture, test_multiple_branch_coverage_written_to_coverage_data, NULL); add_test_for_fixture("/gjs/coverage/branches_for_multiple_case_statements_fallthrough", &coverage_fixture, test_branches_for_multiple_case_statements_fallthrough, NULL); add_test_for_fixture("/gjs/coverage/not_hit_branch_point_written_to_coverage_data", &coverage_fixture, test_branch_not_hit_written_to_coverage_data, NULL); add_test_for_fixture("/gjs/coverage/function_names_written_to_coverage_data", &coverage_fixture, test_function_names_written_to_coverage_data, NULL); add_test_for_fixture("/gjs/coverage/function_lines_written_to_coverage_data", &coverage_fixture, test_function_lines_written_to_coverage_data, NULL); add_test_for_fixture("/gjs/coverage/function_hit_counts_written_to_coverage_data", &coverage_fixture, test_function_hit_counts_written_to_coverage_data, NULL); add_test_for_fixture("/gjs/coverage/big_function_hit_counts_written_to_coverage_data", &coverage_fixture, test_function_hit_counts_for_big_functions_written_to_coverage_data, NULL); add_test_for_fixture("/gjs/coverage/little_function_hit_counts_written_to_coverage_data", &coverage_fixture, test_function_hit_counts_for_little_functions_written_to_coverage_data, NULL); add_test_for_fixture("/gjs/coverage/total_function_coverage_written_to_coverage_data", &coverage_fixture, test_total_function_coverage_written_to_coverage_data, NULL); add_test_for_fixture("/gjs/coverage/single_line_hit_written_to_coverage_data", &coverage_fixture, test_single_line_hit_written_to_coverage_data, NULL); add_test_for_fixture("/gjs/coverage/hits_on_multiline_if_cond", &coverage_fixture, test_hits_on_multiline_if_cond, NULL); add_test_for_fixture("/gjs/coverage/full_line_tally_written_to_coverage_data", &coverage_fixture, test_full_line_tally_written_to_coverage_data, NULL); add_test_for_fixture("/gjs/coverage/no_hits_for_unexecuted_file", &coverage_fixture, test_no_hits_to_coverage_data_for_unexecuted, NULL); add_test_for_fixture("/gjs/coverage/end_of_record_section_written_to_coverage_data", &coverage_fixture, test_end_of_record_section_written_to_coverage_data, NULL); FixturedTest coverage_for_multiple_files_to_single_output_fixture = { sizeof(GjsCoverageMultpleSourcesFixutre), gjs_coverage_multiple_source_files_to_single_output_fixture_set_up, gjs_coverage_multiple_source_files_to_single_output_fixture_tear_down }; add_test_for_fixture("/gjs/coverage/multiple_source_file_records_written_to_coverage_data", &coverage_for_multiple_files_to_single_output_fixture, test_multiple_source_file_records_written_to_coverage_data, NULL); add_test_for_fixture("/gjs/coverage/correct_line_coverage_data_written_for_both_sections", &coverage_for_multiple_files_to_single_output_fixture, test_correct_line_coverage_data_written_for_both_source_file_sections, NULL); } cjs-5.2.0/test/test-bus.conf0000644000175000017500000000367114144444702016042 0ustar jpeisachjpeisach session unix:tmpdir=/tmp 1000000000 1000000000 1000000000 120000 240000 100000 10000 100000 10000 50000 50000 50000 300000 cjs-5.2.0/test/gjs-test-common.cpp0000644000175000017500000000416314144444702017154 0ustar jpeisachjpeisach/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* * Copyright © 2018 Philip Chimento * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #include #include #include #include #include #include #include #include "test/gjs-test-common.h" // COMPAT: https://gitlab.gnome.org/GNOME/glib/-/merge_requests/1553 #ifdef __clang_analyzer__ void g_assertion_message(const char*, const char*, int, const char*, const char*) __attribute__((analyzer_noreturn)); #endif char* gjs_test_get_exception_message(JSContext* cx) { if (!JS_IsExceptionPending(cx)) return nullptr; JS::RootedValue v_exc(cx); g_assert_true(JS_GetPendingException(cx, &v_exc)); g_assert_true(v_exc.isObject()); JS::RootedObject exc(cx, &v_exc.toObject()); JSErrorReport* report = JS_ErrorFromException(cx, exc); g_assert_nonnull(report); char* retval = g_strdup(report->message().c_str()); g_assert_nonnull(retval); JS_ClearPendingException(cx); return retval; } cjs-5.2.0/test/meson.build0000644000175000017500000000163114144444702015561 0ustar jpeisachjpeisach### Unit tests ################################################################# mock_js_resources_files = gnome.compile_resources('mock-js-resources', 'mock-js-resources.gresource.xml', c_name: 'mock_js_resources', source_dir: '..') gjs_tests_sources = [ 'gjs-tests.cpp', 'gjs-test-common.cpp', 'gjs-test-common.h', 'gjs-test-utils.cpp', 'gjs-test-utils.h', 'gjs-test-call-args.cpp', 'gjs-test-coverage.cpp', 'gjs-test-rooting.cpp', 'gjs-test-no-introspection-object.cpp', 'gjs-test-no-introspection-object.h', ] gjs_tests = executable('gjs-tests', gjs_tests_sources, mock_js_resources_files, cpp_args: ['-DGJS_COMPILATION'] + directory_defines, include_directories: top_include, dependencies: libgjs_dep) test('API tests', gjs_tests, args: ['--tap', '--keep-going', '--verbose'], depends: gjs_private_typelib, env: tests_environment, protocol: 'tap', suite: 'C') cjs-5.2.0/test/gjs-test-common.h0000644000175000017500000000252014144444702016614 0ustar jpeisachjpeisach/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* * Copyright © 2018 Philip Chimento * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #ifndef TEST_GJS_TEST_COMMON_H_ #define TEST_GJS_TEST_COMMON_H_ struct JSContext; char* gjs_test_get_exception_message(JSContext* cx); #endif // TEST_GJS_TEST_COMMON_H_ cjs-5.2.0/test/gjs-test-utils.cpp0000644000175000017500000000420514144444702017021 0ustar jpeisachjpeisach/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* * Copyright (c) 2008 litl, LLC * Copyright (c) 2016 Endless Mobile, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #include #include #include #include #include #include #include "cjs/context.h" #include "cjs/jsapi-util.h" #include "test/gjs-test-common.h" #include "test/gjs-test-utils.h" void gjs_unit_test_fixture_setup(GjsUnitTestFixture* fx, const void*) { fx->gjs_context = gjs_context_new(); fx->cx = (JSContext *) gjs_context_get_native_context(fx->gjs_context); JS::RootedObject global(fx->cx, gjs_get_import_global(fx->cx)); fx->realm = JS::EnterRealm(fx->cx, global); } void gjs_unit_test_destroy_context(GjsUnitTestFixture *fx) { GjsAutoChar message = gjs_test_get_exception_message(fx->cx); if (message) g_printerr("**\n%s\n", message.get()); JS::LeaveRealm(fx->cx, fx->realm); g_object_unref(fx->gjs_context); } void gjs_unit_test_fixture_teardown(GjsUnitTestFixture* fx, const void*) { gjs_unit_test_destroy_context(fx); } cjs-5.2.0/test/gjs-test-no-introspection-object.h0000644000175000017500000000324714144444702022111 0ustar jpeisachjpeisach/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* * Copyright © 2020 Endless Mobile Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #ifndef TEST_GJS_TEST_NO_INTROSPECTION_OBJECT_H_ #define TEST_GJS_TEST_NO_INTROSPECTION_OBJECT_H_ #include #define GJSTEST_TYPE_NO_INTROSPECTION_OBJECT \ gjstest_no_introspection_object_get_type() G_DECLARE_FINAL_TYPE(GjsTestNoIntrospectionObject, gjstest_no_introspection_object, GJSTEST, NO_INTROSPECTION_OBJECT, GObject) GjsTestNoIntrospectionObject* gjstest_no_introspection_object_peek(); #endif // TEST_GJS_TEST_NO_INTROSPECTION_OBJECT_H_ cjs-5.2.0/COPYING.LGPL0000644000175000017500000006131414144444702014234 0ustar jpeisachjpeisach GNU LIBRARY GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1991 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the library GPL. It is numbered 2 because it goes with version 2 of the ordinary GPL.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Library General Public License, applies to some specially designated Free Software Foundation software, and to any other libraries whose authors decide to use it. You can use it for your libraries, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library, or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link a program with the library, you must provide complete object files to the recipients so that they can relink them with the library, after making changes to the library and recompiling it. And you must show them these terms so they know their rights. Our method of protecting your rights has two steps: (1) copyright the library, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the library. Also, for each distributor's protection, we want to make certain that everyone understands that there is no warranty for this free library. If the library is modified by someone else and passed on, we want its recipients to know that what they have is not the original version, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that companies distributing free software will individually obtain patent licenses, thus in effect transforming the program into proprietary software. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License, which was designed for utility programs. This license, the GNU Library General Public License, applies to certain designated libraries. This license is quite different from the ordinary one; be sure to read it in full, and don't assume that anything in it is the same as in the ordinary license. The reason we have a separate public license for some libraries is that they blur the distinction we usually make between modifying or adding to a program and simply using it. Linking a program with a library, without changing the library, is in some sense simply using the library, and is analogous to running a utility program or application program. However, in a textual and legal sense, the linked executable is a combined work, a derivative of the original library, and the ordinary General Public License treats it as such. Because of this blurred distinction, using the ordinary General Public License for libraries did not effectively promote software sharing, because most developers did not use the libraries. We concluded that weaker conditions might promote sharing better. However, unrestricted linking of non-free programs would deprive the users of those programs of all benefit from the free status of the libraries themselves. This Library General Public License is intended to permit developers of non-free programs to use free libraries, while preserving your freedom as a user of such programs to change the free libraries that are incorporated in them. (We have not seen how to achieve this as regards changes in header files, but we have achieved it as regards changes in the actual functions of the Library.) The hope is that this will lead to faster development of free libraries. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, while the latter only works together with the library. Note that it is possible for a library to be covered by the ordinary General Public License rather than by this special one. GNU LIBRARY GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Library General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also compile or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. c) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. d) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Library General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307 USA. Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. , 1 April 1990 Ty Coon, President of Vice That's all there is to it! cjs-5.2.0/modules/0000755000175000017500000000000014144444702014107 5ustar jpeisachjpeisachcjs-5.2.0/modules/cairo-gradient.cpp0000644000175000017500000001015214144444702017502 0ustar jpeisachjpeisach/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* Copyright 2010 litl, LLC. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #include #include #include #include #include // for JSPROP_READONLY #include #include #include #include "cjs/jsapi-class.h" #include "cjs/jsapi-util-args.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" #include "modules/cairo-private.h" GJS_DEFINE_PROTO_ABSTRACT_WITH_PARENT("Gradient", cairo_gradient, cairo_pattern, JSCLASS_BACKGROUND_FINALIZE) static void gjs_cairo_gradient_finalize(JSFreeOp *fop, JSObject *obj) { gjs_cairo_pattern_finalize_pattern(fop, obj); } /* Properties */ // clang-format off JSPropertySpec gjs_cairo_gradient_proto_props[] = { JS_STRING_SYM_PS(toStringTag, "Gradient", JSPROP_READONLY), JS_PS_END}; // clang-format on /* Methods */ GJS_JSAPI_RETURN_CONVENTION static bool addColorStopRGB_func(JSContext *context, unsigned argc, JS::Value *vp) { GJS_GET_THIS(context, argc, vp, argv, obj); double offset, red, green, blue; if (!gjs_parse_call_args(context, "addColorStopRGB", argv, "ffff", "offset", &offset, "red", &red, "green", &green, "blue", &blue)) return false; cairo_pattern_t* pattern = gjs_cairo_pattern_get_pattern(context, obj); if (!pattern) return false; cairo_pattern_add_color_stop_rgb(pattern, offset, red, green, blue); if (!gjs_cairo_check_status(context, cairo_pattern_status(pattern), "pattern")) return false; argv.rval().setUndefined(); return true; } GJS_JSAPI_RETURN_CONVENTION static bool addColorStopRGBA_func(JSContext *context, unsigned argc, JS::Value *vp) { GJS_GET_THIS(context, argc, vp, argv, obj); double offset, red, green, blue, alpha; if (!gjs_parse_call_args(context, "addColorStopRGBA", argv, "fffff", "offset", &offset, "red", &red, "green", &green, "blue", &blue, "alpha", &alpha)) return false; cairo_pattern_t* pattern = gjs_cairo_pattern_get_pattern(context, obj); if (!pattern) return false; cairo_pattern_add_color_stop_rgba(pattern, offset, red, green, blue, alpha); if (!gjs_cairo_check_status(context, cairo_pattern_status(pattern), "pattern")) return false; argv.rval().setUndefined(); return true; } JSFunctionSpec gjs_cairo_gradient_proto_funcs[] = { JS_FN("addColorStopRGB", addColorStopRGB_func, 0, 0), JS_FN("addColorStopRGBA", addColorStopRGBA_func, 0, 0), // getColorStopRGB // getColorStopRGBA JS_FS_END}; JSFunctionSpec gjs_cairo_gradient_static_funcs[] = { JS_FS_END }; cjs-5.2.0/modules/cairo-path.cpp0000644000175000017500000000715314144444702016650 0ustar jpeisachjpeisach/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* Copyright 2010 Red Hat, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #include #include #include #include #include // for JSPROP_READONLY #include #include #include #include // for JS_GetClass, JS_GetInstancePrivate #include "cjs/jsapi-class.h" #include "cjs/jsapi-util.h" [[nodiscard]] static JSObject* gjs_cairo_path_get_proto(JSContext*); GJS_DEFINE_PROTO_ABSTRACT("Path", cairo_path, JSCLASS_BACKGROUND_FINALIZE) static void gjs_cairo_path_finalize(JSFreeOp*, JSObject* obj) { using AutoCairoPath = GjsAutoPointer; AutoCairoPath path = static_cast(JS_GetPrivate(obj)); JS_SetPrivate(obj, nullptr); } /* Properties */ // clang-format off JSPropertySpec gjs_cairo_path_proto_props[] = { JS_STRING_SYM_PS(toStringTag, "Path", JSPROP_READONLY), JS_PS_END}; // clang-format on JSFunctionSpec gjs_cairo_path_proto_funcs[] = { JS_FS_END }; JSFunctionSpec gjs_cairo_path_static_funcs[] = { JS_FS_END }; /** * gjs_cairo_path_from_path: * @context: the context * @path: cairo_path_t to attach to the object * * Constructs a pattern wrapper given cairo pattern. * NOTE: This function takes ownership of the path. */ JSObject * gjs_cairo_path_from_path(JSContext *context, cairo_path_t *path) { g_return_val_if_fail(context, nullptr); g_return_val_if_fail(path, nullptr); JS::RootedObject proto(context, gjs_cairo_path_get_proto(context)); JS::RootedObject object(context, JS_NewObjectWithGivenProto(context, &gjs_cairo_path_class, proto)); if (!object) { gjs_throw(context, "failed to create path"); return nullptr; } g_assert(!JS_GetPrivate(object)); JS_SetPrivate(object, path); return object; } /** * gjs_cairo_path_get_path: * @cx: the context * @path_wrapper: path wrapper * * Returns: the path attached to the wrapper. */ cairo_path_t* gjs_cairo_path_get_path(JSContext* cx, JS::HandleObject path_wrapper) { g_return_val_if_fail(cx, nullptr); g_return_val_if_fail(path_wrapper, nullptr); auto* path = static_cast(JS_GetInstancePrivate( cx, path_wrapper, &gjs_cairo_path_class, nullptr)); if (!path) { gjs_throw(cx, "Expected Cairo.Path but got %s", JS_GetClass(path_wrapper)->name); return nullptr; } return path; } cjs-5.2.0/modules/cairo-solid-pattern.cpp0000644000175000017500000001163514144444702020501 0ustar jpeisachjpeisach/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* Copyright 2010 litl, LLC. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #include #include #include #include #include #include // for JSPROP_READONLY #include #include #include #include // for JS_NewObjectWithGivenProto #include "cjs/jsapi-class.h" #include "cjs/jsapi-util-args.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" #include "modules/cairo-private.h" [[nodiscard]] static JSObject* gjs_cairo_solid_pattern_get_proto(JSContext*); GJS_DEFINE_PROTO_ABSTRACT_WITH_PARENT("SolidPattern", cairo_solid_pattern, cairo_pattern, JSCLASS_BACKGROUND_FINALIZE) static void gjs_cairo_solid_pattern_finalize(JSFreeOp *fop, JSObject *obj) { gjs_cairo_pattern_finalize_pattern(fop, obj); } // clang-format off JSPropertySpec gjs_cairo_solid_pattern_proto_props[] = { JS_STRING_SYM_PS(toStringTag, "SolidPattern", JSPROP_READONLY), JS_PS_END}; // clang-format on GJS_JSAPI_RETURN_CONVENTION static bool createRGB_func(JSContext *context, unsigned argc, JS::Value *vp) { JS::CallArgs argv = JS::CallArgsFromVp (argc, vp); double red, green, blue; cairo_pattern_t *pattern; JSObject *pattern_wrapper; if (!gjs_parse_call_args(context, "createRGB", argv, "fff", "red", &red, "green", &green, "blue", &blue)) return false; pattern = cairo_pattern_create_rgb(red, green, blue); if (!gjs_cairo_check_status(context, cairo_pattern_status(pattern), "pattern")) return false; pattern_wrapper = gjs_cairo_solid_pattern_from_pattern(context, pattern); cairo_pattern_destroy(pattern); argv.rval().setObjectOrNull(pattern_wrapper); return true; } GJS_JSAPI_RETURN_CONVENTION static bool createRGBA_func(JSContext *context, unsigned argc, JS::Value *vp) { JS::CallArgs argv = JS::CallArgsFromVp (argc, vp); double red, green, blue, alpha; cairo_pattern_t *pattern; JSObject *pattern_wrapper; if (!gjs_parse_call_args(context, "createRGBA", argv, "ffff", "red", &red, "green", &green, "blue", &blue, "alpha", &alpha)) return false; pattern = cairo_pattern_create_rgba(red, green, blue, alpha); if (!gjs_cairo_check_status(context, cairo_pattern_status(pattern), "pattern")) return false; pattern_wrapper = gjs_cairo_solid_pattern_from_pattern(context, pattern); cairo_pattern_destroy(pattern); argv.rval().setObjectOrNull(pattern_wrapper); return true; } JSFunctionSpec gjs_cairo_solid_pattern_proto_funcs[] = { JS_FN("createRGB", createRGB_func, 0, 0), JS_FN("createRGBA", createRGBA_func, 0, 0), JS_FS_END}; JSFunctionSpec gjs_cairo_solid_pattern_static_funcs[] = { JS_FS_END }; JSObject * gjs_cairo_solid_pattern_from_pattern(JSContext *context, cairo_pattern_t *pattern) { g_return_val_if_fail(context, nullptr); g_return_val_if_fail(pattern, nullptr); g_return_val_if_fail( cairo_pattern_get_type(pattern) == CAIRO_PATTERN_TYPE_SOLID, nullptr); JS::RootedObject proto(context, gjs_cairo_solid_pattern_get_proto(context)); JS::RootedObject object(context, JS_NewObjectWithGivenProto(context, &gjs_cairo_solid_pattern_class, proto)); if (!object) { gjs_throw(context, "failed to create solid pattern"); return nullptr; } gjs_cairo_pattern_construct(object, pattern); return object; } cjs-5.2.0/modules/core/0000755000175000017500000000000014144444702015037 5ustar jpeisachjpeisachcjs-5.2.0/modules/core/_cairo.js0000644000175000017500000000547314144444702016642 0ustar jpeisachjpeisach// Copyright 2010 litl, LLC. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to // deal in the Software without restriction, including without limitation the // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or // sell copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. /* exported Antialias, Content, Extend, FillRule, Filter, FontSlant, FontWeight, Format, LineCap, LineJoin, Operator, PatternType, SurfaceType */ var Antialias = { DEFAULT: 0, NONE: 1, GRAY: 2, SUBPIXEL: 3, }; var Content = { COLOR: 0x1000, ALPHA: 0x2000, COLOR_ALPHA: 0x3000, }; var Extend = { NONE: 0, REPEAT: 1, REFLECT: 2, PAD: 3, }; var FillRule = { WINDING: 0, EVEN_ODD: 1, }; var Filter = { FAST: 0, GOOD: 1, BEST: 2, NEAREST: 3, BILINEAR: 4, GAUSSIAN: 5, }; var FontSlant = { NORMAL: 0, ITALIC: 1, OBLIQUE: 2, }; var FontWeight = { NORMAL: 0, BOLD: 1, }; var Format = { ARGB32: 0, RGB24: 1, A8: 2, A1: 3, // The value of 4 is reserved by a deprecated enum value RGB16_565: 5, }; var LineCap = { BUTT: 0, ROUND: 1, SQUASH: 2, }; var LineJoin = { MITER: 0, ROUND: 1, BEVEL: 2, }; var Operator = { CLEAR: 0, SOURCE: 1, OVER: 2, IN: 3, OUT: 4, ATOP: 5, DEST: 6, DEST_OVER: 7, DEST_IN: 8, DEST_OUT: 9, DEST_ATOP: 10, XOR: 11, ADD: 12, SATURATE: 13, MULTIPLY: 14, SCREEN: 15, OVERLAY: 16, DARKEN: 17, LIGHTEN: 18, COLOR_DODGE: 19, COLOR_BURN: 20, HARD_LIGHT: 21, SOFT_LIGHT: 22, DIFFERENCE: 23, EXCLUSION: 24, HSL_HUE: 25, HSL_SATURATION: 26, HSL_COLOR: 27, HSL_LUMINOSITY: 28, }; var PatternType = { SOLID: 0, SURFACE: 1, LINEAR: 2, RADIAL: 3, }; var SurfaceType = { IMAGE: 0, PDF: 1, PS: 2, XLIB: 3, XCB: 4, GLITZ: 5, QUARTZ: 6, WIN32: 7, BEOS: 8, DIRECTFB: 9, SVG: 10, OS2: 11, WIN32_PRINTING: 12, QUARTZ_IMAGE: 13, }; cjs-5.2.0/modules/core/_common.js0000644000175000017500000001041114144444702017021 0ustar jpeisachjpeisach// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- // Copyright 2020 Philip Chimento // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to // deal in the Software without restriction, including without limitation the // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or // sell copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. /* exported _checkAccessors */ // This is a helper module in which to put code that is common between the // legacy GObject.Class system and the new GObject.registerClass system. function _generateAccessors(pspec, propdesc, GObject) { const {name, flags} = pspec; const readable = flags & GObject.ParamFlags.READABLE; const writable = flags & GObject.ParamFlags.WRITABLE; if (!propdesc) { propdesc = { configurable: true, enumerable: true, }; } if (readable && writable) { if (!propdesc.get && !propdesc.set) { const privateName = Symbol(`__autogeneratedAccessor__${name}`); const defaultValue = pspec.get_default_value(); propdesc.get = function () { if (!(privateName in this)) this[privateName] = defaultValue; return this[privateName]; }; propdesc.set = function (value) { if (value !== this[privateName]) { this[privateName] = value; this.notify(name); } }; } else if (!propdesc.get) { propdesc.get = function () { throw new Error(`setter defined without getter for property ${name}`); }; } else if (!propdesc.set) { propdesc.set = function () { throw new Error(`getter defined without setter for property ${name}`); }; } } else if (readable && !propdesc.get) { propdesc.get = function () { throw new Error(`missing getter for read-only property ${name}`); }; } else if (writable && !propdesc.set) { propdesc.set = function () { throw new Error(`missing setter for write-only property ${name}`); }; } return propdesc; } function _checkAccessors(proto, pspec, GObject) { const {name, flags} = pspec; const underscoreName = name.replace(/-/g, '_'); const camelName = name.replace(/-([a-z])/g, match => match[1].toUpperCase()); let propdesc = Object.getOwnPropertyDescriptor(proto, name); let dashPropdesc = propdesc, underscorePropdesc, camelPropdesc; const nameIsCompound = name.includes('-'); if (nameIsCompound) { underscorePropdesc = Object.getOwnPropertyDescriptor(proto, underscoreName); camelPropdesc = Object.getOwnPropertyDescriptor(proto, camelName); if (!propdesc) propdesc = underscorePropdesc; if (!propdesc) propdesc = camelPropdesc; } const readable = flags & GObject.ParamFlags.READABLE; const writable = flags & GObject.ParamFlags.WRITABLE; if (!propdesc || (readable && !propdesc.get) || (writable && !propdesc.set)) propdesc = _generateAccessors(pspec, propdesc, GObject); if (!dashPropdesc) Object.defineProperty(proto, name, propdesc); if (nameIsCompound) { if (!underscorePropdesc) Object.defineProperty(proto, underscoreName, propdesc); if (!camelPropdesc) Object.defineProperty(proto, camelName, propdesc); } } cjs-5.2.0/modules/core/_gettext.js0000644000175000017500000000622614144444702017226 0ustar jpeisachjpeisach// Copyright 2009 Red Hat, Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to // deal in the Software without restriction, including without limitation the // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or // sell copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. /* exported bindtextdomain, dcgettext, dgettext, dngettext, domain, dpgettext, gettext, LocaleCategory, ngettext, pgettext, setlocale, textdomain */ /** * This module provides a convenience layer for the "gettext" family of functions, * relying on GLib for the actual implementation. * * Usage: * * const Gettext = imports.gettext; * * Gettext.textdomain("myapp"); * Gettext.bindtextdomain("myapp", "/usr/share/locale"); * * let translated = Gettext.gettext("Hello world!"); */ const GLib = imports.gi.GLib; const CjsPrivate = imports.gi.CjsPrivate; var LocaleCategory = CjsPrivate.LocaleCategory; function setlocale(category, locale) { return CjsPrivate.setlocale(category, locale); } function textdomain(dom) { return CjsPrivate.textdomain(dom); } function bindtextdomain(dom, location) { return CjsPrivate.bindtextdomain(dom, location); } function gettext(msgid) { return GLib.dgettext(null, msgid); } function dgettext(dom, msgid) { return GLib.dgettext(dom, msgid); } function dcgettext(dom, msgid, category) { return GLib.dcgettext(dom, msgid, category); } function ngettext(msgid1, msgid2, n) { return GLib.dngettext(null, msgid1, msgid2, n); } function dngettext(dom, msgid1, msgid2, n) { return GLib.dngettext(dom, msgid1, msgid2, n); } // FIXME: missing dcngettext ? function pgettext(context, msgid) { return GLib.dpgettext2(null, context, msgid); } function dpgettext(dom, context, msgid) { return GLib.dpgettext2(dom, context, msgid); } /** * Create an object with bindings for gettext, ngettext, * and pgettext bound to a particular translation domain. * * @param {string} domainName Translation domain string * @returns {object} an object with gettext bindings */ function domain(domainName) { return { gettext(msgid) { return GLib.dgettext(domainName, msgid); }, ngettext(msgid1, msgid2, n) { return GLib.dngettext(domainName, msgid1, msgid2, n); }, pgettext(context, msgid) { return GLib.dpgettext2(domainName, context, msgid); }, }; } cjs-5.2.0/modules/core/overrides/0000755000175000017500000000000014144444702017041 5ustar jpeisachjpeisachcjs-5.2.0/modules/core/overrides/Gtk.js0000644000175000017500000001370214144444702020127 0ustar jpeisachjpeisach// application/javascript;version=1.8 // Copyright 2013 Giovanni Campagna // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to // deal in the Software without restriction, including without limitation the // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or // sell copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. const Legacy = imports._legacy; const {Gio, CjsPrivate, GObject} = imports.gi; let Gtk; let BuilderScope; function _init() { Gtk = this; Gtk.children = GObject.__gtkChildren__; Gtk.cssName = GObject.__gtkCssName__; Gtk.internalChildren = GObject.__gtkInternalChildren__; Gtk.template = GObject.__gtkTemplate__; let {GtkWidgetClass} = Legacy.defineGtkLegacyObjects(GObject, Gtk); Gtk.Widget.prototype.__metaclass__ = GtkWidgetClass; if (Gtk.Container && Gtk.Container.prototype.child_set_property) { Gtk.Container.prototype.child_set_property = function (child, property, value) { CjsPrivate.gtk_container_child_set_property(this, child, property, value); }; } Gtk.Widget.prototype._init = function (params) { if (this.constructor[Gtk.template]) { if (!BuilderScope) { Gtk.Widget.set_connect_func.call(this.constructor, (builder, obj, signalName, handlerName, connectObj, flags) => { const swapped = flags & GObject.ConnectFlags.SWAPPED; const closure = _createClosure( builder, this, handlerName, swapped, connectObj); if (flags & GObject.ConnectFlags.AFTER) obj.connect_after(signalName, closure); else obj.connect(signalName, closure); }); } } GObject.Object.prototype._init.call(this, params); if (this.constructor[Gtk.template]) { let children = this.constructor[Gtk.children] || []; for (let child of children) { this[child.replace(/-/g, '_')] = this.get_template_child(this.constructor, child); } let internalChildren = this.constructor[Gtk.internalChildren] || []; for (let child of internalChildren) { this[`_${child.replace(/-/g, '_')}`] = this.get_template_child(this.constructor, child); } } }; Gtk.Widget._classInit = function (klass) { let template = klass[Gtk.template]; let cssName = klass[Gtk.cssName]; let children = klass[Gtk.children]; let internalChildren = klass[Gtk.internalChildren]; if (template) { klass.prototype._instance_init = function () { this.init_template(); }; } klass = GObject.Object._classInit(klass); if (cssName) Gtk.Widget.set_css_name.call(klass, cssName); if (template) { if (typeof template === 'string') { if (template.startsWith('resource:///')) { Gtk.Widget.set_template_from_resource.call(klass, template.slice(11)); } else if (template.startsWith('file:///')) { let file = Gio.File.new_for_uri(template); let [, contents] = file.load_contents(null); Gtk.Widget.set_template.call(klass, contents); } } else { Gtk.Widget.set_template.call(klass, template); } if (BuilderScope) Gtk.Widget.set_template_scope.call(klass, new BuilderScope()); } if (children) { children.forEach(child => Gtk.Widget.bind_template_child_full.call(klass, child, false, 0)); } if (internalChildren) { internalChildren.forEach(child => Gtk.Widget.bind_template_child_full.call(klass, child, true, 0)); } return klass; }; if (Gtk.Widget.prototype.get_first_child) { Gtk.Widget.prototype[Symbol.iterator] = function* () { for (let c = this.get_first_child(); c; c = c.get_next_sibling()) yield c; }; } if (Gtk.BuilderScope) { BuilderScope = GObject.registerClass({ Implements: [Gtk.BuilderScope], }, class extends GObject.Object { vfunc_create_closure(builder, handlerName, flags, connectObject) { const swapped = flags & Gtk.BuilderClosureFlags.SWAPPED; return _createClosure( builder, builder.get_current_object(), handlerName, swapped, connectObject); } }); } } function _createClosure(builder, thisArg, handlerName, swapped, connectObject) { connectObject = connectObject || thisArg; if (swapped) { throw new Error('Unsupported template signal flag "swapped"'); } else if (typeof thisArg[handlerName] === 'undefined') { throw new Error(`A handler called ${handlerName} was not ` + `defined on ${thisArg}`); } return thisArg[handlerName].bind(connectObject); } cjs-5.2.0/modules/core/overrides/GObject.js0000644000175000017500000007454614144444702020734 0ustar jpeisachjpeisach/* exported _init, interfaces, properties, registerClass, requires, signals */ // Copyright 2011 Jasper St. Pierre // Copyright 2017 Philip Chimento , // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to // deal in the Software without restriction, including without limitation the // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or // sell copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. const Gi = imports._gi; const CjsPrivate = imports.gi.CjsPrivate; const {_checkAccessors} = imports._common; const Legacy = imports._legacy; let GObject; var GTypeName = Symbol('GType name'); var GTypeFlags = Symbol('GType flags'); var interfaces = Symbol('GObject interfaces'); var properties = Symbol('GObject properties'); var requires = Symbol('GObject interface requires'); var signals = Symbol('GObject signals'); // These four will be aliased to GTK var _gtkChildren = Symbol('GTK widget template children'); var _gtkCssName = Symbol('GTK widget CSS name'); var _gtkInternalChildren = Symbol('GTK widget template internal children'); var _gtkTemplate = Symbol('GTK widget template'); function registerClass(...args) { let klass = args[0]; if (args.length === 2) { // The two-argument form is the convenient syntax without ESnext // decorators and class data properties. The first argument is an // object with meta info such as properties and signals. The second // argument is the class expression for the class itself. // // var MyClass = GObject.registerClass({ // Properties: { ... }, // Signals: { ... }, // }, class MyClass extends GObject.Object { // _init() { ... } // }); // // When decorators and class data properties become part of the JS // standard, this function can be used directly as a decorator. let metaInfo = args[0]; klass = args[1]; if ('GTypeName' in metaInfo) klass[GTypeName] = metaInfo.GTypeName; if ('GTypeFlags' in metaInfo) klass[GTypeFlags] = metaInfo.GTypeFlags; if ('Implements' in metaInfo) klass[interfaces] = metaInfo.Implements; if ('Properties' in metaInfo) klass[properties] = metaInfo.Properties; if ('Signals' in metaInfo) klass[signals] = metaInfo.Signals; if ('Requires' in metaInfo) klass[requires] = metaInfo.Requires; if ('CssName' in metaInfo) klass[_gtkCssName] = metaInfo.CssName; if ('Template' in metaInfo) klass[_gtkTemplate] = metaInfo.Template; if ('Children' in metaInfo) klass[_gtkChildren] = metaInfo.Children; if ('InternalChildren' in metaInfo) klass[_gtkInternalChildren] = metaInfo.InternalChildren; } if (!(klass.prototype instanceof GObject.Object) && !(klass.prototype instanceof GObject.Interface)) { throw new TypeError('GObject.registerClass() used with invalid base ' + `class (is ${Object.getPrototypeOf(klass).name})`); } // Find the "least derived" class with a _classInit static function; there // definitely is one, since this class must inherit from GObject let initclass = klass; while (typeof initclass._classInit === 'undefined') initclass = Object.getPrototypeOf(initclass.prototype).constructor; return initclass._classInit(klass); } // Some common functions between GObject.Class and GObject.Interface function _createSignals(gtype, sigs) { for (let signalName in sigs) { let obj = sigs[signalName]; let flags = obj.flags !== undefined ? obj.flags : GObject.SignalFlags.RUN_FIRST; let accumulator = obj.accumulator !== undefined ? obj.accumulator : GObject.AccumulatorType.NONE; let rtype = obj.return_type !== undefined ? obj.return_type : GObject.TYPE_NONE; let paramtypes = obj.param_types !== undefined ? obj.param_types : []; try { obj.signal_id = Gi.signal_new(gtype, signalName, flags, accumulator, rtype, paramtypes); } catch (e) { throw new TypeError(`Invalid signal ${signalName}: ${e.message}`); } } } function _getCallerBasename() { const stackLines = new Error().stack.trim().split('\n'); const lineRegex = new RegExp(/@(.+:\/\/)?(.*\/)?(.+)\.js:\d+(:[\d]+)?$/); let thisFile = null; let thisDir = null; for (let line of stackLines) { let match = line.match(lineRegex); if (match) { let scriptDir = match[2]; let scriptBasename = match[3]; if (!thisFile) { thisDir = scriptDir; thisFile = scriptBasename; continue; } if (scriptDir === thisDir && scriptBasename === thisFile) continue; if (scriptDir && scriptDir.startsWith('/org/gnome/gjs/')) continue; let basename = scriptBasename; if (scriptDir) { scriptDir = scriptDir.replace(/^\/|\/$/g, ''); basename = `${scriptDir.split('/').reverse()[0]}_${basename}`; } return basename; } } return null; } function _createGTypeName(klass) { const sanitizeGType = s => s.replace(/[^a-z0-9+_-]/gi, '_'); if (klass.hasOwnProperty(GTypeName)) { let sanitized = sanitizeGType(klass[GTypeName]); if (sanitized !== klass[GTypeName]) { logError(new RangeError(`Provided GType name '${klass[GTypeName]}' ` + `is not valid; automatically sanitized to '${sanitized}'`)); } return sanitized; } let gtypeClassName = klass.name; if (GObject.gtypeNameBasedOnJSPath) { let callerBasename = _getCallerBasename(); if (callerBasename) gtypeClassName = `${callerBasename}_${gtypeClassName}`; } return sanitizeGType(`Gjs_${gtypeClassName}`); } function _propertiesAsArray(klass) { let propertiesArray = []; if (klass.hasOwnProperty(properties)) { for (let prop in klass[properties]) propertiesArray.push(klass[properties][prop]); } return propertiesArray; } function _copyAllDescriptors(target, source, filter) { Object.getOwnPropertyNames(source) .filter(key => !['prototype', 'constructor'].concat(filter).includes(key)) .concat(Object.getOwnPropertySymbols(source)) .forEach(key => { let descriptor = Object.getOwnPropertyDescriptor(source, key); Object.defineProperty(target, key, descriptor); }); } function _interfacePresent(required, klass) { if (!klass[interfaces]) return false; if (klass[interfaces].includes(required)) return true; // implemented here // Might be implemented on a parent class return _interfacePresent(required, Object.getPrototypeOf(klass)); } function _checkInterface(iface, proto) { // Checks for specific interfaces // Default vfunc_async_init() will run vfunc_init() in a thread and crash. // Change error message when https://gitlab.gnome.org/GNOME/gjs/issues/72 // has been solved. if (iface.$gtype.name === 'GAsyncInitable' && !Object.getOwnPropertyNames(proto).includes('vfunc_init_async')) throw new Error("It's not currently possible to implement Gio.AsyncInitable."); // Check that proto implements all of this interface's required interfaces. // "proto" refers to the object's prototype (which implements the interface) // whereas "iface.prototype" is the interface's prototype (which may still // contain unimplemented methods.) if (typeof iface[requires] === 'undefined') return; let unfulfilledReqs = iface[requires].filter(required => { // Either the interface is not present or it is not listed before the // interface that requires it or the class does not inherit it. This is // so that required interfaces don't copy over properties from other // interfaces that require them. let ifaces = proto.constructor[interfaces]; return (!_interfacePresent(required, proto.constructor) || ifaces.indexOf(required) > ifaces.indexOf(iface)) && !(proto instanceof required); }).map(required => // required.name will be present on JS classes, but on introspected // GObjects it will be the C name. The alternative is just so that // we print something if there is garbage in Requires. required.name || required); if (unfulfilledReqs.length > 0) { throw new Error('The following interfaces must be implemented before ' + `${iface.name}: ${unfulfilledReqs.join(', ')}`); } } function _init() { GObject = this; function _makeDummyClass(obj, name, upperName, gtypeName, actual) { let gtype = GObject.type_from_name(gtypeName); obj[`TYPE_${upperName}`] = gtype; obj[name] = function (v) { return actual(v); }; obj[name].$gtype = gtype; } GObject.gtypeNameBasedOnJSPath = false; _makeDummyClass(GObject, 'VoidType', 'NONE', 'void', function () {}); _makeDummyClass(GObject, 'Char', 'CHAR', 'gchar', Number); _makeDummyClass(GObject, 'UChar', 'UCHAR', 'guchar', Number); _makeDummyClass(GObject, 'Unichar', 'UNICHAR', 'gint', String); GObject.TYPE_BOOLEAN = GObject.type_from_name('gboolean'); GObject.Boolean = Boolean; Boolean.$gtype = GObject.TYPE_BOOLEAN; _makeDummyClass(GObject, 'Int', 'INT', 'gint', Number); _makeDummyClass(GObject, 'UInt', 'UINT', 'guint', Number); _makeDummyClass(GObject, 'Long', 'LONG', 'glong', Number); _makeDummyClass(GObject, 'ULong', 'ULONG', 'gulong', Number); _makeDummyClass(GObject, 'Int64', 'INT64', 'gint64', Number); _makeDummyClass(GObject, 'UInt64', 'UINT64', 'guint64', Number); GObject.TYPE_ENUM = GObject.type_from_name('GEnum'); GObject.TYPE_FLAGS = GObject.type_from_name('GFlags'); _makeDummyClass(GObject, 'Float', 'FLOAT', 'gfloat', Number); GObject.TYPE_DOUBLE = GObject.type_from_name('gdouble'); GObject.Double = Number; Number.$gtype = GObject.TYPE_DOUBLE; GObject.TYPE_STRING = GObject.type_from_name('gchararray'); GObject.String = String; String.$gtype = GObject.TYPE_STRING; GObject.TYPE_POINTER = GObject.type_from_name('gpointer'); GObject.TYPE_BOXED = GObject.type_from_name('GBoxed'); GObject.TYPE_PARAM = GObject.type_from_name('GParam'); GObject.TYPE_INTERFACE = GObject.type_from_name('GInterface'); GObject.TYPE_OBJECT = GObject.type_from_name('GObject'); GObject.TYPE_VARIANT = GObject.type_from_name('GVariant'); _makeDummyClass(GObject, 'Type', 'GTYPE', 'GType', GObject.type_from_name); GObject.ParamSpec.char = function (name, nick, blurb, flags, minimum, maximum, defaultValue) { return GObject.param_spec_char(name, nick, blurb, minimum, maximum, defaultValue, flags); }; GObject.ParamSpec.uchar = function (name, nick, blurb, flags, minimum, maximum, defaultValue) { return GObject.param_spec_uchar(name, nick, blurb, minimum, maximum, defaultValue, flags); }; GObject.ParamSpec.int = function (name, nick, blurb, flags, minimum, maximum, defaultValue) { return GObject.param_spec_int(name, nick, blurb, minimum, maximum, defaultValue, flags); }; GObject.ParamSpec.uint = function (name, nick, blurb, flags, minimum, maximum, defaultValue) { return GObject.param_spec_uint(name, nick, blurb, minimum, maximum, defaultValue, flags); }; GObject.ParamSpec.long = function (name, nick, blurb, flags, minimum, maximum, defaultValue) { return GObject.param_spec_long(name, nick, blurb, minimum, maximum, defaultValue, flags); }; GObject.ParamSpec.ulong = function (name, nick, blurb, flags, minimum, maximum, defaultValue) { return GObject.param_spec_ulong(name, nick, blurb, minimum, maximum, defaultValue, flags); }; GObject.ParamSpec.int64 = function (name, nick, blurb, flags, minimum, maximum, defaultValue) { return GObject.param_spec_int64(name, nick, blurb, minimum, maximum, defaultValue, flags); }; GObject.ParamSpec.uint64 = function (name, nick, blurb, flags, minimum, maximum, defaultValue) { return GObject.param_spec_uint64(name, nick, blurb, minimum, maximum, defaultValue, flags); }; GObject.ParamSpec.float = function (name, nick, blurb, flags, minimum, maximum, defaultValue) { return GObject.param_spec_float(name, nick, blurb, minimum, maximum, defaultValue, flags); }; GObject.ParamSpec.boolean = function (name, nick, blurb, flags, defaultValue) { return GObject.param_spec_boolean(name, nick, blurb, defaultValue, flags); }; GObject.ParamSpec.flags = function (name, nick, blurb, flags, flagsType, defaultValue) { return GObject.param_spec_flags(name, nick, blurb, flagsType, defaultValue, flags); }; GObject.ParamSpec.enum = function (name, nick, blurb, flags, enumType, defaultValue) { return GObject.param_spec_enum(name, nick, blurb, enumType, defaultValue, flags); }; GObject.ParamSpec.double = function (name, nick, blurb, flags, minimum, maximum, defaultValue) { return GObject.param_spec_double(name, nick, blurb, minimum, maximum, defaultValue, flags); }; GObject.ParamSpec.string = function (name, nick, blurb, flags, defaultValue) { return GObject.param_spec_string(name, nick, blurb, defaultValue, flags); }; GObject.ParamSpec.boxed = function (name, nick, blurb, flags, boxedType) { return GObject.param_spec_boxed(name, nick, blurb, boxedType, flags); }; GObject.ParamSpec.object = function (name, nick, blurb, flags, objectType) { return GObject.param_spec_object(name, nick, blurb, objectType, flags); }; GObject.ParamSpec.param = function (name, nick, blurb, flags, paramType) { return GObject.param_spec_param(name, nick, blurb, paramType, flags); }; GObject.ParamSpec.override = Gi.override_property; Object.defineProperties(GObject.ParamSpec.prototype, { 'name': { configurable: false, enumerable: false, get() { return this.get_name(); }, }, '_nick': { configurable: false, enumerable: false, get() { return this.get_nick(); }, }, 'nick': { configurable: false, enumerable: false, get() { return this.get_nick(); }, }, '_blurb': { configurable: false, enumerable: false, get() { return this.get_blurb(); }, }, 'blurb': { configurable: false, enumerable: false, get() { return this.get_blurb(); }, }, 'default_value': { configurable: false, enumerable: false, get() { return this.get_default_value(); }, }, 'flags': { configurable: false, enumerable: false, get() { return CjsPrivate.param_spec_get_flags(this); }, }, 'value_type': { configurable: false, enumerable: false, get() { return CjsPrivate.param_spec_get_value_type(this); }, }, 'owner_type': { configurable: false, enumerable: false, get() { return CjsPrivate.param_spec_get_owner_type(this); }, }, }); let {GObjectMeta, GObjectInterface} = Legacy.defineGObjectLegacyObjects(GObject); GObject.Class = GObjectMeta; GObject.Interface = GObjectInterface; GObject.Object.prototype.__metaclass__ = GObject.Class; // For compatibility with Lang.Class... we need a _construct // or the Lang.Class constructor will fail. GObject.Object.prototype._construct = function (...args) { this._init(...args); return this; }; GObject.registerClass = registerClass; GObject.Object._classInit = function (klass) { let gtypename = _createGTypeName(klass); let gflags = klass.hasOwnProperty(GTypeFlags) ? klass[GTypeFlags] : 0; let gobjectInterfaces = klass.hasOwnProperty(interfaces) ? klass[interfaces] : []; let propertiesArray = _propertiesAsArray(klass); let parent = Object.getPrototypeOf(klass); let gobjectSignals = klass.hasOwnProperty(signals) ? klass[signals] : []; propertiesArray.forEach(pspec => _checkAccessors(klass.prototype, pspec, GObject)); let newClass = Gi.register_type(parent.prototype, gtypename, gflags, gobjectInterfaces, propertiesArray); Object.setPrototypeOf(newClass, parent); _createSignals(newClass.$gtype, gobjectSignals); _copyAllDescriptors(newClass, klass); gobjectInterfaces.forEach(iface => _copyAllDescriptors(newClass.prototype, iface.prototype, ['toString'])); _copyAllDescriptors(newClass.prototype, klass.prototype); Object.getOwnPropertyNames(newClass.prototype) .filter(name => name.startsWith('vfunc_') || name.startsWith('on_')) .forEach(name => { let descr = Object.getOwnPropertyDescriptor(newClass.prototype, name); if (typeof descr.value !== 'function') return; let func = newClass.prototype[name]; if (name.startsWith('vfunc_')) { newClass.prototype[Gi.hook_up_vfunc_symbol](name.slice(6), func); } else if (name.startsWith('on_')) { let id = GObject.signal_lookup(name.slice(3).replace('_', '-'), newClass.$gtype); if (id !== 0) { GObject.signal_override_class_closure(id, newClass.$gtype, function (...argArray) { let emitter = argArray.shift(); return func.apply(emitter, argArray); }); } } }); gobjectInterfaces.forEach(iface => _checkInterface(iface, newClass.prototype)); // For backwards compatibility only. Use instanceof instead. newClass.implements = function (iface) { if (iface.$gtype) return GObject.type_is_a(newClass.$gtype, iface.$gtype); return false; }; return newClass; }; GObject.Interface._classInit = function (klass) { let gtypename = _createGTypeName(klass); let gobjectInterfaces = klass.hasOwnProperty(requires) ? klass[requires] : []; let props = _propertiesAsArray(klass); let gobjectSignals = klass.hasOwnProperty(signals) ? klass[signals] : []; let newInterface = Gi.register_interface(gtypename, gobjectInterfaces, props); _createSignals(newInterface.$gtype, gobjectSignals); _copyAllDescriptors(newInterface, klass); Object.getOwnPropertyNames(klass.prototype) .filter(key => key !== 'constructor') .concat(Object.getOwnPropertySymbols(klass.prototype)) .forEach(key => { let descr = Object.getOwnPropertyDescriptor(klass.prototype, key); // Create wrappers on the interface object so that generics work (e.g. // SomeInterface.some_function(this, blah) instead of // SomeInterface.prototype.some_function.call(this, blah) if (typeof descr.value === 'function') { let interfaceProto = klass.prototype; // capture in closure newInterface[key] = function (thisObj, ...args) { return interfaceProto[key].call(thisObj, ...args); }; } Object.defineProperty(newInterface.prototype, key, descr); }); return newInterface; }; /** * Use this to signify a function that must be overridden in an * implementation of the interface. */ GObject.NotImplementedError = class NotImplementedError extends Error { get name() { return 'NotImplementedError'; } }; // These will be copied in the Gtk overrides // Use __X__ syntax to indicate these variables should not be used publicly. GObject.__gtkCssName__ = _gtkCssName; GObject.__gtkTemplate__ = _gtkTemplate; GObject.__gtkChildren__ = _gtkChildren; GObject.__gtkInternalChildren__ = _gtkInternalChildren; // Expose GObject static properties for ES6 classes GObject.GTypeName = GTypeName; GObject.requires = requires; GObject.interfaces = interfaces; GObject.properties = properties; GObject.signals = signals; // Replacement for non-introspectable g_object_set() GObject.Object.prototype.set = function (params) { Object.assign(this, params); }; // fake enum for signal accumulators, keep in sync with gi/object.c GObject.AccumulatorType = { NONE: 0, FIRST_WINS: 1, TRUE_HANDLED: 2, }; GObject.Object.prototype.disconnect = function (id) { return GObject.signal_handler_disconnect(this, id); }; GObject.Object.prototype.block_signal_handler = function (id) { return GObject.signal_handler_block(this, id); }; GObject.Object.prototype.unblock_signal_handler = function (id) { return GObject.signal_handler_unblock(this, id); }; GObject.Object.prototype.stop_emission_by_name = function (detailedName) { return GObject.signal_stop_emission_by_name(this, detailedName); }; // A simple workaround if you have a class with .connect, .disconnect or .emit // methods (such as Gio.Socket.connect or NMClient.Device.disconnect) // The original g_signal_* functions are not introspectable anyway, because // we need our own handling of signal argument marshalling GObject.signal_connect = function (object, name, handler) { return GObject.Object.prototype.connect.call(object, name, handler); }; GObject.signal_connect_after = function (object, name, handler) { return GObject.Object.prototype.connect_after.call(object, name, handler); }; GObject.signal_emit_by_name = function (object, ...nameAndArgs) { return GObject.Object.prototype.emit.apply(object, nameAndArgs); }; // Replacements for signal_handler_find() and similar functions, which can't // work normally since we connect private closures GObject._real_signal_handler_find = GObject.signal_handler_find; GObject._real_signal_handlers_block_matched = GObject.signal_handlers_block_matched; GObject._real_signal_handlers_unblock_matched = GObject.signal_handlers_unblock_matched; GObject._real_signal_handlers_disconnect_matched = GObject.signal_handlers_disconnect_matched; /** * Finds the first signal handler that matches certain selection criteria. * The criteria are passed as properties of a match object. * The match object has to be non-empty for successful matches. * If no handler was found, a falsy value is returned. * @function * @param {GObject.Object} instance - the instance owning the signal handler * to be found. * @param {Object} match - a properties object indicating whether to match * by signal ID, detail, or callback function. * @param {string} [match.signalId] - signal the handler has to be connected * to. * @param {string} [match.detail] - signal detail the handler has to be * connected to. * @param {Function} [match.func] - the callback function the handler will * invoke. * @returns {number|BigInt|Object|null} A valid non-0 signal handler ID for * a successful match. */ GObject.signal_handler_find = function (instance, match) { // For backwards compatibility if (arguments.length === 7) // eslint-disable-next-line prefer-rest-params return GObject._real_signal_handler_find(...arguments); return instance[Gi.signal_find_symbol](match); }; /** * Blocks all handlers on an instance that match certain selection criteria. * The criteria are passed as properties of a match object. * The match object has to have at least `func` for successful matches. * If no handlers were found, 0 is returned, the number of blocked handlers * otherwise. * @function * @param {GObject.Object} instance - the instance owning the signal handler * to be found. * @param {Object} match - a properties object indicating whether to match * by signal ID, detail, or callback function. * @param {string} [match.signalId] - signal the handler has to be connected * to. * @param {string} [match.detail] - signal detail the handler has to be * connected to. * @param {Function} match.func - the callback function the handler will * invoke. * @returns {number} The number of handlers that matched. */ GObject.signal_handlers_block_matched = function (instance, match) { // For backwards compatibility if (arguments.length === 7) // eslint-disable-next-line prefer-rest-params return GObject._real_signal_handlers_block_matched(...arguments); return instance[Gi.signals_block_symbol](match); }; /** * Unblocks all handlers on an instance that match certain selection * criteria. * The criteria are passed as properties of a match object. * The match object has to have at least `func` for successful matches. * If no handlers were found, 0 is returned, the number of unblocked * handlers otherwise. * The match criteria should not apply to any handlers that are not * currently blocked. * @function * @param {GObject.Object} instance - the instance owning the signal handler * to be found. * @param {Object} match - a properties object indicating whether to match * by signal ID, detail, or callback function. * @param {string} [match.signalId] - signal the handler has to be connected * to. * @param {string} [match.detail] - signal detail the handler has to be * connected to. * @param {Function} match.func - the callback function the handler will * invoke. * @returns {number} The number of handlers that matched. */ GObject.signal_handlers_unblock_matched = function (instance, match) { // For backwards compatibility if (arguments.length === 7) // eslint-disable-next-line prefer-rest-params return GObject._real_signal_handlers_unblock_matched(...arguments); return instance[Gi.signals_unblock_symbol](match); }; /** * Disconnects all handlers on an instance that match certain selection * criteria. * The criteria are passed as properties of a match object. * The match object has to have at least `func` for successful matches. * If no handlers were found, 0 is returned, the number of disconnected * handlers otherwise. * @function * @param {GObject.Object} instance - the instance owning the signal handler * to be found. * @param {Object} match - a properties object indicating whether to match * by signal ID, detail, or callback function. * @param {string} [match.signalId] - signal the handler has to be connected * to. * @param {string} [match.detail] - signal detail the handler has to be * connected to. * @param {Function} match.func - the callback function the handler will * invoke. * @returns {number} The number of handlers that matched. */ GObject.signal_handlers_disconnect_matched = function (instance, match) { // For backwards compatibility if (arguments.length === 7) // eslint-disable-next-line prefer-rest-params return GObject._real_signal_handlers_disconnect_matched(...arguments); return instance[Gi.signals_disconnect_symbol](match); }; // Also match the macros used in C APIs, even though they're not introspected /** * Blocks all handlers on an instance that match `func`. * @function * @param {GObject.Object} instance - the instance to block handlers from. * @param {Function} func - the callback function the handler will invoke. * @returns {number} The number of handlers that matched. */ GObject.signal_handlers_block_by_func = function (instance, func) { return instance[Gi.signals_block_symbol]({func}); }; /** * Unblocks all handlers on an instance that match `func`. * @function * @param {GObject.Object} instance - the instance to unblock handlers from. * @param {Function} func - the callback function the handler will invoke. * @returns {number} The number of handlers that matched. */ GObject.signal_handlers_unblock_by_func = function (instance, func) { return instance[Gi.signals_unblock_symbol]({func}); }; /** * Disconnects all handlers on an instance that match `func`. * @function * @param {GObject.Object} instance - the instance to remove handlers from. * @param {Function} func - the callback function the handler will invoke. * @returns {number} The number of handlers that matched. */ GObject.signal_handlers_disconnect_by_func = function (instance, func) { return instance[Gi.signals_disconnect_symbol]({func}); }; GObject.signal_handlers_disconnect_by_data = function () { throw new Error('GObject.signal_handlers_disconnect_by_data() is not \ introspectable. Use GObject.signal_handlers_disconnect_by_func() instead.'); }; } cjs-5.2.0/modules/core/overrides/Gio.js0000644000175000017500000005436514144444702020132 0ustar jpeisachjpeisach// Copyright 2011 Giovanni Campagna // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to // deal in the Software without restriction, including without limitation the // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or // sell copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. var GLib = imports.gi.GLib; var CjsPrivate = imports.gi.CjsPrivate; var Signals = imports.signals; var Gio; // Ensures that a Gio.UnixFDList being passed into or out of a DBus method with // a parameter type that includes 'h' somewhere, actually has entries in it for // each of the indices being passed as an 'h' parameter. function _validateFDVariant(variant, fdList) { switch (String.fromCharCode(variant.classify())) { case 'b': case 'y': case 'n': case 'q': case 'i': case 'u': case 'x': case 't': case 'd': case 'o': case 'g': case 's': return; case 'h': { const val = variant.get_handle(); const numFds = fdList.get_length(); if (val >= numFds) { throw new Error(`handle ${val} is out of range of Gio.UnixFDList ` + `containing ${numFds} FDs`); } return; } case 'v': _validateFDVariant(variant.get_variant(), fdList); return; case 'm': { let val = variant.get_maybe(); if (val) _validateFDVariant(val, fdList); return; } case 'a': case '(': case '{': { let nElements = variant.n_children(); for (let ix = 0; ix < nElements; ix++) _validateFDVariant(variant.get_child_value(ix), fdList); return; } } throw new Error('Assertion failure: this code should not be reached'); } function _proxyInvoker(methodName, sync, inSignature, argArray) { var replyFunc; var flags = 0; var cancellable = null; let fdList = null; /* Convert argArray to a *real* array */ argArray = Array.prototype.slice.call(argArray); /* The default replyFunc only logs the responses */ replyFunc = _logReply; var signatureLength = inSignature.length; var minNumberArgs = signatureLength; var maxNumberArgs = signatureLength + 4; if (argArray.length < minNumberArgs) { throw new Error(`Not enough arguments passed for method: ${ methodName}. Expected ${minNumberArgs}, got ${argArray.length}`); } else if (argArray.length > maxNumberArgs) { throw new Error(`Too many arguments passed for method ${methodName}. ` + `Maximum is ${maxNumberArgs} including one callback, ` + 'Gio.Cancellable, Gio.UnixFDList, and/or flags'); } while (argArray.length > signatureLength) { var argNum = argArray.length - 1; var arg = argArray.pop(); if (typeof arg === 'function' && !sync) { replyFunc = arg; } else if (typeof arg === 'number') { flags = arg; } else if (arg instanceof Gio.Cancellable) { cancellable = arg; } else if (arg instanceof Gio.UnixFDList) { fdList = arg; } else { throw new Error(`Argument ${argNum} of method ${methodName} is ` + `${typeof arg}. It should be a callback, flags, ` + 'Gio.UnixFDList, or a Gio.Cancellable'); } } const inTypeString = `(${inSignature.join('')})`; const inVariant = new GLib.Variant(inTypeString, argArray); if (inTypeString.includes('h')) { if (!fdList) { throw new Error(`Method ${methodName} with input type containing ` + '\'h\' must have a Gio.UnixFDList as an argument'); } _validateFDVariant(inVariant, fdList); } var asyncCallback = (proxy, result) => { try { const [outVariant, outFdList] = proxy.call_with_unix_fd_list_finish(result); replyFunc(outVariant.deepUnpack(), null, outFdList); } catch (e) { replyFunc([], e, null); } }; if (sync) { const [outVariant, outFdList] = this.call_with_unix_fd_list_sync( methodName, inVariant, flags, -1, fdList, cancellable); if (fdList) return [outVariant.deepUnpack(), outFdList]; return outVariant.deepUnpack(); } return this.call_with_unix_fd_list(methodName, inVariant, flags, -1, fdList, cancellable, asyncCallback); } function _logReply(result, exc) { if (exc !== null) log(`Ignored exception from dbus method: ${exc}`); } function _makeProxyMethod(method, sync) { var i; var name = method.name; var inArgs = method.in_args; var inSignature = []; for (i = 0; i < inArgs.length; i++) inSignature.push(inArgs[i].signature); return function (...args) { return _proxyInvoker.call(this, name, sync, inSignature, args); }; } function _convertToNativeSignal(proxy, senderName, signalName, parameters) { Signals._emit.call(proxy, signalName, senderName, parameters.deepUnpack()); } function _propertyGetter(name) { let value = this.get_cached_property(name); return value ? value.deepUnpack() : null; } function _propertySetter(name, signature, value) { let variant = new GLib.Variant(signature, value); this.set_cached_property(name, variant); this.call('org.freedesktop.DBus.Properties.Set', new GLib.Variant('(ssv)', [this.g_interface_name, name, variant]), Gio.DBusCallFlags.NONE, -1, null, (proxy, result) => { try { this.call_finish(result); } catch (e) { log(`Could not set property ${name} on remote object ${ this.g_object_path}: ${e.message}`); } }); } function _addDBusConvenience() { let info = this.g_interface_info; if (!info) return; if (info.signals.length > 0) this.connect('g-signal', _convertToNativeSignal); let i, methods = info.methods; for (i = 0; i < methods.length; i++) { var method = methods[i]; this[`${method.name}Remote`] = _makeProxyMethod(methods[i], false); this[`${method.name}Sync`] = _makeProxyMethod(methods[i], true); } let properties = info.properties; for (i = 0; i < properties.length; i++) { let name = properties[i].name; let signature = properties[i].signature; let flags = properties[i].flags; let getter = () => { throw new Error(`Property ${name} is not readable`); }; let setter = () => { throw new Error(`Property ${name} is not writable`); }; if (flags & Gio.DBusPropertyInfoFlags.READABLE) getter = _propertyGetter.bind(this, name); if (flags & Gio.DBusPropertyInfoFlags.WRITABLE) setter = _propertySetter.bind(this, name, signature); Object.defineProperty(this, name, { get: getter, set: setter, configurable: false, enumerable: true, }); } } function _makeProxyWrapper(interfaceXml) { var info = _newInterfaceInfo(interfaceXml); var iname = info.name; return function (bus, name, object, asyncCallback, cancellable, flags = Gio.DBusProxyFlags.NONE) { var obj = new Gio.DBusProxy({ g_connection: bus, g_interface_name: iname, g_interface_info: info, g_name: name, g_flags: flags, g_object_path: object, }); if (!cancellable) cancellable = null; if (asyncCallback) { obj.init_async(GLib.PRIORITY_DEFAULT, cancellable, (initable, result) => { let caughtErrorWhenInitting = null; try { initable.init_finish(result); } catch (e) { caughtErrorWhenInitting = e; } if (caughtErrorWhenInitting === null) asyncCallback(initable, null); else asyncCallback(null, caughtErrorWhenInitting); }); } else { obj.init(cancellable); } return obj; }; } function _newNodeInfo(constructor, value) { if (typeof value === 'string') return constructor(value); throw TypeError(`Invalid type ${Object.prototype.toString.call(value)}`); } function _newInterfaceInfo(value) { var nodeInfo = Gio.DBusNodeInfo.new_for_xml(value); return nodeInfo.interfaces[0]; } function _injectToMethod(klass, method, addition) { var previous = klass[method]; klass[method] = function (...args) { addition.apply(this, args); return previous.apply(this, args); }; } function _injectToStaticMethod(klass, method, addition) { var previous = klass[method]; klass[method] = function (...parameters) { let obj = previous.apply(this, parameters); addition.apply(obj, parameters); return obj; }; } function _wrapFunction(klass, method, addition) { var previous = klass[method]; klass[method] = function (...args) { args.unshift(previous); return addition.apply(this, args); }; } function _makeOutSignature(args) { var ret = '('; for (var i = 0; i < args.length; i++) ret += args[i].signature; return `${ret})`; } function _handleMethodCall(info, impl, methodName, parameters, invocation) { // prefer a sync version if available if (this[methodName]) { let retval; try { const fdList = invocation.get_message().get_unix_fd_list(); retval = this[methodName](...parameters.deepUnpack(), fdList); } catch (e) { if (e instanceof GLib.Error) { invocation.return_gerror(e); } else { let name = e.name; if (!name.includes('.')) { // likely to be a normal JS error name = `org.gnome.gjs.JSError.${name}`; } logError(e, `Exception in method call: ${methodName}`); invocation.return_dbus_error(name, e.message); } return; } if (retval === undefined) { // undefined (no return value) is the empty tuple retval = new GLib.Variant('()', []); } try { let outFdList = null; if (!(retval instanceof GLib.Variant)) { // attempt packing according to out signature let methodInfo = info.lookup_method(methodName); let outArgs = methodInfo.out_args; let outSignature = _makeOutSignature(outArgs); if (outSignature.includes('h') && retval[retval.length - 1] instanceof Gio.UnixFDList) { outFdList = retval.pop(); } else if (outArgs.length === 1) { // if one arg, we don't require the handler wrapping it // into an Array retval = [retval]; } retval = new GLib.Variant(outSignature, retval); } invocation.return_value_with_unix_fd_list(retval, outFdList); } catch (e) { // if we don't do this, the other side will never see a reply invocation.return_dbus_error('org.gnome.gjs.JSError.ValueError', 'Service implementation returned an incorrect value type'); } } else if (this[`${methodName}Async`]) { const fdList = invocation.get_message().get_unix_fd_list(); this[`${methodName}Async`](parameters.deepUnpack(), invocation, fdList); } else { log(`Missing handler for DBus method ${methodName}`); invocation.return_gerror(new Gio.DBusError({ code: Gio.DBusError.UNKNOWN_METHOD, message: `Method ${methodName} is not implemented`, })); } } function _handlePropertyGet(info, impl, propertyName) { let propInfo = info.lookup_property(propertyName); let jsval = this[propertyName]; if (jsval !== undefined) return new GLib.Variant(propInfo.signature, jsval); else return null; } function _handlePropertySet(info, impl, propertyName, newValue) { this[propertyName] = newValue.deepUnpack(); } function _wrapJSObject(interfaceInfo, jsObj) { var info; if (interfaceInfo instanceof Gio.DBusInterfaceInfo) info = interfaceInfo; else info = Gio.DBusInterfaceInfo.new_for_xml(interfaceInfo); info.cache_build(); var impl = new CjsPrivate.DBusImplementation({g_interface_info: info}); impl.connect('handle-method-call', function (self, methodName, parameters, invocation) { return _handleMethodCall.call(jsObj, info, self, methodName, parameters, invocation); }); impl.connect('handle-property-get', function (self, propertyName) { return _handlePropertyGet.call(jsObj, info, self, propertyName); }); impl.connect('handle-property-set', function (self, propertyName, value) { return _handlePropertySet.call(jsObj, info, self, propertyName, value); }); return impl; } function* _listModelIterator() { let _index = 0; const _len = this.get_n_items(); while (_index < _len) yield this.get_item(_index++); } function _promisify(proto, asyncFunc, finishFunc) { if (proto[`_original_${asyncFunc}`] !== undefined) return; proto[`_original_${asyncFunc}`] = proto[asyncFunc]; proto[asyncFunc] = function (...args) { if (!args.every(arg => typeof arg !== 'function')) return this[`_original_${asyncFunc}`](...args); return new Promise((resolve, reject) => { const callStack = new Error().stack.split('\n').filter(line => !line.match(/promisify/)).join('\n'); this[`_original_${asyncFunc}`](...args, function (source, res) { try { const result = source !== null && source[finishFunc] !== undefined ? source[finishFunc](res) : proto[finishFunc](res); if (Array.isArray(result) && result.length > 1 && result[0] === true) result.shift(); resolve(result); } catch (error) { if (error.stack) error.stack += `### Promise created here: ###\n${callStack}`; else error.stack = callStack; reject(error); } }); }); }; } function _init() { Gio = this; Gio.DBus = { get session() { return Gio.bus_get_sync(Gio.BusType.SESSION, null); }, get system() { return Gio.bus_get_sync(Gio.BusType.SYSTEM, null); }, // Namespace some functions get: Gio.bus_get, get_finish: Gio.bus_get_finish, get_sync: Gio.bus_get_sync, own_name: Gio.bus_own_name, own_name_on_connection: Gio.bus_own_name_on_connection, unown_name: Gio.bus_unown_name, watch_name: Gio.bus_watch_name, watch_name_on_connection: Gio.bus_watch_name_on_connection, unwatch_name: Gio.bus_unwatch_name, }; Gio.DBusConnection.prototype.watch_name = function (name, flags, appeared, vanished) { return Gio.bus_watch_name_on_connection(this, name, flags, appeared, vanished); }; Gio.DBusConnection.prototype.unwatch_name = function (id) { return Gio.bus_unwatch_name(id); }; Gio.DBusConnection.prototype.own_name = function (name, flags, acquired, lost) { return Gio.bus_own_name_on_connection(this, name, flags, acquired, lost); }; Gio.DBusConnection.prototype.unown_name = function (id) { return Gio.bus_unown_name(id); }; _injectToMethod(Gio.DBusProxy.prototype, 'init', _addDBusConvenience); _injectToMethod(Gio.DBusProxy.prototype, 'init_async', _addDBusConvenience); _injectToStaticMethod(Gio.DBusProxy, 'new_sync', _addDBusConvenience); _injectToStaticMethod(Gio.DBusProxy, 'new_finish', _addDBusConvenience); _injectToStaticMethod(Gio.DBusProxy, 'new_for_bus_sync', _addDBusConvenience); _injectToStaticMethod(Gio.DBusProxy, 'new_for_bus_finish', _addDBusConvenience); Gio.DBusProxy.prototype.connectSignal = Signals._connect; Gio.DBusProxy.prototype.disconnectSignal = Signals._disconnect; Gio.DBusProxy.makeProxyWrapper = _makeProxyWrapper; // Some helpers _wrapFunction(Gio.DBusNodeInfo, 'new_for_xml', _newNodeInfo); Gio.DBusInterfaceInfo.new_for_xml = _newInterfaceInfo; Gio.DBusExportedObject = CjsPrivate.DBusImplementation; Gio.DBusExportedObject.wrapJSObject = _wrapJSObject; // ListStore Gio.ListStore.prototype[Symbol.iterator] = _listModelIterator; // Promisify Gio._promisify = _promisify; // Temporary Gio.File.prototype fix Gio._LocalFilePrototype = Gio.File.new_for_path('').constructor.prototype; // Override Gio.Settings and Gio.SettingsSchema - the C API asserts if // trying to access a nonexistent schema or key, which is not handy for // shell-extension writers Gio.SettingsSchema.prototype._realGetKey = Gio.SettingsSchema.prototype.get_key; Gio.SettingsSchema.prototype.get_key = function (key) { if (!this.has_key(key)) throw new Error(`GSettings key ${key} not found in schema ${this.get_id()}`); return this._realGetKey(key); }; Gio.Settings.prototype._realMethods = Object.assign({}, Gio.Settings.prototype); function createCheckedMethod(method, checkMethod = '_checkKey') { return function (id, ...args) { this[checkMethod](id); return this._realMethods[method].call(this, id, ...args); }; } Object.assign(Gio.Settings.prototype, { _realInit: Gio.Settings.prototype._init, // add manually, not enumerable _init(props = {}) { // 'schema' is a deprecated alias for schema_id const schemaIdProp = ['schema', 'schema-id', 'schema_id', 'schemaId'].find(prop => prop in props); const settingsSchemaProp = ['settings-schema', 'settings_schema', 'settingsSchema'].find(prop => prop in props); if (!schemaIdProp && !settingsSchemaProp) { throw new Error('One of property \'schema-id\' or ' + '\'settings-schema\' are required for Gio.Settings'); } const source = Gio.SettingsSchemaSource.get_default(); const settingsSchema = settingsSchemaProp ? props[settingsSchemaProp] : source.lookup(props[schemaIdProp], true); if (!settingsSchema) throw new Error(`GSettings schema ${props[schemaIdProp]} not found`); const settingsSchemaPath = settingsSchema.get_path(); if (props['path'] === undefined && !settingsSchemaPath) { throw new Error('Attempting to create schema ' + `'${settingsSchema.get_id()}' without a path`); } if (props['path'] !== undefined && settingsSchemaPath && props['path'] !== settingsSchemaPath) { throw new Error(`GSettings created for path '${props['path']}'` + `, but schema specifies '${settingsSchemaPath}'`); } return this._realInit(props); }, _checkKey(key) { // Avoid using has_key(); checking a JS array is faster than calling // through G-I. if (!this._keys) this._keys = this.settings_schema.list_keys(); if (!this._keys.includes(key)) throw new Error(`GSettings key ${key} not found in schema ${this.schema_id}`); }, _checkChild(name) { if (!this._children) this._children = this.list_children(); if (!this._children.includes(name)) throw new Error(`Child ${name} not found in GSettings schema ${this.schema_id}`); }, get_boolean: createCheckedMethod('get_boolean'), set_boolean: createCheckedMethod('set_boolean'), get_double: createCheckedMethod('get_double'), set_double: createCheckedMethod('set_double'), get_enum: createCheckedMethod('get_enum'), set_enum: createCheckedMethod('set_enum'), get_flags: createCheckedMethod('get_flags'), set_flags: createCheckedMethod('set_flags'), get_int: createCheckedMethod('get_int'), set_int: createCheckedMethod('set_int'), get_int64: createCheckedMethod('get_int64'), set_int64: createCheckedMethod('set_int64'), get_string: createCheckedMethod('get_string'), set_string: createCheckedMethod('set_string'), get_strv: createCheckedMethod('get_strv'), set_strv: createCheckedMethod('set_strv'), get_uint: createCheckedMethod('get_uint'), set_uint: createCheckedMethod('set_uint'), get_uint64: createCheckedMethod('get_uint64'), set_uint64: createCheckedMethod('set_uint64'), get_value: createCheckedMethod('get_value'), set_value: createCheckedMethod('set_value'), bind: createCheckedMethod('bind'), bind_writable: createCheckedMethod('bind_writable'), create_action: createCheckedMethod('create_action'), get_default_value: createCheckedMethod('get_default_value'), get_user_value: createCheckedMethod('get_user_value'), is_writable: createCheckedMethod('is_writable'), reset: createCheckedMethod('reset'), get_child: createCheckedMethod('get_child', '_checkChild'), }); } cjs-5.2.0/modules/core/overrides/GLib.js0000644000175000017500000003760014144444702020222 0ustar jpeisachjpeisach// Copyright 2011 Giovanni Campagna // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to // deal in the Software without restriction, including without limitation the // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or // sell copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. const ByteArray = imports.byteArray; let GLib; const SIMPLE_TYPES = ['b', 'y', 'n', 'q', 'i', 'u', 'x', 't', 'h', 'd', 's', 'o', 'g']; function _readSingleType(signature, forceSimple) { let char = signature.shift(); let isSimple = false; if (!SIMPLE_TYPES.includes(char)) { if (forceSimple) throw new TypeError('Invalid GVariant signature (a simple type was expected)'); } else { isSimple = true; } if (char === 'm' || char === 'a') return [char].concat(_readSingleType(signature, false)); if (char === '{') { let key = _readSingleType(signature, true); let val = _readSingleType(signature, false); let close = signature.shift(); if (close !== '}') throw new TypeError('Invalid GVariant signature for type DICT_ENTRY (expected "}"'); return [char].concat(key, val, close); } if (char === '(') { let res = [char]; while (true) { if (signature.length === 0) throw new TypeError('Invalid GVariant signature for type TUPLE (expected ")")'); let next = signature[0]; if (next === ')') { signature.shift(); return res.concat(next); } let el = _readSingleType(signature); res = res.concat(el); } } // Valid types are simple types, arrays, maybes, tuples, dictionary entries and variants if (!isSimple && char !== 'v') throw new TypeError(`Invalid GVariant signature (${char} is not a valid type)`); return [char]; } function _makeBytes(byteArray) { if (byteArray instanceof Uint8Array || byteArray instanceof ByteArray.ByteArray) return ByteArray.toGBytes(byteArray); else return new GLib.Bytes(byteArray); } function _packVariant(signature, value) { if (signature.length === 0) throw new TypeError('GVariant signature cannot be empty'); let char = signature.shift(); switch (char) { case 'b': return GLib.Variant.new_boolean(value); case 'y': return GLib.Variant.new_byte(value); case 'n': return GLib.Variant.new_int16(value); case 'q': return GLib.Variant.new_uint16(value); case 'i': return GLib.Variant.new_int32(value); case 'u': return GLib.Variant.new_uint32(value); case 'x': return GLib.Variant.new_int64(value); case 't': return GLib.Variant.new_uint64(value); case 'h': return GLib.Variant.new_handle(value); case 'd': return GLib.Variant.new_double(value); case 's': return GLib.Variant.new_string(value); case 'o': return GLib.Variant.new_object_path(value); case 'g': return GLib.Variant.new_signature(value); case 'v': return GLib.Variant.new_variant(value); case 'm': if (value !== null) { return GLib.Variant.new_maybe(null, _packVariant(signature, value)); } else { return GLib.Variant.new_maybe(new GLib.VariantType( _readSingleType(signature, false).join('')), null); } case 'a': { let arrayType = _readSingleType(signature, false); if (arrayType[0] === 's') { // special case for array of strings return GLib.Variant.new_strv(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 = _makeBytes(value); } return GLib.Variant.new_from_bytes(new GLib.VariantType('ay'), bytes, true); } let arrayValue = []; if (arrayType[0] === '{') { // special case for dictionaries for (let key in value) { let copy = [].concat(arrayType); let child = _packVariant(copy, [key, value[key]]); arrayValue.push(child); } } else { for (let i = 0; i < value.length; i++) { let copy = [].concat(arrayType); let child = _packVariant(copy, value[i]); arrayValue.push(child); } } return GLib.Variant.new_array(new GLib.VariantType(arrayType.join('')), arrayValue); } case '(': { let children = []; for (let i = 0; i < value.length; i++) { let next = signature[0]; if (next === ')') break; children.push(_packVariant(signature, value[i])); } if (signature[0] !== ')') throw new TypeError('Invalid GVariant signature for type TUPLE (expected ")")'); signature.shift(); return GLib.Variant.new_tuple(children); } case '{': { let key = _packVariant(signature, value[0]); let child = _packVariant(signature, value[1]); if (signature[0] !== '}') throw new TypeError('Invalid GVariant signature for type DICT_ENTRY (expected "}")'); signature.shift(); return GLib.Variant.new_dict_entry(key, child); } default: throw new TypeError(`Invalid GVariant signature (unexpected character ${char})`); } } function _unpackVariant(variant, deep, recursive = false) { switch (String.fromCharCode(variant.classify())) { case 'b': return variant.get_boolean(); case 'y': return variant.get_byte(); case 'n': return variant.get_int16(); case 'q': return variant.get_uint16(); case 'i': return variant.get_int32(); case 'u': return variant.get_uint32(); case 'x': return variant.get_int64(); case 't': return variant.get_uint64(); case 'h': return variant.get_handle(); case 'd': return variant.get_double(); case 'o': case 'g': case 's': // g_variant_get_string has length as out argument return variant.get_string()[0]; case 'v': { const ret = variant.get_variant(); if (deep && recursive && ret instanceof GLib.Variant) return _unpackVariant(ret, deep, recursive); return ret; } case 'm': { let val = variant.get_maybe(); if (deep && val) return _unpackVariant(val, deep, recursive); else return val; } case 'a': if (variant.is_of_type(new GLib.VariantType('a{?*}'))) { // special case containers let ret = { }; let nElements = variant.n_children(); for (let i = 0; i < nElements; i++) { // always unpack the dictionary entry, and always unpack // the key (or it cannot be added as a key) let val = _unpackVariant(variant.get_child_value(i), deep, recursive); let key; if (!deep) key = _unpackVariant(val[0], true); else key = val[0]; ret[key] = val[1]; } return ret; } if (variant.is_of_type(new GLib.VariantType('ay'))) { // special case byte arrays return variant.get_data_as_bytes().toArray(); } // fall through case '(': case '{': { let ret = []; let nElements = variant.n_children(); for (let i = 0; i < nElements; i++) { let val = variant.get_child_value(i); if (deep) ret.push(_unpackVariant(val, deep, recursive)); else ret.push(val); } return ret; } } throw new Error('Assertion failure: this code should not be reached'); } function _notIntrospectableError(funcName, replacement) { return new Error(`${funcName} is not introspectable. Use ${replacement} instead.`); } function _warnNotIntrospectable(funcName, replacement) { logError(_notIntrospectableError(funcName, replacement)); } function _escapeCharacterSetChars(char) { if ('-^]\\'.includes(char)) return `\\${char}`; return char; } function _init() { // this is imports.gi.GLib GLib = this; // small HACK: we add a matches() method to standard Errors so that // you can do "if (e.matches(Ns.FooError, Ns.FooError.SOME_CODE))" // without checking instanceof Error.prototype.matches = function () { return false; }; this.Variant._new_internal = function (sig, value) { let signature = Array.prototype.slice.call(sig); let variant = _packVariant(signature, value); if (signature.length !== 0) throw new TypeError('Invalid GVariant signature (more than one single complete type)'); return variant; }; // Deprecate version of new GLib.Variant() this.Variant.new = function (sig, value) { return new GLib.Variant(sig, value); }; this.Variant.prototype.unpack = function () { return _unpackVariant(this, false); }; this.Variant.prototype.deepUnpack = function () { return _unpackVariant(this, true); }; // backwards compatibility alias this.Variant.prototype.deep_unpack = this.Variant.prototype.deepUnpack; // Note: discards type information, if the variant contains any 'v' types this.Variant.prototype.recursiveUnpack = function () { return _unpackVariant(this, true, true); }; this.Variant.prototype.toString = function () { return `[object variant of type "${this.get_type_string()}"]`; }; this.Bytes.prototype.toArray = function () { return imports.byteArray.fromGBytes(this); }; this.log_structured = function (logDomain, logLevel, stringFields) { let fields = {}; for (let key in stringFields) fields[key] = new GLib.Variant('s', stringFields[key]); GLib.log_variant(logDomain, logLevel, new GLib.Variant('a{sv}', fields)); }; this.VariantDict.prototype.lookup = function (key, variantType = null, deep = false) { if (typeof variantType === 'string') variantType = new GLib.VariantType(variantType); const variant = this.lookup_value(key, variantType); if (variant === null) return null; return _unpackVariant(variant, deep); }; // Prevent user code from calling GLib string manipulation functions that // return the same string that was passed in. These can't be annotated // properly, and will mostly crash. // Here we provide approximate implementations of the functions so that if // they had happened to work in the past, they will continue working, but // log a stack trace and a suggestion of what to use instead. // Exceptions are thrown instead for GLib.stpcpy() of which the return value // is useless anyway and GLib.ascii_formatd() which is too complicated to // implement here. this.stpcpy = function () { throw _notIntrospectableError('GLib.stpcpy()', 'the + operator'); }; this.strstr_len = function (haystack, len, needle) { _warnNotIntrospectable('GLib.strstr_len()', 'String.indexOf()'); let searchString = haystack; if (len !== -1) searchString = searchString.slice(0, len); const index = searchString.indexOf(needle); if (index === -1) return null; return haystack.slice(index); }; this.strrstr = function (haystack, needle) { _warnNotIntrospectable('GLib.strrstr()', 'String.lastIndexOf()'); const index = haystack.lastIndexOf(needle); if (index === -1) return null; return haystack.slice(index); }; this.strrstr_len = function (haystack, len, needle) { _warnNotIntrospectable('GLib.strrstr_len()', 'String.lastIndexOf()'); let searchString = haystack; if (len !== -1) searchString = searchString.slice(0, len); const index = searchString.lastIndexOf(needle); if (index === -1) return null; return haystack.slice(index); }; this.strup = function (string) { _warnNotIntrospectable('GLib.strup()', 'String.toUpperCase() or GLib.ascii_strup()'); return string.toUpperCase(); }; this.strdown = function (string) { _warnNotIntrospectable('GLib.strdown()', 'String.toLowerCase() or GLib.ascii_strdown()'); return string.toLowerCase(); }; this.strreverse = function (string) { _warnNotIntrospectable('GLib.strreverse()', 'Array.reverse() and String.join()'); return [...string].reverse().join(''); }; this.ascii_dtostr = function (unused, len, number) { _warnNotIntrospectable('GLib.ascii_dtostr()', 'JS string conversion'); return `${number}`.slice(0, len); }; this.ascii_formatd = function () { throw _notIntrospectableError('GLib.ascii_formatd()', 'Number.toExponential() and string interpolation'); }; this.strchug = function (string) { _warnNotIntrospectable('GLib.strchug()', 'String.trimStart()'); return string.trimStart(); }; this.strchomp = function (string) { _warnNotIntrospectable('GLib.strchomp()', 'String.trimEnd()'); return string.trimEnd(); }; // g_strstrip() is a macro and therefore doesn't even appear in the GIR // file, but we may as well include it here since it's trivial this.strstrip = function (string) { _warnNotIntrospectable('GLib.strstrip()', 'String.trim()'); return string.trim(); }; this.strdelimit = function (string, delimiters, newDelimiter) { _warnNotIntrospectable('GLib.strdelimit()', 'String.replace()'); if (delimiters === null) delimiters = GLib.STR_DELIMITERS; if (typeof newDelimiter === 'number') newDelimiter = String.fromCharCode(newDelimiter); const delimiterChars = delimiters.split(''); const escapedDelimiterChars = delimiterChars.map(_escapeCharacterSetChars); const delimiterRegex = new RegExp(`[${escapedDelimiterChars.join('')}]`, 'g'); return string.replace(delimiterRegex, newDelimiter); }; this.strcanon = function (string, validChars, substitutor) { _warnNotIntrospectable('GLib.strcanon()', 'String.replace()'); if (typeof substitutor === 'number') substitutor = String.fromCharCode(substitutor); const validArray = validChars.split(''); const escapedValidArray = validArray.map(_escapeCharacterSetChars); const invalidRegex = new RegExp(`[^${escapedValidArray.join('')}]`, 'g'); return string.replace(invalidRegex, substitutor); }; } cjs-5.2.0/modules/core/overrides/.eslintrc.yml0000644000175000017500000000011214144444702021457 0ustar jpeisachjpeisach--- rules: no-unused-vars: - error - varsIgnorePattern: ^_init$ cjs-5.2.0/modules/core/overrides/cairo.js0000644000175000017500000000030714144444702020474 0ustar jpeisachjpeisach// This override adds the builtin Cairo bindings to imports.gi.cairo. // (It's confusing to have two incompatible ways to import Cairo.) function _init() { Object.assign(this, imports.cairo); } cjs-5.2.0/modules/core/_format.js0000644000175000017500000000442014144444702017024 0ustar jpeisachjpeisach// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported vprintf */ const CjsPrivate = imports.gi.CjsPrivate; function vprintf(string, args) { let i = 0; let usePos = false; return string.replace(/%(?:([1-9][0-9]*)\$)?(I+)?([0-9]+)?(?:\.([0-9]+))?(.)/g, function (str, posGroup, flagsGroup, widthGroup, precisionGroup, genericGroup) { if (precisionGroup !== '' && precisionGroup !== undefined && genericGroup !== 'f') throw new Error("Precision can only be specified for 'f'"); let hasAlternativeIntFlag = flagsGroup && flagsGroup.indexOf('I') !== -1; if (hasAlternativeIntFlag && genericGroup !== 'd') throw new Error("Alternative output digits can only be specfied for 'd'"); let pos = parseInt(posGroup, 10) || 0; if (!usePos && i === 0) usePos = pos > 0; if (usePos && pos === 0 || !usePos && pos > 0) throw new Error('Numbered and unnumbered conversion specifications cannot be mixed'); let fillChar = widthGroup && widthGroup[0] === '0' ? '0' : ' '; let width = parseInt(widthGroup, 10) || 0; function fillWidth(s, c, w) { let fill = c.repeat(w); return fill.substr(s.length) + s; } function getArg() { return usePos ? args[pos - 1] : args[i++]; } let s = ''; switch (genericGroup) { case '%': return '%'; case 's': s = String(getArg()); break; case 'd': { let intV = parseInt(getArg()); if (hasAlternativeIntFlag) s = CjsPrivate.format_int_alternative_output(intV); else s = intV.toString(); break; } case 'x': s = parseInt(getArg()).toString(16); break; case 'f': if (precisionGroup === '' || precisionGroup === undefined) s = parseFloat(getArg()).toString(); else s = parseFloat(getArg()).toFixed(parseInt(precisionGroup)); break; default: throw new Error(`Unsupported conversion character %${genericGroup}`); } return fillWidth(s, fillChar, width); }); } cjs-5.2.0/modules/core/_signals.js0000644000175000017500000001346714144444702017207 0ustar jpeisachjpeisach/* * Copyright (c) 2008 litl, LLC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ /* exported addSignalMethods */ // A couple principals of this simple signal system: // 1) should look just like our GObject signal binding // 2) memory and safety matter more than speed of connect/disconnect/emit // 3) the expectation is that a given object will have a very small number of // connections, but they may be to different signal names function _connect(name, callback) { // be paranoid about callback arg since we'd start to throw from emit() // if it was messed up if (typeof callback !== 'function') throw new Error('When connecting signal must give a callback that is a function'); // we instantiate the "signal machinery" only on-demand if anything // gets connected. if (!('_signalConnections' in this)) { this._signalConnections = []; this._nextConnectionId = 1; } let id = this._nextConnectionId; this._nextConnectionId += 1; // this makes it O(n) in total connections to emit, but I think // it's right to optimize for low memory and reentrancy-safety // rather than speed this._signalConnections.push({ id, name, callback, 'disconnected': false, }); return id; } function _disconnect(id) { if ('_signalConnections' in this) { let i; let length = this._signalConnections.length; for (i = 0; i < length; ++i) { let connection = this._signalConnections[i]; if (connection.id === id) { if (connection.disconnected) throw new Error(`Signal handler id ${id} already disconnected`); // set a flag to deal with removal during emission connection.disconnected = true; this._signalConnections.splice(i, 1); return; } } } throw new Error(`No signal connection ${id} found`); } function _signalHandlerIsConnected(id) { if (!('_signalConnections' in this)) return false; const {length} = this._signalConnections; for (let i = 0; i < length; ++i) { const connection = this._signalConnections[i]; if (connection.id === id) return !connection.disconnected; } return false; } function _disconnectAll() { if ('_signalConnections' in this) { while (this._signalConnections.length > 0) _disconnect.call(this, this._signalConnections[0].id); } } function _emit(name, ...args) { // may not be any signal handlers at all, if not then return if (!('_signalConnections' in this)) return; // To deal with re-entrancy (removal/addition while // emitting), we copy out a list of what was connected // at emission start; and just before invoking each // handler we check its disconnected flag. let handlers = []; let i; let length = this._signalConnections.length; for (i = 0; i < length; ++i) { let connection = this._signalConnections[i]; if (connection.name === name) handlers.push(connection); } // create arg array which is emitter + everything passed in except // signal name. Would be more convenient not to pass emitter to // the callback, but trying to be 100% consistent with GObject // which does pass it in. Also if we pass in the emitter here, // people don't create closures with the emitter in them, // which would be a cycle. let argArray = [this, ...args]; length = handlers.length; for (i = 0; i < length; ++i) { let connection = handlers[i]; if (!connection.disconnected) { try { // since we pass "null" for this, the global object will be used. let ret = connection.callback.apply(null, argArray); // if the callback returns true, we don't call the next // signal handlers if (ret === true) break; } catch (e) { // just log any exceptions so that callbacks can't disrupt // signal emission logError(e, `Exception in callback for signal: ${name}`); } } } } function _addSignalMethod(proto, functionName, func) { if (proto[functionName] && proto[functionName] !== func) log(`WARNING: addSignalMethods is replacing existing ${proto} ${functionName} method`); proto[functionName] = func; } function addSignalMethods(proto) { _addSignalMethod(proto, 'connect', _connect); _addSignalMethod(proto, 'disconnect', _disconnect); _addSignalMethod(proto, 'emit', _emit); _addSignalMethod(proto, 'signalHandlerIsConnected', _signalHandlerIsConnected); // this one is not in GObject, but useful _addSignalMethod(proto, 'disconnectAll', _disconnectAll); } cjs-5.2.0/modules/script/0000755000175000017500000000000014144444702015413 5ustar jpeisachjpeisachcjs-5.2.0/modules/script/jsUnit.js0000644000175000017500000003523614144444702017236 0ustar jpeisachjpeisach/* @author Edward Hieatt, edward@jsunit.net */ /* - JsUnit - Copyright (C) 2001-4 Edward Hieatt, edward@jsunit.net - Copyright (C) 2008 litl, LLC - - Version: MPL 1.1/GPL 2.0/LGPL 2.1 - - The contents of this file are subject to the Mozilla Public License Version - 1.1 (the "License"); you may not use this file except in compliance with - the License. You may obtain a copy of the License at - http://www.mozilla.org/MPL/ - - Software distributed under the License is distributed on an "AS IS" basis, - WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - for the specific language governing rights and limitations under the - License. - - The Original Code is Edward Hieatt code. - - The Initial Developer of the Original Code is - Edward Hieatt, edward@jsunit.net. - Portions created by the Initial Developer are Copyright (C) 2003 - the Initial Developer. All Rights Reserved. - - Author Edward Hieatt, edward@jsunit.net - - Alternatively, the contents of this file may be used under the terms of - either the GNU General Public License Version 2 or later (the "GPL"), or - the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - in which case the provisions of the GPL or the LGPL are applicable instead - of those above. If you wish to allow use of your version of this file only - under the terms of either the GPL or the LGPL, and not to allow others to - use your version of this file under the terms of the MPL, indicate your - decision by deleting the provisions above and replace them with the notice - and other provisions required by the LGPL or the GPL. If you do not delete - the provisions above, a recipient may use your version of this file under - the terms of any one of the MPL, the GPL or the LGPL. */ var JSUNIT_UNDEFINED_VALUE; var JSUNIT_VERSION="2.1"; var isTestPageLoaded = false; // GJS: introduce implicit variable to avoid exceptions var top = null; //hack for NS62 bug function jsUnitFixTop() { var tempTop = top; if (!tempTop) { tempTop = window; while (typeof tempTop.parent !== 'undefined') { tempTop = tempTop.parent; if (tempTop.top && tempTop.top.jsUnitTestSuite) { tempTop = tempTop.top; break; } } } top = tempTop; } jsUnitFixTop(); function _displayStringForValue(aVar) { if (aVar === null) return 'null'; if (aVar === top.JSUNIT_UNDEFINED_VALUE) return 'undefined'; return aVar; } function fail(failureMessage) { throw new JsUnitException(null, failureMessage); } function error(errorMessage) { throw new Error(errorMessage); } function argumentsIncludeComments(expectedNumberOfNonCommentArgs, args) { return args.length == expectedNumberOfNonCommentArgs + 1; } function commentArg(expectedNumberOfNonCommentArgs, args) { if (argumentsIncludeComments(expectedNumberOfNonCommentArgs, args)) return args[0]; return null; } function nonCommentArg(desiredNonCommentArgIndex, expectedNumberOfNonCommentArgs, args) { return argumentsIncludeComments(expectedNumberOfNonCommentArgs, args) ? args[desiredNonCommentArgIndex] : args[desiredNonCommentArgIndex - 1]; } function _validateArguments(expectedNumberOfNonCommentArgs, args) { if (!( args.length == expectedNumberOfNonCommentArgs || (args.length == expectedNumberOfNonCommentArgs + 1 && typeof(args[0]) == 'string') )) error('Incorrect arguments passed to assert function'); } function _assert(comment, booleanValue, failureMessage) { if (!booleanValue) throw new JsUnitException(comment, failureMessage); } function assert() { _validateArguments(1, arguments); var booleanValue=nonCommentArg(1, 1, arguments); if (typeof(booleanValue) != 'boolean') error('Bad argument to assert(boolean)'); _assert(commentArg(1, arguments), booleanValue === true, 'Call to assert(boolean) with false'); } function assertTrue() { _validateArguments(1, arguments); var booleanValue=nonCommentArg(1, 1, arguments); if (typeof(booleanValue) != 'boolean') error('Bad argument to assertTrue(boolean)'); _assert(commentArg(1, arguments), booleanValue === true, 'Call to assertTrue(boolean) with false'); } function assertFalse() { _validateArguments(1, arguments); var booleanValue=nonCommentArg(1, 1, arguments); if (typeof(booleanValue) != 'boolean') error('Bad argument to assertFalse(boolean)'); _assert(commentArg(1, arguments), booleanValue === false, 'Call to assertFalse(boolean) with true'); } function assertEquals() { _validateArguments(2, arguments); var var1=nonCommentArg(1, 2, arguments); var var2=nonCommentArg(2, 2, arguments); _assert(commentArg(2, arguments), var1 === var2, 'Expected ' + var1 + ' (' + typeof(var1) + ') but was ' + _displayStringForValue(var2) + ' (' + typeof(var2) + ')'); } function assertNotEquals() { _validateArguments(2, arguments); var var1=nonCommentArg(1, 2, arguments); var var2=nonCommentArg(2, 2, arguments); _assert(commentArg(2, arguments), var1 !== var2, 'Expected not to be ' + _displayStringForValue(var2)); } function assertNull() { _validateArguments(1, arguments); var aVar=nonCommentArg(1, 1, arguments); _assert(commentArg(1, arguments), aVar === null, 'Expected null but was ' + _displayStringForValue(aVar)); } function assertNotNull() { _validateArguments(1, arguments); var aVar=nonCommentArg(1, 1, arguments); _assert(commentArg(1, arguments), aVar !== null, 'Expected not to be null'); } function assertUndefined() { _validateArguments(1, arguments); var aVar=nonCommentArg(1, 1, arguments); _assert(commentArg(1, arguments), aVar === top.JSUNIT_UNDEFINED_VALUE, 'Expected undefined but was ' + _displayStringForValue(aVar)); } function assertNotUndefined() { _validateArguments(1, arguments); var aVar=nonCommentArg(1, 1, arguments); _assert(commentArg(1, arguments), aVar !== top.JSUNIT_UNDEFINED_VALUE, 'Expected not to be undefined'); } function assertNaN() { _validateArguments(1, arguments); var aVar=nonCommentArg(1, 1, arguments); _assert(commentArg(1, arguments), isNaN(aVar), 'Expected NaN'); } function assertNotNaN() { _validateArguments(1, arguments); var aVar=nonCommentArg(1, 1, arguments); _assert(commentArg(1, arguments), !isNaN(aVar), 'Expected not NaN'); } // GJS: assertRaises(function) function assertRaises() { _validateArguments(1, arguments); var fun=nonCommentArg(1, 1, arguments); var exception; if (typeof(fun) != 'function') error("Bad argument to assertRaises(function)"); var retval; try { retval = fun(); } catch (e) { exception = e; } _assert(commentArg(1, arguments), exception !== top.JSUNIT_UNDEFINED_VALUE, "Call to assertRaises(function) did not raise an exception. Return value was " + _displayStringForValue(retval) + ' (' + typeof(retval) + ')'); } function isLoaded() { return isTestPageLoaded; } function setUp() { } function tearDown() { } function getFunctionName(aFunction) { var name = aFunction.toString().match(/function (\w*)/)[1]; if ((name == null) || (name.length == 0)) name = 'anonymous'; return name; } function parseErrorStack(excp) { var stack = []; var name; if (!excp || !excp.stack) { return stack; } var stacklist = excp.stack.split('\n'); for (var i = 0; i < stacklist.length - 1; i++) { var framedata = stacklist[i]; name = framedata.match(/^(\w*)/)[1]; if (!name) { name = 'anonymous'; } var line = framedata.match(/(:\d+)$/)[1]; if (line) { name += line; } stack[stack.length] = name; } // remove top level anonymous functions to match IE while (stack.length && stack[stack.length - 1] == 'anonymous') { stack.length = stack.length - 1; } return stack; } function JsUnitException(comment, message) { this.isJsUnitException = true; this.comment = comment; this.message = message; this.stack = (new Error()).stack; } JsUnitException.prototype = Object.create(Error.prototype, {}); function warn() { if (top.tracer != null) top.tracer.warn(arguments[0], arguments[1]); } function inform() { if (top.tracer != null) top.tracer.inform(arguments[0], arguments[1]); } function info() { inform(arguments[0], arguments[1]); } function debug() { if (top.tracer != null) top.tracer.debug(arguments[0], arguments[1]); } function setjsUnitTracer(ajsUnitTracer) { top.tracer=ajsUnitTracer; } function trim(str) { if (str == null) return null; var startingIndex = 0; var endingIndex = str.length-1; while (str.substring(startingIndex, startingIndex+1) == ' ') startingIndex++; while (str.substring(endingIndex, endingIndex+1) == ' ') endingIndex--; if (endingIndex < startingIndex) return ''; return str.substring(startingIndex, endingIndex+1); } function isBlank(str) { return trim(str) == ''; } // the functions push(anArray, anObject) and pop(anArray) // exist because the JavaScript Array.push(anObject) and Array.pop() // functions are not available in IE 5.0 function push(anArray, anObject) { anArray[anArray.length]=anObject; } function pop(anArray) { if (anArray.length>=1) { delete anArray[anArray.length - 1]; anArray.length--; } } // safe, strict access to jsUnitParmHash function jsUnitGetParm(name) { if (typeof(top.jsUnitParmHash[name]) != 'undefined') { return top.jsUnitParmHash[name]; } return null; } if (top && typeof(top.xbDEBUG) != 'undefined' && top.xbDEBUG.on && top.testManager) { top.xbDebugTraceObject('top.testManager.containerTestFrame', 'JSUnitException'); // asserts top.xbDebugTraceFunction('top.testManager.containerTestFrame', '_displayStringForValue'); top.xbDebugTraceFunction('top.testManager.containerTestFrame', 'error'); top.xbDebugTraceFunction('top.testManager.containerTestFrame', 'argumentsIncludeComments'); top.xbDebugTraceFunction('top.testManager.containerTestFrame', 'commentArg'); top.xbDebugTraceFunction('top.testManager.containerTestFrame', 'nonCommentArg'); top.xbDebugTraceFunction('top.testManager.containerTestFrame', '_validateArguments'); top.xbDebugTraceFunction('top.testManager.containerTestFrame', '_assert'); top.xbDebugTraceFunction('top.testManager.containerTestFrame', 'assert'); top.xbDebugTraceFunction('top.testManager.containerTestFrame', 'assertTrue'); top.xbDebugTraceFunction('top.testManager.containerTestFrame', 'assertEquals'); top.xbDebugTraceFunction('top.testManager.containerTestFrame', 'assertNotEquals'); top.xbDebugTraceFunction('top.testManager.containerTestFrame', 'assertNull'); top.xbDebugTraceFunction('top.testManager.containerTestFrame', 'assertNotNull'); top.xbDebugTraceFunction('top.testManager.containerTestFrame', 'assertUndefined'); top.xbDebugTraceFunction('top.testManager.containerTestFrame', 'assertNotUndefined'); top.xbDebugTraceFunction('top.testManager.containerTestFrame', 'assertNaN'); top.xbDebugTraceFunction('top.testManager.containerTestFrame', 'assertNotNaN'); top.xbDebugTraceFunction('top.testManager.containerTestFrame', 'isLoaded'); top.xbDebugTraceFunction('top.testManager.containerTestFrame', 'setUp'); top.xbDebugTraceFunction('top.testManager.containerTestFrame', 'tearDown'); top.xbDebugTraceFunction('top.testManager.containerTestFrame', 'getFunctionName'); top.xbDebugTraceFunction('top.testManager.containerTestFrame', 'warn'); top.xbDebugTraceFunction('top.testManager.containerTestFrame', 'inform'); top.xbDebugTraceFunction('top.testManager.containerTestFrame', 'debug'); top.xbDebugTraceFunction('top.testManager.containerTestFrame', 'setjsUnitTracer'); top.xbDebugTraceFunction('top.testManager.containerTestFrame', 'trim'); top.xbDebugTraceFunction('top.testManager.containerTestFrame', 'isBlank'); top.xbDebugTraceFunction('top.testManager.containerTestFrame', 'newOnLoadEvent'); top.xbDebugTraceFunction('top.testManager.containerTestFrame', 'push'); top.xbDebugTraceFunction('top.testManager.containerTestFrame', 'pop'); } function newOnLoadEvent() { isTestPageLoaded = true; } function jsUnitSetOnLoad(windowRef, onloadHandler) { var isKonqueror = navigator.userAgent.indexOf('Konqueror/') != -1 || navigator.userAgent.indexOf('Safari/') != -1; if (typeof(windowRef.attachEvent) != 'undefined') { // Internet Explorer, Opera windowRef.attachEvent("onload", onloadHandler); } else if (typeof(windowRef.addEventListener) != 'undefined' && !isKonqueror){ // Mozilla, Konqueror // exclude Konqueror due to load issues windowRef.addEventListener("load", onloadHandler, false); } else if (typeof(windowRef.document.addEventListener) != 'undefined' && !isKonqueror) { // DOM 2 Events // exclude Mozilla, Konqueror due to load issues windowRef.document.addEventListener("load", onloadHandler, false); } else if (typeof(windowRef.onload) != 'undefined' && windowRef.onload) { windowRef.jsunit_original_onload = windowRef.onload; windowRef.onload = function() { windowRef.jsunit_original_onload(); onloadHandler(); }; } else { // browsers that do not support windowRef.attachEvent or // windowRef.addEventListener will override a page's own onload event windowRef.onload=onloadHandler; } } // GJS: comment out as isLoaded() isn't terribly useful for us //jsUnitSetOnLoad(window, newOnLoadEvent); // GJS: entry point to run all functions named as test*, surrounded by // calls to setUp() and tearDown() function gjstestRun(window_, setUp, tearDown) { var propName; var rv = 0; var failures = []; if (!window_) window_ = window; if (!setUp) setUp = window_.setUp; if (!tearDown) tearDown = window_.tearDown; for (propName in window_) { if (!propName.match(/^test\w+/)) continue; var testFunction = window_[propName]; if (typeof(testFunction) != 'function') continue; log("running test " + propName); setUp(); try { testFunction(); } catch (e) { var result = null; if (typeof(e.isJsUnitException) != 'undefined' && e.isJsUnitException) { result = ''; if (e.comment != null) result += ('"' + e.comment + '"\n'); result += e.message; if (e.stack) result += '\n\nStack trace follows:\n' + e.stack; // assertion failure, kind of expected so just log it and flag the // whole test as failed log(result); rv = 1; failures.push(propName); } else { // unexpected error, let the shell handle it throw e; } } tearDown(); } if (failures.length > 0) { log(failures.length + " tests failed in this file"); log("Failures were: " + failures.join(", ")); } // if gjstestRun() is the last call in a file, this becomes the // exit code of the test program, so 0 = success, 1 = failed return rv; } cjs-5.2.0/modules/script/mainloop.js0000644000175000017500000000632114144444702017571 0ustar jpeisachjpeisach/* -*- mode: js; indent-tabs-mode: nil; -*- */ // Copyright (c) 2012 Giovanni Campagna // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to // deal in the Software without restriction, including without limitation the // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or // sell copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. /* exported idle_add, idle_source, quit, run, source_remove, timeout_add, timeout_add_seconds, timeout_seconds_source, timeout_source */ // A layer of convenience and backwards-compatibility over GLib MainLoop facilities const GLib = imports.gi.GLib; const GObject = imports.gi.GObject; var _mainLoops = {}; function run(name) { if (!_mainLoops[name]) _mainLoops[name] = GLib.MainLoop.new(null, false); _mainLoops[name].run(); } function quit(name) { if (!_mainLoops[name]) throw new Error('No main loop with this id'); let loop = _mainLoops[name]; _mainLoops[name] = null; if (!loop.is_running()) throw new Error('Main loop was stopped already'); loop.quit(); } // eslint-disable-next-line camelcase function idle_source(handler, priority) { let s = GLib.idle_source_new(); GObject.source_set_closure(s, handler); if (priority !== undefined) s.set_priority(priority); return s; } // eslint-disable-next-line camelcase function idle_add(handler, priority) { return idle_source(handler, priority).attach(null); } // eslint-disable-next-line camelcase function timeout_source(timeout, handler, priority) { let s = GLib.timeout_source_new(timeout); GObject.source_set_closure(s, handler); if (priority !== undefined) s.set_priority(priority); return s; } // eslint-disable-next-line camelcase function timeout_seconds_source(timeout, handler, priority) { let s = GLib.timeout_source_new_seconds(timeout); GObject.source_set_closure(s, handler); if (priority !== undefined) s.set_priority(priority); return s; } // eslint-disable-next-line camelcase function timeout_add(timeout, handler, priority) { return timeout_source(timeout, handler, priority).attach(null); } // eslint-disable-next-line camelcase function timeout_add_seconds(timeout, handler, priority) { return timeout_seconds_source(timeout, handler, priority).attach(null); } // eslint-disable-next-line camelcase function source_remove(id) { return GLib.source_remove(id); } cjs-5.2.0/modules/script/tweener/0000755000175000017500000000000014144444702017064 5ustar jpeisachjpeisachcjs-5.2.0/modules/script/tweener/equations.js0000644000175000017500000006562714144444702021452 0ustar jpeisachjpeisach/* -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil; -*- */ /* eslint-disable no-unused-vars, valid-jsdoc */ /* Copyright 2008 litl, LLC. */ /** * Equations * Main equations for the Tweener class * * @author Zeh Fernando, Nate Chatellier * @version 1.0.2 */ /* exported easeInBack, easeInBounce, easeInCirc, easeInCubic, easeInElastic, easeInExpo, easeInOutBack, easeInOutBounce, easeInOutCirc, easeInOutCubic, easeInOutElastic, easeInOutExpo, easeInOutQuad, easeInOutQuart, easeInOutQuint, easeInOutSine, easeInQuad, easeInQuart, easeInQuint, easeInSine, easeNone, easeOutBack, easeOutBounce, easeOutCirc, easeOutCubic, easeOutElastic, easeOutExpo, easeOutInBack, easeOutInBounce, easeOutInCirc, easeOutInCubic, easeOutInElastic, easeOutInExpo, easeOutInQuad, easeOutInQuart, easeOutInQuint, easeOutInSine, easeOutQuad, easeOutQuart, easeOutQuint, easeOutSine, linear */ /* Disclaimer for Robert Penner's Easing Equations license: TERMS OF USE - EASING EQUATIONS Open source under the BSD License. Copyright © 2001 Robert Penner All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the author nor the names of contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ // ================================================================================================================================== // TWEENING EQUATIONS functions ----------------------------------------------------------------------------------------------------- // (the original equations are Robert Penner's work as mentioned on the disclaimer) /** * Easing equation function for a simple linear tweening, with no easing. * * @param t Current time (in frames or seconds). * @param b Starting value. * @param c Change needed in value. * @param d Expected easing duration (in frames or seconds). * @return The correct value. */ function easeNone(t, b, c, d, pParams) { return c * t / d + b; } /* Useful alias */ function linear(t, b, c, d, pParams) { return easeNone(t, b, c, d, pParams); } /** * Easing equation function for a quadratic (t^2) easing in: accelerating from zero velocity. * * @param t Current time (in frames or seconds). * @param b Starting value. * @param c Change needed in value. * @param d Expected easing duration (in frames or seconds). * @return The correct value. */ function easeInQuad(t, b, c, d, pParams) { return c * (t /= d) * t + b; } /** * Easing equation function for a quadratic (t^2) easing out: decelerating to zero velocity. * * @param t Current time (in frames or seconds). * @param b Starting value. * @param c Change needed in value. * @param d Expected easing duration (in frames or seconds). * @return The correct value. */ function easeOutQuad(t, b, c, d, pParams) { return -c * (t /= d) * (t - 2) + b; } /** * Easing equation function for a quadratic (t^2) easing in/out: acceleration until halfway, then deceleration. * * @param t Current time (in frames or seconds). * @param b Starting value. * @param c Change needed in value. * @param d Expected easing duration (in frames or seconds). * @return The correct value. */ function easeInOutQuad(t, b, c, d, pParams) { if ((t /= d / 2) < 1) return c / 2 * t * t + b; return -c / 2 * (--t * (t - 2) - 1) + b; } /** * Easing equation function for a quadratic (t^2) easing out/in: deceleration until halfway, then acceleration. * * @param t Current time (in frames or seconds). * @param b Starting value. * @param c Change needed in value. * @param d Expected easing duration (in frames or seconds). * @return The correct value. */ function easeOutInQuad(t, b, c, d, pParams) { if (t < d / 2) return easeOutQuad(t * 2, b, c / 2, d, pParams); return easeInQuad(t * 2 - d, b + c / 2, c / 2, d, pParams); } /** * Easing equation function for a cubic (t^3) easing in: accelerating from zero velocity. * * @param t Current time (in frames or seconds). * @param b Starting value. * @param c Change needed in value. * @param d Expected easing duration (in frames or seconds). * @return The correct value. */ function easeInCubic(t, b, c, d, pParams) { return c * (t /= d) * t * t + b; } /** * Easing equation function for a cubic (t^3) easing out: decelerating from zero velocity. * * @param t Current time (in frames or seconds). * @param b Starting value. * @param c Change needed in value. * @param d Expected easing duration (in frames or seconds). * @return The correct value. */ function easeOutCubic(t, b, c, d, pParams) { return c * ((t = t / d - 1) * t * t + 1) + b; } /** * Easing equation function for a cubic (t^3) easing in/out: acceleration until halfway, then deceleration. * * @param t Current time (in frames or seconds). * @param b Starting value. * @param c Change needed in value. * @param d Expected easing duration (in frames or seconds). * @return The correct value. */ function easeInOutCubic(t, b, c, d, pParams) { if ((t /= d / 2) < 1) return c / 2 * t * t * t + b; return c / 2 * ((t -= 2) * t * t + 2) + b; } /** * Easing equation function for a cubic (t^3) easing out/in: deceleration until halfway, then acceleration. * * @param t Current time (in frames or seconds). * @param b Starting value. * @param c Change needed in value. * @param d Expected easing duration (in frames or seconds). * @return The correct value. */ function easeOutInCubic(t, b, c, d, pParams) { if (t < d / 2) return easeOutCubic(t * 2, b, c / 2, d, pParams); return easeInCubic(t * 2 - d, b + c / 2, c / 2, d, pParams); } /** * Easing equation function for a quartic (t^4) easing in: accelerating from zero velocity. * * @param t Current time (in frames or seconds). * @param b Starting value. * @param c Change needed in value. * @param d Expected easing duration (in frames or seconds). * @return The correct value. */ function easeInQuart(t, b, c, d, pParams) { return c * (t /= d) * t * t * t + b; } /** * Easing equation function for a quartic (t^4) easing out: decelerating from zero velocity. * * @param t Current time (in frames or seconds). * @param b Starting value. * @param c Change needed in value. * @param d Expected easing duration (in frames or seconds). * @return The correct value. */ function easeOutQuart(t, b, c, d, pParams) { return -c * ((t = t / d - 1) * t * t * t - 1) + b; } /** * Easing equation function for a quartic (t^4) easing in/out: acceleration until halfway, then deceleration. * * @param t Current time (in frames or seconds). * @param b Starting value. * @param c Change needed in value. * @param d Expected easing duration (in frames or seconds). * @return The correct value. */ function easeInOutQuart(t, b, c, d, pParams) { if ((t /= d / 2) < 1) return c / 2 * t * t * t * t + b; return -c / 2 * ((t -= 2) * t * t * t - 2) + b; } /** * Easing equation function for a quartic (t^4) easing out/in: deceleration until halfway, then acceleration. * * @param t Current time (in frames or seconds). * @param b Starting value. * @param c Change needed in value. * @param d Expected easing duration (in frames or seconds). * @return The correct value. */ function easeOutInQuart(t, b, c, d, pParams) { if (t < d / 2) return easeOutQuart(t * 2, b, c / 2, d, pParams); return easeInQuart(t * 2 - d, b + c / 2, c / 2, d, pParams); } /** * Easing equation function for a quintic (t^5) easing in: accelerating from zero velocity. * * @param t Current time (in frames or seconds). * @param b Starting value. * @param c Change needed in value. * @param d Expected easing duration (in frames or seconds). * @return The correct value. */ function easeInQuint(t, b, c, d, pParams) { return c * (t /= d) * t * t * t * t + b; } /** * Easing equation function for a quintic (t^5) easing out: decelerating from zero velocity. * * @param t Current time (in frames or seconds). * @param b Starting value. * @param c Change needed in value. * @param d Expected easing duration (in frames or seconds). * @return The correct value. */ function easeOutQuint(t, b, c, d, pParams) { return c * ((t = t / d - 1) * t * t * t * t + 1) + b; } /** * Easing equation function for a quintic (t^5) easing in/out: acceleration until halfway, then deceleration. * * @param t Current time (in frames or seconds). * @param b Starting value. * @param c Change needed in value. * @param d Expected easing duration (in frames or seconds). * @return The correct value. */ function easeInOutQuint(t, b, c, d, pParams) { if ((t /= d / 2) < 1) return c / 2 * t * t * t * t * t + b; return c / 2 * ((t -= 2) * t * t * t * t + 2) + b; } /** * Easing equation function for a quintic (t^5) easing out/in: deceleration until halfway, then acceleration. * * @param t Current time (in frames or seconds). * @param b Starting value. * @param c Change needed in value. * @param d Expected easing duration (in frames or seconds). * @return The correct value. */ function easeOutInQuint(t, b, c, d, pParams) { if (t < d / 2) return easeOutQuint(t * 2, b, c / 2, d, pParams); return easeInQuint(t * 2 - d, b + c / 2, c / 2, d, pParams); } /** * Easing equation function for a sinusoidal (sin(t)) easing in: accelerating from zero velocity. * * @param t Current time (in frames or seconds). * @param b Starting value. * @param c Change needed in value. * @param d Expected easing duration (in frames or seconds). * @return The correct value. */ function easeInSine(t, b, c, d, pParams) { return -c * Math.cos(t / d * (Math.PI / 2)) + c + b; } /** * Easing equation function for a sinusoidal (sin(t)) easing out: decelerating from zero velocity. * * @param t Current time (in frames or seconds). * @param b Starting value. * @param c Change needed in value. * @param d Expected easing duration (in frames or seconds). * @return The correct value. */ function easeOutSine(t, b, c, d, pParams) { return c * Math.sin(t / d * (Math.PI / 2)) + b; } /** * Easing equation function for a sinusoidal (sin(t)) easing in/out: acceleration until halfway, then deceleration. * * @param t Current time (in frames or seconds). * @param b Starting value. * @param c Change needed in value. * @param d Expected easing duration (in frames or seconds). * @return The correct value. */ function easeInOutSine(t, b, c, d, pParams) { return -c / 2 * (Math.cos(Math.PI * t / d) - 1) + b; } /** * Easing equation function for a sinusoidal (sin(t)) easing out/in: deceleration until halfway, then acceleration. * * @param t Current time (in frames or seconds). * @param b Starting value. * @param c Change needed in value. * @param d Expected easing duration (in frames or seconds). * @return The correct value. */ function easeOutInSine(t, b, c, d, pParams) { if (t < d / 2) return easeOutSine(t * 2, b, c / 2, d, pParams); return easeInSine(t * 2 - d, b + c / 2, c / 2, d, pParams); } /** * Easing equation function for an exponential (2^t) easing in: accelerating from zero velocity. * * @param t Current time (in frames or seconds). * @param b Starting value. * @param c Change needed in value. * @param d Expected easing duration (in frames or seconds). * @return The correct value. */ function easeInExpo(t, b, c, d, pParams) { return t <= 0 ? b : c * Math.pow(2, 10 * (t / d - 1)) + b; } /** * Easing equation function for an exponential (2^t) easing out: decelerating from zero velocity. * * @param t Current time (in frames or seconds). * @param b Starting value. * @param c Change needed in value. * @param d Expected easing duration (in frames or seconds). * @return The correct value. */ function easeOutExpo(t, b, c, d, pParams) { return t >= d ? b + c : c * (-Math.pow(2, -10 * t / d) + 1) + b; } /** * Easing equation function for an exponential (2^t) easing in/out: acceleration until halfway, then deceleration. * * @param t Current time (in frames or seconds). * @param b Starting value. * @param c Change needed in value. * @param d Expected easing duration (in frames or seconds). * @return The correct value. */ function easeInOutExpo(t, b, c, d, pParams) { if (t <= 0) return b; if (t >= d) return b + c; if ((t /= d / 2) < 1) return c / 2 * Math.pow(2, 10 * (t - 1)) + b; return c / 2 * (-Math.pow(2, -10 * --t) + 2) + b; } /** * Easing equation function for an exponential (2^t) easing out/in: deceleration until halfway, then acceleration. * * @param t Current time (in frames or seconds). * @param b Starting value. * @param c Change needed in value. * @param d Expected easing duration (in frames or seconds). * @return The correct value. */ function easeOutInExpo(t, b, c, d, pParams) { if (t < d / 2) return easeOutExpo(t * 2, b, c / 2, d, pParams); return easeInExpo(t * 2 - d, b + c / 2, c / 2, d, pParams); } /** * Easing equation function for a circular (sqrt(1-t^2)) easing in: accelerating from zero velocity. * * @param t Current time (in frames or seconds). * @param b Starting value. * @param c Change needed in value. * @param d Expected easing duration (in frames or seconds). * @return The correct value. */ function easeInCirc(t, b, c, d, pParams) { return -c * (Math.sqrt(1 - (t /= d) * t) - 1) + b; } /** * Easing equation function for a circular (sqrt(1-t^2)) easing out: decelerating from zero velocity. * * @param t Current time (in frames or seconds). * @param b Starting value. * @param c Change needed in value. * @param d Expected easing duration (in frames or seconds). * @return The correct value. */ function easeOutCirc(t, b, c, d, pParams) { return c * Math.sqrt(1 - (t = t / d - 1) * t) + b; } /** * Easing equation function for a circular (sqrt(1-t^2)) easing in/out: acceleration until halfway, then deceleration. * * @param t Current time (in frames or seconds). * @param b Starting value. * @param c Change needed in value. * @param d Expected easing duration (in frames or seconds). * @return The correct value. */ function easeInOutCirc(t, b, c, d, pParams) { if ((t /= d / 2) < 1) return -c / 2 * (Math.sqrt(1 - t * t) - 1) + b; return c / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1) + b; } /** * Easing equation function for a circular (sqrt(1-t^2)) easing out/in: deceleration until halfway, then acceleration. * * @param t Current time (in frames or seconds). * @param b Starting value. * @param c Change needed in value. * @param d Expected easing duration (in frames or seconds). * @return The correct value. */ function easeOutInCirc(t, b, c, d, pParams) { if (t < d / 2) return easeOutCirc(t * 2, b, c / 2, d, pParams); return easeInCirc(t * 2 - d, b + c / 2, c / 2, d, pParams); } /** * Easing equation function for an elastic (exponentially decaying sine wave) easing in: accelerating from zero velocity. * * @param t Current time (in frames or seconds). * @param b Starting value. * @param c Change needed in value. * @param d Expected easing duration (in frames or seconds). * @param a Amplitude. * @param p Period. * @return The correct value. */ function easeInElastic(t, b, c, d, pParams) { if (t <= 0) return b; if ((t /= d) >= 1) return b + c; var p = !pParams || isNaN(pParams.period) ? d * .3 : pParams.period; var s; var a = !pParams || isNaN(pParams.amplitude) ? 0 : pParams.amplitude; if (!a || a < Math.abs(c)) { a = c; s = p / 4; } else { s = p / (2 * Math.PI) * Math.asin(c / a); } return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p)) + b; } /** * Easing equation function for an elastic (exponentially decaying sine wave) easing out: decelerating from zero velocity. * * @param t Current time (in frames or seconds). * @param b Starting value. * @param c Change needed in value. * @param d Expected easing duration (in frames or seconds). * @param a Amplitude. * @param p Period. * @return The correct value. */ function easeOutElastic(t, b, c, d, pParams) { if (t <= 0) return b; if ((t /= d) >= 1) return b + c; var p = !pParams || isNaN(pParams.period) ? d * .3 : pParams.period; var s; var a = !pParams || isNaN(pParams.amplitude) ? 0 : pParams.amplitude; if (!a || a < Math.abs(c)) { a = c; s = p / 4; } else { s = p / (2 * Math.PI) * Math.asin(c / a); } return a * Math.pow(2, -10 * t) * Math.sin((t * d - s) * (2 * Math.PI) / p) + c + b; } /** * Easing equation function for an elastic (exponentially decaying sine wave) easing in/out: acceleration until halfway, then deceleration. * * @param t Current time (in frames or seconds). * @param b Starting value. * @param c Change needed in value. * @param d Expected easing duration (in frames or seconds). * @param a Amplitude. * @param p Period. * @return The correct value. */ function easeInOutElastic(t, b, c, d, pParams) { if (t <= 0) return b; if ((t /= d / 2) >= 2) return b + c; var p = !pParams || isNaN(pParams.period) ? d * (.3 * 1.5) : pParams.period; var s; var a = !pParams || isNaN(pParams.amplitude) ? 0 : pParams.amplitude; if (!a || a < Math.abs(c)) { a = c; s = p / 4; } else { s = p / (2 * Math.PI) * Math.asin(c / a); } if (t < 1) return -.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p)) + b; return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p) * .5 + c + b; } /** * Easing equation function for an elastic (exponentially decaying sine wave) easing out/in: deceleration until halfway, then acceleration. * * @param t Current time (in frames or seconds). * @param b Starting value. * @param c Change needed in value. * @param d Expected easing duration (in frames or seconds). * @param a Amplitude. * @param p Period. * @return The correct value. */ function easeOutInElastic(t, b, c, d, pParams) { if (t < d / 2) return easeOutElastic(t * 2, b, c / 2, d, pParams); return easeInElastic(t * 2 - d, b + c / 2, c / 2, d, pParams); } /** * Easing equation function for a back (overshooting cubic easing: (s+1)*t^3 - s*t^2) easing in: accelerating from zero velocity. * * @param t Current time (in frames or seconds). * @param b Starting value. * @param c Change needed in value. * @param d Expected easing duration (in frames or seconds). * @param s Overshoot ammount: higher s means greater overshoot (0 produces cubic easing with no overshoot, and the default value of 1.70158 produces an overshoot of 10 percent). * @return The correct value. */ function easeInBack(t, b, c, d, pParams) { var s = !pParams || isNaN(pParams.overshoot) ? 1.70158 : pParams.overshoot; return c * (t /= d) * t * ((s + 1) * t - s) + b; } /** * Easing equation function for a back (overshooting cubic easing: (s+1)*t^3 - s*t^2) easing out: decelerating from zero velocity. * * @param t Current time (in frames or seconds). * @param b Starting value. * @param c Change needed in value. * @param d Expected easing duration (in frames or seconds). * @param s Overshoot ammount: higher s means greater overshoot (0 produces cubic easing with no overshoot, and the default value of 1.70158 produces an overshoot of 10 percent). * @return The correct value. */ function easeOutBack(t, b, c, d, pParams) { var s = !pParams || isNaN(pParams.overshoot) ? 1.70158 : pParams.overshoot; return c * ((t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b; } /** * Easing equation function for a back (overshooting cubic easing: (s+1)*t^3 - s*t^2) easing in/out: acceleration until halfway, then deceleration. * * @param t Current time (in frames or seconds). * @param b Starting value. * @param c Change needed in value. * @param d Expected easing duration (in frames or seconds). * @param s Overshoot ammount: higher s means greater overshoot (0 produces cubic easing with no overshoot, and the default value of 1.70158 produces an overshoot of 10 percent). * @return The correct value. */ function easeInOutBack(t, b, c, d, pParams) { var s = !pParams || isNaN(pParams.overshoot) ? 1.70158 : pParams.overshoot; if ((t /= d / 2) < 1) return c / 2 * (t * t * (((s *= (1.525)) + 1) * t - s)) + b; return c / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2) + b; } /** * Easing equation function for a back (overshooting cubic easing: (s+1)*t^3 - s*t^2) easing out/in: deceleration until halfway, then acceleration. * * @param t Current time (in frames or seconds). * @param b Starting value. * @param c Change needed in value. * @param d Expected easing duration (in frames or seconds). * @param s Overshoot ammount: higher s means greater overshoot (0 produces cubic easing with no overshoot, and the default value of 1.70158 produces an overshoot of 10 percent). * @return The correct value. */ function easeOutInBack(t, b, c, d, pParams) { if (t < d / 2) return easeOutBack(t * 2, b, c / 2, d, pParams); return easeInBack(t * 2 - d, b + c / 2, c / 2, d, pParams); } /** * Easing equation function for a bounce (exponentially decaying parabolic bounce) easing in: accelerating from zero velocity. * * @param t Current time (in frames or seconds). * @param b Starting value. * @param c Change needed in value. * @param d Expected easing duration (in frames or seconds). * @return The correct value. */ function easeInBounce(t, b, c, d, pParams) { return c - easeOutBounce(d - t, 0, c, d) + b; } /** * Easing equation function for a bounce (exponentially decaying parabolic bounce) easing out: decelerating from zero velocity. * * @param t Current time (in frames or seconds). * @param b Starting value. * @param c Change needed in value. * @param d Expected easing duration (in frames or seconds). * @return The correct value. */ function easeOutBounce(t, b, c, d, pParams) { if ((t /= d) < 1 / 2.75) return c * (7.5625 * t * t) + b; else if (t < 2 / 2.75) return c * (7.5625 * (t -= (1.5 / 2.75)) * t + .75) + b; else if (t < 2.5 / 2.75) return c * (7.5625 * (t -= (2.25 / 2.75)) * t + .9375) + b; else return c * (7.5625 * (t -= (2.625 / 2.75)) * t + .984375) + b; } /** * Easing equation function for a bounce (exponentially decaying parabolic bounce) easing in/out: acceleration until halfway, then deceleration. * * @param t Current time (in frames or seconds). * @param b Starting value. * @param c Change needed in value. * @param d Expected easing duration (in frames or seconds). * @return The correct value. */ function easeInOutBounce(t, b, c, d, pParams) { if (t < d / 2) return easeInBounce(t * 2, 0, c, d) * .5 + b; else return easeOutBounce(t * 2 - d, 0, c, d) * .5 + c * .5 + b; } /** * Easing equation function for a bounce (exponentially decaying parabolic bounce) easing out/in: deceleration until halfway, then acceleration. * * @param t Current time (in frames or seconds). * @param b Starting value. * @param c Change needed in value. * @param d Expected easing duration (in frames or seconds). * @return The correct value. */ function easeOutInBounce(t, b, c, d, pParams) { if (t < d / 2) return easeOutBounce(t * 2, b, c / 2, d, pParams); return easeInBounce(t * 2 - d, b + c / 2, c / 2, d, pParams); } cjs-5.2.0/modules/script/tweener/tweenList.js0000644000175000017500000001026014144444702021377 0ustar jpeisachjpeisach/* -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil; -*- */ /* Copyright 2008 litl, LLC. */ /** * The tween list object. Stores all of the properties and information that pertain to individual tweens. * * @author Nate Chatellier, Zeh Fernando * @version 1.0.4 * @private */ /* exported makePropertiesChain, TweenList */ /* Licensed under the MIT License Copyright (c) 2006-2007 Zeh Fernando and Nate Chatellier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. http://code.google.com/p/tweener/ http://code.google.com/p/tweener/wiki/License */ function TweenList(scope, timeStart, timeComplete, useFrames, transition, transitionParams) { this._init(scope, timeStart, timeComplete, useFrames, transition, transitionParams); } TweenList.prototype = { _init(scope, timeStart, timeComplete, userFrames, transition, transitionParams) { this.scope = scope; this.timeStart = timeStart; this.timeComplete = timeComplete; this.userFrames = userFrames; this.transition = transition; this.transitionParams = transitionParams; /* Other default information */ this.properties = {}; this.isPaused = false; this.timePaused = undefined; this.isCaller = false; this.updatesSkipped = 0; this.timesCalled = 0; this.skipUpdates = 0; this.hasStarted = false; }, clone(omitEvents) { var tween = new TweenList(this.scope, this.timeStart, this.timeComplete, this.userFrames, this.transition, this.transitionParams); tween.properties = []; for (let name in this.properties) tween.properties[name] = this.properties[name]; tween.skipUpdates = this.skipUpdates; tween.updatesSkipped = this.updatesSkipped; if (!omitEvents) { tween.onStart = this.onStart; tween.onUpdate = this.onUpdate; tween.onComplete = this.onComplete; tween.onOverwrite = this.onOverwrite; tween.onError = this.onError; tween.onStartParams = this.onStartParams; tween.onUpdateParams = this.onUpdateParams; tween.onCompleteParams = this.onCompleteParams; tween.onOverwriteParams = this.onOverwriteParams; tween.onStartScope = this.onStartScope; tween.onUpdateScope = this.onUpdateScope; tween.onCompleteScope = this.onCompleteScope; tween.onOverwriteScope = this.onOverwriteScope; tween.onErrorScope = this.onErrorScope; } tween.rounded = this.rounded; tween.min = this.min; tween.max = this.max; tween.isPaused = this.isPaused; tween.timePaused = this.timePaused; tween.isCaller = this.isCaller; tween.count = this.count; tween.timesCalled = this.timesCalled; tween.waitFrames = this.waitFrames; tween.hasStarted = this.hasStarted; return tween; }, }; function makePropertiesChain(obj) { /* Tweener has a bunch of code here to get all the properties of all * the objects we inherit from (the objects in the 'base' property). * I don't think that applies to JavaScript... */ return obj; } cjs-5.2.0/modules/script/tweener/tweener.js0000644000175000017500000006640614144444702021107 0ustar jpeisachjpeisach/* -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil; -*- */ /* eslint-disable block-scoped-var, eqeqeq, no-shadow, prefer-rest-params */ /* Copyright 2008 litl, LLC. */ /** * Tweener * Transition controller for movieclips, sounds, textfields and other objects * * @author Zeh Fernando, Nate Chatellier, Arthur Debert * @version 1.31.71 */ /* exported addCaller, addTween, FrameTicker, getTweenCount, getTimeScale, pauseAllTweens, pauseTweens, PropertyList, registerSpecialProperty, registerSpecialPropertyModifier, registerSpecialPropertySplitter, removeAllTweens, removeTweens, restrictedWords, resumeAllTweens, resumeTweens, setFrameTicker, setTimeScale */ /* Licensed under the MIT License Copyright (c) 2006-2007 Zeh Fernando and Nate Chatellier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. http://code.google.com/p/tweener/ http://code.google.com/p/tweener/wiki/License */ const GLib = imports.gi.GLib; const TweenList = imports.tweener.tweenList; const Signals = imports.signals; var _inited = false; var _engineExists = false; var _tweenList = null; var _timeScale = 1; var _specialPropertyList = []; var _specialPropertyModifierList = []; var _specialPropertySplitterList = []; /* * Ticker should implement: * * property FRAME_RATE * start() * stop() * getTime() gets time in milliseconds from start() * signal prepare-frame * */ var _ticker = null; var _prepareFrameId = 0; /* default frame ticker */ function FrameTicker() { this._init(); } FrameTicker.prototype = { FRAME_RATE: 65, _init() { }, start() { this._currentTime = 0; let me = this; this._timeoutID = GLib.timeout_add( GLib.PRIORITY_DEFAULT, Math.floor(1000 / me.FRAME_RATE), function () { me._currentTime += 1000 / me.FRAME_RATE; me.emit('prepare-frame'); return true; }); }, stop() { if ('_timeoutID' in this) { GLib.source_remove(this._timeoutID); delete this._timeoutID; } this._currentTime = 0; }, getTime() { return this._currentTime; }, }; Signals.addSignalMethods(FrameTicker.prototype); _ticker = new FrameTicker(); /* TODOs: * * Special properties: * * Special properties are 'proxy' properties used in Tweener to tween * (animate) things that are not proper properties per se. One example * given is the 'frame' of an object in ActionScript, which is not an * object property. Using the special property '_frame' you could animate * it like this: * * Tweener.addTween(myMovieClip, {_frame:20, time:1}); * * which would be equivalent to applying a fast-forward to it. * * This properties need a special support in the code, and I've removed it * for now until we see the need for it in our clutter based stuff. */ /* This is a bit pointless now, but let's keep it anyway... */ function _init() { if (_inited) return; _inited = true; } function setFrameTicker(ticker) { _ticker = ticker; } function _startEngine() { if (_engineExists) return; _engineExists = true; _tweenList = []; if (!_ticker) throw new Error('Must call setFrameTicker()'); _prepareFrameId = _ticker.connect('prepare-frame', _onEnterFrame); _ticker.start(); } function _stopEngine() { if (!_engineExists) return; _engineExists = false; _tweenList = false; _ticker.disconnect(_prepareFrameId); _prepareFrameId = 0; _ticker.stop(); } function _getCurrentTweeningTime() { return _ticker.getTime(); } function _removeTweenByIndex(i) { _tweenList[i] = null; var finalRemoval = arguments[1]; if (finalRemoval != undefined && finalRemoval) _tweenList.splice(i, 1); return true; } function _resumeTweenByIndex(i) { var tweening = _tweenList[i]; if (tweening == null || !tweening.isPaused) return false; var currentTime = _getCurrentTweeningTime(tweening); tweening.timeStart += currentTime - tweening.timePaused; tweening.timeComplete += currentTime - tweening.timePaused; tweening.timePaused = undefined; tweening.isPaused = false; return true; } /* FIXME: any way to get the function name from the fn itself? */ function _callOnFunction(fn, fnname, scope, fallbackScope, params) { if (fn) { var eventScope = scope ? scope : fallbackScope; try { fn.apply(eventScope, params); } catch (e) { logError(e, `Error calling ${fnname}`); } } } function _updateTweenByIndex(i) { var tweening = _tweenList[i]; if (tweening == null || !tweening.scope) return false; var currentTime = _getCurrentTweeningTime(tweening); if (currentTime < tweening.timeStart) return true; // Hasn't started, so return true var scope = tweening.scope; var t, b, c, d, nv; var isOver = false; if (tweening.isCaller) { do { t = (tweening.timeComplete - tweening.timeStart) / tweening.count * (tweening.timesCalled + 1); b = tweening.timeStart; c = tweening.timeComplete - tweening.timeStart; d = tweening.timeComplete - tweening.timeStart; nv = tweening.transition(t, b, c, d); if (currentTime >= nv) { _callOnFunction(tweening.onUpdate, 'onUpdate', tweening.onUpdateScope, scope, tweening.onUpdateParams); tweening.timesCalled++; if (tweening.timesCalled >= tweening.count) { isOver = true; break; } if (tweening.waitFrames) break; } } while (currentTime >= nv); } else { var mustUpdate, name; if (currentTime >= tweening.timeComplete) { isOver = true; mustUpdate = true; } else { mustUpdate = tweening.skipUpdates < 1 || !tweening.skipUpdates || tweening.updatesSkipped >= tweening.skipUpdates; } if (!tweening.hasStarted) { _callOnFunction(tweening.onStart, 'onStart', tweening.onStartScope, scope, tweening.onStartParams); for (name in tweening.properties) { var pv; if (tweening.properties[name].isSpecialProperty) { // It's a special property, tunnel via the special property function if (_specialPropertyList[name].preProcess != undefined) tweening.properties[name].valueComplete = _specialPropertyList[name].preProcess(scope, _specialPropertyList[name].parameters, tweening.properties[name].originalValueComplete, tweening.properties[name].extra); pv = _specialPropertyList[name].getValue(scope, _specialPropertyList[name].parameters, tweening.properties[name].extra); } else { // Directly read property pv = scope[name]; } tweening.properties[name].valueStart = isNaN(pv) ? tweening.properties[name].valueComplete : pv; } mustUpdate = true; tweening.hasStarted = true; } if (mustUpdate) { for (name in tweening.properties) { var property = tweening.properties[name]; if (isOver) { // Tweening time has finished, just set it to the final value nv = property.valueComplete; } else if (property.hasModifier) { // Modified t = currentTime - tweening.timeStart; d = tweening.timeComplete - tweening.timeStart; nv = tweening.transition(t, 0, 1, d, tweening.transitionParams); nv = property.modifierFunction(property.valueStart, property.valueComplete, nv, property.modifierParameters); } else { // Normal update t = currentTime - tweening.timeStart; b = property.valueStart; c = property.valueComplete - property.valueStart; d = tweening.timeComplete - tweening.timeStart; nv = tweening.transition(t, b, c, d, tweening.transitionParams); } if (tweening.rounded) nv = Math.round(nv); if (tweening.min !== undefined && nv < tweening.min) nv = tweening.min; if (tweening.max !== undefined && nv > tweening.max) nv = tweening.max; if (property.isSpecialProperty) { // It's a special property, tunnel via the special property method _specialPropertyList[name].setValue(scope, nv, _specialPropertyList[name].parameters, tweening.properties[name].extra); } else { // Directly set property scope[name] = nv; } } tweening.updatesSkipped = 0; _callOnFunction(tweening.onUpdate, 'onUpdate', tweening.onUpdateScope, scope, tweening.onUpdateParams); } else { tweening.updatesSkipped++; } } if (isOver) { _callOnFunction(tweening.onComplete, 'onComplete', tweening.onCompleteScope, scope, tweening.onCompleteParams); } return !isOver; } function _updateTweens() { if (_tweenList.length == 0) return false; for (let i = 0; i < _tweenList.length; i++) { if (_tweenList[i] == undefined || !_tweenList[i].isPaused) { if (!_updateTweenByIndex(i)) _removeTweenByIndex(i); if (_tweenList[i] == null) { _removeTweenByIndex(i, true); i--; } } } return true; } /* Ran once every 'frame'. It's the main engine, updates all existing tweenings */ function _onEnterFrame() { if (!_updateTweens()) _stopEngine(); return true; } var restrictedWords = { time: true, delay: true, userFrames: true, skipUpdates: true, transition: true, transitionParams: true, onStart: true, onUpdate: true, onComplete: true, onOverwrite: true, onError: true, rounded: true, min: true, max: true, onStartParams: true, onUpdateParams: true, onCompleteParams: true, onOverwriteParams: true, onStartScope: true, onUpdateScope: true, onCompleteScope: true, onOverwriteScope: true, onErrorScope: true, }; function _constructPropertyList(obj) { var properties = {}; var modifiedProperties = {}; for (let istr in obj) { if (restrictedWords[istr]) continue; if (_specialPropertySplitterList[istr] != undefined) { // Special property splitter var splitProperties = _specialPropertySplitterList[istr].splitValues(obj[istr], _specialPropertySplitterList[istr].parameters); for (let i = 0; i < splitProperties.length; i++) { if (_specialPropertySplitterList[splitProperties[i].name] != undefined) { var splitProperties2 = _specialPropertySplitterList[splitProperties[i].name].splitValues(splitProperties[i].value, _specialPropertySplitterList[splitProperties[i].name].parameters); for (let j = 0; j < splitProperties2.length; j++) { properties[splitProperties2[j].name] = { valueStart: undefined, valueComplete: splitProperties2[j].value, arrayIndex: splitProperties2[j].arrayIndex, isSpecialProperty: false, }; } } else { properties[splitProperties[i].name] = { valueStart: undefined, valueComplete: splitProperties[i].value, arrayIndex: splitProperties[i].arrayIndex, isSpecialProperty: false, }; } } } else if (_specialPropertyModifierList[istr] != undefined) { // Special property modifier let tempModifiedProperties = _specialPropertyModifierList[istr].modifyValues(obj[istr]); for (let i = 0; i < tempModifiedProperties.length; i++) { modifiedProperties[tempModifiedProperties[i].name] = { modifierParameters: tempModifiedProperties[i].parameters, modifierFunction: _specialPropertyModifierList[istr].getValue, }; } } else { properties[istr] = { valueStart: undefined, valueComplete: obj[istr], }; } } // Adds the modifiers to the list of properties for (let istr in modifiedProperties) { if (properties[istr]) { properties[istr].modifierParameters = modifiedProperties[istr].modifierParameters; properties[istr].modifierFunction = modifiedProperties[istr].modifierFunction; } } return properties; } function PropertyInfo(valueStart, valueComplete, originalValueComplete, arrayIndex, extra, isSpecialProperty, modifierFunction, modifierParameters) { this._init(valueStart, valueComplete, originalValueComplete, arrayIndex, extra, isSpecialProperty, modifierFunction, modifierParameters); } PropertyInfo.prototype = { _init(valueStart, valueComplete, originalValueComplete, arrayIndex, extra, isSpecialProperty, modifierFunction, modifierParameters) { this.valueStart = valueStart; this.valueComplete = valueComplete; this.originalValueComplete = originalValueComplete; this.arrayIndex = arrayIndex; this.extra = extra; this.isSpecialProperty = isSpecialProperty; this.hasModifier = Boolean(modifierFunction); this.modifierFunction = modifierFunction; this.modifierParameters = modifierParameters; }, }; function _addTweenOrCaller(target, tweeningParameters, isCaller) { if (!target) return false; var scopes; // List of objects to tween if (Array.isArray(target)) { // The first argument is an array scopes = target.concat(); // XXX: To copy the array I guess } else { // The first argument(s) is(are) object(s) scopes = new Array(target); } var obj, istr; if (isCaller) { obj = tweeningParameters; } else { obj = TweenList.makePropertiesChain(tweeningParameters); var properties = _constructPropertyList(obj); // Verifies whether the properties exist or not, for warning messages for (istr in properties) { if (_specialPropertyList[istr] != undefined) { properties[istr].isSpecialProperty = true; } else { for (var i = 0; i < scopes.length; i++) { if (scopes[i][istr] == undefined) { log(`The property ${istr} doesn't seem to be a ` + `normal object property of ${scopes[i]} or a ` + 'registered special property'); } } } } } // Creates the main engine if it isn't active if (!_inited) _init(); if (!_engineExists) _startEngine(); // Creates a "safer", more strict tweening object var time = obj.time || 0; var delay = obj.delay || 0; var transition; // FIXME: Tweener allows you to use functions with an all lower-case name if (typeof obj.transition == 'string') transition = imports.tweener.equations[obj.transition]; else transition = obj.transition; if (!transition) transition = imports.tweener.equations['easeOutExpo']; var tween; for (let i = 0; i < scopes.length; i++) { if (!isCaller) { // Make a copy of the properties var copyProperties = {}; for (istr in properties) { copyProperties[istr] = new PropertyInfo(properties[istr].valueStart, properties[istr].valueComplete, properties[istr].valueComplete, properties[istr].arrayIndex || 0, {}, properties[istr].isSpecialProperty || false, properties[istr].modifierFunction || null, properties[istr].modifierParameters || null); } } tween = new TweenList.TweenList(scopes[i], _ticker.getTime() + delay * 1000 / _timeScale, _ticker.getTime() + (delay * 1000 + time * 1000) / _timeScale, false, transition, obj.transitionParams || null); tween.properties = isCaller ? null : copyProperties; tween.onStart = obj.onStart; tween.onUpdate = obj.onUpdate; tween.onComplete = obj.onComplete; tween.onOverwrite = obj.onOverwrite; tween.onError = obj.onError; tween.onStartParams = obj.onStartParams; tween.onUpdateParams = obj.onUpdateParams; tween.onCompleteParams = obj.onCompleteParams; tween.onOverwriteParams = obj.onOverwriteParams; tween.onStartScope = obj.onStartScope; tween.onUpdateScope = obj.onUpdateScope; tween.onCompleteScope = obj.onCompleteScope; tween.onOverwriteScope = obj.onOverwriteScope; tween.onErrorScope = obj.onErrorScope; tween.rounded = obj.rounded; tween.min = obj.min; tween.max = obj.max; tween.skipUpdates = obj.skipUpdates; tween.isCaller = isCaller; if (isCaller) { tween.count = obj.count; tween.waitFrames = obj.waitFrames; } if (!isCaller) { // Remove other tweenings that occur at the same time removeTweensByTime(tween.scope, tween.properties, tween.timeStart, tween.timeComplete); } // And finally adds it to the list _tweenList.push(tween); // Immediate update and removal if it's an immediate tween // If not deleted, it executes at the end of this frame execution if (time == 0 && delay == 0) { var myT = _tweenList.length - 1; _updateTweenByIndex(myT); _removeTweenByIndex(myT); } } return true; } function addTween(target, tweeningParameters) { return _addTweenOrCaller(target, tweeningParameters, false); } function addCaller(target, tweeningParameters) { return _addTweenOrCaller(target, tweeningParameters, true); } function _getNumberOfProperties(object) { var totalProperties = 0; // the following line is disabled becasue eslint was picking up the following error: the variable name is defined but never used, however since it is required to search the object it is used and we'll allow the line to be ignored to get rid of the error message /* eslint-disable-next-line */ for (let name in object) { totalProperties++; } return totalProperties; } function removeTweensByTime(scope, properties, timeStart, timeComplete) { var removed = false; var removedLocally; var name; for (let i = 0; i < _tweenList.length; i++) { removedLocally = false; if (_tweenList[i] && scope == _tweenList[i].scope && timeComplete > _tweenList[i].timeStart && timeStart < _tweenList[i].timeComplete) { for (name in _tweenList[i].properties) { if (properties[name]) { if (!removedLocally) { _callOnFunction(_tweenList[i].onOverwrite, 'onOverwrite', _tweenList[i].onOverwriteScope, _tweenList[i].scope, _tweenList[i].onOverwriteParams); } _tweenList[i].properties[name] = undefined; delete _tweenList[i].properties[name]; removedLocally = true; removed = true; } } if (removedLocally && _getNumberOfProperties(_tweenList[i].properties) == 0) _removeTweenByIndex(i); } } return removed; } function _pauseTweenByIndex(i) { var tweening = _tweenList[i]; if (tweening == null || tweening.isPaused) return false; tweening.timePaused = _getCurrentTweeningTime(tweening); tweening.isPaused = true; return true; } function _splitTweens(tween, properties) { var originalTween = _tweenList[tween]; var newTween = originalTween.clone(); var name; for (let i = 0; i < properties.length; i++) { name = properties[i]; if (originalTween.properties[name]) { originalTween.properties[name] = undefined; delete originalTween.properties[name]; } } var found = false; for (name in newTween.properties) { found = false; for (let i = 0; i < properties.length; i++) { if (properties[i] == name) { found = true; break; } } if (!found) { newTween.properties[name] = undefined; delete newTween.properties[name]; } } _tweenList.push(newTween); return _tweenList.length - 1; } function _affectTweens(affectFunction, scope, properties) { var affected = false; if (!_tweenList) return false; for (let i = 0; i < _tweenList.length; i++) { if (!_tweenList[i] || _tweenList[i].scope != scope) continue; if (properties.length == 0) { // Can check everything affectFunction(i); affected = true; } else { // Must check whether this tween must have specific properties affected var affectedProperties = []; for (let j = 0; j < properties.length; j++) { if (_tweenList[i].properties[properties[j]]) affectedProperties.push(properties[j]); } if (affectedProperties.length > 0) { var objectProperties = _getNumberOfProperties(_tweenList[i].properties); if (objectProperties == affectedProperties.length) { // The list of properties is the same as all properties, so affect it all affectFunction(i); affected = true; } else { // The properties are mixed, so split the tween and affect only certian specific // properties var splicedTweenIndex = _splitTweens(i, affectedProperties); affectFunction(splicedTweenIndex); affected = true; } } } } return affected; } function _isInArray(string, array) { var l = array.length; for (let i = 0; i < l; i++) { if (array[i] == string) return true; } return false; } function _affectTweensWithFunction(func, args) { var properties = []; var scope = args[0]; var affected = false; var scopes; if (Array.isArray(scope)) scopes = scope.concat(); else scopes = new Array(scope); for (let i = 1; args[i] != undefined; i++) { if (typeof args[i] == 'string' && !_isInArray(args[i], properties)) { if (_specialPropertySplitterList[args[i]]) { // special property, get splitter array first var sps = _specialPropertySplitterList[arguments[i]]; var specialProps = sps.splitValues(scope, null); for (let j = 0; j < specialProps.length; j++) properties.push(specialProps[j].name); } else { properties.push(args[i]); } } } // the return now value means: "affect at least one tween" for (let i = 0; i < scopes.length; i++) affected = affected || _affectTweens(func, scopes[i], properties); return affected; } function resumeTweens() { return _affectTweensWithFunction(_resumeTweenByIndex, arguments); } function pauseTweens() { return _affectTweensWithFunction(_pauseTweenByIndex, arguments); } function removeTweens() { return _affectTweensWithFunction(_removeTweenByIndex, arguments); } function _mapOverTweens(func) { var rv = false; if (_tweenList == null) return false; for (let i = 0; i < _tweenList.length; i++) { if (func(i)) rv = true; } return rv; } function pauseAllTweens() { return _mapOverTweens(_pauseTweenByIndex); } function resumeAllTweens() { return _mapOverTweens(_resumeTweenByIndex); } function removeAllTweens() { return _mapOverTweens(_removeTweenByIndex); } function getTweenCount(scope) { if (!_tweenList) return 0; var c = 0; for (let i = 0; i < _tweenList.length; i++) { if (_tweenList[i] && _tweenList[i].scope == scope) c += _getNumberOfProperties(_tweenList[i].properties); } return c; } function registerSpecialProperty(name, getFunction, setFunction, parameters, preProcessFunction) { _specialPropertyList[name] = { getValue: getFunction, setValue: setFunction, parameters, preProcess: preProcessFunction, }; } function registerSpecialPropertyModifier(name, modifyFunction, getFunction) { _specialPropertyModifierList[name] = { modifyValues: modifyFunction, getValue: getFunction, }; } function registerSpecialPropertySplitter(name, splitFunction, parameters) { _specialPropertySplitterList[name] = { splitValues: splitFunction, parameters, }; } function setTimeScale(scale) { _timeScale = scale; } function getTimeScale() { return _timeScale; } cjs-5.2.0/modules/script/lang.js0000644000175000017500000000635514144444702016703 0ustar jpeisachjpeisach/* -*- mode: js; indent-tabs-mode: nil; -*- */ /* exported bind, copyProperties, copyPublicProperties, countProperties, Class, getMetaClass, Interface */ // Copyright (c) 2008 litl, LLC // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to // deal in the Software without restriction, including without limitation the // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or // sell copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. // Utilities that are "meta-language" things like manipulating object props var {Class, Interface, getMetaClass} = imports._legacy; function countProperties(obj) { let count = 0; for (let unusedProperty in obj) count += 1; return count; } function getPropertyDescriptor(obj, property) { if (obj.hasOwnProperty(property)) return Object.getOwnPropertyDescriptor(obj, property); return getPropertyDescriptor(Object.getPrototypeOf(obj), property); } function _copyProperty(source, dest, property) { let descriptor = getPropertyDescriptor(source, property); Object.defineProperty(dest, property, descriptor); } function copyProperties(source, dest) { for (let property in source) _copyProperty(source, dest, property); } function copyPublicProperties(source, dest) { for (let property in source) { if (typeof property === 'string' && property.startsWith('_')) continue; else _copyProperty(source, dest, property); } } /** * Binds obj to callback. Makes it possible to refer to "obj" * using this within the callback. * @param {object} obj the object to bind * @param {function} callback callback to bind obj in * @param {*} bindArguments additional arguments to the callback * @returns {function} a new callback */ function bind(obj, callback, ...bindArguments) { if (typeof obj !== 'object') { throw new Error(`first argument to Lang.bind() must be an object, not ${ typeof obj}`); } if (typeof callback !== 'function') { throw new Error(`second argument to Lang.bind() must be a function, not ${ typeof callback}`); } // Use ES5 Function.prototype.bind, but only if not passing any bindArguments, // because ES5 has them at the beginning, not at the end if (arguments.length === 2) return callback.bind(obj); let me = obj; return function (...args) { args = args.concat(bindArguments); return callback.apply(me, args); }; } cjs-5.2.0/modules/script/cairo.js0000644000175000017500000000233014144444702017044 0ustar jpeisachjpeisach// Copyright 2010 litl, LLC. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to // deal in the Software without restriction, including without limitation the // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or // sell copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. // Merge stuff defined in the shared imports._cairo and then in native code Object.assign(this, imports._cairo, imports.cairoNative); cjs-5.2.0/modules/script/package.js0000644000175000017500000002667614144444702017365 0ustar jpeisachjpeisach// Copyright 2012 Giovanni Campagna // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to // deal in the Software without restriction, including without limitation the // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or // sell copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. /* exported checkSymbol, datadir, init, initFormat, initGettext, initSubmodule, libdir, localedir, moduledir, name, pkgdatadir, pkglibdir, prefix, require, requireSymbol, run, start, version */ /** * This module provides a set of convenience APIs for building packaged * applications. */ const GLib = imports.gi.GLib; const GIRepository = imports.gi.GIRepository; const Gio = imports.gi.Gio; const GObject = imports.gi.GObject; const System = imports.system; const Gettext = imports.gettext; // public var name; var version; var prefix; var datadir; var libdir; var pkgdatadir; var pkglibdir; var moduledir; var localedir; // private let _pkgname; let _base; let _submoduledir; function _findEffectiveEntryPointName() { let entryPoint = System.programInvocationName; while (GLib.file_test(entryPoint, GLib.FileTest.IS_SYMLINK)) entryPoint = GLib.file_read_link(entryPoint); return GLib.path_get_basename(entryPoint); } function _runningFromSource() { let binary = Gio.File.new_for_path(System.programInvocationName); let sourceBinary = Gio.File.new_for_path(`./src/${name}`); return binary.equal(sourceBinary); } function _runningFromMesonSource() { return GLib.getenv('MESON_BUILD_ROOT') && GLib.getenv('MESON_SOURCE_ROOT'); } function _makeNamePath(n) { return `/${n.replace(/\./g, '/')}`; } /** * Initialize directories and global variables. Must be called * before any of other API in Package is used. * `params` must be an object with at least the following keys: * - name: the package name ($(PACKAGE_NAME) in autotools, * eg. org.foo.Bar) * - version: the package version * - prefix: the installation prefix * * init() will take care to check if the program is running from * the source directory or not, by looking for a 'src' directory. * * At the end, the global variable 'pkg' will contain the * Package module (imports.package). Additionally, the following * module variables will be available: * - name: the base name of the entry point (eg. org.foo.Bar.App) * - version: same as in @params * - prefix: the installation prefix (as passed in @params) * - datadir, libdir: the final datadir and libdir when installed; * usually, these would be prefix + '/share' and * and prefix + '/lib' (or '/lib64') * - pkgdatadir: the directory to look for private data files, such as * images, stylesheets and UI definitions; * this will be datadir + name when installed and * './data' when running from the source tree * - pkglibdir: the directory to look for private typelibs and C * libraries; * this will be libdir + name when installed and * './lib' when running from the source tree * - moduledir: the directory to look for JS modules; * this will be pkglibdir when installed and * './src' when running from the source tree * - localedir: the directory containing gettext translation files; * this will be datadir + '/locale' when installed * and './po' in the source tree * * All paths are absolute and will not end with '/'. * * As a side effect, init() calls GLib.set_prgname(). * * @param {object} params package parameters */ function init(params) { globalThis.pkg = imports.package; _pkgname = params.name; name = _findEffectiveEntryPointName(); version = params.version; // Must call it first, because it can only be called // once, and other library calls might have it as a // side effect GLib.set_prgname(name); prefix = params.prefix; libdir = params.libdir; datadir = GLib.build_filenamev([prefix, 'share']); let libpath, girpath; if (_runningFromSource()) { log('Running from source tree, using local files'); // Running from source directory _base = GLib.get_current_dir(); _submoduledir = _base; pkglibdir = GLib.build_filenamev([_base, 'lib']); libpath = GLib.build_filenamev([pkglibdir, '.libs']); girpath = pkglibdir; pkgdatadir = GLib.build_filenamev([_base, 'data']); localedir = GLib.build_filenamev([_base, 'po']); moduledir = GLib.build_filenamev([_base, 'src']); GLib.setenv('GSETTINGS_SCHEMA_DIR', pkgdatadir, true); } else if (_runningFromMesonSource()) { log('Running from Meson, using local files'); let bld = GLib.getenv('MESON_BUILD_ROOT'); let src = GLib.getenv('MESON_SOURCE_ROOT'); pkglibdir = libpath = girpath = GLib.build_filenamev([bld, 'lib']); pkgdatadir = GLib.build_filenamev([bld, 'data']); localedir = GLib.build_filenamev([bld, 'po']); _submoduledir = GLib.build_filenamev([bld, 'subprojects']); GLib.setenv('GSETTINGS_SCHEMA_DIR', pkgdatadir, true); try { let resource = Gio.Resource.load(GLib.build_filenamev([bld, 'src', `${name}.src.gresource`])); resource._register(); moduledir = `resource://${_makeNamePath(name)}/js`; } catch (e) { moduledir = GLib.build_filenamev([src, 'src']); } } else { _base = prefix; pkglibdir = GLib.build_filenamev([libdir, _pkgname]); libpath = pkglibdir; girpath = GLib.build_filenamev([pkglibdir, 'girepository-1.0']); pkgdatadir = GLib.build_filenamev([datadir, _pkgname]); localedir = GLib.build_filenamev([datadir, 'locale']); try { let resource = Gio.Resource.load(GLib.build_filenamev([pkgdatadir, `${name}.src.gresource`])); resource._register(); moduledir = `resource://${_makeNamePath(name)}/js`; } catch (e) { moduledir = pkgdatadir; } } imports.searchPath.unshift(moduledir); GIRepository.Repository.prepend_search_path(girpath); GIRepository.Repository.prepend_library_path(libpath); try { let resource = Gio.Resource.load(GLib.build_filenamev([pkgdatadir, `${name}.data.gresource`])); resource._register(); } catch (e) { } } /** * This is a convenience function if your package has a * single entry point. * You must define a main(ARGV) function inside a main.js * module in moduledir. * @param {object} params see init() */ function start(params) { init(params); run(imports.main); } /** * This is the function to use if you want to have multiple * entry points in one package. * You must define a main(ARGV) function inside the passed * in module, and then the launcher would be * * imports.package.init(...); * imports.package.run(imports.entrypoint); * * @param {object} module the module to run * @returns {number|undefined} the exit code of the module's main() function */ function run(module) { return module.main([System.programInvocationName].concat(ARGV)); } /** * Mark a dependency on a specific version of one or more * external GI typelibs. * `libs` must be an object whose keys are a typelib name, * and values are the respective version. The empty string * indicates any version. * @param {object} libs the external dependencies to import */ function require(libs) { for (let l in libs) requireSymbol(l, libs[l]); } /** * As checkSymbol(), but exit with an error if the * dependency cannot be satisfied. * @param {string} lib an external dependency to import * @param {string} [ver] version of the dependency * @param {string} [symbol] symbol to check for */ function requireSymbol(lib, ver, symbol) { if (!checkSymbol(lib, ver, symbol)) { if (symbol) printerr(`Unsatisfied dependency: No ${symbol} in ${lib}`); else printerr(`Unsatisfied dependency: ${lib}`); System.exit(1); } } /** * Check whether an external GI typelib can be imported * and provides @symbol. * * Symbols may refer to * - global functions ('main_quit') * - classes ('Window') * - class / instance methods ('IconTheme.get_default' / 'IconTheme.has_icon') * - GObject properties ('Window.default_height') * * @param {string} lib an external dependency to import * @param {string} [ver] version of the dependency * @param {string} [symbol] symbol to check for * @return {boolean} true if `lib` can be imported and provides `symbol`, false * otherwise */ function checkSymbol(lib, ver, symbol) { let Lib = null; if (ver) imports.gi.versions[lib] = ver; try { Lib = imports.gi[lib]; } catch (e) { return false; } if (!symbol) return true; // Done let [klass, sym] = symbol.split('.'); if (klass === symbol) // global symbol return typeof Lib[symbol] !== 'undefined'; let obj = Lib[klass]; if (typeof obj === 'undefined') return false; if (typeof obj[sym] !== 'undefined' || obj.prototype && typeof obj.prototype[sym] !== 'undefined') return true; // class- or object method // GObject property let pspec = null; if (GObject.type_is_a(obj.$gtype, GObject.TYPE_INTERFACE)) { let iface = GObject.type_default_interface_ref(obj.$gtype); pspec = GObject.Object.interface_find_property(iface, sym); } else if (GObject.type_is_a(obj.$gtype, GObject.TYPE_OBJECT)) { pspec = GObject.Object.find_property.call(obj.$gtype, sym); } return pspec !== null; } function initGettext() { Gettext.bindtextdomain(_pkgname, localedir); Gettext.textdomain(_pkgname); let gettext = imports.gettext; globalThis._ = gettext.gettext; globalThis.C_ = gettext.pgettext; globalThis.N_ = function (x) { return x; }; } function initFormat() { let format = imports.format; String.prototype.format = format.format; } function initSubmodule(moduleName) { if (_runningFromMesonSource() || _runningFromSource()) { // Running from source tree, add './moduleName' to search paths let submoduledir = GLib.build_filenamev([_submoduledir, moduleName]); let libpath; if (_runningFromMesonSource()) libpath = submoduledir; else libpath = GLib.build_filenamev([submoduledir, '.libs']); GIRepository.Repository.prepend_search_path(submoduledir); GIRepository.Repository.prepend_library_path(libpath); } else { // Running installed, submodule is in $(pkglibdir), nothing to do } } cjs-5.2.0/modules/script/gettext.js0000644000175000017500000000267614144444702017450 0ustar jpeisachjpeisach// Copyright 2019 Evan Welsh // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to // deal in the Software without restriction, including without limitation the // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or // sell copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. /* exported LocaleCategory, bindtextdomain, dcgettext, dgettext, dngettext, domain, dpgettext, gettext, ngettext, pgettext, setlocale, textdomain */ var { LocaleCategory, bindtextdomain, dcgettext, dgettext, dngettext, domain, dpgettext, gettext, ngettext, pgettext, setlocale, textdomain, } = imports._gettext; cjs-5.2.0/modules/script/signals.js0000644000175000017500000000315514144444702017415 0ustar jpeisachjpeisach/* * Copyright (c) 2008 litl, LLC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ /* exported addSignalMethods, WithSignals */ const Lang = imports.lang; // Private API, remains exported for backwards compatibility reasons var {_connect, _disconnect, _emit, _signalHandlerIsConnected, _disconnectAll} = imports._signals; // Public API var {addSignalMethods} = imports._signals; var WithSignals = new Lang.Interface({ Name: 'WithSignals', connect: _connect, disconnect: _disconnect, emit: _emit, signalHandlerIsConnected: _signalHandlerIsConnected, disconnectAll: _disconnectAll, }); cjs-5.2.0/modules/script/_bootstrap/0000755000175000017500000000000014144444702017567 5ustar jpeisachjpeisachcjs-5.2.0/modules/script/_bootstrap/coverage.js0000644000175000017500000000024114144444702021715 0ustar jpeisachjpeisach(function (exports) { 'use strict'; exports.debugger = new Debugger(exports.debuggee); exports.debugger.collectCoverageInfo = true; })(globalThis); cjs-5.2.0/modules/script/_bootstrap/debugger.js0000644000175000017500000005760514144444702021726 0ustar jpeisachjpeisach/* global debuggee, quit, loadNative, readline, uneval */ /* -*- indent-tabs-mode: nil; js-indent-level: 4 -*- * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /* * This is a simple command-line debugger for GJS programs. It is based on * jorendb, which is a toy debugger for shell-js programs included in the * SpiderMonkey source. * * To run it: gjs -d path/to/file.js * Execution will stop at debugger statements, and you'll get a prompt before * the first frame is executed. */ const {print, logError} = loadNative('_print'); // Debugger state. var focusedFrame = null; var topFrame = null; var debuggeeValues = {}; var nextDebuggeeValueIndex = 1; var lastExc = null; var options = {pretty: true}; var breakpoints = [undefined]; // Breakpoint numbers start at 1 // Cleanup functions to run when we next re-enter the repl. var replCleanups = []; // Convert a debuggee value v to a string. function dvToString(v) { if (typeof v === 'undefined') return 'undefined'; // uneval(undefined) === '(void 0)', confusing if (v === null) return 'null'; // typeof null === 'object', so avoid that case return typeof v !== 'object' || v === null ? uneval(v) : `[object ${v.class}]`; } function summarizeObject(dv) { const obj = {}; for (var name of dv.getOwnPropertyNames()) { var v = dv.getOwnPropertyDescriptor(name).value; if (v instanceof Debugger.Object) v = '(...)'; obj[name] = v; } return obj; } function debuggeeValueToString(dv, style = {pretty: options.pretty}) { const dvrepr = dvToString(dv); if (!style.pretty || dv === null || typeof dv !== 'object') return [dvrepr, undefined]; if (['Error', 'GIRespositoryNamespace', 'GObject_Object'].includes(dv.class)) { const errval = debuggeeGlobalWrapper.executeInGlobalWithBindings( 'v.toString()', {v: dv}); return [dvrepr, errval['return']]; } if (style.brief) return [dvrepr, JSON.stringify(summarizeObject(dv), null, 4)]; const str = debuggeeGlobalWrapper.executeInGlobalWithBindings( 'JSON.stringify(v, null, 4)', {v: dv}); if ('throw' in str) { if (style.noerror) return [dvrepr, undefined]; const substyle = {}; Object.assign(substyle, style); substyle.noerror = true; return [dvrepr, debuggeeValueToString(str.throw, substyle)]; } return [dvrepr, str['return']]; } function showDebuggeeValue(dv, style = {pretty: options.pretty}) { const i = nextDebuggeeValueIndex++; debuggeeValues[`$${i}`] = dv; const [brief, full] = debuggeeValueToString(dv, style); print(`$${i} = ${brief}`); if (full !== undefined) print(full); } Object.defineProperty(Debugger.Frame.prototype, 'num', { configurable: true, enumerable: false, get() { let i = 0; let f; for (f = topFrame; f && f !== this; f = f.older) i++; return f === null ? undefined : i; }, }); Debugger.Frame.prototype.describeFrame = function () { if (this.type === 'call') { return `${this.callee.name || ''}(${ this.arguments.map(dvToString).join(', ')})`; } else if (this.type === 'global') { return 'toplevel'; } else { return `${this.type} code`; } }; Debugger.Frame.prototype.describePosition = function () { if (this.script) return this.script.describeOffset(this.offset); return null; }; Debugger.Frame.prototype.describeFull = function () { const fr = this.describeFrame(); const pos = this.describePosition(); if (pos) return `${fr} at ${pos}`; return fr; }; Object.defineProperty(Debugger.Frame.prototype, 'line', { configurable: true, enumerable: false, get() { if (this.script) return this.script.getOffsetLocation(this.offset).lineNumber; else return null; }, }); Debugger.Script.prototype.describeOffset = function describeOffset(offset) { const {lineNumber, columnNumber} = this.getOffsetLocation(offset); const url = this.url || ''; return `${url}:${lineNumber}:${columnNumber}`; }; function showFrame(f, n) { if (f === undefined || f === null) { f = focusedFrame; if (f === null) { print('No stack.'); return; } } if (n === undefined) { n = f.num; if (n === undefined) throw new Error('Internal error: frame not on stack'); } print(`#${n.toString().padEnd(4)} ${f.describeFull()}`); } function saveExcursion(fn) { const tf = topFrame, ff = focusedFrame; try { return fn(); } finally { topFrame = tf; focusedFrame = ff; } } // Accept debugger commands starting with '#' so that scripting the debugger // can be annotated function commentCommand(comment) { void comment; } // Evaluate an expression in the Debugger global - used for debugging the // debugger function evalCommand(expr) { eval(expr); } function quitCommand() { dbg.removeAllDebuggees(); quit(0); } quitCommand.summary = 'Quit the debugger'; quitCommand.helpText = `USAGE quit`; function backtraceCommand() { if (topFrame === null) print('No stack.'); for (var i = 0, f = topFrame; f; i++, f = f.older) showFrame(f, i); } backtraceCommand.summary = 'Print backtrace of all stack frames'; backtraceCommand.helpText = `USAGE bt`; function setCommand(rest) { var space = rest.indexOf(' '); if (space === -1) { print('Invalid set