-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
d7197bb
commit 59c5e7c
Showing
5 changed files
with
311 additions
and
0 deletions.
There are no files selected for viewing
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
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 | ||
|
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
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 | ||
# |
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
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` | ||
|
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
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) |
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
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); | ||
} |