summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLembrun <amadeusk7@free.fr>2021-03-09 21:08:39 +0100
committerLembrun <amadeusk7@free.fr>2021-03-09 21:08:39 +0100
commit6d9c28ce10832aa727de1a326d9459a035ff3aba (patch)
tree86215f5abcfcbc6253f484f55ab77af991d3eb34
parent8130c0316a737ddea1624351fa60b1812d0d2015 (diff)
parent0a38fff4c675b384289728a4d45f9c1fe1f9d4fc (diff)
downloadqutebrowser-6d9c28ce10832aa727de1a326d9459a035ff3aba.tar.gz
qutebrowser-6d9c28ce10832aa727de1a326d9459a035ff3aba.zip
Merge branch 'master' into Add-utils/resources.py
-rw-r--r--doc/changelog.asciidoc10
-rw-r--r--misc/requirements/requirements-flake8.txt2
-rw-r--r--misc/requirements/requirements-mypy.txt12
-rw-r--r--misc/requirements/requirements-pyinstaller.txt2
-rw-r--r--misc/requirements/requirements-pyroma.txt4
-rw-r--r--misc/requirements/requirements-sphinx.txt4
-rw-r--r--misc/requirements/requirements-tests.txt6
-rw-r--r--misc/requirements/requirements-tox.txt4
-rwxr-xr-xmisc/userscripts/readability3
-rwxr-xr-xmisc/userscripts/readability-js3
-rw-r--r--pytest.ini1
-rw-r--r--qutebrowser/browser/webengine/webenginetab.py59
-rw-r--r--qutebrowser/keyinput/modeman.py11
-rw-r--r--qutebrowser/mainwindow/tabbedbrowser.py14
-rw-r--r--qutebrowser/qutebrowser.py3
-rw-r--r--requirements.txt8
-rw-r--r--scripts/dev/ci/docker/Dockerfile.j27
-rw-r--r--tests/conftest.py6
-rw-r--r--tests/end2end/features/misc.feature12
-rw-r--r--tests/end2end/features/qutescheme.feature2
-rw-r--r--tests/unit/browser/webengine/test_webenginetab.py94
-rw-r--r--tests/unit/javascript/conftest.py7
-rw-r--r--tests/unit/javascript/test_greasemonkey.py189
23 files changed, 234 insertions, 229 deletions
diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc
index 3840f369d..d57698df7 100644
--- a/doc/changelog.asciidoc
+++ b/doc/changelog.asciidoc
@@ -60,6 +60,12 @@ Changed
- The `fileselect.*.command` settings now support file selectors writing the
selected paths to stdout, which is used if no `{}` placeholder is contained in
the configured command.
+- The `--debug-flag` argument now understands a new `log-sensitive-keys` value
+ which logs all keypresses (including those in insert/passthrough/prompt/...
+ mode) for debugging.
+- The `readability` and `readability-js` userscripts now add a
+ `qute-readability` CSS class to the page, so that it can be styled easily via
+ a user stylesheet.
Fixed
~~~~~
@@ -87,6 +93,10 @@ Fixed
properly.
- The "try again" button on error pages now works correctly with JavaScript
disabled.
+- If a GreaseMonkey script doesn't have a "@run-at" comment, qutebrowser
+ accidentally treated that as "@run-at document-idle". However, other
+ GreaseMonkey implementations default to "@run-at document-end" instead, which
+ is what qutebrowser now does, too.
[[v2.0.2]]
v2.0.2 (2021-02-04)
diff --git a/misc/requirements/requirements-flake8.txt b/misc/requirements/requirements-flake8.txt
index 493fa3cac..c83b57860 100644
--- a/misc/requirements/requirements-flake8.txt
+++ b/misc/requirements/requirements-flake8.txt
@@ -2,7 +2,7 @@
attrs==20.3.0
flake8==3.8.4
-flake8-bugbear==20.11.1
+flake8-bugbear==21.3.1
flake8-builtins==1.5.3
flake8-comprehensions==3.3.1
flake8-copyright==0.2.2
diff --git a/misc/requirements/requirements-mypy.txt b/misc/requirements/requirements-mypy.txt
index 070339ed6..dfa80656b 100644
--- a/misc/requirements/requirements-mypy.txt
+++ b/misc/requirements/requirements-mypy.txt
@@ -1,10 +1,10 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
chardet==4.0.0
-diff-cover==4.2.1
-importlib-metadata==3.7.0
-importlib-resources==5.1.1
-inflect==5.2.0
+diff-cover==4.2.3
+importlib-metadata==3.7.2
+importlib-resources==5.1.2
+inflect==3.0.2
Jinja2==2.11.3
jinja2-pluralize==0.3.0
lxml==4.6.2
@@ -12,8 +12,8 @@ MarkupSafe==1.1.1
mypy==0.812
mypy-extensions==0.4.3
pluggy==0.13.1
-Pygments==2.8.0
+Pygments==2.8.1
PyQt5-stubs==5.15.2.0
typed-ast==1.4.2
typing-extensions==3.7.4.3
-zipp==3.4.0
+zipp==3.4.1
diff --git a/misc/requirements/requirements-pyinstaller.txt b/misc/requirements/requirements-pyinstaller.txt
index 05a59200f..5b7c0137a 100644
--- a/misc/requirements/requirements-pyinstaller.txt
+++ b/misc/requirements/requirements-pyinstaller.txt
@@ -2,4 +2,4 @@
altgraph==0.17
pyinstaller==4.2
-pyinstaller-hooks-contrib==2020.11
+pyinstaller-hooks-contrib==2021.1
diff --git a/misc/requirements/requirements-pyroma.txt b/misc/requirements/requirements-pyroma.txt
index 22a195e66..b64b99e24 100644
--- a/misc/requirements/requirements-pyroma.txt
+++ b/misc/requirements/requirements-pyroma.txt
@@ -1,5 +1,5 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
docutils==0.16
-Pygments==2.5.2
-pyroma==2.6.1
+Pygments==2.8.1
+pyroma==3.1
diff --git a/misc/requirements/requirements-sphinx.txt b/misc/requirements/requirements-sphinx.txt
index 495b8dcf5..352be342a 100644
--- a/misc/requirements/requirements-sphinx.txt
+++ b/misc/requirements/requirements-sphinx.txt
@@ -10,12 +10,12 @@ imagesize==1.2.0
Jinja2==2.11.3
MarkupSafe==1.1.1
packaging==20.9
-Pygments==2.8.0
+Pygments==2.8.1
pyparsing==2.4.7
pytz==2021.1
requests==2.25.1
snowballstemmer==2.1.0
-Sphinx==3.5.1
+Sphinx==3.5.2
sphinxcontrib-applehelp==1.0.2
sphinxcontrib-devhelp==1.0.2
sphinxcontrib-htmlhelp==1.0.3
diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt
index bf214be0d..2bfaf91e0 100644
--- a/misc/requirements/requirements-tests.txt
+++ b/misc/requirements/requirements-tests.txt
@@ -15,7 +15,7 @@ filelock==3.0.12
Flask==1.1.2
glob2==0.7
hunter==3.3.1
-hypothesis==6.3.4
+hypothesis==6.6.0
icdiff==1.9.1
idna==2.10
iniconfig==1.1.1
@@ -33,7 +33,7 @@ pluggy==0.13.1
pprintpp==0.4.0
py==1.10.0
py-cpuinfo==7.0.0
-Pygments==2.8.0
+Pygments==2.8.1
pyparsing==2.4.7
pytest==6.2.2
pytest-bdd==4.0.2
@@ -48,7 +48,7 @@ pytest-repeat==0.9.1
pytest-rerunfailures==9.1.1
pytest-xdist==2.2.1
pytest-xvfb==2.0.0
-PyVirtualDisplay==2.0
+PyVirtualDisplay==2.1
requests==2.25.1
requests-file==1.5.1
six==1.15.0
diff --git a/misc/requirements/requirements-tox.txt b/misc/requirements/requirements-tox.txt
index 1e6382e1e..d44522118 100644
--- a/misc/requirements/requirements-tox.txt
+++ b/misc/requirements/requirements-tox.txt
@@ -8,9 +8,9 @@ pip==21.0.1
pluggy==0.13.1
py==1.10.0
pyparsing==2.4.7
-setuptools==54.0.0
+setuptools==54.1.1
six==1.15.0
toml==0.10.2
-tox==3.22.0
+tox==3.23.0
virtualenv==20.4.2
wheel==0.36.2
diff --git a/misc/userscripts/readability b/misc/userscripts/readability
index f9cbbf829..a6a6f2d52 100755
--- a/misc/userscripts/readability
+++ b/misc/userscripts/readability
@@ -57,6 +57,9 @@ with codecs.open(os.environ['QUTE_HTML'], 'r', 'utf-8') as source:
title = doc.title()
content = doc.summary().replace('<html>', HEADER % title)
+ # add a class to make styling the page easier
+ content = content.replace('<body>', '<body class="qute-readability">')
+
with codecs.open(tmpfile, 'w', 'utf-8') as target:
target.write(content.lstrip())
diff --git a/misc/userscripts/readability-js b/misc/userscripts/readability-js
index 2f24e065d..532df51c6 100755
--- a/misc/userscripts/readability-js
+++ b/misc/userscripts/readability-js
@@ -131,6 +131,9 @@ getDOM(target, domOpts).then(dom => {
let article = reader.parse();
let content = util.format(HEADER, article.title) + article.content;
+ // add a class to make styling the page easier
+ content = content.replace('<body>', '<body class="qute-readability">')
+
fs.writeFile(tmpFile, content, (err) => {
if (err) {
qute.messageError([`"${err}"`])
diff --git a/pytest.ini b/pytest.ini
index d0f41948b..7f4a58de3 100644
--- a/pytest.ini
+++ b/pytest.ini
@@ -34,7 +34,6 @@ markers =
no_invalid_lines: Don't fail on unparsable lines in end2end tests
fake_os: Fake utils.is_* to a fake operating system
unicode_locale: Tests which need a unicode locale to work
- qtwebkit6021_xfail: Tests which would fail on WebKit version 602.1
js_headers: Sets JS headers dynamically on QtWebEngine (unsupported on some versions)
qtwebkit_pdf_imageformat_skip: Broken on QtWebKit with PDF image format plugin installed
windows_skip: Tests which should be skipped on Windows
diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py
index 4a80dd623..69ddbe6e1 100644
--- a/qutebrowser/browser/webengine/webenginetab.py
+++ b/qutebrowser/browser/webengine/webenginetab.py
@@ -1080,18 +1080,11 @@ class _WebEngineScripts(QObject):
removed = page_scripts.remove(script)
assert removed, script.name()
- def _inject_greasemonkey_scripts(self, scripts=None, injection_point=None,
- remove_first=True):
+ def _inject_greasemonkey_scripts(self, scripts):
"""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.
+ scripts: A list of GreasemonkeyScripts.
"""
if sip.isdeleted(self._widget):
return
@@ -1102,49 +1095,49 @@ class _WebEngineScripts(QObject):
# 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:
- self._remove_all_greasemonkey_scripts()
-
- if not scripts:
- return
+ self._remove_all_greasemonkey_scripts()
for script in scripts:
new_script = QWebEngineScript()
+
try:
world = int(script.jsworld)
if not 0 <= world <= qtutils.MAX_WORLD_ID:
log.greasemonkey.error(
- "script {} has invalid value for '@qute-js-world'"
- ": {}, should be between 0 and {}"
- .format(
- script.name,
- script.jsworld,
- qtutils.MAX_WORLD_ID))
+ f"script {script.name} has invalid value for '@qute-js-world'"
+ f": {script.jsworld}, should be between 0 and "
+ f"{qtutils.MAX_WORLD_ID}")
continue
except ValueError:
try:
- world = _JS_WORLD_MAP[usertypes.JsWorld[
- script.jsworld.lower()]]
+ 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))
+ f"script {script.name} has invalid value for '@qute-js-world'"
+ f": {script.jsworld}")
continue
new_script.setWorldId(world)
+
+ # Corresponds to "@run-at document-end" which is the default according to
+ # https://wiki.greasespot.net/Metadata_Block#.40run-at - however,
+ # QtWebEngine uses QWebEngineScript.Deferred (@run-at document-idle) as
+ # default.
+ #
+ # NOTE that this needs to be done before setSourceCode, so that
+ # QtWebEngine's parsing of GreaseMonkey tags will override it if there is a
+ # @run-at comment.
+ new_script.setInjectionPoint(QWebEngineScript.DocumentReady)
+
new_script.setSourceCode(script.code())
- new_script.setName("GM-{}".format(script.name))
+ new_script.setName(f"GM-{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)
- elif script.needs_document_end_workaround():
- log.greasemonkey.debug("Forcing @run-at document-end for {}"
- .format(script.name))
+ if script.needs_document_end_workaround():
+ log.greasemonkey.debug(
+ f"Forcing @run-at document-end for {script.name}")
new_script.setInjectionPoint(QWebEngineScript.DocumentReady)
- log.greasemonkey.debug('adding script: {}'
- .format(new_script.name()))
+ log.greasemonkey.debug(f'adding script: {new_script.name()}')
page_scripts.insert(new_script)
def _inject_site_specific_quirks(self):
diff --git a/qutebrowser/keyinput/modeman.py b/qutebrowser/keyinput/modeman.py
index 08c5a151b..c00120596 100644
--- a/qutebrowser/keyinput/modeman.py
+++ b/qutebrowser/keyinput/modeman.py
@@ -86,9 +86,10 @@ def init(win_id: int, parent: QObject) -> 'ModeManager':
hintmanager = hints.HintManager(win_id, parent=parent)
objreg.register('hintmanager', hintmanager, scope='window',
window=win_id, command_only=True)
-
modeman.hintmanager = hintmanager
+ log_sensitive_keys = 'log-sensitive-keys' in objects.debug_flags
+
keyparsers: ParserDictType = {
usertypes.KeyMode.normal:
modeparsers.NormalKeyParser(
@@ -110,7 +111,7 @@ def init(win_id: int, parent: QObject) -> 'ModeManager':
commandrunner=commandrunner,
parent=modeman,
passthrough=True,
- do_log=False,
+ do_log=log_sensitive_keys,
supports_count=False),
usertypes.KeyMode.passthrough:
@@ -120,7 +121,7 @@ def init(win_id: int, parent: QObject) -> 'ModeManager':
commandrunner=commandrunner,
parent=modeman,
passthrough=True,
- do_log=False,
+ do_log=log_sensitive_keys,
supports_count=False),
usertypes.KeyMode.command:
@@ -130,7 +131,7 @@ def init(win_id: int, parent: QObject) -> 'ModeManager':
commandrunner=commandrunner,
parent=modeman,
passthrough=True,
- do_log=False,
+ do_log=log_sensitive_keys,
supports_count=False),
usertypes.KeyMode.prompt:
@@ -140,7 +141,7 @@ def init(win_id: int, parent: QObject) -> 'ModeManager':
commandrunner=commandrunner,
parent=modeman,
passthrough=True,
- do_log=False,
+ do_log=log_sensitive_keys,
supports_count=False),
usertypes.KeyMode.yesno:
diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py
index 52241d777..8d2801d31 100644
--- a/qutebrowser/mainwindow/tabbedbrowser.py
+++ b/qutebrowser/mainwindow/tabbedbrowser.py
@@ -929,16 +929,12 @@ class TabbedBrowser(QWidget):
return
messages = {
- browsertab.TerminationStatus.abnormal:
- "Renderer process exited with status {}".format(code),
- browsertab.TerminationStatus.crashed:
- "Renderer process crashed",
- browsertab.TerminationStatus.killed:
- "Renderer process was killed",
- browsertab.TerminationStatus.unknown:
- "Renderer process did not start",
+ browsertab.TerminationStatus.abnormal: "Renderer process exited",
+ browsertab.TerminationStatus.crashed: "Renderer process crashed",
+ browsertab.TerminationStatus.killed: "Renderer process was killed",
+ browsertab.TerminationStatus.unknown: "Renderer process did not start",
}
- msg = messages[status]
+ msg = messages[status] + f" (status {code})"
def show_error_page(html):
tab.set_html(html)
diff --git a/qutebrowser/qutebrowser.py b/qutebrowser/qutebrowser.py
index 64c175293..9e1fb91cd 100644
--- a/qutebrowser/qutebrowser.py
+++ b/qutebrowser/qutebrowser.py
@@ -173,6 +173,7 @@ def debug_flag_error(flag):
log-requests: Log all network requests.
log-cookies: Log cookies in cookie filter.
log-scroll-pos: Log all scrolling changes.
+ log-sensitive-keys: Log keypresses in passthrough modes.
stack: Enable Chromium stack logging.
chromium: Enable Chromium logging.
wait-renderer-process: Wait for debugger in renderer process.
@@ -181,7 +182,7 @@ def debug_flag_error(flag):
"""
valid_flags = ['debug-exit', 'pdb-postmortem', 'no-sql-history',
'no-scroll-filtering', 'log-requests', 'log-cookies',
- 'log-scroll-pos', 'stack', 'chromium',
+ 'log-scroll-pos', 'log-sensitive-keys', 'stack', 'chromium',
'wait-renderer-process', 'avoid-chromium-init', 'werror']
if flag in valid_flags:
diff --git a/requirements.txt b/requirements.txt
index c6eb86d6f..5572e206c 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -3,11 +3,11 @@
adblock==0.4.2 ; python_version!="3.10"
colorama==0.4.4
dataclasses==0.6 ; python_version<"3.7"
-importlib-metadata==3.7.0 ; python_version<"3.8"
-importlib-resources==5.1.1 ; python_version<"3.9"
+importlib-metadata==3.7.2 ; python_version<"3.8"
+importlib-resources==5.1.2 ; python_version<"3.9"
Jinja2==2.11.3
MarkupSafe==1.1.1
-Pygments==2.8.0
+Pygments==2.8.1
PyYAML==5.4.1
typing-extensions==3.7.4.3
-zipp==3.4.0
+zipp==3.4.1
diff --git a/scripts/dev/ci/docker/Dockerfile.j2 b/scripts/dev/ci/docker/Dockerfile.j2
index 03e5684ad..d3fc82793 100644
--- a/scripts/dev/ci/docker/Dockerfile.j2
+++ b/scripts/dev/ci/docker/Dockerfile.j2
@@ -1,12 +1,5 @@
FROM archlinux:latest
-# WORKAROUND for glibc 2.33 and old Docker
-# See https://github.com/actions/virtual-environments/issues/2658
-# Thanks to https://github.com/lxqt/lxqt-panel/pull/1562
-RUN patched_glibc=glibc-linux4-2.33-4-x86_64.pkg.tar.zst && \
- curl -LO "https://repo.archlinuxcn.org/x86_64/$patched_glibc" && \
- bsdtar -C / -xvf "$patched_glibc"
-
{% if unstable %}
RUN sed -i '/^# after the header/a[kde-unstable]\nInclude = /etc/pacman.d/mirrorlist\n\n[testing]\nInclude = /etc/pacman.d/mirrorlist' /etc/pacman.conf
{% endif %}
diff --git a/tests/conftest.py b/tests/conftest.py
index ea7381a2f..ee945ac4c 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -109,12 +109,6 @@ def _apply_platform_markers(config, item):
pytest.mark.skipif,
sys.getfilesystemencoding() == 'ascii',
"Skipped because of ASCII locale"),
-
- ('qtwebkit6021_xfail',
- pytest.mark.xfail,
- version.qWebKitVersion and # type: ignore[unreachable]
- version.qWebKitVersion() == '602.1',
- "Broken on WebKit 602.1")
]
for searched_marker, new_marker_kind, condition, default_reason in markers:
diff --git a/tests/end2end/features/misc.feature b/tests/end2end/features/misc.feature
index 351135fab..e6a02e038 100644
--- a/tests/end2end/features/misc.feature
+++ b/tests/end2end/features/misc.feature
@@ -140,7 +140,7 @@ Feature: Various utility commands.
Scenario: :jseval --file using a file that doesn't exist as js-code
When I run :jseval --file /nonexistentfile
- Then the error "[Errno 2] No such file or directory: '/nonexistentfile'" should be shown
+ Then the error "[Errno 2] *: '/nonexistentfile'" should be shown
And "No output or error" should not be logged
# :debug-webaction
@@ -528,13 +528,13 @@ Feature: Various utility commands.
@qtwebkit_skip @no_invalid_lines @posix
Scenario: Renderer crash
When I run :open -t chrome://crash
- Then "Renderer process crashed" should be logged
+ Then "Renderer process crashed (status *)" should be logged
And "* 'Error loading chrome://crash/'" should be logged
@qtwebkit_skip @no_invalid_lines @flaky
Scenario: Renderer kill
When I run :open -t chrome://kill
- Then "Renderer process was killed" should be logged
+ Then "Renderer process was killed (status *)" should be logged
And "* 'Error loading chrome://kill/'" should be logged
# https://github.com/qutebrowser/qutebrowser/issues/2290
@@ -544,7 +544,7 @@ Feature: Various utility commands.
And I open data/numbers/1.txt
And I open data/numbers/2.txt in a new tab
And I run :open chrome://kill
- And I wait for "Renderer process was killed" in the log
+ And I wait for "Renderer process was killed (status *)" in the log
And I open data/numbers/3.txt
Then no crash should happen
@@ -554,11 +554,11 @@ Feature: Various utility commands.
When I open data/crashers/webrtc.html in a new tab
And I run :reload
And I wait until data/crashers/webrtc.html is loaded
- Then "Renderer process crashed" should not be logged
+ Then "Renderer process crashed (status *)" should not be logged
Scenario: InstalledApps crash
When I open data/crashers/installedapp.html in a new tab
- Then "Renderer process was killed" should not be logged
+ Then "Renderer process was killed (status *)" should not be logged
## Other
diff --git a/tests/end2end/features/qutescheme.feature b/tests/end2end/features/qutescheme.feature
index 286f8f80a..1424bbf09 100644
--- a/tests/end2end/features/qutescheme.feature
+++ b/tests/end2end/features/qutescheme.feature
@@ -215,7 +215,7 @@ Feature: Special qute:// pages
Scenario: Running :pyeval --file using a non existing file
When I run :debug-pyeval --file nonexistentfile
- Then the error "[Errno 2] No such file or directory: 'nonexistentfile'" should be shown
+ Then the error "[Errno 2] *: 'nonexistentfile'" should be shown
Scenario: Running :pyeval with --quiet
When I run :debug-pyeval --quiet 1+1
diff --git a/tests/unit/browser/webengine/test_webenginetab.py b/tests/unit/browser/webengine/test_webenginetab.py
index 7827c379b..156f7d26f 100644
--- a/tests/unit/browser/webengine/test_webenginetab.py
+++ b/tests/unit/browser/webengine/test_webenginetab.py
@@ -20,6 +20,7 @@
"""Test webenginetab."""
import logging
+import textwrap
import pytest
QtWebEngineWidgets = pytest.importorskip("PyQt5.QtWebEngineWidgets")
@@ -35,15 +36,38 @@ webenginetab = pytest.importorskip(
pytestmark = pytest.mark.usefixtures('greasemonkey_manager')
+class ScriptsHelper:
+
+ """Helper to get the processed (usually Greasemonkey) scripts."""
+
+ def __init__(self, tab):
+ self._tab = tab
+
+ def get_scripts(self, prefix='GM-'):
+ return [
+ s for s in self._tab._widget.page().scripts().toList()
+ if s.name().startswith(prefix)
+ ]
+
+ def get_script(self):
+ scripts = self.get_scripts()
+ assert len(scripts) == 1
+ return scripts[0]
+
+ def inject(self, scripts):
+ self._tab._scripts._inject_greasemonkey_scripts(scripts)
+ return self.get_scripts()
+
+
class TestWebengineScripts:
"""Test the _WebEngineScripts utility class."""
@pytest.fixture
- def webengine_scripts(self, webengine_tab):
- return webengine_tab._scripts
+ def scripts_helper(self, webengine_tab):
+ return ScriptsHelper(webengine_tab)
- def test_greasemonkey_undefined_world(self, webengine_scripts, caplog):
+ def test_greasemonkey_undefined_world(self, scripts_helper, caplog):
"""Make sure scripts with non-existent worlds are rejected."""
scripts = [
greasemonkey.GreasemonkeyScript(
@@ -51,18 +75,16 @@ class TestWebengineScripts:
]
with caplog.at_level(logging.ERROR, 'greasemonkey'):
- webengine_scripts._inject_greasemonkey_scripts(scripts)
+ injected = scripts_helper.inject(scripts)
assert len(caplog.records) == 1
msg = caplog.messages[0]
assert "has invalid value for '@qute-js-world': Mars" in msg
- collection = webengine_scripts._widget.page().scripts().toList()
- assert not any(script.name().startswith('GM-')
- for script in collection)
+
+ assert not injected
@pytest.mark.parametrize("worldid", [-1, 257])
- def test_greasemonkey_out_of_range_world(self, worldid, webengine_scripts,
- caplog):
+ def test_greasemonkey_out_of_range_world(self, worldid, scripts_helper, caplog):
"""Make sure scripts with out-of-range worlds are rejected."""
scripts = [
greasemonkey.GreasemonkeyScript(
@@ -70,19 +92,18 @@ class TestWebengineScripts:
]
with caplog.at_level(logging.ERROR, 'greasemonkey'):
- webengine_scripts._inject_greasemonkey_scripts(scripts)
+ injected = scripts_helper.inject(scripts)
assert len(caplog.records) == 1
msg = caplog.messages[0]
assert "has invalid value for '@qute-js-world': " in msg
assert "should be between 0 and" in msg
- collection = webengine_scripts._widget.page().scripts().toList()
- assert not any(script.name().startswith('GM-')
- for script in collection)
+
+ assert not injected
@pytest.mark.parametrize("worldid", [0, 10])
def test_greasemonkey_good_worlds_are_passed(self, worldid,
- webengine_scripts, caplog):
+ scripts_helper, caplog):
"""Make sure scripts with valid worlds have it set."""
scripts = [
greasemonkey.GreasemonkeyScript(
@@ -91,13 +112,11 @@ class TestWebengineScripts:
]
with caplog.at_level(logging.ERROR, 'greasemonkey'):
- webengine_scripts._inject_greasemonkey_scripts(scripts)
+ scripts_helper.inject(scripts)
- collection = webengine_scripts._widget.page().scripts()
- assert collection.toList()[-1].worldId() == worldid
+ assert scripts_helper.get_script().worldId() == worldid
- def test_greasemonkey_document_end_workaround(self, monkeypatch,
- webengine_scripts):
+ def test_greasemonkey_document_end_workaround(self, monkeypatch, scripts_helper):
"""Make sure document-end is forced when needed."""
monkeypatch.setattr(greasemonkey.objects, 'backend',
usertypes.Backend.QtWebEngine)
@@ -109,13 +128,42 @@ class TestWebengineScripts:
('run-at', 'document-start'),
], None)
]
+ scripts_helper.inject(scripts)
- webengine_scripts._inject_greasemonkey_scripts(scripts)
-
- collection = webengine_scripts._widget.page().scripts()
- script = collection.toList()[-1]
+ script = scripts_helper.get_script()
assert script.injectionPoint() == QWebEngineScript.DocumentReady
+ @pytest.mark.parametrize('run_at, expected', [
+ # UserScript::DocumentElementCreation
+ ('document-start', QWebEngineScript.DocumentCreation),
+ # UserScript::DocumentLoadFinished
+ ('document-end', QWebEngineScript.DocumentReady),
+ # UserScript::AfterLoad
+ ('document-idle', QWebEngineScript.Deferred),
+ # default according to https://wiki.greasespot.net/Metadata_Block#.40run-at
+ (None, QWebEngineScript.DocumentReady),
+ ])
+ def test_greasemonkey_run_at_values(self, scripts_helper, run_at, expected):
+ if run_at is None:
+ script = """
+ // ==UserScript==
+ // @name qutebrowser test userscript
+ // ==/UserScript==
+ """
+ else:
+ script = f"""
+ // ==UserScript==
+ // @name qutebrowser test userscript
+ // @run-at {run_at}
+ // ==/UserScript==
+ """
+
+ script = textwrap.dedent(script.lstrip('\n'))
+ scripts = [greasemonkey.GreasemonkeyScript.parse(script)]
+ scripts_helper.inject(scripts)
+
+ assert scripts_helper.get_script().injectionPoint() == expected
+
def test_notification_permission_workaround():
"""Make sure the value for QWebEnginePage::Notifications is correct."""
diff --git a/tests/unit/javascript/conftest.py b/tests/unit/javascript/conftest.py
index 47884687d..85d5ebe0a 100644
--- a/tests/unit/javascript/conftest.py
+++ b/tests/unit/javascript/conftest.py
@@ -28,6 +28,7 @@ import jinja2
from PyQt5.QtCore import QUrl
import qutebrowser
+from qutebrowser.utils import usertypes
class JSTester:
@@ -113,7 +114,7 @@ class JSTester:
source = f.read()
self.run(source, expected)
- def run(self, source: str, expected, world=None) -> None:
+ def run(self, source: str, expected=usertypes.UNSET, world=None) -> None:
"""Run the given javascript source.
Args:
@@ -123,7 +124,9 @@ class JSTester:
"""
with self.qtbot.wait_callback() as callback:
self.tab.run_js_async(source, callback, world=world)
- callback.assert_called_with(expected)
+
+ if expected is not usertypes.UNSET:
+ callback.assert_called_with(expected)
@pytest.fixture
diff --git a/tests/unit/javascript/test_greasemonkey.py b/tests/unit/javascript/test_greasemonkey.py
index c28b9c8f7..3a3ea0294 100644
--- a/tests/unit/javascript/test_greasemonkey.py
+++ b/tests/unit/javascript/test_greasemonkey.py
@@ -25,7 +25,7 @@ import pytest
import py.path # pylint: disable=no-name-in-module
from PyQt5.QtCore import QUrl
-from qutebrowser.utils import usertypes
+from qutebrowser.utils import usertypes, version
from qutebrowser.browser import greasemonkey
from qutebrowser.misc import objects
@@ -77,8 +77,7 @@ def test_get_scripts_by_url(url, expected_matches):
gm_manager = greasemonkey.GreasemonkeyManager()
scripts = gm_manager.scripts_for(QUrl(url))
- assert (len(scripts.start + scripts.end + scripts.idle) ==
- expected_matches)
+ assert len(scripts.start + scripts.end + scripts.idle) == expected_matches
@pytest.mark.parametrize("url, expected_matches", [
@@ -102,8 +101,7 @@ def test_regex_includes_scripts_for(url, expected_matches):
gm_manager = greasemonkey.GreasemonkeyManager()
scripts = gm_manager.scripts_for(QUrl(url))
- assert (len(scripts.start + scripts.end + scripts.idle) ==
- expected_matches)
+ assert len(scripts.start + scripts.end + scripts.idle) == expected_matches
def test_no_metadata(caplog):
@@ -229,124 +227,87 @@ def test_required_scripts_are_included(download_stub, tmpdir):
assert scripts[0].excludes
-class TestWindowIsolation:
+def test_window_isolation(js_tester, request):
"""Check that greasemonkey scripts get a shadowed global scope."""
+ # Change something in the global scope
+ setup_script = "window.$ = 'global'"
- @pytest.fixture
- def setup(self):
- # pylint: disable=attribute-defined-outside-init
- class SetupData:
- pass
- ret = SetupData()
-
- # Change something in the global scope
- ret.setup_script = "window.$ = 'global'"
-
- # Greasemonkey script to report back on its scope.
- test_script = greasemonkey.GreasemonkeyScript.parse(
- textwrap.dedent("""
- // ==UserScript==
- // @name scopetest
- // ==/UserScript==
- // Check the thing the page set is set to the expected type
- result.push(window.$);
- result.push($);
- // Now overwrite it
- window.$ = 'shadowed';
- // And check everything is how the script would expect it to be
- // after just writing to the "global" scope
- result.push(window.$);
- result.push($);
- """)
- )
-
- # The compiled source of that scripts with some additional setup
- # bookending it.
- ret.test_script = "\n".join([
- """
- const result = [];
- """,
- test_script.code(),
- """
- // Now check that the actual global scope has
- // not been overwritten
+ # Greasemonkey script to report back on its scope.
+ test_gm_script = greasemonkey.GreasemonkeyScript.parse(
+ textwrap.dedent("""
+ // ==UserScript==
+ // @name scopetest
+ // ==/UserScript==
+ // Check the thing the page set is set to the expected type
+ result.push(window.$);
+ result.push($);
+ // Now overwrite it
+ window.$ = 'shadowed';
+ // And check everything is how the script would expect it to be
+ // after just writing to the "global" scope
result.push(window.$);
result.push($);
- // And return our findings
- result;
- """
- ])
+ """)
+ )
+
+ # The compiled source of that scripts with some additional setup
+ # bookending it.
+ test_script = "\n".join([
+ """
+ const result = [];
+ """,
+ test_gm_script.code(),
+ """
+ // Now check that the actual global scope has
+ // not been overwritten
+ result.push(window.$);
+ result.push($);
+ // And return our findings
+ result;
+ """
+ ])
- # What we expect the script to report back.
- ret.expected = ["global", "global",
- "shadowed", "shadowed",
- "global", "global"]
- return ret
+ # What we expect the script to report back.
+ expected = ["global", "global", "shadowed", "shadowed", "global", "global"]
- def test_webengine(self, qtbot, webengineview, setup):
- page = webengineview.page()
- page.runJavaScript(setup.setup_script)
+ # The JSCore in 602.1 doesn't fully support Proxy.
+ xfail = False
+ if (js_tester.tab.backend == usertypes.Backend.QtWebKit and
+ version.qWebKitVersion() == '602.1'):
+ expected[-1] = 'shadowed'
+ expected[-2] = 'shadowed'
+ xfail = True
- with qtbot.wait_callback() as callback:
- page.runJavaScript(setup.test_script, callback)
- callback.assert_called_with(setup.expected)
+ js_tester.run(setup_script)
+ js_tester.run(test_script, expected=expected)
- # The JSCore in 602.1 doesn't fully support Proxy.
- @pytest.mark.qtwebkit6021_xfail
- def test_webkit(self, webview, setup):
- elem = webview.page().mainFrame().documentElement()
- elem.evaluateJavaScript(setup.setup_script)
- result = elem.evaluateJavaScript(setup.test_script)
- assert result == setup.expected
+ if xfail:
+ pytest.xfail("Broken on WebKit 602.1")
-class TestSharedWindowProxy:
+def test_shared_window_proxy(js_tester):
"""Check that all scripts have access to the same window proxy."""
+ # Greasemonkey script to add a property to the window proxy.
+ test_script_a = greasemonkey.GreasemonkeyScript.parse(
+ textwrap.dedent("""
+ // ==UserScript==
+ // @name a
+ // ==/UserScript==
+ // Set a value from script a
+ window.$ = 'test';
+ """)
+ ).code()
+
+ # Greasemonkey script to retrieve a property from the window proxy.
+ test_script_b = greasemonkey.GreasemonkeyScript.parse(
+ textwrap.dedent("""
+ // ==UserScript==
+ // @name b
+ // ==/UserScript==
+ // Check that the value is accessible from script b
+ return [window.$, $];
+ """)
+ ).code()
- @pytest.fixture
- def setup(self):
- # pylint: disable=attribute-defined-outside-init
- class SetupData:
- pass
- ret = SetupData()
-
- # Greasemonkey script to add a property to the window proxy.
- ret.test_script_a = greasemonkey.GreasemonkeyScript.parse(
- textwrap.dedent("""
- // ==UserScript==
- // @name a
- // ==/UserScript==
- // Set a value from script a
- window.$ = 'test';
- """)
- ).code()
-
- # Greasemonkey script to retrieve a property from the window proxy.
- ret.test_script_b = greasemonkey.GreasemonkeyScript.parse(
- textwrap.dedent("""
- // ==UserScript==
- // @name b
- // ==/UserScript==
- // Check that the value is accessible from script b
- return [window.$, $];
- """)
- ).code()
-
- # What we expect the script to report back.
- ret.expected = ["test", "test"]
- return ret
-
- def test_webengine(self, qtbot, webengineview, setup):
- page = webengineview.page()
-
- with qtbot.wait_callback() as callback:
- page.runJavaScript(setup.test_script_a, callback)
- with qtbot.wait_callback() as callback:
- page.runJavaScript(setup.test_script_b, callback)
- callback.assert_called_with(setup.expected)
-
- def test_webkit(self, webview, setup):
- elem = webview.page().mainFrame().documentElement()
- elem.evaluateJavaScript(setup.test_script_a)
- result = elem.evaluateJavaScript(setup.test_script_b)
- assert result == setup.expected
+ js_tester.run(test_script_a)
+ js_tester.run(test_script_b, expected=["test", "test"])