From 6b7a39685e43a8728eaf24567b407b0f625cff7a Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 9 Aug 2016 12:44:13 +0200 Subject: [PATCH] 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. --- qutebrowser/browser/webengine/webenginetab.py | 45 ++++++--- qutebrowser/javascript/.eslintrc.yaml | 4 - qutebrowser/javascript/scroll.js | 90 +++++++++-------- qutebrowser/javascript/webelem.js | 98 ++++++++++--------- qutebrowser/utils/javascript.py | 10 +- 5 files changed, 139 insertions(+), 108 deletions(-) diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index 7f43d352a..08bfaa6f2 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -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) diff --git a/qutebrowser/javascript/.eslintrc.yaml b/qutebrowser/javascript/.eslintrc.yaml index e40805deb..b5a3c5a23 100644 --- a/qutebrowser/javascript/.eslintrc.yaml +++ b/qutebrowser/javascript/.eslintrc.yaml @@ -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" diff --git a/qutebrowser/javascript/scroll.js b/qutebrowser/javascript/scroll.js index df3a09e78..5c2e87166 100644 --- a/qutebrowser/javascript/scroll.js +++ b/qutebrowser/javascript/scroll.js @@ -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; +})(); diff --git a/qutebrowser/javascript/webelem.js b/qutebrowser/javascript/webelem.js index 0103cac00..2c86b137b 100644 --- a/qutebrowser/javascript/webelem.js +++ b/qutebrowser/javascript/webelem.js @@ -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 + // 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 - // 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; +})(); diff --git a/qutebrowser/utils/javascript.py b/qutebrowser/utils/javascript.py index a40fa7075..897643df2 100644 --- a/qutebrowser/utils/javascript.py +++ b/qutebrowser/utils/javascript.py @@ -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), )