Connect WebKit WebView form to a Python callback?
I am writing a small Python and WebKit app; I am using WebKit as the UI for my app.
What I want to do is to create an interactive element in WebKit (namely a combo box or a set of clickable regions) and when the user interacts with these elements I can call a Python callback to do some processing and then update the WebKit view with new information.
Here is an example of what I want to do:
- User is presented with a combo box of options (combo box displayed, I assume, using a HTML form in WebKit).
- When the combo box option is selected,
on_combo_selected()
is called in my Python script which then grabs some data. - The data is passed to WebKit and the view is updated.
How can I do this?
Ways to communicate from an embedded WebKit widget to the controlling Python program
Gtk or Qt:
- set
window.status
from JavaScript; trap the corresponding event in Python - set
document.title
from JavaScript; trap the corresponding event in Python - redirect the page to a custom URL (say,
x-my-app:thing1/thing2/thing3
); trapnavigation-policy-decision-requested
event in Python and look at the URL that's being navigated to, and deal with it if it's your custom URL scheme
Qt only:
- use
frame.addToJavaScriptWindowObject
to add a (specifically designed) Python object to the JavaScript global namespace; call methods on it from JavaScript. (See http://pysnippet.blogspot.com/2010/01/calling-python-from-javascript-in-pyqts.html for an example.)
Methods which theoretically should work but in practice don't, very well
- Fire a custom DOM event on an HTML element (which would include the document object) from JavaScript; trap that event in Python by navigating the WebKit DOM from Python and listening for events. This works, but I can't find a way of having Python be able to read custom data from the event, which means that you can't pass information other than the event having fired.
Ways to communicate from Python to JavaScript
- use
webview.execute_script(js_code)
. Note that if you're passing variable values in with the JS, it's a good idea to JSON encode them; that way you can worry a bit less about escaping, and JS can read JSON natively
Here's some example Gtk code:
from gi.repository import Gtk,WebKit
import json
w = Gtk.Window()
v = WebKit.WebView()
sw = Gtk.ScrolledWindow()
w.add(sw)
sw.add(v)
w.set_size_request(400,300)
w.connect("destroy", lambda q: Gtk.main_quit())
def window_title_change(v, param):
if not v.get_title():
return
if v.get_title().startswith("msgtopython:::"):
message = v.get_title().split(":::",1)[1]
# Now, send a message back to JavaScript
return_message = "You chose '%s'. How interesting." % message
v.execute_script("jscallback(%s)" % json.dumps(return_message))
v.connect("notify::title", window_title_change)
v.load_html_string("""<!doctype html>
<html>
<head>
<title>A demo</title>
<style>
body { font-family: Ubuntu, sans-serif; }
h1 { font-size: 1.3em; }
</style>
<body>
<h1>A tiny JavaScript demonstration</h1>
<form>
<p>What's your favourite thing about Jono? <select>
<option>------choose one------</option>
<option>his beard</option>
<option>his infectious sense of humour</option>
<option>his infectious diseases</option>
<option>his guitar ability</option>
<option>his wife</option>
</select></p>
</form>
<p id="out"></p>
<script>
document.querySelector("select").addEventListener("change", function() {
var chosenOption = this.options[this.selectedIndex].text;
// Now send that text back to Python by setting the title
document.title = "msgtopython:::" + chosenOption;
}, false);
function jscallback(msg) {
document.getElementById("out").innerHTML = msg;
}
</script>
</body>
</html>
""", "file:///")
w.show_all()
Gtk.main()
It's been a while since I played with that but here's what I did:
Use something like
<form>
<select onchange="location.href='mycombo://'+this.value">
<option>foo</option>
<option>bar</option>
<option>baz</option>
</select>
</form>
in your HTML. So when the user selects an item a mycombo
-URI with the selected value is called. To catch this connect a handler to your WebView's navigation-requested
signal:
self.webview.connect("navigation-requested", self.on_navigation_requested)
In your signal handler you check if the request is for a mycombo
URI. If so call on_combo_selected
:
def on_navigation_requested(self, view, frame, req, data=None):
uri = req.get_uri()
scheme, path=uri.split(':', 1)
if scheme == 'mycombo':
self.on_combo_selected(path)
return True
else:
return False
To update your WebView use its execute_script
function to run some JavaScript code that does the updates, like:
self.webview.execute_script("document.title='Updated!';")