~aperezdc/kiln

f7a3ce5126b2d76b8af67ee75ba6786edafd914a — Adrian Perez de Castro 4 years ago 0324345
wip: WebKit extension
4 files changed, 242 insertions(+), 0 deletions(-)

M doc/sitemap.txt
A doc/webkit-extension.md
A wkext/extension.c
M wkext/meson.build
M doc/sitemap.txt => doc/sitemap.txt +1 -0
@@ 2,4 2,5 @@ index.md
	building.md
	writing-modules.md
	c-index
	webkit-extension.md
	jscbuiltin.md

A doc/webkit-extension.md => doc/webkit-extension.md +23 -0
@@ 0,0 1,23 @@
WebKit Extension
================


Initialization Parameters
-------------------------

The extension supports a number of parameters which can be passed in a
`GVariant` containing a dictionary with string keys, using
`webkit_web_context_set_web_extensions_initialization_user_data()`.
The following are recognized:

| Key | Type | Contents |
|-----|------|----------|
| `loader`    | `b`  | Expose the module loader to JavaScript. Disabled by default. |
| `paths`     | `as` | Array of file system paths used for module lookup. |
| `whitelist` | `as` | Array of module names to allow loading. |
| `blacklist` | `as` | Array of module names to block from loading. |
| `preload`   | `as` | Array of module names to preload. |


User Messages
-------------

A wkext/extension.c => wkext/extension.c +207 -0
@@ 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);
}

M wkext/meson.build => wkext/meson.build +11 -0
@@ 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,
	],
)