Modularize javascript code

We now load the JS code as a QWebEngineScript, which sets up
window._qutebrowser with various "modules". That means we don't have to
pass the whole module every time we want to execute something.
This commit is contained in:
Florian Bruhin 2016-08-09 12:44:13 +02:00
parent 00673ef7da
commit 6b7a39685e
5 changed files with 139 additions and 108 deletions

View File

@ -33,7 +33,7 @@ from PyQt5.QtWebEngineWidgets import QWebEnginePage, QWebEngineScript
from qutebrowser.browser import browsertab
from qutebrowser.browser.webengine import webview, webengineelem
from qutebrowser.utils import usertypes, qtutils, log, javascript
from qutebrowser.utils import usertypes, qtutils, log, javascript, utils
class WebEnginePrinting(browsertab.AbstractPrinting):
@ -191,11 +191,9 @@ class WebEngineScroller(browsertab.AbstractScroller):
super()._init_widget(widget)
page = widget.page()
try:
page.scrollPositionChanged.connect(
self._on_scroll_pos_changed)
page.scrollPositionChanged.connect(self._update_pos)
except AttributeError:
log.stub('scrollPositionChanged, on Qt < 5.7')
self._on_scroll_pos_changed()
def _key_press(self, key, count=1):
# FIXME:qtwebengine Abort scrolling if the minimum/maximum was reached.
@ -209,9 +207,9 @@ class WebEngineScroller(browsertab.AbstractScroller):
QApplication.postEvent(recipient, release_evt)
@pyqtSlot()
def _on_scroll_pos_changed(self):
def _update_pos(self):
"""Update the scroll position attributes when it changed."""
def update_scroll_pos(jsret):
def update_pos_cb(jsret):
"""Callback after getting scroll position via JS."""
if jsret is None:
# This can happen when the callback would get called after
@ -222,8 +220,8 @@ class WebEngineScroller(browsertab.AbstractScroller):
self._pos_px = QPoint(jsret['px']['x'], jsret['px']['y'])
self.perc_changed.emit(*self._pos_perc)
js_code = javascript.assemble('scroll', 'scroll_pos')
self._tab.run_js_async(js_code, update_scroll_pos)
js_code = javascript.assemble('scroll', 'pos')
self._tab.run_js_async(js_code, update_pos_cb)
def pos_px(self):
return self._pos_px
@ -232,7 +230,7 @@ class WebEngineScroller(browsertab.AbstractScroller):
return self._pos_perc
def to_perc(self, x=None, y=None):
js_code = javascript.assemble('scroll', 'scroll_to_perc', x, y)
js_code = javascript.assemble('scroll', 'to_perc', x, y)
self._tab.run_js_async(js_code)
def to_point(self, point):
@ -243,7 +241,7 @@ class WebEngineScroller(browsertab.AbstractScroller):
self._tab.run_js_async("window.scrollBy({x}, {y});".format(x=x, y=y))
def delta_page(self, x=0, y=0):
js_code = javascript.assemble('scroll', 'scroll_delta_page', x, y)
js_code = javascript.assemble('scroll', 'delta_page', x, y)
self._tab.run_js_async(js_code)
def up(self, count=1):
@ -334,6 +332,31 @@ class WebEngineTab(browsertab.AbstractTab):
self._set_widget(widget)
self._connect_signals()
self.backend = usertypes.Backend.QtWebEngine
# init js stuff
self._init_js()
def _init_js(self):
js_code = '\n'.join([
'"use strict";',
'window._qutebrowser = {};',
utils.read_file('javascript/scroll.js'),
utils.read_file('javascript/webelem.js'),
])
script = QWebEngineScript()
script.setInjectionPoint(QWebEngineScript.DocumentCreation)
page = self._widget.page()
script.setSourceCode(js_code)
try:
page.runJavaScript("", QWebEngineScript.ApplicationWorld)
except TypeError:
# We're unable to pass a world to runJavaScript
script.setWorldId(QWebEngineScript.MainWorld)
else:
script.setWorldId(QWebEngineScript.ApplicationWorld)
# FIXME:qtwebengine what about runsOnSubFrames?
page.scripts().insert(script)
def openurl(self, url):
self._openurl_prepare(url)
@ -427,7 +450,7 @@ class WebEngineTab(browsertab.AbstractTab):
callback(elems)
def find_all_elements(self, selector, callback, *, only_visible=False):
js_code = javascript.assemble('webelem', 'find_all_elements', selector)
js_code = javascript.assemble('webelem', 'find_all', selector)
js_cb = functools.partial(self._find_all_elements_js_cb, callback)
self.run_js_async(js_code, js_cb)

View File

@ -33,7 +33,3 @@ rules:
no-undefined: "off"
wrap-iife: ["error", "inside"]
func-names: "off"
# FIXME turn these on again after using a _qutebrowser object
no-unused-vars: "off"
no-implicit-globals: "off"

View File

@ -19,51 +19,57 @@
"use strict";
function _qutebrowser_scroll_to_perc(x, y) {
var elem = document.documentElement;
var x_px = window.scrollX;
var y_px = window.scrollY;
window._qutebrowser.scroll = (function() {
var funcs = {};
if (x !== undefined) {
x_px = (elem.scrollWidth - elem.clientWidth) / 100 * x;
}
funcs.to_perc = function(x, y) {
var elem = document.documentElement;
var x_px = window.scrollX;
var y_px = window.scrollY;
if (y !== undefined) {
y_px = (elem.scrollHeight - elem.clientHeight) / 100 * y;
}
if (x !== undefined) {
x_px = (elem.scrollWidth - elem.clientWidth) / 100 * x;
}
window.scroll(x_px, y_px);
}
if (y !== undefined) {
y_px = (elem.scrollHeight - elem.clientHeight) / 100 * y;
}
function _qutebrowser_scroll_delta_page(x, y) {
var dx = document.documentElement.clientWidth * x;
var dy = document.documentElement.clientHeight * y;
window.scrollBy(dx, dy);
}
function _qutebrowser_scroll_pos() {
var elem = document.documentElement;
var dx = elem.scrollWidth - elem.clientWidth;
var dy = elem.scrollHeight - elem.clientHeight;
var perc_x, perc_y;
if (dx === 0) {
perc_x = 0;
} else {
perc_x = 100 / dx * window.scrollX;
}
if (dy === 0) {
perc_y = 0;
} else {
perc_y = 100 / dy * window.scrollY;
}
var pos = {
"perc": {"x": perc_x, "y": perc_y},
"px": {"x": window.scrollX, "y": window.scrollY},
window.scroll(x_px, y_px);
};
// console.log(JSON.stringify(pos));
return pos;
}
funcs.delta_page = function(x, y) {
var dx = document.documentElement.clientWidth * x;
var dy = document.documentElement.clientHeight * y;
window.scrollBy(dx, dy);
};
funcs.pos = function() {
var elem = document.documentElement;
var dx = elem.scrollWidth - elem.clientWidth;
var dy = elem.scrollHeight - elem.clientHeight;
var perc_x, perc_y;
if (dx === 0) {
perc_x = 0;
} else {
perc_x = 100 / dx * window.scrollX;
}
if (dy === 0) {
perc_y = 0;
} else {
perc_y = 100 / dy * window.scrollY;
}
var pos = {
"perc": {"x": perc_x, "y": perc_y},
"px": {"x": window.scrollX, "y": window.scrollY},
};
// console.log(JSON.stringify(pos));
return pos;
};
return funcs;
})();

View File

@ -19,60 +19,62 @@
"use strict";
document._qutebrowser_elements = [];
window._qutebrowser.webelem = (function() {
var funcs = {};
var elements = [];
function serialize_elem(elem, id) {
var out = {
"id": id,
"text": elem.text,
"tag_name": elem.tagName,
"outer_xml": elem.outerHTML,
};
function _qutebrowser_serialize_elem(elem, id) {
var out = {
"id": id,
"text": elem.text,
"tag_name": elem.tagName,
"outer_xml": elem.outerHTML,
var attributes = {};
for (var i = 0; i < elem.attributes.length; ++i) {
var attr = elem.attributes[i];
attributes[attr.name] = attr.value;
}
out.attributes = attributes;
// console.log(JSON.stringify(out));
return out;
}
funcs.find_all = function(selector) {
var elems = document.querySelectorAll(selector);
var out = [];
var id = elements.length;
for (var i = 0; i < elems.length; ++i) {
var elem = elems[i];
out.push(serialize_elem(elem, id));
elements[id] = elem;
id++;
}
return out;
};
var attributes = {};
for (var i = 0; i < elem.attributes.length; ++i) {
var attr = elem.attributes[i];
attributes[attr.name] = attr.value;
}
out.attributes = attributes;
funcs.focus_element = function() {
var elem = document.activeElement;
// console.log(JSON.stringify(out));
if (!elem || elem === document.body) {
// "When there is no selection, the active element is the page's
// <body> or null."
return null;
}
return out;
}
var id = elements.length;
return serialize_elem(elem, id);
};
function _qutebrowser_find_all_elements(selector) {
var elems = document.querySelectorAll(selector);
var out = [];
var id = document._qutebrowser_elements.length;
funcs.get_element = function(id) {
return elements[id];
};
for (var i = 0; i < elems.length; ++i) {
var elem = elems[i];
out.push(_qutebrowser_serialize_elem(elem, id));
document._qutebrowser_elements[id] = elem;
id++;
}
return out;
}
function _qutebrowser_focus_element() {
var elem = document.activeElement;
if (!elem || elem === document.body) {
// "When there is no selection, the active element is the page's <body>
// or null."
return null;
}
var id = document._qutebrowser_elements.length;
return _qutebrowser_serialize_elem(elem, id);
}
function _qutebrowser_get_element(id) {
return document._qutebrowser_elements[id];
}
return funcs;
})();

View File

@ -19,6 +19,7 @@
"""Utilities related to javascript interaction."""
import textwrap
from qutebrowser.utils import utils
@ -62,10 +63,13 @@ def _convert_js_arg(arg):
arg, type(arg).__name__))
def assemble(name, function, *args):
def assemble(module, function, *args):
"""Assemble a javascript file and a function call."""
code = "{code}\n_qutebrowser_{function}({args});".format(
code=utils.read_file('javascript/{}.js'.format(name)),
code = textwrap.dedent("""
"use strict";
window._qutebrowser.{module}.{function}({args});
""").format(
module=module,
function=function,
args=', '.join(_convert_js_arg(arg) for arg in args),
)