WebKit2 and DomDocument/JavaScriptCore (Python3)

别等时光非礼了梦想. 提交于 2019-12-24 11:27:59

问题


I am converting a Python3 application to use WebKit2 instead of WebKit (which is no longer available in Debian Buster).

In the application the user can (de)select check boxes which I read from the Python3 application. In the original code I could simply get the DomDocument of the Webview and iterate through the child objects to return the value of the object with a given name (sample code below).

In WebKit2 the get_dom_document function is no longer available and the WebKit2 documentation is not clear on how to proceed.

Does anybody know how to get the DomDocument from a WebKit2 Webview or another way to iterate through all objects by name and get their value?

Sample code DomDocument from WebKit Webview:

def get_element_values(self, element_name):
    values = []
    doc = self.get_dom_document()
    elements = doc.get_elements_by_name(element_name)
    for i in range(elements.get_length()):
        child = elements.item(i)
        value = child.get_value().strip()
        if not child.get_checked():
            value = ''
        if value:
            values.append(value)
    return values

Note: "self" is the WebKit.Webview.

[EDIT]

I have been experimenting with this. First I tried to get the DomDocument by getting the WebPage object first. I found WebView.get_page_id() and WebKit2WebExtension.get_page(page_id) but unfortunately that gave me this error: AttributeError: 'gi.repository.WebKit2WebExtension' object has no attribute 'get_page'.

After that I tried JavaScriptCore and insert JavaScript with WebView.run_javascript() and WebView.run_javascript_finish(). I get a JavaScriptResult object back but when I try to get the value with .get_value() I get this error: TypeError: Couldn't find foreign struct converter for 'JavaScriptCore.Value'.

There was a user with the same issue here, but the proposed solution was too complicated for me as well.

Here's the code I used for testing (check the get_element_values() and javascript_finished() functions):

#! /usr/bin/env python3

# WebKit2 reference: https://webkitgtk.org/reference/webkit2gtk/stable
# Code examples: https://github.com/sidus-dev/poseidon/blob/master/poseidon.py

import gi
gi.require_version('WebKit2', '4.0')
gi.require_version('WebKit2WebExtension', '4.0')
from gi.repository import WebKit2, WebKit2WebExtension, Gtk
from os.path import exists
import webbrowser
import re


class SimpleBrowser(WebKit2.WebView):
    def __init__(self):
        WebKit2.WebView.__init__(self)

        # Get version
        self.webkit_ver = WebKit2.get_major_version(), WebKit2.get_minor_version(), WebKit2.get_micro_version()
        print(("WebKit2 Version: {0}".format('.'.join(map(str, self.webkit_ver)))))

        # Signals
        self.connect('decide-policy', self.on_decide_policy)
        #self.connect("load_changed", self.on_load_changed)
        self.connect("load-failed", self.on_load_failed)
        self.connect('button-press-event', lambda w, e: e.button == 3)

        # Settings
        s = self.get_settings()
        s.set_property('allow_file_access_from_file_urls', True)
        s.set_property('enable-spatial-navigation', False)
        s.set_property('enable_javascript', True)


    def show_html(self, html_or_url):
        if exists(html_or_url):
            matchObj = re.search('^file:\/\/', html_or_url)
            if not matchObj:
                html_or_url = "file://{0}".format(html_or_url)
        matchObj = re.search('^[a-z]+:\/\/', html_or_url)
        if matchObj:
            self.load_uri(html_or_url)
        else:
            self.load_html(html_or_url)
        self.show()

    def get_element_values(self, object, element_name):
        # JavaScript > dead end :(
        # https://webkitgtk.org/reference/webkit2gtk/stable/WebKitWebView.html#webkit-web-view-run-javascript
        js = 'var e = document.getElementsByName("' + element_name + '"); var r = []; var c = 0; ' \
             'for (var i = 0; i < e.length; i++) { if (e[i].checked) { r[c] = e[i].value; c++;} }'
        self.run_javascript(js, None, self.javascript_finished, None);
        return

        # DOM > dead end :(
        # https://webkitgtk.org/reference/webkit2gtk/stable/WebKitWebPage.html
        # https://webkitgtk.org/reference/webkit2gtk/stable/WebKitWebView.html#webkit-web-view-get-page-id
        page_id = self.get_page_id()
        print((page_id))
        # https://webkitgtk.org/reference/webkit2gtk/stable/WebKitWebExtension.html#webkit-web-extension-get-page
        # AttributeError: 'gi.repository.WebKit2WebExtension' object has no attribute 'get_page'
        web_page = WebKit2WebExtension.get_page(page_id)
        print((web_page))

    def javascript_finished(self, webview, result, user_data):
        # https://webkitgtk.org/reference/webkit2gtk/stable/WebKitWebView.html#webkit-web-view-run-javascript-finish
        js_result = self.run_javascript_finish(result)
        print((">>> js_result = %s" % str(js_result)))
        # TypeError: Couldn't find foreign struct converter for 'JavaScriptCore.Value'
        value = js_result.get_value()
        print((">>> value = %s" % str(value)))

    def on_decide_policy(self, webview, decision, decision_type):
        # User clicked on a <a href link: open uri in new tab or new default webview
        if (decision_type == WebKit2.PolicyDecisionType.NAVIGATION_ACTION):
            action = decision.get_navigation_action()
            action_type = action.get_navigation_type()
            if action_type == WebKit2.NavigationType.LINK_CLICKED:
                decision.ignore()
                uri = action.get_request().get_uri()
                # Open link in default browser
                webbrowser.open_new_tab(uri)
        else:
            if decision is not None:
                decision.use()

    def on_load_changed(webview, event):
        # TODO: get html of loaded page
        if event == WebKit2.LoadEvent.FINISHED:
            resource = webview.get_main_resource()
            resource.get_data()
            html = resource.get_data_finish(None)
            print(html)

    def on_load_failed(webview, event, url, error):
        print("Error loading", url, "-", error)

html = '<html><body style="background-color:#E6E6E6;"><h1>WebKit2 Test</h1>' \
       '<p><a href="https://solydxk.com">link</a></p><form onsubmit="return false;">' \
       '<input type="checkbox" name="chktst" value="checkbox1" checked /> CheckBox 1<br />' \
       '<input type="checkbox" name="chktst" value="checkbox2" /> CheckBox 2<br />' \
       '<button onclick="alert(show_values());">JS Show Values</button>' \
       '</form>' \
       '<script>' \
       'function show_values() {' \
       'var e = document.getElementsByName("chktst"); var r = []; var c = 0;' \
       'for (var i = 0; i < e.length; i++) {' \
       '    if (e[i].checked) { r[c] = e[i].value; c++;}' \
       '}return r;}' \
       '</script>' \
       '</body></html>'


win = Gtk.Window()
win.connect("delete-event", Gtk.main_quit)
win.set_default_size(600,400)

webview = SimpleBrowser()
webview.show_html(html)
box = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0)
button = Gtk.Button(label="Python Show Values")
button.connect("clicked", webview.get_element_values, 'chktst')

win.add(box)
box.pack_start(webview, True, True, 0)
box.pack_start(button, False, False, 1)

win.show_all()

Gtk.main() 

[EDIT2]

In Debian Stretch you need to install the packages from backports:

sudo apt-get install -t stretch-backports gir1.2-javascriptcoregtk-4.0 gir1.2-webkit2-4.0 libjavascriptcoregtk-4.0-18 libwebkit2gtk-4.0-37  libwebkit2gtk-4.0-37-gtk2

Now, WebKit2.JavascriptResult.get_js_value() will return a JavaScriptCore.Value object. I will test this further and post back an answer if I find one.

Check this bug report: https://bugs.webkit.org/show_bug.cgi?id=136989


回答1:


I found out that if I would return the getElementsByClassName object itself the following error was thrown:

GLib.Error: WebKitJavascriptError: (699)

Which only tells me it failed.

I could however let the JavaScript function return a string or array with values and convert that to a string in Python.

Not ideal, but it is workable for what I need it to do.

Here's the example code (check the comments for further information):

#! /usr/bin/env python3

# WebKit2 reference: https://webkitgtk.org/reference/webkit2gtk/stable

import gi
gi.require_version('Gtk', '3.0')
gi.require_version('WebKit2', '4.0')
from gi.repository import Gtk
from gi.repository import WebKit2
from gi.repository import GObject
from os.path import exists
import webbrowser
import re
import sys


class SimpleBrowser(WebKit2.WebView):
    # Create custom signals
    __gsignals__ = {
        "js-finished" : (GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE, ()),
        "html-response-finished" : (GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE, ())
    }

    def __init__(self):
        WebKit2.WebView.__init__(self)

        # Get version
        webkit_ver = WebKit2.get_major_version(), WebKit2.get_minor_version(), WebKit2.get_micro_version()
        print(("WebKit2 Version: {0}".format('.'.join(map(str, webkit_ver)))))
        if webkit_ver[0] < 2 or \
           webkit_ver[1] < 22:
               print(("ERROR: upgrade WebKit2 to version 2.22.x or higher"))
               sys.exit()

        # Store JS output
        self.js_value = None
        # Store html response
        self.html_response = None

        # WebKit2 Signals
        self.connect('decide-policy', self.on_decide_policy)
        self.connect("load_changed", self.on_load_changed)
        self.connect("load-failed", self.on_load_failed)
        self.connect('button-press-event', lambda w, e: e.button == 3)

        # Settings
        s = self.get_settings()
        s.set_property('allow_file_access_from_file_urls', True)
        s.set_property('enable-spatial-navigation', False)
        s.set_property('enable_javascript', True)

    def show_html(self, html_or_url):
        if exists(html_or_url):
            matchObj = re.search('^file:\/\/', html_or_url)
            if not matchObj:
                html_or_url = "file://{0}".format(html_or_url)
        matchObj = re.search('^[a-z]+:\/\/', html_or_url)
        if matchObj:
            self.load_uri(html_or_url)
        else:
            self.load_html(html_or_url)
        self.show()

    def js_run(self, function_name, js_return=True):
        # JavaScript
        # https://webkitgtk.org/reference/webkit2gtk/stable/WebKitWebView.html#webkit-web-view-run-javascript
        run_js_finish = self._js_finish if js_return else None
        self.run_javascript(function_name, None, run_js_finish, None);

    def _js_finish(self, webview, result, user_data=None):
        # https://webkitgtk.org/reference/webkit2gtk/stable/WebKitWebView.html#webkit-web-view-run-javascript-finish
        js_result = self.run_javascript_finish(result)
        if js_result is not None:
            # https://webkitgtk.org/reference/jsc-glib/stable/JSCValue.html

            # TypeError: Couldn't find foreign struct converter for 'JavaScriptCore.Value':
            # Make sure you have WebKit2 2.22.x or higher installed.
            # For Debian Stretch you need the backports packages:
            # apt install -t stretch-backports gir1.2-javascriptcoregtk-4.0 gir1.2-webkit2-4.0 libjavascriptcoregtk-4.0-18 libwebkit2gtk-4.0-37  libwebkit2gtk-4.0-37-gtk2

            # Couldn't handle anything but string :(
            # If returning the getElementsByClassName object itself: GLib.Error: WebKitJavascriptError:  (699)
            value = js_result.get_js_value()
            self.js_value = value.to_string()
            #print((self.js_value))
            self.emit('js-finished')

    def on_decide_policy(self, webview, decision, decision_type):
        # User clicked on a <a href link: open uri in new tab or new default webview
        if (decision_type == WebKit2.PolicyDecisionType.NAVIGATION_ACTION):
            action = decision.get_navigation_action()
            action_type = action.get_navigation_type()
            if action_type == WebKit2.NavigationType.LINK_CLICKED:
                decision.ignore()
                uri = action.get_request().get_uri()
                # Open link in default browser
                webbrowser.open_new_tab(uri)
        else:
            if decision is not None:
                decision.use()

    def on_load_changed(self, webview, event):
        # Get html of loaded page
        if event == WebKit2.LoadEvent.FINISHED:
            resource = webview.get_main_resource()
            resource.get_data(None, self._get_response_data_finish, None)

    def  _get_response_data_finish(self, resource, result, user_data=None):
        self.html_response = resource.get_data_finish(result)
        self.emit('html-response-finished')

    def on_load_failed(webview, event, url, error):
        print("Error loading", url, "-", error)

html = '<html>' \
       '<script>' \
       'function get_checked_values(class_name) {' \
       'var e = document.getElementsByClassName(class_name); var r = []; var c = 0;' \
       'if (e.length == 0) { e = document.getElementsById(class_name); }' \
       'for (var i = 0; i < e.length; i++) {' \
       '    if (e[i].checked) { r[c] = e[i].value; c++;}' \
       '}return r;}' \
       '</script>' \
       '</head><body style="background-color:#E6E6E6;"><h1>WebKit2 JavaScript Test</h1>' \
       '<p><a href="https://solydxk.com">link</a></p><form onsubmit="return false;">' \
       '<input type="checkbox" class="chktst" value="checkbox1" checked /> CheckBox 1<br />' \
       '<input type="checkbox" class="chktst" value="checkbox2" /> CheckBox 2<br />' \
       '</form></body></html>'

# Create test window
win = Gtk.Window()
webview = SimpleBrowser()
win.set_default_size(600,400)
button = Gtk.Button(label="Python Show Checked Values")
box = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0)
box.pack_start(webview, True, True, 0)
box.pack_start(button, False, False, 1)
win.add(box)

# Functions
def get_element_values(self, element_name):
    webview.js_run('get_checked_values("{0}")'.format(element_name))

def show_js_value(object):
    print((webview.js_value))

def show_html_response(object):
    print((webview.html_response))

# Signals
win.connect("delete-event", Gtk.main_quit)
button.connect("clicked", get_element_values, 'chktst')
webview.connect("js-finished", show_js_value)
#webview.connect("html-response-finished", show_html_response)

# Load html and show window
webview.show_html(html)
win.show_all()

Gtk.main() 


来源:https://stackoverflow.com/questions/52870923/webkit2-and-domdocument-javascriptcore-python3

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!