Last active
July 29, 2022 12:01
-
-
Save WebReflection/7ab0addec037508cc8380a9c37d285f2 to your computer and use it in GitHub Desktop.
A GJS WebKit JSON Communication Channel Example
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const JSONChannel = (Private => class JSONChannel { | |
// (c) Andrea Giammarchi - @WebReflection (ISC) | |
constructor(secret=Array.from( | |
crypto.getRandomValues(new Uint8Array(8)) | |
).map(i => i.toString(36)).join('')) { | |
const listener = e => this.emit('message', null, e.detail); | |
document.addEventListener(`${secret}:gjs`, listener); | |
Private.set(this, { | |
secret, listener, | |
fn: Object.create(null) | |
}); | |
} | |
close() { | |
const ref = Private.get(this); | |
Private.delete(this); | |
document.removeEventListener(`${ref.secret}:gjs`, ref.listener); | |
} | |
send(data) { | |
document.title = `${Private.get(this).secret}:js=${JSON.stringify(data)}`; | |
// one day the following should work instead | |
// webkit.messageHandlers[Private.get(this).secret].postMessage(data); | |
} | |
emit(type, err, data) { | |
const listeners = Private.get(this); | |
if (type in listeners) listeners[type].forEach( | |
fn => fn.call(this, err, data) | |
); | |
} | |
on(type, fn) { | |
const listeners = Private.get(this); | |
if (!(type in listeners)) listeners[type] = new Set; | |
listeners[type].add(fn); | |
return this; | |
} | |
removeListener(type, fn) { | |
const listeners = Private.get(this); | |
if (type in listeners) listeners[type].delete(fn); | |
return this; | |
} | |
})(new WeakMap); |
Alternative full GJS program
#!/usr/bin/env gjs
imports.gi.versions.Gtk = '3.0';
((GLib, Gtk, Gdk, WebKit2) => {'use strict';
Gtk.init(null);
const
param = (name, value) => {
const re = new RegExp('^--' + name + '(=.*)?$', 'i');
return ARGV.some(p => re.test(p)) ? RegExp.$1.slice(1) : value;
},
Screen = Gdk.Screen.get_default(),
FULLSCREEN = param('fullscreen', false) !== false,
WIDTH = +param('width', FULLSCREEN ? Screen.get_width() : 480),
HEIGHT = +param('height', FULLSCREEN ? Screen.get_height() : 320),
CURRENT_DIR = GLib.get_current_dir(),
PATH_SEPARATOR = /^\//.test(CURRENT_DIR) ? '/' : '\\',
window = new Gtk.Window({
title: 'GJS Unframed Browser',
type : Gtk.WindowType.TOPLEVEL,
decorated: false,
window_position: Gtk.WindowPosition.CENTER
}),
webView = new WebKit2.WebView(),
wvSettings = webView.get_settings(),
wvUCM = webView.get_user_content_manager(),
gtkSettings = Gtk.Settings.get_default()
;
const black = new Gdk.RGBA();
black.parse('rgb(0,0,0)');
webView.set_background_color(black);
const secret = randomString(8);
const validSecret = new RegExp(`^(${secret}):js=`);
webView.connect('load-changed', (self, loadEvent, data) => {
switch (loadEvent) {
case WebKit2.LoadEvent.COMMITTED:
case WebKit2.LoadEvent.FINISHED:
self.run_javascript(
`dispatchEvent(new CustomEvent('gjs:ready', {detail:new JSONChannel('${secret}')}));`,
null,
(self, result, error) => {
self.run_javascript_finish(result);
}
);
break;
}
});
webView.connect('notify::title', (self, params) => {
if (validSecret.test(self.title)) {
const secret = RegExp.$1;
const data = JSON.parse(self.title.slice(secret.length + 4));
print(data);
self.run_javascript(
`document.dispatchEvent(
new CustomEvent(
'${secret}:gjs',
{detail: ${JSON.stringify('pong')}}
)
);`,
null,
(self, result, error) => {
self.run_javascript_finish(result);
}
);
}
});
// one day the following should work instead
if (wvUCM.register_script_message_handler(secret)) {
wvUCM.connect('script-message-received', (self, js) => {
print(js);
});
}
window.set_default_size(WIDTH, HEIGHT);
gtkSettings.gtk_application_prefer_dark_theme = true;
gtkSettings.gtk_theme_name = 'Adwaita';
[
'allow-file-access-from-file-urls',
'allow-universal-access-from-file-urls',
'enable_webgl',
'enable_webaudio',
'enable_accelerated_compositing',
'javascript-can-access-clipboard',
'javascript-can-open-windows-automatically'
].forEach(function (key) {
wvSettings[key] = true;
});
// longest method name in history?
wvSettings.set_enable_write_console_messages_to_stdout(true);
webView.load_uri(param('uri', ['file:', '', '', GLib.get_current_dir(), 'index.html'].join(PATH_SEPARATOR)));
window.connect('show', () => {
if (FULLSCREEN) window.fullscreen();
Gtk.main();
});
window.connect('destroy', () => Gtk.main_quit());
window.connect('delete_event', () => false);
window.add(webView);
window.show_all();
function randomString(size) {
let chars = [];
while (size--) chars[size] = GLib.random_int_range(0, 0xFF).toString(36);
return chars.join('');
}
})(
imports.gi.GLib,
imports.gi.Gtk,
imports.gi.Gdk,
imports.gi.WebKit2
);
Alternative page code
addEventListener(
'gjs:ready',
e => {
const GJSChannel = e.detail;
GJSChannel
.on('message', (err, data) => {
if (err) console.error(err);
else console.log(data);
})
.send('ping');
},
{once: true}
);
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Example
GJS side ...
basic html example