Cannot throw JS errors across FFI boundary
The following program demonstrates how you can throw an exception that can't be caught:
import System from 'system';
import GObject from 'gi://GObject';
import Gtk from 'gi://Gtk?version=4.0';
const App = GObject.registerClass(class extends Gtk.Application {
vfunc_activate() {
super.vfunc_activate();
const win = new Gtk.ApplicationWindow({application: this});
win.connect('close-request', () => {
this.quit();
throw 'No catch, only log';
});
win.present();
}
});
const app = new App({applicationId: 'app.test'});
try {
app.run([System.programInvocationName, ...System.programArgs]);
} catch (e) {
print('catch');
}
The close-request
signal handler doesn't have a GError**
parameter in C, so there's nowhere to throw the JS value to. It is just logged instead. The try
block at the bottom of the program doesn't do anything.
It should be possible to implement this such that exceptions thrown into an FFI boundary are stored and re-thrown when control returns back to JS at a higher level. But when exactly? In this case, we'd want the exception to be re-thrown when the main loop exits, at the end of Application.run
. But that's definitely not always the case. Here's a similar program that throws an exception in a button clicked handler:
import System from 'system';
import GObject from 'gi://GObject';
import Gtk from 'gi://Gtk?version=4.0';
const App = GObject.registerClass(class extends Gtk.Application {
vfunc_activate() {
super.vfunc_activate();
const win = new Gtk.ApplicationWindow({application: this});
win.connect('close-request', () => {
this.quit();
});
const button = new Gtk.Button({label: 'Throw'});
button.connect('clicked', () => {
throw 'No catch, only log';
});
win.set_child(button);
win.present();
}
});
const app = new App({applicationId: 'app.test'});
try {
app.run([System.programInvocationName, ...System.programArgs]);
} catch (e) {
print('catch');
}
Would we want to stop the main loop if an exception was thrown in a signal handler? That seems like the right thing to do in this second example. But that would potentially break a lot of existing code, like actually make code that previously worked stop working.