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 import browsertab
from qutebrowser.browser.webengine import webview, webengineelem 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): class WebEnginePrinting(browsertab.AbstractPrinting):
@ -191,11 +191,9 @@ class WebEngineScroller(browsertab.AbstractScroller):
super()._init_widget(widget) super()._init_widget(widget)
page = widget.page() page = widget.page()
try: try:
page.scrollPositionChanged.connect( page.scrollPositionChanged.connect(self._update_pos)
self._on_scroll_pos_changed)
except AttributeError: except AttributeError:
log.stub('scrollPositionChanged, on Qt < 5.7') log.stub('scrollPositionChanged, on Qt < 5.7')
self._on_scroll_pos_changed()
def _key_press(self, key, count=1): def _key_press(self, key, count=1):
# FIXME:qtwebengine Abort scrolling if the minimum/maximum was reached. # FIXME:qtwebengine Abort scrolling if the minimum/maximum was reached.
@ -209,9 +207,9 @@ class WebEngineScroller(browsertab.AbstractScroller):
QApplication.postEvent(recipient, release_evt) QApplication.postEvent(recipient, release_evt)
@pyqtSlot() @pyqtSlot()
def _on_scroll_pos_changed(self): def _update_pos(self):
"""Update the scroll position attributes when it changed.""" """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.""" """Callback after getting scroll position via JS."""
if jsret is None: if jsret is None:
# This can happen when the callback would get called after # 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._pos_px = QPoint(jsret['px']['x'], jsret['px']['y'])
self.perc_changed.emit(*self._pos_perc) self.perc_changed.emit(*self._pos_perc)
js_code = javascript.assemble('scroll', 'scroll_pos') js_code = javascript.assemble('scroll', 'pos')
self._tab.run_js_async(js_code, update_scroll_pos) self._tab.run_js_async(js_code, update_pos_cb)
def pos_px(self): def pos_px(self):
return self._pos_px return self._pos_px
@ -232,7 +230,7 @@ class WebEngineScroller(browsertab.AbstractScroller):
return self._pos_perc return self._pos_perc
def to_perc(self, x=None, y=None): 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) self._tab.run_js_async(js_code)
def to_point(self, point): 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)) self._tab.run_js_async("window.scrollBy({x}, {y});".format(x=x, y=y))
def delta_page(self, x=0, y=0): 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) self._tab.run_js_async(js_code)
def up(self, count=1): def up(self, count=1):
@ -334,6 +332,31 @@ class WebEngineTab(browsertab.AbstractTab):
self._set_widget(widget) self._set_widget(widget)
self._connect_signals() self._connect_signals()
self.backend = usertypes.Backend.QtWebEngine 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): def openurl(self, url):
self._openurl_prepare(url) self._openurl_prepare(url)
@ -427,7 +450,7 @@ class WebEngineTab(browsertab.AbstractTab):
callback(elems) callback(elems)
def find_all_elements(self, selector, callback, *, only_visible=False): 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) js_cb = functools.partial(self._find_all_elements_js_cb, callback)
self.run_js_async(js_code, js_cb) self.run_js_async(js_code, js_cb)

View File

@ -33,7 +33,3 @@ rules:
no-undefined: "off" no-undefined: "off"
wrap-iife: ["error", "inside"] wrap-iife: ["error", "inside"]
func-names: "off" 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"; "use strict";
function _qutebrowser_scroll_to_perc(x, y) { window._qutebrowser.scroll = (function() {
var elem = document.documentElement; var funcs = {};
var x_px = window.scrollX;
var y_px = window.scrollY;
if (x !== undefined) { funcs.to_perc = function(x, y) {
x_px = (elem.scrollWidth - elem.clientWidth) / 100 * x; var elem = document.documentElement;
} var x_px = window.scrollX;
var y_px = window.scrollY;
if (y !== undefined) { if (x !== undefined) {
y_px = (elem.scrollHeight - elem.clientHeight) / 100 * y; 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) { window.scroll(x_px, y_px);
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},
}; };
// console.log(JSON.stringify(pos)); funcs.delta_page = function(x, y) {
return pos; 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"; "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 attributes = {};
var out = { for (var i = 0; i < elem.attributes.length; ++i) {
"id": id, var attr = elem.attributes[i];
"text": elem.text, attributes[attr.name] = attr.value;
"tag_name": elem.tagName, }
"outer_xml": elem.outerHTML, 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 = {}; funcs.focus_element = function() {
for (var i = 0; i < elem.attributes.length; ++i) { var elem = document.activeElement;
var attr = elem.attributes[i];
attributes[attr.name] = attr.value;
}
out.attributes = attributes;
// 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) { funcs.get_element = function(id) {
var elems = document.querySelectorAll(selector); return elements[id];
var out = []; };
var id = document._qutebrowser_elements.length;
for (var i = 0; i < elems.length; ++i) { return funcs;
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];
}

View File

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