Skip to content

Commit

Permalink
Add missing plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
bellegarde-c committed Feb 6, 2017
1 parent d7197bb commit 59c5e7c
Show file tree
Hide file tree
Showing 5 changed files with 311 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
*~
/python-webextension/*.o
/python-webextension/*.so
/python-webextension/*.py
/eolie-cli
/po
/help
Expand Down
31 changes: 31 additions & 0 deletions python-webextension/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#
# Makefile
# Adrian Perez, 2015-08-25 11:58
#

CFLAGS ?= -Os -Wall

PYTHON ?= python3
PKG_MODULES := pygobject-3.0 webkit2gtk-web-extension-4.0 ${PYTHON}
WEB_EXT_FLAGS := $(shell pkg-config ${PKG_MODULES} --cflags)
WEB_EXT_LIBS := $(shell pkg-config ${PKG_MODULES} --libs)

CPPFLAGS += ${WEB_EXT_FLAGS}
LDLIBS += ${WEB_EXT_LIBS}

all: pythonloader.so

pythonloader.so: pythonloader.o
${LD} ${LDFLAGS} -fPIC -shared -o $@ $^ ${LDLIBS}
pythonloader.so: CFLAGS += -fPIC

install:
/bin/true
uninstall:
/bin/true

clean:
${RM} pythonloader.o pythonloader.so

# vim:ft=make
#
50 changes: 50 additions & 0 deletions python-webextension/README
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
WebKit2GTK+ Python WebExtension loader
======================================

This is some exploratory code towards finding a good solution which can be
shipped built-in to allow
[loading of Python extensions in WebKit2GTK+](https://bugs.webkit.org/show_bug.cgi?id=140745)


How does it work?
-----------------

The [pythonloader.c](pythonloader.c) file contains a small WebExtension
written in C, which is a thin shim that will:

1. Initialize an embedded Python interpreter.
2. Import the `gi.repository.WebKit2WebExtension` module, to ensure that the
types used to implement WebExtensions are registered into PyGObject.
3. Import the `extension` Python module ([extension.py](extension.py)).
4. Invoke the `extension.initialize()` function, passing as arguments a
[WebKitWebExtension](http://webkitgtk.org/reference/webkit2gtk/stable/WebKitWebExtension.html)
instance, and a
[GVariant](https://developer.gnome.org/glib/stable/glib-GVariant.html)
which contains the additional the value previously set with
[webkit_web_context_set_web_extensions_initialization_user_data()](http://webkitgtk.org/reference/webkit2gtk/stable/WebKitWebContext.html#webkit-web-context-set-web-extensions-initialization-user-data).

The Python extension can use all the functionality exposed via
GObject-Introspection, except for the `WebKit`, and `WebKit2` modules (web
extensions run in a process separate from the normal WebKit library, and using
them is unsupported — and will most likely crash your program). In particular,
the following modules included with WebKit2GTK+ can be used:

* [Web Extensions](http://webkitgtk.org/reference/webkit2gtk/stable/ch02.html)
* [DOM bindings](http://webkitgtk.org/reference/webkitdomgtk/stable/index.html)

Any other module exposed by GObject-Introspection can be used, as long as they
do not use the `WebKit`, or `WebKit2` modules.


Trying it out
-------------

You will need the following components installed, including their development
headers and libraries:

* WebKit2GTK+, version 2.4, or newer.
* Python 3.2, or newer.
* A working PyGObject installation.
* GNU Make.
* `pkg-config`

66 changes: 66 additions & 0 deletions python-webextension/extension.py.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Copyright (c) 2014-2016 Cedric Bellegarde <[email protected]>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program 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 General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

import sys
# Make sure we'll find the pygobject module, even in JHBuild
sys.path.insert(1, '@pyexecdir@')
# Make sure we'll find the eolie modules, even in JHBuild
sys.path.insert(1, '@pythondir@')

from gi.repository import Gio

from eolie.settings import Settings
from eolie.database_adblock import DatabaseAdblock
from eolie.sqlcursor import SqlCursor


class Application(Gio.Application):
def new():
"""
Return a new Settings object
"""
app = Gio.Application.new(None, Gio.ApplicationFlags.IS_SERVICE)
app.__class__ = Application
app.cursors = {}
return app

app = Application.new()
settings = Settings.new()
adblock = DatabaseAdblock()


def on_send_request(webpage, request, redirect):
"""
Filter based on adblock db
@param webpage as WebKit2WebExtension.WebPage
@param request as WebKitURIRequest
@param redirect as WebKit2WebExtension.URIResponse
"""
uri = request.get_uri()
if settings.get_value('adblock') and adblock.is_blocked(uri):
return True

def on_page_created(extension, webpage):
"""
Connect to send request
@param extension as WebKit2WebExtension
@param webpage as WebKit2WebExtension.WebPage
"""
webpage.connect("send-request", on_send_request)


def initialize(extension, arguments):
"""
Connect to page created
@param extension as WebKit2WebExtension
"""
extension.connect("page-created", on_page_created)
161 changes: 161 additions & 0 deletions python-webextension/pythonloader.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
/*
* pythonloader.c
* Copyright (C) 2015-2016 Adrian Perez <[email protected]>
* Copyright (C) 2016 Nathan Hoad <[email protected]>
*
* Distributed under terms of the MIT license.
*/

#include <webkit2/webkit-web-extension.h>
#include <pygobject.h>
#include <stdarg.h>

/*
* XXX: Hacky workaround ahead!
*
* PyGObject internally uses _pygi_struct_new_from_g_type() to wrap
* GVariant instances into a gi.Struct, but the function is not in the
* public API. Instead, we temporarily wrap the GVariant into a GValue
* (ugh!), and use pyg_value_as_pyobject(), which in turn calls function
* pygi_value_to_py_structured_type() —also private—, and finally that
* in turn calls _pygi_struct_new_from_g_type() as initially desired.
*/
static PyObject*
g_variant_to_pyobject (GVariant *variant)
{
GValue value = { 0, };
g_value_init (&value, G_TYPE_VARIANT);
g_value_set_variant (&value, variant);
return pyg_value_as_pyobject (&value, FALSE);
}

#define py_auto __attribute__((cleanup(py_object_cleanup)))

static void
py_object_cleanup(void *ptr)
{
PyObject **py_obj_location = ptr;
if (py_obj_location) {
Py_DECREF (*py_obj_location);
*py_obj_location = NULL;
}
}


#define PY_CHECK_ACT(expr, act, err_fmt, ...) \
do { \
if (!(expr)) { \
g_printerr (err_fmt, ##__VA_ARGS__); \
if (PyErr_Occurred ()) { \
g_printerr (": "); \
PyErr_Print (); \
} else { \
g_printerr (" (no error given)"); \
} \
act; \
} \
} while (0)

#define PY_CHECK(expr, err_fmt, ...) \
PY_CHECK_ACT (expr, return, err_fmt, ##__VA_ARGS__)


/* This would be "extension.py" from the source directory. */
static const char *extension_name = "extension";


static gboolean
pygi_require (const gchar *module, ...)
{
PyObject py_auto *gi_module = PyImport_ImportModule ("gi");
PY_CHECK_ACT (gi_module, return FALSE, "Could not import 'gi'");

PyObject py_auto *func = PyObject_GetAttrString (gi_module, "require_version");
PY_CHECK_ACT (func, return FALSE,
"Could not obtain 'gi.require_version'");
PY_CHECK_ACT (PyCallable_Check (func), return FALSE,
"Object 'gi.require_version' is not callable");

gboolean result = TRUE;
va_list arglist;
va_start (arglist, module);
while (module) {
/*
* For each module and version, call: gi.require(module, version)
*/
const gchar *version = va_arg (arglist, const gchar*);
{
PyObject py_auto *args = Py_BuildValue ("(ss)", module, version);
PyObject py_auto *rval = PyObject_CallObject (func, args);
PY_CHECK_ACT (rval, result = FALSE; break,
"Error calling 'gi.require_version(\"%s\", \"%s\")'",
module, version);
}
module = va_arg (arglist, const gchar*);
}
va_end (arglist);
return result;
}


G_MODULE_EXPORT void
webkit_web_extension_initialize_with_user_data (WebKitWebExtension *extension,
GVariant *user_data)
{
Py_Initialize ();

#if PY_VERSION_HEX < 0x03000000
const char *argv[] = { "", NULL };
#else
wchar_t *argv[] = { L"", NULL };
#endif

PySys_SetArgvEx (1, argv, 0);

pygobject_init (-1, -1, -1);
if (PyErr_Occurred ()) {
g_printerr ("Could not initialize PyGObject");
return;
}

pyg_enable_threads ();
PyEval_InitThreads ();

if (!pygi_require ("GLib", "2.0",
"WebKit2WebExtension", "4.0",
NULL))
return;

PyObject py_auto *web_ext_module =
PyImport_ImportModule ("gi.repository.WebKit2WebExtension");
PY_CHECK (web_ext_module,
"Could not import 'gi.repository.WebKit2WebExtension'");

/*
* TODO: Instead of assuming that the Python import path contains the
* directory where the extension is, manually load and compile the
* extension code, then use PyImport_AddModule() to programmatically
* create a new module and PyImport_ExecCodeModule() to import it
* from a bytecode object.
*/
PyObject py_auto *py_filename = PyUnicode_FromString (extension_name);
PyObject py_auto *py_module = PyImport_Import (py_filename);
PY_CHECK (py_module, "Could not import '%s'", extension_name);

PyObject py_auto *py_func = PyObject_GetAttrString (py_module, "initialize");
PY_CHECK (py_func, "Could not obtain '%s.initialize'", extension_name);
PY_CHECK (PyCallable_Check (py_func),
"Object '%s.initialize' is not callable", extension_name);

PyObject py_auto *py_extension = pygobject_new (G_OBJECT (extension));
PyObject py_auto *py_extra_args = g_variant_to_pyobject (user_data);

PY_CHECK (py_extra_args, "Cannot create GLib.Variant");

PyObject py_auto py_auto *func_args = PyTuple_New (2);
PyTuple_SetItem (func_args, 0, py_extension);
PyTuple_SetItem (func_args, 1, py_extra_args);

PyObject py_auto *py_retval = PyObject_CallObject (py_func, func_args);
PY_CHECK (py_retval, "Error calling '%s.initialize'", extension_name);
}

0 comments on commit 59c5e7c

Please sign in to comment.