forked from Mirrors/apostrophe
135 lines
4.3 KiB
Python
135 lines
4.3 KiB
Python
import gi
|
|
|
|
gi.require_version('Gtk', '3.0')
|
|
gi.require_version('WebKit2', '4.0')
|
|
from gi.repository import WebKit2, GLib
|
|
|
|
|
|
class WebViewScroller:
|
|
"""Provides read/write scrolling functionality to a WebView.
|
|
|
|
It does so using JavaScript, by continuously monitoring it while loaded and focused.
|
|
The alternative is using a WebExtension and C-bindings (see reference), but that is more
|
|
complicated implementation-wise, and build-wise, at least we start building with Meson.
|
|
|
|
Reference: https://github.com/aperezdc/webkit2gtk-python-webextension-example
|
|
"""
|
|
|
|
SETUP_SROLL_SCALE_JS = """
|
|
const e = document.documentElement;
|
|
function get_scroll_scale() {
|
|
if (document.readyState !== "complete" || e.clientHeight === 0) {
|
|
return null;
|
|
} else if (e.scrollHeight <= e.clientHeight) {
|
|
return 0;
|
|
} else {
|
|
return e.scrollTop / (e.scrollHeight - e.clientHeight);
|
|
}
|
|
}
|
|
function set_scroll_scale(scale) {
|
|
if (document.readyState !== "complete") {
|
|
window.addEventListener("load", function() { set_scroll_scale(scale); });
|
|
} else if (e.clientHeight === 0) {
|
|
window.addEventListener("resize", function() { set_scroll_scale(scale); });
|
|
} else if (e.scrollHeight > e.clientHeight) {
|
|
e.scrollTop = (e.scrollHeight - e.clientHeight) * scale;
|
|
}
|
|
return get_scroll_scale();
|
|
}
|
|
get_scroll_scale();
|
|
""".strip()
|
|
|
|
GET_SCROLL_SCALE_JS = "get_scroll_scale();"
|
|
|
|
SET_SCROLL_SCALE_JS = "set_scroll_scale({:.16f});"
|
|
|
|
def __init__(self, webview):
|
|
super().__init__()
|
|
|
|
self.webview = webview
|
|
|
|
self.webview.connect("focus-in-event", self.on_focus_changed)
|
|
self.webview.connect("focus-out-event", self.on_focus_changed)
|
|
self.webview.connect("load-changed", self.on_load_changed)
|
|
self.webview.connect("destroy", self.on_destroy)
|
|
|
|
self.scroll_scale = 0
|
|
|
|
self.state_loaded = False
|
|
self.state_setup = False
|
|
self.state_focused = True
|
|
self.state_dirty = False
|
|
self.state_waiting = False
|
|
|
|
self.timeout_id = None
|
|
|
|
def get_scroll_scale(self):
|
|
return self.scroll_scale
|
|
|
|
def set_scroll_scale(self, scale):
|
|
self.scroll_scale = scale
|
|
self.state_dirty = True
|
|
self.state_loop()
|
|
|
|
def on_focus_changed(self, _webview, event):
|
|
self.state_focused = event.in_
|
|
self.state_loop()
|
|
|
|
def on_load_changed(self, _webview, event):
|
|
self.state_loaded = event == WebKit2.LoadEvent.FINISHED
|
|
self.state_loop()
|
|
|
|
def on_destroy(self, _widget):
|
|
self.state_loaded = False
|
|
self.state_focused = False
|
|
self.state_loop()
|
|
self.webview = None
|
|
|
|
def setup_scroll_state(self):
|
|
self.state_waiting = True
|
|
self.state_setup = True
|
|
self.webview.run_javascript(
|
|
self.SETUP_SROLL_SCALE_JS, None, self.sync_scroll_scale)
|
|
|
|
def read_scroll_scale(self):
|
|
self.state_waiting = True
|
|
self.webview.run_javascript(
|
|
self.GET_SCROLL_SCALE_JS, None, self.sync_scroll_scale)
|
|
|
|
def write_scroll_scale(self):
|
|
self.state_waiting = True
|
|
self.state_dirty = False
|
|
self.webview.run_javascript(
|
|
self.SET_SCROLL_SCALE_JS.format(self.scroll_scale), None, self.sync_scroll_scale)
|
|
|
|
def sync_scroll_scale(self, _webview, result):
|
|
self.state_waiting = False
|
|
result = self.webview.run_javascript_finish(result)
|
|
self.state_loop(result.get_js_value().to_double())
|
|
|
|
def state_loop(self, scroll_scale=None, read_delay=500):
|
|
# Remove any pending callbacks
|
|
if self.timeout_id:
|
|
GLib.source_remove(self.timeout_id)
|
|
self.timeout_id = None
|
|
|
|
# Set scroll scale if specified, and the state is not dirty
|
|
if scroll_scale is not None and not self.state_dirty:
|
|
self.scroll_scale = scroll_scale
|
|
|
|
# Handle the current state
|
|
if self.state_waiting:
|
|
return
|
|
elif not self.state_loaded:
|
|
return
|
|
elif not self.state_setup:
|
|
self.setup_scroll_state()
|
|
elif self.state_dirty:
|
|
self.write_scroll_scale()
|
|
elif self.state_focused:
|
|
if read_delay > 0:
|
|
self.timeout_id = GLib.timeout_add(read_delay, self.state_loop, None, 0)
|
|
else:
|
|
self.read_scroll_scale()
|
|
|