@@ 0,0 1,207 @@
+/*
+ * extension.c
+ * Copyright (C) 2020 Adrian Perez de Castro <aperez@igalia.com>
+ *
+ * Distributed under terms of the MIT license.
+ */
+
+#include "../libkn/kiln.h"
+
+#if defined(KILN_WKEXT_GTK)
+# include <webkit2/webkit-web-extension.h>
+#elif defined(KILN_WKEXT_WPE)
+# include <wpe/webkit-web-extension.h>
+#else
+# error Unsupported configuration
+#endif
+
+static GQuark script_world_quark (void);
+G_DEFINE_QUARK (Kiln-WebKitScriptWorld, script_world);
+#define SCRIPT_WORLD (script_world_quark ())
+
+static gboolean s_module_loader = FALSE;
+static GStrv s_module_paths = NULL;
+static GStrv s_module_preload = NULL;
+static GHashTable *s_module_whitelist = NULL;
+static GHashTable *s_module_blacklist = NULL;
+
+__attribute__((destructor))
+static void
+destroy_options (void)
+{
+ g_clear_pointer (&s_module_paths, g_strfreev);
+ g_clear_pointer (&s_module_preload, g_strfreev);
+ g_clear_pointer (&s_module_whitelist, g_hash_table_unref);
+ g_clear_pointer (&s_module_blacklist, g_hash_table_unref);
+}
+
+static GVariant*
+param_lookup (GVariantDict *dict,
+ const char *key,
+ const GVariantType *type)
+{
+ g_autoptr(GVariant) value = g_variant_dict_lookup_value (dict, key, NULL);
+ if (!value) {
+ g_debug ("Initialization parameter '%s' not set.", key);
+ return NULL;
+ }
+
+ if (!g_variant_is_of_type (value, type)) {
+ const char *type_name;
+ if (type == G_VARIANT_TYPE_BOOLEAN) {
+ type_name = "boolean";
+ } else if (type == G_VARIANT_TYPE_STRING_ARRAY) {
+ type_name = "array of strings";
+ } else {
+ type_name = g_variant_type_peek_string (type);
+ }
+ g_critical ("Initialization parameter '%s' is not a %s.", key, type_name);
+ return NULL;
+ }
+
+ return g_steal_pointer (&value);
+}
+
+static void
+param_boolean (GVariantDict *dict,
+ const char *key,
+ gboolean *result)
+{
+ g_autoptr(GVariant) value = param_lookup (dict, key, G_VARIANT_TYPE_BOOLEAN);
+ if (value) {
+ *result = g_variant_get_boolean (value);
+ g_debug ("Initialization parameter '%s' set to %s.",
+ key, *result ? "TRUE" : "FALSE");
+ }
+}
+
+static void
+param_strv (GVariantDict *dict,
+ const char *key,
+ GStrv *result)
+{
+ g_autoptr(GVariant) value = param_lookup (dict, key,
+ G_VARIANT_TYPE_STRING_ARRAY);
+ if (!value)
+ return;
+
+ size_t n_items = 0;
+ GStrv items = g_variant_dup_strv (value, &n_items);
+ g_debug ("Initialization parameter '%s' has %zu values.", key, n_items);
+ for (size_t i = 0; i < n_items; ++i)
+ g_debug (" - %s", items[i]);
+
+ *result = items;
+}
+
+static void
+param_strset (GVariantDict *dict,
+ const char *key,
+ GHashTable **result)
+{
+ g_autoptr(GVariant) value = param_lookup (dict, key,
+ G_VARIANT_TYPE_STRING_ARRAY);
+ if (!value)
+ return;
+
+ size_t n_items = 0;
+ GStrv items = g_variant_dup_strv (value, &n_items);
+ g_debug ("Initialization parameter '%s' has %zu values.", key, n_items);
+
+ GHashTable *h = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ g_free,
+ NULL);
+ for (size_t i = 0; i < n_items; ++i) {
+ char *item = g_steal_pointer (&items[i]);
+ const gboolean added = g_hash_table_add (h, item);
+ g_debug (" - %s%s", item, added ? "" : " (duplicate)");
+ }
+ *result = h;
+}
+
+static void
+params (GVariant *data)
+{
+ g_auto(GVariantDict) dict = G_VARIANT_DICT_INIT (data);
+ param_boolean (&dict, "loader", &s_module_loader);
+ param_strv (&dict, "paths", &s_module_paths);
+ param_strv (&dict, "preload", &s_module_preload);
+ param_strset (&dict, "whitelist", &s_module_whitelist);
+ param_strset (&dict, "blacklist", &s_module_blacklist);
+}
+
+static void
+on_window_object_cleared (WebKitScriptWorld *script_world,
+ WebKitWebPage *page,
+ WebKitFrame *frame)
+{
+ g_debug ("%s: script world @ %p, page @ %p, frame @ %p",
+ G_STRFUNC, script_world, page, frame);
+
+ g_autoptr(JSCContext) context =
+ webkit_frame_get_js_context_for_script_world (frame, script_world);
+
+ g_autoptr(KilnLoader) loader = kiln_loader_new (context);
+
+ for (unsigned i = 0; s_module_paths && s_module_paths[i]; ++i) {
+ kiln_loader_append_path (loader, s_module_paths[i]);
+ }
+
+ for (unsigned i = 0; s_module_preload && s_module_preload[i]; ++i) {
+ g_autoptr(GError) error = NULL;
+ if (!kiln_loader_open_module (loader, s_module_preload[i], &error)) {
+ g_critical ("Cannot preload '%s' module: %s.",
+ s_module_preload[i], error->message);
+ }
+ }
+}
+
+static void
+on_page_created (WebKitWebExtension *extension,
+ WebKitWebPage *page)
+{
+ WebKitFrame *frame = webkit_web_page_get_main_frame (page);
+ WebKitScriptWorld *script_world =
+ g_object_get_qdata (G_OBJECT (extension), SCRIPT_WORLD);
+ g_autoptr(JSCContext) context =
+ webkit_frame_get_js_context_for_script_world (frame, script_world);
+}
+
+G_MODULE_EXPORT
+void
+webkit_web_extension_initialize_with_user_data (WebKitWebExtension *extension,
+ GVariant *data)
+{
+ if (data) {
+ static const char *variant_type_string = "a{s*}";
+ g_assert (g_variant_type_string_is_valid (variant_type_string));
+ g_autoptr(GVariantType) variant_type =
+ g_variant_type_new (variant_type_string);
+
+ if (g_variant_is_of_type (data, variant_type)) {
+ params (data);
+ } else {
+ g_warning ("Initialization parameters have wrong type,"
+ " extension disabled.");
+ return;
+ }
+ }
+
+ g_autoptr(WebKitScriptWorld) script_world = webkit_script_world_new ();
+ g_debug ("Created script world '%s' [%p]",
+ webkit_script_world_get_name (script_world),
+ script_world);
+
+ g_signal_connect (script_world, "window-object-cleared",
+ G_CALLBACK (on_window_object_cleared),
+ NULL);
+
+ g_object_set_qdata_full (G_OBJECT (extension),
+ SCRIPT_WORLD,
+ g_steal_pointer (&script_world),
+ g_object_unref);
+
+ g_signal_connect (extension, "page-created",
+ G_CALLBACK (on_page_created), NULL);
+}
@@ 0,0 1,11 @@
+wkext_module = shared_module('kiln-webkit-' + opt_webkit_port,
+ 'extension.c',
+ c_args: [
+ '-DKILN_WKEXT_' + opt_webkit_port.to_upper(),
+ '-DG_LOG_DOMAIN="Kiln-WebKit"',
+ ],
+ dependencies: [
+ libkn_dep,
+ webkit_extension_dep,
+ ],
+)