diff --git a/qutebrowser/browser/greasemonkey.py b/qutebrowser/browser/greasemonkey.py index db8246bab..cff45f3ac 100644 --- a/qutebrowser/browser/greasemonkey.py +++ b/qutebrowser/browser/greasemonkey.py @@ -58,6 +58,7 @@ class GreasemonkeyScript: self.run_at = None self.script_meta = None self.runs_on_sub_frames = True + self.jsworld = "main" for name, value in properties: if name == 'name': self.name = value @@ -77,6 +78,8 @@ class GreasemonkeyScript: self.runs_on_sub_frames = False elif name == 'require': self.requires.append(value) + elif name == 'qute-js-world': + self.jsworld = value HEADER_REGEX = r'// ==UserScript==|\n+// ==/UserScript==\n' PROPS_REGEX = r'// @(?P[^\s]+)\s*(?P.*)' diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index 8e88a6efb..aa1c014ef 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -853,9 +853,17 @@ class _WebEngineScripts(QObject): self._inject_early_js('js', js_code, subframes=True) self._init_stylesheet() - greasemonkey = objreg.get('greasemonkey') - greasemonkey.scripts_reloaded.connect(self._inject_userscripts) - self._inject_userscripts() + # The Greasemonkey metadata block support in QtWebEngine only starts at + # Qt 5.8. With 5.7.1, we need to inject the scripts ourselves in + # response to urlChanged. + if not qtutils.version_check('5.8'): + self._widget.page().urlChanged.connect( + self._inject_greasemonkey_scripts_for_url) + else: + greasemonkey = objreg.get('greasemonkey') + greasemonkey.scripts_reloaded.connect( + self._inject_all_greasemonkey_scripts) + self._inject_all_greasemonkey_scripts() def _init_stylesheet(self): """Initialize custom stylesheets. @@ -872,40 +880,77 @@ class _WebEngineScripts(QObject): ) self._inject_early_js('stylesheet', js_code, subframes=True) - def _inject_userscripts(self): - """Register user JavaScript files with the global profiles.""" - # The Greasemonkey metadata block support in QtWebEngine only starts at - # Qt 5.8. With 5.7.1, we need to inject the scripts ourselves in - # response to urlChanged. - if not qtutils.version_check('5.8'): + def _inject_greasemonkey_scripts_for_url(self, url): + greasemonkey = objreg.get('greasemonkey') + matching_scripts = greasemonkey.scripts_for(url) + self._inject_greasemonkey_scripts( + matching_scripts.start, QWebEngineScript.DocumentCreation, True) + self._inject_greasemonkey_scripts( + matching_scripts.end, QWebEngineScript.DocumentReady, False) + self._inject_greasemonkey_scripts( + matching_scripts.idle, QWebEngineScript.Deferred, False) + + def _inject_all_greasemonkey_scripts(self): + greasemonkey = objreg.get('greasemonkey') + scripts = greasemonkey.all_scripts() + self._inject_greasemonkey_scripts(scripts) + + def _inject_greasemonkey_scripts(self, scripts=None, injection_point=None, + remove_first=True): + """Register user JavaScript files with the current tab. + + Args: + scripts: A list of GreasemonkeyScripts, or None to add all + known by the Greasemonkey subsystem. + injection_point: The QWebEngineScript::InjectionPoint stage + to inject the script into, None to use + auto-detection. + remove_first: Whether to remove all previously injected + scripts before adding these ones. + """ + if sip.isdeleted(self._widget): return - # Since we are inserting scripts into profile.scripts they won't - # just get replaced by new gm scripts like if we were injecting them - # ourselves so we need to remove all gm scripts, while not removing - # any other stuff that might have been added. Like the one for - # stylesheets. - greasemonkey = objreg.get('greasemonkey') - scripts = self._widget.page().scripts() - for script in scripts.toList(): - if script.name().startswith("GM-"): - log.greasemonkey.debug('Removing script: {}' - .format(script.name())) - removed = scripts.remove(script) - assert removed, script.name() + # Since we are inserting scripts into a per-tab collection, + # rather than just injecting scripts on page load, we need to + # make sure we replace existing scripts, not just add new ones. + # While, taking care not to remove any other scripts that might + # have been added elsewhere, like the one for stylesheets. + page_scripts = self._widget.page().scripts() + if remove_first: + for script in page_scripts.toList(): + if script.name().startswith("GM-"): + log.greasemonkey.debug('Removing script: {}' + .format(script.name())) + removed = page_scripts.remove(script) + assert removed, script.name() - # Then add the new scripts. - for script in greasemonkey.all_scripts(): - # @run-at (and @include/@exclude/@match) is parsed by - # QWebEngineScript. + if not scripts: + return + + for script in scripts: new_script = QWebEngineScript() - new_script.setWorldId(QWebEngineScript.MainWorld) + try: + world = int(script.jsworld) + except ValueError: + try: + world = _JS_WORLD_MAP[usertypes.JsWorld[ + script.jsworld.lower()]] + except KeyError: + log.greasemonkey.error( + "script {} has invalid value for '@qute-js-world'" + ": {}".format(script.name, script.jsworld)) + continue + new_script.setWorldId(world) new_script.setSourceCode(script.code()) new_script.setName("GM-{}".format(script.name)) new_script.setRunsOnSubFrames(script.runs_on_sub_frames) + # Override the @run-at value parsed by QWebEngineScript if desired. + if injection_point: + new_script.setInjectionPoint(injection_point) log.greasemonkey.debug('adding script: {}' .format(new_script.name())) - scripts.insert(new_script) + page_scripts.insert(new_script) class WebEngineTab(browsertab.AbstractTab): diff --git a/qutebrowser/browser/webengine/webview.py b/qutebrowser/browser/webengine/webview.py index 6a854d513..ca4126833 100644 --- a/qutebrowser/browser/webengine/webview.py +++ b/qutebrowser/browser/webengine/webview.py @@ -20,11 +20,10 @@ """The main browser widget for QtWebEngine.""" import sip -from PyQt5.QtCore import pyqtSignal, pyqtSlot, QUrl, PYQT_VERSION +from PyQt5.QtCore import pyqtSignal, QUrl, PYQT_VERSION from PyQt5.QtGui import QPalette from PyQt5.QtWidgets import QWidget -from PyQt5.QtWebEngineWidgets import (QWebEngineView, QWebEnginePage, - QWebEngineScript) +from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEnginePage from qutebrowser.browser import shared from qutebrowser.browser.webengine import webenginesettings, certificateerror @@ -168,7 +167,6 @@ class WebEnginePage(QWebEnginePage): self._theme_color = theme_color self._set_bg_color() config.instance.changed.connect(self._set_bg_color) - self.urlChanged.connect(self._inject_userjs) @config.change_filter('colors.webpage.bg') def _set_bg_color(self): @@ -263,43 +261,3 @@ class WebEnginePage(QWebEnginePage): is_main_frame=is_main_frame) self.navigation_request.emit(navigation) return navigation.accepted - - @pyqtSlot('QUrl') - def _inject_userjs(self, url): - """Inject userscripts registered for `url` into the current page.""" - if qtutils.version_check('5.8'): - # Handled in webenginetab with the builtin Greasemonkey - # support. - return - - # Using QWebEnginePage.scripts() to hold the user scripts means - # we don't have to worry ourselves about where to inject the - # page but also means scripts hang around for the tab lifecycle. - # So clear them here. - scripts = self.scripts() - for script in scripts.toList(): - if script.name().startswith("GM-"): - log.greasemonkey.debug("Removing script: {}" - .format(script.name())) - removed = scripts.remove(script) - assert removed, script.name() - - def _add_script(script, injection_point): - new_script = QWebEngineScript() - new_script.setInjectionPoint(injection_point) - new_script.setWorldId(QWebEngineScript.MainWorld) - new_script.setSourceCode(script.code()) - new_script.setName("GM-{}".format(script.name)) - new_script.setRunsOnSubFrames(script.runs_on_sub_frames) - log.greasemonkey.debug("Adding script: {}" - .format(new_script.name())) - scripts.insert(new_script) - - greasemonkey = objreg.get('greasemonkey') - matching_scripts = greasemonkey.scripts_for(url) - for script in matching_scripts.start: - _add_script(script, QWebEngineScript.DocumentCreation) - for script in matching_scripts.end: - _add_script(script, QWebEngineScript.DocumentReady) - for script in matching_scripts.idle: - _add_script(script, QWebEngineScript.Deferred)