Refactor initialization of internal JavaScript

- Initialize JavaScript in webenginesettings.py instead of webenginetab.py
- Move JavaScript snippet into a .js file
- Make sure scripts can be re-run and do nothing if already run.
- Run scripts on DocumentCreation *and* DocumentReady. Closes #3717.
- Give each script an unique name for debugging.
- Also make custom stylesheets work on chrome:// pages
This commit is contained in:
Florian Bruhin 2018-03-19 09:14:55 +01:00
parent f2864c6253
commit 1b84bbd61d
6 changed files with 81 additions and 36 deletions

View File

@ -169,31 +169,65 @@ class WebEngineSettings(websettings.AbstractSettings):
self._ATTRIBUTES[name] = [value]
def _inject_early_js(profile, name, js_code, *,
world=QWebEngineScript.ApplicationWorld, subframes=False):
"""Inject the given script to run early on a page load.
This runs the script both on DocumentCreation and DocumentReady as on some
internal pages, DocumentCreation will not work.
That is a WORKAROUND for https://bugreports.qt.io/browse/QTBUG-66011
"""
for injection in ['creation', 'ready']:
injection_points = {
'creation': QWebEngineScript.DocumentCreation,
'ready': QWebEngineScript.DocumentReady,
}
script = QWebEngineScript()
script.setInjectionPoint(injection_points[injection])
script.setSourceCode(js_code)
script.setWorldId(world)
script.setRunsOnSubFrames(subframes)
script.setName('_qute_{}_{}'.format(name, injection))
profile.scripts().insert(script)
def _remove_early_js(profile, name):
"""Remove an early QWebEngineScript."""
scripts = profile.scripts()
for injection in ['creation', 'ready']:
full_name = '_qute_{}_{}'.format(name, injection)
script = scripts.findScript(full_name)
if not script.isNull():
scripts.remove(script)
def _init_stylesheet(profile):
"""Initialize custom stylesheets.
Partially inspired by QupZilla:
https://github.com/QupZilla/qupzilla/blob/v2.0/src/lib/app/mainapplication.cpp#L1063-L1101
"""
old_script = profile.scripts().findScript('_qute_stylesheet')
if not old_script.isNull():
profile.scripts().remove(old_script)
_remove_early_js(profile, 'stylesheet')
css = shared.get_user_stylesheet()
source = '\n'.join([
'"use strict";',
'window._qutebrowser = window._qutebrowser || {};',
js_code = javascript.wrap_global(
'stylesheet',
utils.read_file('javascript/stylesheet.js'),
javascript.assemble('stylesheet', 'set_css', css),
])
)
_inject_early_js(profile, 'stylesheet', js_code, subframes=True)
script = QWebEngineScript()
script.setName('_qute_stylesheet')
script.setInjectionPoint(QWebEngineScript.DocumentCreation)
script.setWorldId(QWebEngineScript.ApplicationWorld)
script.setRunsOnSubFrames(True)
script.setSourceCode(source)
profile.scripts().insert(script)
def _init_js(profile):
"""Initialize global qutebrowser JavaScript."""
js_code = javascript.wrap_global(
'scripts',
utils.read_file('javascript/scroll.js'),
utils.read_file('javascript/webelem.js'),
utils.read_file('javascript/caret.js'),
)
# FIXME:qtwebengine what about subframes=True?
_inject_early_js(profile, 'js', js_code, subframes=True)
def _update_stylesheet():
@ -288,6 +322,7 @@ def _update_settings(option):
def _init_profile(profile):
"""Init the given profile."""
_init_js(profile)
_init_stylesheet(profile)
_set_http_headers(profile)
_set_http_cache_size(profile)

View File

@ -630,31 +630,10 @@ class WebEngineTab(browsertab.AbstractTab):
self._set_widget(widget)
self._connect_signals()
self.backend = usertypes.Backend.QtWebEngine
self._init_js()
self._child_event_filter = None
self._saved_zoom = None
self._reload_url = None
def _init_js(self):
js_code = '\n'.join([
'"use strict";',
'window._qutebrowser = window._qutebrowser || {};',
utils.read_file('javascript/scroll.js'),
utils.read_file('javascript/webelem.js'),
utils.read_file('javascript/caret.js'),
])
script = QWebEngineScript()
# We can't use DocumentCreation here as WORKAROUND for
# https://bugreports.qt.io/browse/QTBUG-66011
script.setInjectionPoint(QWebEngineScript.DocumentReady)
script.setSourceCode(js_code)
page = self._widget.page()
script.setWorldId(QWebEngineScript.ApplicationWorld)
# FIXME:qtwebengine what about runsOnSubFrames?
page.scripts().insert(script)
def _install_event_filter(self):
self._widget.focusProxy().installEventFilter(self._mouse_event_filter)
self._child_event_filter = mouse.ChildEventFilter(

View File

@ -2,3 +2,4 @@
pac_utils.js
# Actually a jinja template so eslint chokes on the {{}} syntax.
greasemonkey_wrapper.js
global_wrapper.js

View File

@ -0,0 +1,12 @@
(function() {
"use strict";
if (!("_qutebrowser" in window)) {
window._qutebrowser = {"initialized": {}};
}
if (window._qutebrowser.initialized["{{name}}"]) {
return;
}
{{code}}
window._qutebrowser.initialized["{{name}}"] = true;
})();

View File

@ -20,6 +20,9 @@
"""Utilities related to javascript interaction."""
from qutebrowser.utils import jinja
def string_escape(text):
"""Escape values special to javascript in strings.
@ -70,3 +73,9 @@ def assemble(module, function, *args):
parts = ['window', '_qutebrowser', module, function]
code = '"use strict";\n{}({});'.format('.'.join(parts), js_args)
return code
def wrap_global(name, *sources):
"""Wrap a script using window._qutebrowser."""
template = jinja.js_environment.get_template('global_wrapper.js')
return template.render(code='\n'.join(sources), name=name)

View File

@ -100,3 +100,12 @@ def test_convert_js_arg(arg, expected):
def test_assemble(base, expected_base):
expected = '"use strict";\n{}.func(23);'.format(expected_base)
assert javascript.assemble(base, 'func', 23) == expected
def test_wrap_global():
source = javascript.wrap_global('name',
'console.log("foo");',
'console.log("bar");')
assert 'window._qutebrowser.initialized["name"]' in source
assert 'console.log("foo");' in source
assert 'console.log("bar");' in source