Merge branch 'master' into Add-utils/resources.py
This commit is contained in:
commit
6d9c28ce10
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -2,4 +2,4 @@
|
|||
|
||||
altgraph==0.17
|
||||
pyinstaller==4.2
|
||||
pyinstaller-hooks-contrib==2020.11
|
||||
pyinstaller-hooks-contrib==2021.1
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
||||
|
|
|
|||
|
|
@ -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}"`])
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 %}
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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."""
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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($);
|
||||
// And return our findings
|
||||
result;
|
||||
"""
|
||||
])
|
||||
// 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($);
|
||||
""")
|
||||
)
|
||||
|
||||
# What we expect the script to report back.
|
||||
ret.expected = ["global", "global",
|
||||
"shadowed", "shadowed",
|
||||
"global", "global"]
|
||||
return ret
|
||||
# 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;
|
||||
"""
|
||||
])
|
||||
|
||||
def test_webengine(self, qtbot, webengineview, setup):
|
||||
page = webengineview.page()
|
||||
page.runJavaScript(setup.setup_script)
|
||||
|
||||
with qtbot.wait_callback() as callback:
|
||||
page.runJavaScript(setup.test_script, callback)
|
||||
callback.assert_called_with(setup.expected)
|
||||
# What we expect the script to report back.
|
||||
expected = ["global", "global", "shadowed", "shadowed", "global", "global"]
|
||||
|
||||
# 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
|
||||
xfail = False
|
||||
if (js_tester.tab.backend == usertypes.Backend.QtWebKit and
|
||||
version.qWebKitVersion() == '602.1'):
|
||||
expected[-1] = 'shadowed'
|
||||
expected[-2] = 'shadowed'
|
||||
xfail = True
|
||||
|
||||
js_tester.run(setup_script)
|
||||
js_tester.run(test_script, expected=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()
|
||||
|
||||
@pytest.fixture
|
||||
def setup(self):
|
||||
# pylint: disable=attribute-defined-outside-init
|
||||
class SetupData:
|
||||
pass
|
||||
ret = SetupData()
|
||||
# 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()
|
||||
|
||||
# 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"])
|
||||
|
|
|
|||
Loading…
Reference in New Issue