diff --git a/qutebrowser/browser/webengine/webenginesettings.py b/qutebrowser/browser/webengine/webenginesettings.py index 46bfcab59..57a4b7583 100644 --- a/qutebrowser/browser/webengine/webenginesettings.py +++ b/qutebrowser/browser/webengine/webenginesettings.py @@ -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) diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index 87adbc81e..6f4deebae 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -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( diff --git a/qutebrowser/javascript/.eslintignore b/qutebrowser/javascript/.eslintignore index 036a72cfe..65143f360 100644 --- a/qutebrowser/javascript/.eslintignore +++ b/qutebrowser/javascript/.eslintignore @@ -2,3 +2,4 @@ pac_utils.js # Actually a jinja template so eslint chokes on the {{}} syntax. greasemonkey_wrapper.js +global_wrapper.js diff --git a/qutebrowser/javascript/global_wrapper.js b/qutebrowser/javascript/global_wrapper.js new file mode 100644 index 000000000..a302bd5d1 --- /dev/null +++ b/qutebrowser/javascript/global_wrapper.js @@ -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; +})(); diff --git a/qutebrowser/utils/javascript.py b/qutebrowser/utils/javascript.py index 335b1b983..93df8e70f 100644 --- a/qutebrowser/utils/javascript.py +++ b/qutebrowser/utils/javascript.py @@ -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) diff --git a/tests/unit/utils/test_javascript.py b/tests/unit/utils/test_javascript.py index 864483bf0..29e090fd0 100644 --- a/tests/unit/utils/test_javascript.py +++ b/tests/unit/utils/test_javascript.py @@ -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