summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorÁrni Dagur <arni@dagur.eu>2020-12-19 20:30:17 +0000
committerÁrni Dagur <arni@dagur.eu>2020-12-19 20:30:17 +0000
commit729d6c9d8fd2c8420f97a2a80eca2875c0af5126 (patch)
tree114321b7ce68a3ef233016c340ab4c1c61b63152
parentc72e181ed3b2eb2f28c4a7eb97eba10c5b91f157 (diff)
parent8e7f24bc0c6cdb333fef1a4a72d659aaeb7bbf09 (diff)
downloadqutebrowser-729d6c9d8fd2c8420f97a2a80eca2875c0af5126.tar.gz
qutebrowser-729d6c9d8fd2c8420f97a2a80eca2875c0af5126.zip
Merge branch 'master' into more-sophisticated-adblock
-rw-r--r--.bumpversion.cfg2
-rw-r--r--.github/workflows/ci.yml14
-rw-r--r--.github/workflows/docker.yml1
-rw-r--r--.gitignore1
-rw-r--r--README.asciidoc5
-rw-r--r--doc/changelog.asciidoc43
-rw-r--r--doc/help/commands.asciidoc9
-rw-r--r--doc/qutebrowser.1.asciidoc3
-rw-r--r--doc/userscripts.asciidoc2
-rw-r--r--misc/org.qutebrowser.qutebrowser.appdata.xml1
-rw-r--r--misc/requirements/requirements-check-manifest.txt3
-rw-r--r--misc/requirements/requirements-dev.txt6
-rw-r--r--misc/requirements/requirements-flake8.txt4
-rw-r--r--misc/requirements/requirements-mypy.txt4
-rw-r--r--misc/requirements/requirements-pylint.txt4
-rw-r--r--misc/requirements/requirements-pyqt-5.15.0.txt5
-rw-r--r--misc/requirements/requirements-pyqt-5.15.0.txt-raw4
-rw-r--r--misc/requirements/requirements-pyroma.txt2
-rw-r--r--misc/requirements/requirements-qutebrowser.txt-raw3
-rw-r--r--misc/requirements/requirements-sphinx.txt7
-rw-r--r--misc/requirements/requirements-tests.txt18
-rw-r--r--misc/requirements/requirements-tox.txt6
-rw-r--r--misc/userscripts/README.md7
-rwxr-xr-xmisc/userscripts/format_json2
-rwxr-xr-xmisc/userscripts/kodi111
-rwxr-xr-xmisc/userscripts/qr8
-rwxr-xr-xmisc/userscripts/readability-js4
-rw-r--r--qutebrowser/__init__.py2
-rw-r--r--qutebrowser/app.py5
-rw-r--r--qutebrowser/browser/browsertab.py9
-rw-r--r--qutebrowser/browser/commands.py3
-rw-r--r--qutebrowser/browser/greasemonkey.py45
-rw-r--r--qutebrowser/browser/shared.py6
-rw-r--r--qutebrowser/browser/webengine/darkmode.py10
-rw-r--r--qutebrowser/browser/webengine/webenginesettings.py139
-rw-r--r--qutebrowser/browser/webengine/webenginetab.py57
-rw-r--r--qutebrowser/commands/userscripts.py1
-rw-r--r--qutebrowser/completion/models/listcategory.py6
-rw-r--r--qutebrowser/config/qtargs.py10
-rw-r--r--qutebrowser/misc/earlyinit.py4
-rw-r--r--qutebrowser/misc/utilcmds.py10
-rw-r--r--qutebrowser/qutebrowser.py5
-rw-r--r--qutebrowser/utils/jinja.py3
-rw-r--r--qutebrowser/utils/utils.py41
-rw-r--r--qutebrowser/utils/version.py1
-rw-r--r--requirements.txt3
-rwxr-xr-xscripts/dev/build_release.py8
-rw-r--r--scripts/dev/check_coverage.py2
-rw-r--r--scripts/dev/ci/docker/Dockerfile.j22
-rw-r--r--scripts/dev/ci/problemmatchers.py2
-rw-r--r--scripts/dev/misc_checks.py31
-rw-r--r--scripts/dev/recompile_requirements.py40
-rw-r--r--scripts/dev/update_version.py2
-rw-r--r--scripts/mkvenv.py14
-rwxr-xr-xsetup.py3
-rw-r--r--tests/conftest.py2
-rw-r--r--tests/end2end/data/adblock/external_logo.html15
-rw-r--r--tests/end2end/data/adblock/qutebrowser1
-rw-r--r--tests/end2end/features/private.feature46
-rw-r--r--tests/end2end/fixtures/quteprocess.py4
-rw-r--r--tests/end2end/fixtures/testprocess.py5
-rw-r--r--tests/end2end/test_invocations.py14
-rw-r--r--tests/helpers/fixtures.py2
-rw-r--r--tests/unit/browser/webengine/test_darkmode.py20
-rw-r--r--tests/unit/browser/webengine/test_webenginesettings.py64
-rw-r--r--tests/unit/config/test_qtargs.py23
-rw-r--r--tests/unit/javascript/test_greasemonkey.py7
-rw-r--r--tests/unit/scripts/test_problemmatchers.py38
-rw-r--r--tests/unit/utils/test_utils.py49
-rw-r--r--tests/unit/utils/test_version.py2
-rw-r--r--tox.ini8
71 files changed, 773 insertions, 270 deletions
diff --git a/.bumpversion.cfg b/.bumpversion.cfg
index fa5e2a66e..2e62dc15b 100644
--- a/.bumpversion.cfg
+++ b/.bumpversion.cfg
@@ -1,5 +1,5 @@
[bumpversion]
-current_version = 1.14.0
+current_version = 1.14.1
commit = True
message = Release v{new_version}
tag = True
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 8e1f1341f..fbab9ee9c 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -90,7 +90,7 @@ jobs:
- uses: actions/checkout@v2
- name: Set up problem matchers
run: "python scripts/dev/ci/problemmatchers.py py38 ${{ runner.temp }}"
- - run: tox -e py38
+ - run: tox -e py
tests:
if: "!contains(github.event.head_commit.message, '[ci skip]')"
@@ -112,6 +112,10 @@ jobs:
- testenv: py38-pyqt514
os: ubuntu-20.04
python: 3.8
+ ### PyQt 5.15.0 (Python 3.9)
+ - testenv: py39-pyqt5150
+ os: ubuntu-20.04
+ python: 3.9
### PyQt 5.15 (Python 3.9, with coverage)
- testenv: py39-pyqt515-cov
os: ubuntu-20.04
@@ -173,14 +177,6 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v2
- with:
- # We must fetch at least the immediate parents so that if this is
- # a pull request then we can checkout the head.
- fetch-depth: 2
- # If this run was triggered by a pull request event, then checkout
- # the head of the pull request instead of the merge commit.
- - run: git checkout HEAD^2
- if: ${{ github.event_name == 'pull_request' }}
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml
index 03510ad6e..06707eb3f 100644
--- a/.github/workflows/docker.yml
+++ b/.github/workflows/docker.yml
@@ -7,6 +7,7 @@ on:
jobs:
docker:
+ if: "github.repository == 'qutebrowser/qutebrowser'"
runs-on: ubuntu-20.04
strategy:
matrix:
diff --git a/.gitignore b/.gitignore
index 50c67dee4..31c4ca3b7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -35,6 +35,7 @@ Sessionx.vim
/.pytest_cache
/.testmondata
/.hypothesis
+/.benchmarks
.mypy_cache
/prof
/venv
diff --git a/README.asciidoc b/README.asciidoc
index 203327127..2e70257d3 100644
--- a/README.asciidoc
+++ b/README.asciidoc
@@ -125,12 +125,15 @@ The following software and libraries are required to run qutebrowser:
sensitive data.**
* https://www.riverbankcomputing.com/software/pyqt/intro[PyQt] 5.12.0 or newer
for Python 3
-* https://pypi.python.org/pypi/setuptools/[pkg_resources/setuptools]
+* https://pypi.python.org/pypi/setuptools/[pkg_resources/setuptools] (being
+ phased out for qutebrowser v2.0.0)
* https://fdik.org/pyPEG/[pyPEG2]
* http://jinja.pocoo.org/[jinja2]
* http://pygments.org/[pygments]
* https://github.com/yaml/pyyaml[PyYAML]
* https://www.attrs.org/[attrs]
+* https://importlib-resources.readthedocs.io/[importlib_resources] (on Python
+ 3.8 or older)
The following libraries are optional:
diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc
index 9cfa73806..5a0ce3938 100644
--- a/doc/changelog.asciidoc
+++ b/doc/changelog.asciidoc
@@ -42,6 +42,12 @@ Major changes
still relying on it. The `cssutils` project is also dead upstream, with its
repository being gone after Bitbucket
https://bitbucket.org/blog/sunsetting-mercurial-support-in-bitbucket[removed Mercurial support].
+- TODO: The former dependency on the `pkg_resources` module (part of the
+ `setuptools` project) got dropped.
+- A new dependency on the `importlib_resources` module got introduced for
+ Python versions up to and including 3.8. Note that the stdlib
+ `importlib.resources` module for Python 3.7 and 3.8 is missing the needed APIs,
+ thus requiring the backports for those versions as well.
Removed
~~~~~~~
@@ -64,6 +70,12 @@ Added
settings might stop working. As a (currently undocumented) escape hatch, this
version adds a `QUTE_DARKMODE_VARIANT=qt_515_2` environment variable which can
be set to get the correct behavior in (transitive) situations like this.
+- New `--desktop-file-name` commandline argument, which can be used to customize
+ the desktop filename passed to Qt (which is used to set the `app_id` on
+ Wayland).
+- New userscripts:
+ - `kodi` to play videos in Kodi
+ - `qr` to generate a QR code of the current URL
Changed
~~~~~~~
@@ -83,14 +95,30 @@ Changed
pre-selected in the prompt shown by qutebrowser.
- URLs such as `::1/foo` are now handled as a search term or local file rather
than IPv6. Use `[::1]/foo` to force parsing as IPv6 instead.
+- The `mkvenv.py` script now runs a "smoke test" after setting up the virtual
+ environment to ensure it's working as expected. If necessary, the test can be
+ skipped via a new `--skip-smoke-test` flag.
+- Both qutebrowser userscripts and Greasemonkey scripts are now additionally
+ picked up from qutebrowser's config directory (the `userscripts` and
+ `greasemonkey` subdirectories of e.g. `~/.config/qutebrowser/`) rather than only
+ the data directory (the same subdirectories of e.g.
+ `~/.local/share/qutebrowser/`).
+- The `:later` command now understands a time specification like `5m` or
+ `1h5m2s`, rather than just taking milliseconds.
Fixed
~~~~~
- With interpolated color settings (`colors.tabs.indicator.*` and
`colors.downloads.*`), the alpha channel is now handled correctly.
-
-v1.14.1 (unreleased)
+- The `format_json` userscript now uses `env` in its shebang, making it work
+ correctly on systems where `bash` isn't located in `/bin`.
+- TODO: Due to a long-standing bug in the `pkg_resources` dependency, it caused
+ qutebrowser's startup to slow down by around 150ms-1s (heavily depending on
+ the system). Since the dependency is now removed, qutebrowser's startup time
+ thus improved.
+
+v1.14.1 (2020-12-04)
--------------------
Added
@@ -178,8 +206,17 @@ Fixed
installed, it was suggested to install `qt5-webengine-devtools`, which does
not, in fact, exist. It's now correctly suggested to install
`qt5-qtwebengine-devtools` instead.
+- With Qt 5.15.2, lines/borders coming from the `readability-js` userscript
+ were invisible. This is now fixed by changing the border color to grey (with all
+ Qt versions).
+- Due to changes in the underlying Chromium, the
+ `colors.webpage.prefers_color_scheme_dark` setting broke with Qt 5.15.2. It now
+ works properly again.
+- A bug in the `pkg_resources` module used by qutebrowser caused deprecation
+ warnings to appear on start with Python 3.9 on some setups. Those are now
+ hidden.
- Minor performance improvements.
-- (TODO) Fix for various functionality breaking in private windows with v1.14.0,
+- Fix for various functionality breaking in private windows with v1.14.0,
after the last private window is closed. This includes:
* Ad blocking
* Downloads
diff --git a/doc/help/commands.asciidoc b/doc/help/commands.asciidoc
index 3103bfa15..f60f30169 100644
--- a/doc/help/commands.asciidoc
+++ b/doc/help/commands.asciidoc
@@ -597,7 +597,7 @@ Syntax: +:greasemonkey-reload [*--force*]+
Re-read Greasemonkey scripts from disk.
-The scripts are read from a 'greasemonkey' subdirectory in qutebrowser's data directory (see `:version`).
+The scripts are read from a 'greasemonkey' subdirectory in qutebrowser's data or config directories (see `:version`).
==== optional arguments
* +*-f*+, +*--force*+: For any scripts that have required dependencies, re-download them.
@@ -784,12 +784,12 @@ Jump to the mark named by `key`.
[[later]]
=== later
-Syntax: +:later 'ms' 'command'+
+Syntax: +:later 'duration' 'command'+
Execute a command after some time.
==== positional arguments
-* +'ms'+: How many milliseconds to wait.
+* +'duration'+: Duration to wait in format XhYmZs or a number for milliseconds.
* +'command'+: The command to run, with optional args.
==== note
@@ -1308,7 +1308,8 @@ Note that the command is *not* run in a shell, so things like `$VAR` or `> outpu
* +*-v*+, +*--verbose*+: Show notifications when the command started/exited.
* +*-o*+, +*--output*+: Show the output in a new tab.
* +*-m*+, +*--output-messages*+: Show the output as messages.
-* +*-d*+, +*--detach*+: Whether the command should be detached from qutebrowser.
+* +*-d*+, +*--detach*+: Detach the command from qutebrowser so that it continues running when qutebrowser quits.
+
==== count
Given to userscripts as $QUTE_COUNT.
diff --git a/doc/qutebrowser.1.asciidoc b/doc/qutebrowser.1.asciidoc
index 777eddc65..83e7986bc 100644
--- a/doc/qutebrowser.1.asciidoc
+++ b/doc/qutebrowser.1.asciidoc
@@ -62,6 +62,9 @@ show it.
*--backend* '{webkit,webengine}'::
Which backend to use.
+*--desktop-file-name* 'DESKTOP_FILE_NAME'::
+ Set the base name of the desktop entry for this application. Used to set the app_id under Wayland. See https://doc.qt.io/qt-5/qguiapplication.html#desktopFileName-prop
+
=== debug arguments
*-l* '{critical,error,warning,info,debug,vdebug}', *--loglevel* '{critical,error,warning,info,debug,vdebug}'::
Override the configured console loglevel
diff --git a/doc/userscripts.asciidoc b/doc/userscripts.asciidoc
index 2dc34402d..9bbc68ce0 100644
--- a/doc/userscripts.asciidoc
+++ b/doc/userscripts.asciidoc
@@ -18,7 +18,7 @@ mpv, a simple key binding to something like `:spawn mpv {url}` should suffice.
Also note userscripts need to have the executable bit set (`chmod +x`) for
qutebrowser to run them.
-To call a userscript, it needs to be stored in your data directory under
+To call a userscript, it needs to be stored in your config or data directory under
`userscripts` (for example: `~/.local/share/qutebrowser/userscripts/myscript`),
or just use an absolute path.
diff --git a/misc/org.qutebrowser.qutebrowser.appdata.xml b/misc/org.qutebrowser.qutebrowser.appdata.xml
index f0885b8ed..601093418 100644
--- a/misc/org.qutebrowser.qutebrowser.appdata.xml
+++ b/misc/org.qutebrowser.qutebrowser.appdata.xml
@@ -44,6 +44,7 @@
</content_rating>
<releases>
<!-- Add new releases here -->
+<release version="1.14.1" date="2020-12-04"/>
<release version="1.14.0" date="2020-10-15"/>
<release version="1.13.1" date="2020-07-17"/>
<release version="1.13.0" date="2020-06-26"/>
diff --git a/misc/requirements/requirements-check-manifest.txt b/misc/requirements/requirements-check-manifest.txt
index 8b8f6ba1a..f2a5f00dd 100644
--- a/misc/requirements/requirements-check-manifest.txt
+++ b/misc/requirements/requirements-check-manifest.txt
@@ -2,8 +2,7 @@
build==0.1.0
check-manifest==0.45
-packaging==20.4
+packaging==20.8
pep517==0.9.1
pyparsing==2.4.7
-six==1.15.0
toml==0.10.2
diff --git a/misc/requirements/requirements-dev.txt b/misc/requirements/requirements-dev.txt
index 6bc4325cf..f58af8072 100644
--- a/misc/requirements/requirements-dev.txt
+++ b/misc/requirements/requirements-dev.txt
@@ -1,17 +1,17 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
bump2version==1.0.1
-certifi==2020.11.8
+certifi==2020.12.5
cffi==1.14.4
chardet==3.0.4
colorama==0.4.4
-cryptography==3.2.1
+cryptography==3.3.1
github3.py==1.3.0
hunter==3.3.1
idna==2.10
jwcrypto==0.8
manhole==1.6.0
-packaging==20.4
+packaging==20.8
pycparser==2.20
Pympler==0.9
pyparsing==2.4.7
diff --git a/misc/requirements/requirements-flake8.txt b/misc/requirements/requirements-flake8.txt
index d77bdbbf5..6ed02ad61 100644
--- a/misc/requirements/requirements-flake8.txt
+++ b/misc/requirements/requirements-flake8.txt
@@ -6,14 +6,14 @@ flake8-bugbear==20.11.1
flake8-builtins==1.5.3
flake8-comprehensions==3.3.0
flake8-copyright==0.2.2
-flake8-debugger==3.2.1
+flake8-debugger==4.0.0
flake8-deprecated==1.3
flake8-docstrings==1.5.0
flake8-future-import==0.4.6
flake8-mock==0.3
flake8-polyfill==1.0.2
flake8-string-format==0.3.0
-flake8-tidy-imports==4.1.0
+flake8-tidy-imports==4.2.0
flake8-tuple==0.4.1
mccabe==0.6.1
pep8-naming==0.11.1
diff --git a/misc/requirements/requirements-mypy.txt b/misc/requirements/requirements-mypy.txt
index b564cfa18..d640851c9 100644
--- a/misc/requirements/requirements-mypy.txt
+++ b/misc/requirements/requirements-mypy.txt
@@ -4,12 +4,12 @@ diff-cover==4.0.1
inflect==5.0.2
Jinja2==2.11.2
jinja2-pluralize==0.3.0
-lxml==4.6.1
+lxml==4.6.2
MarkupSafe==1.1.1
mypy==0.790
mypy-extensions==0.4.3
pluggy==0.13.1
-Pygments==2.7.2
+Pygments==2.7.3
-e git+https://github.com/stlehmann/PyQt5-stubs.git@704207e90bee7b36ec9861dfa6b39f06a27c6718#egg=PyQt5_stubs
typed-ast==1.4.1
typing-extensions==3.7.4.3
diff --git a/misc/requirements/requirements-pylint.txt b/misc/requirements/requirements-pylint.txt
index e3856a40a..02165c497 100644
--- a/misc/requirements/requirements-pylint.txt
+++ b/misc/requirements/requirements-pylint.txt
@@ -1,10 +1,10 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
astroid==2.3.3 # rq.filter: < 2.4
-certifi==2020.11.8
+certifi==2020.12.5
cffi==1.14.4
chardet==3.0.4
-cryptography==3.2.1
+cryptography==3.3.1
github3.py==1.3.0
idna==2.10
isort==4.3.21
diff --git a/misc/requirements/requirements-pyqt-5.15.0.txt b/misc/requirements/requirements-pyqt-5.15.0.txt
new file mode 100644
index 000000000..b9ee53f65
--- /dev/null
+++ b/misc/requirements/requirements-pyqt-5.15.0.txt
@@ -0,0 +1,5 @@
+# This file is automatically generated by scripts/dev/recompile_requirements.py
+
+PyQt5==5.15.0 # rq.filter: == 5.15.0
+PyQt5-sip==12.8.1
+PyQtWebEngine==5.15.0 # rq.filter: == 5.15.0
diff --git a/misc/requirements/requirements-pyqt-5.15.0.txt-raw b/misc/requirements/requirements-pyqt-5.15.0.txt-raw
new file mode 100644
index 000000000..12d6adb7d
--- /dev/null
+++ b/misc/requirements/requirements-pyqt-5.15.0.txt-raw
@@ -0,0 +1,4 @@
+#@ filter: PyQt5 == 5.15.0
+#@ filter: PyQtWebEngine == 5.15.0
+PyQt5 == 5.15.0
+PyQtWebEngine == 5.15.0
diff --git a/misc/requirements/requirements-pyroma.txt b/misc/requirements/requirements-pyroma.txt
index 1a2dbde7f..d0568a7df 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.7.2
+Pygments==2.7.3
pyroma==2.6
diff --git a/misc/requirements/requirements-qutebrowser.txt-raw b/misc/requirements/requirements-qutebrowser.txt-raw
index b43ee7f6e..2d527aeef 100644
--- a/misc/requirements/requirements-qutebrowser.txt-raw
+++ b/misc/requirements/requirements-qutebrowser.txt-raw
@@ -5,3 +5,6 @@ PyYAML
colorama
attrs
adblock # Optional, for improved adblocking
+importlib-resources
+
+#@ markers: importlib-resources python_version<"3.9"
diff --git a/misc/requirements/requirements-sphinx.txt b/misc/requirements/requirements-sphinx.txt
index 463bb8e73..2cbf8d57d 100644
--- a/misc/requirements/requirements-sphinx.txt
+++ b/misc/requirements/requirements-sphinx.txt
@@ -2,19 +2,18 @@
alabaster==0.7.12
Babel==2.9.0
-certifi==2020.11.8
+certifi==2020.12.5
chardet==3.0.4
docutils==0.16
idna==2.10
imagesize==1.2.0
Jinja2==2.11.2
MarkupSafe==1.1.1
-packaging==20.4
-Pygments==2.7.2
+packaging==20.8
+Pygments==2.7.3
pyparsing==2.4.7
pytz==2020.4
requests==2.25.0
-six==1.15.0
snowballstemmer==2.0.0
Sphinx==3.3.1
sphinxcontrib-applehelp==1.0.2
diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt
index 8b4eb0fba..28fd44126 100644
--- a/misc/requirements/requirements-tests.txt
+++ b/misc/requirements/requirements-tests.txt
@@ -3,9 +3,9 @@
apipkg==1.5
attrs==20.3.0
beautifulsoup4==4.9.3
-certifi==2020.11.8
+certifi==2020.12.5
chardet==3.0.4
-cheroot==8.4.7
+cheroot==8.5.1
click==7.1.2
# colorama==0.4.4
coverage==5.3
@@ -15,7 +15,7 @@ filelock==3.0.12
Flask==1.1.2
glob2==0.7
hunter==3.3.1
-hypothesis==5.41.3
+hypothesis==5.43.3
icdiff==1.9.1
idna==2.10
iniconfig==1.1.1
@@ -26,17 +26,17 @@ Mako==1.1.3
manhole==1.6.0
# MarkupSafe==1.1.1
more-itertools==8.6.0
-packaging==20.4
+packaging==20.8
parse==1.18.0
parse-type==0.5.2
pluggy==0.13.1
pprintpp==0.4.0
-py==1.9.0
+py==1.10.0
py-cpuinfo==7.0.0
-Pygments==2.7.2
+Pygments==2.7.3
pyparsing==2.4.7
-pytest==6.1.2
-pytest-bdd==4.0.1
+pytest==6.2.0
+pytest-bdd==4.0.2
pytest-benchmark==3.2.3
pytest-clarity==0.3.0a0
pytest-cov==2.10.1
@@ -54,7 +54,7 @@ requests==2.25.0
requests-file==1.5.1
six==1.15.0
sortedcontainers==2.3.0
-soupsieve==2.0.1
+soupsieve==2.1
termcolor==1.1.0
tldextract==3.1.0
toml==0.10.2
diff --git a/misc/requirements/requirements-tox.txt b/misc/requirements/requirements-tox.txt
index 95dbdd654..c72393868 100644
--- a/misc/requirements/requirements-tox.txt
+++ b/misc/requirements/requirements-tox.txt
@@ -3,11 +3,11 @@
appdirs==1.4.4
distlib==0.3.1
filelock==3.0.12
-packaging==20.4
+packaging==20.8
pluggy==0.13.1
-py==1.9.0
+py==1.10.0
pyparsing==2.4.7
six==1.15.0
toml==0.10.2
tox==3.20.1
-virtualenv==20.2.1
+virtualenv==20.2.2
diff --git a/misc/userscripts/README.md b/misc/userscripts/README.md
index a17f7164c..669bfa664 100644
--- a/misc/userscripts/README.md
+++ b/misc/userscripts/README.md
@@ -24,7 +24,7 @@ The following userscripts are included in the current directory.
- [qutedmenu](./qutedmenu): Handle open -s && open -t with bemenu.
- [readability](./readability): Executes python-readability on current page and
opens the summary as new tab.
-- [readability-js](./readability-js): Processes the current page with the readability
+- [readability-js](./readability-js): Processes the current page with the readability
library used in Firefox Reader View and opens the summary as new tab.
- [ripbang](./ripbang): Adds DuckDuckGo bang as searchengine.
- [rss](./rss): Keeps track of URLs in RSS feeds and opens new ones.
@@ -32,6 +32,9 @@ The following userscripts are included in the current directory.
- [tor_identity](./tor_identity): Change your tor identity.
- [view_in_mpv](./view_in_mpv): Views the current web page in mpv using
sensible mpv-flags.
+- [qr](./qr): Show a QR code for the current webpage via
+ [qrencode](https://fukuchi.org/works/qrencode/).
+- [kodi](./kodi): Play videos in Kodi.
[castnow]: https://github.com/xat/castnow
[youtube-dl]: https://rg3.github.io/youtube-dl/
@@ -67,6 +70,8 @@ The following userscripts can be found on their own repositories.
and retrieve they when you want.
- [doi](https://github.com/cadadr/configuration/blob/master/qutebrowser/userscripts/doi):
Opens DOIs on Sci-Hub.
+- [1password](https://github.com/tomoakley/dotfiles/blob/master/qutebrowser/userscripts/1password):
+ Integration with 1password on macOS.
[Zotero]: https://www.zotero.org/
[Pocket]: https://getpocket.com/
diff --git a/misc/userscripts/format_json b/misc/userscripts/format_json
index 541408c70..8a83c25fa 100755
--- a/misc/userscripts/format_json
+++ b/misc/userscripts/format_json
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/usr/bin/env bash
set -euo pipefail
#
# Behavior:
diff --git a/misc/userscripts/kodi b/misc/userscripts/kodi
new file mode 100755
index 000000000..63fcc81fe
--- /dev/null
+++ b/misc/userscripts/kodi
@@ -0,0 +1,111 @@
+#!/usr/bin/env bash
+#
+# Behavior:
+# A qutebrowser userscript that plays Twitch, YouTube or Vimeo videos in Kodi via its
+# API.
+#
+# Requirements:
+# awk
+# bash
+# curl
+#
+# Kodi setup:
+# Settings -> Services -> Control
+# enable 'Allow remote control via HTTP'
+# set Username and Password
+# enable 'Allow remote control from applications on this system'
+# Optional yet recommended, setup SSL within Kodi over via a proxy webserver
+#
+# userscript setup:
+# create ~/.config/qutebrowser/kodi_rc with host and authentication information like:
+#
+# HOST="http://127.0.0.1:8080"
+# or
+# HOST="https://kodi.example.com"
+#
+# AUTH="user:password"
+# or
+# AUTH="bas64authenticationinformation"
+#
+# The base64 authentication is the output of
+# `echo -ne "user:password" |base64 --wrap 0`
+# reminder base64 is not encryption
+#
+# For vim users you might want to add '# vim: set nospell filetype=bash' to the
+# kodi_rc file.
+#
+# qutebrowser setup:
+# in ~/.config/qutebrowser/config.py add something like
+#
+# to send video link via hints:
+# config.bind('X', 'hint links userscript kodi')
+# to send current URL:
+# config.bind('X', 'spawn --userscript kodi')
+#
+# troubleshooting:
+# Errors detected within this userscript with have an exit of 231. All other exit
+# codes will come from curl or awk. To test that the kodi_rc file is set up
+# correctly, run the following command. It will display a 'It works!' notification within Kodi.
+#
+# source ~/.config/qutebrowser/kodi_rc ; curl --request POST "$HOST"/jsonrpc --header "Authorization: Basic $AUTH" --header "Content-Type: application/json" --data '{"id":1,"jsonrpc":"2.0","method":"GUI.ShowNotification","params":{"title":"It works!","message":"both HOST and AUTH are correct"}}'
+#
+# In case you miss the notification in Kodi the successful response is:
+#
+# {"id":1,"jsonrpc":"2.0","result":"OK"}
+#
+# Note, curl will display errors for some problems, but not all.
+
+if [[ -z "$QUTE_FIFO" ]] ; then
+ echo "This script is designed to run as a qutebrowser userscript, not as a standalone script."
+ exit 231
+fi
+
+# configuration loading adapted from the password_fill userscript
+QUTE_CONFIG_DIR=${QUTE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/qutebrowser/}
+KODI_CONFIG=${PWFILL_CONFIG:-${QUTE_CONFIG_DIR}/kodi_rc}
+if [[ -f "$KODI_CONFIG" ]] ; then
+ # shellcheck source=/dev/null
+ source "$KODI_CONFIG"
+ if [[ -z "$HOST" || -z "$AUTH" ]] ; then
+ echo "message-error 'HOST and/or AUTH not set in $KODI_CONFIG'" > "$QUTE_FIFO"
+ exit 231
+ fi
+else
+ echo "message-error '$KODI_CONFIG not found'" > "$QUTE_FIFO"
+ exit 231
+fi
+
+# get real URL from twitter links
+if [[ "$QUTE_URL" =~ ^https:\/\/t\.co ]] ; then
+ QUTE_URL=$(curl -o /dev/null --silent --head --write-out '%{redirect_url}' "$QUTE_URL" )
+fi
+
+# regex from https://github.com/dirkjanm/firefox-send-to-xbmc/blob/master/webextension/main.js
+if [[ "$QUTE_URL" =~ ^.*twitch.tv\/([a-zA-Z0-9_]+)$ ]] ; then
+ NAME="${BASH_REMATCH[1]}"
+ JSON='{"jsonrpc":"2.0","method":"Player.Open","params":{"item":{"file":"plugin://plugin.video.twitch/?mode=play&channel_name='$NAME'"}},"id":"2"}'
+
+elif [[ "$QUTE_URL" =~ ^.*twitch.tv\/videos\/([0-9]+)$ ]] ; then
+ NAME="${BASH_REMATCH[1]}"
+ JSON='{"jsonrpc":"2.0","method":"Player.Open","params":{"item":{"file":"plugin://plugin.video.twitch/?mode=play&video_id='$NAME'"}},"id":"2"}'
+
+elif [[ "$QUTE_URL" =~ ^.*vimeo.com\/([0-9]+) ]] ; then
+ NAME="${BASH_REMATCH[1]}"
+ JSON='{"jsonrpc":"2.0","method":"Player.Open","params":{"item":{"file":"plugin://plugin.video.vimeo/play/?video_id='$NAME'"}},"id":"2"}'
+
+elif [[ "$QUTE_URL" =~ ^.*youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=([^#\&\?]*).* ]] ; then
+ NAME="${BASH_REMATCH[1]}"
+ JSON='{"jsonrpc":"2.0","method":"Player.Open","params":{"item":{"file":"plugin://plugin.video.youtube/play/?video_id='$NAME'"}},"id":"2"}'
+fi
+
+if [[ "$JSON" ]] ; then
+ curl \
+ --request POST "$HOST"/jsonrpc \
+ --header "Authorization: Basic $AUTH" \
+ --header "Content-Type: application/json" \
+ --data "$JSON" \
+ --silent > /dev/null
+else
+ URL=$(echo "$QUTE_URL" |awk -F/ '{print $3}')
+ echo "message-warning 'kodi userscript does not support this $URL'" > "$QUTE_FIFO"
+fi
diff --git a/misc/userscripts/qr b/misc/userscripts/qr
new file mode 100755
index 000000000..84215249b
--- /dev/null
+++ b/misc/userscripts/qr
@@ -0,0 +1,8 @@
+#!/usr/bin/env bash
+
+pngfile=$(mktemp --suffix=.png)
+trap 'rm -f "$pngfile"' EXIT
+
+qrencode -t PNG -o "$pngfile" -s 10 "$QUTE_URL"
+echo ":open -t file:///$pngfile" >> "$QUTE_FIFO"
+sleep 1 # give qutebrowser time to open the file before it gets removed
diff --git a/misc/userscripts/readability-js b/misc/userscripts/readability-js
index e189e5ee4..310d1c081 100755
--- a/misc/userscripts/readability-js
+++ b/misc/userscripts/readability-js
@@ -57,7 +57,7 @@ const HEADER = `
table,
th,
td {
- border: 1px solid currentColor;
+ border: 1px solid grey;
border-collapse: collapse;
padding: 6px;
vertical-align: top;
@@ -77,7 +77,7 @@ const HEADER = `
background-color: #dddddd;
}
blockquote {
- border-inline-start: 2px solid #333333 !important;
+ border-inline-start: 2px solid grey !important;
padding: 0;
padding-inline-start: 16px;
margin-inline-start: 24px;
diff --git a/qutebrowser/__init__.py b/qutebrowser/__init__.py
index b5b4b8c7c..dfb1febd4 100644
--- a/qutebrowser/__init__.py
+++ b/qutebrowser/__init__.py
@@ -26,7 +26,7 @@ __copyright__ = "Copyright 2014-2020 Florian Bruhin (The Compiler)"
__license__ = "GPL"
__maintainer__ = __author__
__email__ = "mail@qutebrowser.org"
-__version__ = "1.14.0"
+__version__ = "1.14.1"
__version_info__ = tuple(int(part) for part in __version__.split('.'))
__description__ = "A keyboard-driven, vim-like browser based on PyQt5."
diff --git a/qutebrowser/app.py b/qutebrowser/app.py
index f722c580f..76d52470a 100644
--- a/qutebrowser/app.py
+++ b/qutebrowser/app.py
@@ -96,7 +96,8 @@ def run(args):
q_app = Application(args)
q_app.setOrganizationName("qutebrowser")
q_app.setApplicationName("qutebrowser")
- q_app.setDesktopFileName("org.qutebrowser.qutebrowser")
+ # Default DesktopFileName is org.qutebrowser.qutebrowser, set in `get_argparser()`
+ q_app.setDesktopFileName(args.desktop_file_name)
q_app.setApplicationVersion(qutebrowser.__version__)
if args.version:
@@ -491,8 +492,6 @@ def _init_modules(*, args):
log.init.debug("Misc initialization...")
macros.init()
windowundo.init()
- # Init backend-specific stuff
- browsertab.init()
class Application(QApplication):
diff --git a/qutebrowser/browser/browsertab.py b/qutebrowser/browser/browsertab.py
index dd21cd0e0..bf25455a6 100644
--- a/qutebrowser/browser/browsertab.py
+++ b/qutebrowser/browser/browsertab.py
@@ -83,15 +83,6 @@ def create(win_id: int,
parent=parent)
-def init() -> None:
- """Initialize backend-specific modules."""
- if objects.backend == usertypes.Backend.QtWebEngine:
- from qutebrowser.browser.webengine import webenginetab
- webenginetab.init()
- return
- assert objects.backend == usertypes.Backend.QtWebKit, objects.backend
-
-
class WebTabError(Exception):
"""Base class for various errors."""
diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py
index 34c078d89..18777e250 100644
--- a/qutebrowser/browser/commands.py
+++ b/qutebrowser/browser/commands.py
@@ -1057,7 +1057,8 @@ class CommandDispatcher:
verbose: Show notifications when the command started/exited.
output: Show the output in a new tab.
output_messages: Show the output as messages.
- detach: Whether the command should be detached from qutebrowser.
+ detach: Detach the command from qutebrowser so that it continues
+ running when qutebrowser quits.
cmdline: The commandline to execute.
count: Given to userscripts as $QUTE_COUNT.
"""
diff --git a/qutebrowser/browser/greasemonkey.py b/qutebrowser/browser/greasemonkey.py
index 0305bd589..df8b2b0c2 100644
--- a/qutebrowser/browser/greasemonkey.py
+++ b/qutebrowser/browser/greasemonkey.py
@@ -41,9 +41,12 @@ from qutebrowser.misc import objects
gm_manager = cast('GreasemonkeyManager', None)
-def _scripts_dir():
+def _scripts_dirs():
"""Get the directory of the scripts."""
- return os.path.join(standarddir.data(), 'greasemonkey')
+ return [
+ os.path.join(standarddir.data(), 'greasemonkey'),
+ os.path.join(standarddir.config(), 'greasemonkey'),
+ ]
class GreasemonkeyScript:
@@ -277,18 +280,19 @@ class GreasemonkeyManager(QObject):
self._run_end = []
self._run_idle = []
- scripts_dir = os.path.abspath(_scripts_dir())
- log.greasemonkey.debug("Reading scripts from: {}".format(scripts_dir))
- for script_filename in glob.glob(os.path.join(scripts_dir, '*.js')):
- if not os.path.isfile(script_filename):
- continue
- script_path = os.path.join(scripts_dir, script_filename)
- with open(script_path, encoding='utf-8-sig') as script_file:
- script = GreasemonkeyScript.parse(script_file.read(),
- script_filename)
- if not script.name:
- script.name = script_filename
- self.add_script(script, force)
+ for scripts_dir in _scripts_dirs():
+ scripts_dir = os.path.abspath(scripts_dir)
+ log.greasemonkey.debug("Reading scripts from: {}".format(scripts_dir))
+ for script_filename in glob.glob(os.path.join(scripts_dir, '*.js')):
+ if not os.path.isfile(script_filename):
+ continue
+ script_path = os.path.join(scripts_dir, script_filename)
+ with open(script_path, encoding='utf-8-sig') as script_file:
+ script = GreasemonkeyScript.parse(script_file.read(),
+ script_filename)
+ if not script.name:
+ script.name = script_filename
+ self.add_script(script, force)
self.scripts_reloaded.emit()
def add_script(self, script, force=False):
@@ -325,7 +329,7 @@ class GreasemonkeyManager(QObject):
log.greasemonkey.debug("Loaded script: {}".format(script.name))
def _required_url_to_file_path(self, url):
- requires_dir = os.path.join(_scripts_dir(), 'requires')
+ requires_dir = os.path.join(_scripts_dirs()[0], 'requires')
if not os.path.exists(requires_dir):
os.mkdir(requires_dir)
return os.path.join(requires_dir, utils.sanitize_filename(url))
@@ -426,7 +430,7 @@ def greasemonkey_reload(force=False):
"""Re-read Greasemonkey scripts from disk.
The scripts are read from a 'greasemonkey' subdirectory in
- qutebrowser's data directory (see `:version`).
+ qutebrowser's data or config directories (see `:version`).
Args:
force: For any scripts that have required dependencies,
@@ -440,7 +444,8 @@ def init():
global gm_manager
gm_manager = GreasemonkeyManager()
- try:
- os.mkdir(_scripts_dir())
- except FileExistsError:
- pass
+ for scripts_dir in _scripts_dirs():
+ try:
+ os.mkdir(scripts_dir)
+ except FileExistsError:
+ pass
diff --git a/qutebrowser/browser/shared.py b/qutebrowser/browser/shared.py
index 193a2a0e0..9234e82d8 100644
--- a/qutebrowser/browser/shared.py
+++ b/qutebrowser/browser/shared.py
@@ -82,7 +82,7 @@ def javascript_confirm(url, js_msg, abort_on):
raise CallSuper
msg = 'From <b>{}</b>:<br/>{}'.format(html.escape(url.toDisplayString()),
- js_msg)
+ html.escape(js_msg))
urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
ans = message.ask('Javascript confirm', msg,
mode=usertypes.PromptMode.yesno,
@@ -99,7 +99,7 @@ def javascript_prompt(url, js_msg, default, abort_on):
return (False, "")
msg = '<b>{}</b> asks:<br/>{}'.format(html.escape(url.toDisplayString()),
- js_msg)
+ html.escape(js_msg))
urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
answer = message.ask('Javascript prompt', msg,
mode=usertypes.PromptMode.text,
@@ -122,7 +122,7 @@ def javascript_alert(url, js_msg, abort_on):
return
msg = 'From <b>{}</b>:<br/>{}'.format(html.escape(url.toDisplayString()),
- js_msg)
+ html.escape(js_msg))
urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
message.ask('Javascript alert', msg, mode=usertypes.PromptMode.alert,
abort_on=abort_on, url=urlstr)
diff --git a/qutebrowser/browser/webengine/darkmode.py b/qutebrowser/browser/webengine/darkmode.py
index 630a7bf9e..d067edea3 100644
--- a/qutebrowser/browser/webengine/darkmode.py
+++ b/qutebrowser/browser/webengine/darkmode.py
@@ -264,6 +264,16 @@ def _variant() -> Variant:
def settings() -> Iterator[Tuple[str, str]]:
"""Get necessary blink settings to configure dark mode for QtWebEngine."""
+ if (qtutils.version_check('5.15.2', compiled=False) and
+ config.val.colors.webpage.prefers_color_scheme_dark):
+ # With older Qt versions, this is passed in qtargs.py as --force-dark-mode
+ # instead.
+ #
+ # With Chromium 85 (> Qt 5.15.2), the enumeration has changed in Blink and this
+ # will need to be set to '0' instead:
+ # https://chromium-review.googlesource.com/c/chromium/src/+/2232922
+ yield "preferredColorScheme", "1"
+
if not config.val.colors.webpage.darkmode.enabled:
return
diff --git a/qutebrowser/browser/webengine/webenginesettings.py b/qutebrowser/browser/webengine/webenginesettings.py
index 1526574a7..05e7b4b68 100644
--- a/qutebrowser/browser/webengine/webenginesettings.py
+++ b/qutebrowser/browser/webengine/webenginesettings.py
@@ -26,25 +26,35 @@ Module attributes:
import os
import operator
-from typing import cast, Any, List, Optional, Tuple, Union
+from typing import cast, Any, List, Optional, Tuple, Union, TYPE_CHECKING
from PyQt5.QtGui import QFont
+from PyQt5.QtWidgets import QApplication
from PyQt5.QtWebEngineWidgets import QWebEngineSettings, QWebEngineProfile
-from qutebrowser.browser.webengine import spell, webenginequtescheme
+from qutebrowser.browser import history
+from qutebrowser.browser.webengine import (spell, webenginequtescheme, cookies,
+ webenginedownloads)
from qutebrowser.config import config, websettings
from qutebrowser.config.websettings import AttributeInfo as Attr
-from qutebrowser.utils import standarddir, qtutils, message, log, urlmatch, usertypes
+from qutebrowser.utils import (standarddir, qtutils, message, log,
+ urlmatch, usertypes, objreg)
+if TYPE_CHECKING:
+ from qutebrowser.browser.webengine import interceptor
# The default QWebEngineProfile
default_profile = cast(QWebEngineProfile, None)
# The QWebEngineProfile used for private (off-the-record) windows
private_profile: Optional[QWebEngineProfile] = None
# The global WebEngineSettings object
-global_settings = cast('WebEngineSettings', None)
+_global_settings = cast('WebEngineSettings', None)
parsed_user_agent = None
+_qute_scheme_handler = cast(webenginequtescheme.QuteSchemeHandler, None)
+_req_interceptor = cast('interceptor.RequestInterceptor', None)
+_download_manager = cast(webenginedownloads.DownloadManager, None)
+
class _SettingsWrapper:
@@ -217,6 +227,26 @@ class ProfileSetter:
def __init__(self, profile):
self._profile = profile
+ self._name_to_method = {
+ 'content.cache.size': self.set_http_cache_size,
+ 'content.cookies.store': self.set_persistent_cookie_policy,
+ 'spellcheck.languages': self.set_dictionary_language,
+ }
+
+ # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-75884
+ # (note this isn't actually fixed properly before Qt 5.15)
+ header_bug_fixed = qtutils.version_check('5.15', compiled=False)
+ if header_bug_fixed:
+ for name in ['user_agent', 'accept_language']:
+ self._name_to_method[f'content.headers.{name}'] = self.set_http_headers
+
+ def update_setting(self, name):
+ """Update a setting based on its name."""
+ try:
+ meth = self._name_to_method[name]
+ except KeyError:
+ return
+ meth()
def init_profile(self):
"""Initialize settings on the given profile."""
@@ -267,20 +297,21 @@ class ProfileSetter:
def set_persistent_cookie_policy(self):
"""Set the HTTP Cookie size for the given profile."""
- assert not self._profile.isOffTheRecord()
+ if self._profile.isOffTheRecord():
+ return
if config.val.content.cookies.store:
value = QWebEngineProfile.AllowPersistentCookies
else:
value = QWebEngineProfile.NoPersistentCookies
self._profile.setPersistentCookiesPolicy(value)
- def set_dictionary_language(self, warn=True):
+ def set_dictionary_language(self):
"""Load the given dictionaries."""
filenames = []
for code in config.val.spellcheck.languages or []:
local_filename = spell.local_filename(code)
if not local_filename:
- if warn:
+ if not self._profile.isOffTheRecord():
message.warning("Language {} is not installed - see "
"scripts/dictcli.py in qutebrowser's "
"sources".format(code))
@@ -295,28 +326,10 @@ class ProfileSetter:
def _update_settings(option):
"""Update global settings when qwebsettings changed."""
- global_settings.update_setting(option)
-
- # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-75884
- # (note this isn't actually fixed properly before Qt 5.15)
- header_bug_fixed = qtutils.version_check('5.15', compiled=False)
-
- if option in ['content.headers.user_agent',
- 'content.headers.accept_language'] and header_bug_fixed:
- default_profile.setter.set_http_headers()
- if private_profile:
- private_profile.setter.set_http_headers()
- elif option == 'content.cache.size':
- default_profile.setter.set_http_cache_size()
- if private_profile:
- private_profile.setter.set_http_cache_size()
- elif option == 'content.cookies.store':
- default_profile.setter.set_persistent_cookie_policy()
- # We're not touching the private profile's cookie policy.
- elif option == 'spellcheck.languages':
- default_profile.setter.set_dictionary_language()
- if private_profile:
- private_profile.setter.set_dictionary_language(warn=False)
+ _global_settings.update_setting(option)
+ default_profile.setter.update_setting(option)
+ if private_profile:
+ private_profile.setter.update_setting(option)
def _init_user_agent_str(ua):
@@ -328,33 +341,54 @@ def init_user_agent():
_init_user_agent_str(QWebEngineProfile.defaultProfile().httpUserAgent())
+def _init_profile(profile: QWebEngineProfile) -> None:
+ """Initialize a new QWebEngineProfile.
+
+ This currently only contains the steps which are shared between a private and a
+ non-private profile (at the moment, only the default profile).
+ """
+ profile.setter = ProfileSetter(profile) # type: ignore[attr-defined]
+ profile.setter.init_profile()
+
+ _qute_scheme_handler.install(profile)
+ _req_interceptor.install(profile)
+ _download_manager.install(profile)
+ cookies.install_filter(profile)
+
+ # Clear visited links on web history clear
+ history.web_history.history_cleared.connect(profile.clearAllVisitedLinks)
+ history.web_history.url_cleared.connect(
+ lambda url: profile.clearVisitedLinks([url]))
+
+ _global_settings.init_settings()
+
+
def _init_default_profile():
"""Init the default QWebEngineProfile."""
global default_profile
default_profile = QWebEngineProfile.defaultProfile()
+
init_user_agent()
- default_profile.setter = ProfileSetter( # type: ignore[attr-defined]
- default_profile)
default_profile.setCachePath(
os.path.join(standarddir.cache(), 'webengine'))
default_profile.setPersistentStoragePath(
os.path.join(standarddir.data(), 'webengine'))
- default_profile.setter.init_profile()
- default_profile.setter.set_persistent_cookie_policy()
+
+ _init_profile(default_profile)
def init_private_profile():
"""Init the private QWebEngineProfile."""
global private_profile
- if not qtutils.is_single_process():
- private_profile = QWebEngineProfile()
- private_profile.setter = ProfileSetter( # type: ignore[attr-defined]
- private_profile)
- assert private_profile.isOffTheRecord()
- private_profile.setter.init_profile()
+ if qtutils.is_single_process():
+ return
+
+ private_profile = QWebEngineProfile()
+ assert private_profile.isOffTheRecord()
+ _init_profile(private_profile)
def _init_site_specific_quirks():
@@ -430,14 +464,33 @@ def init():
webenginequtescheme.init()
spell.init()
+ # For some reason we need to keep a reference, otherwise the scheme handler
+ # won't work...
+ # https://www.riverbankcomputing.com/pipermail/pyqt/2016-September/038075.html
+ global _qute_scheme_handler
+ app = QApplication.instance()
+ log.init.debug("Initializing qute://* handler...")
+ _qute_scheme_handler = webenginequtescheme.QuteSchemeHandler(parent=app)
+
+ global _req_interceptor
+ log.init.debug("Initializing request interceptor...")
+ from qutebrowser.browser.webengine import interceptor
+ _req_interceptor = interceptor.RequestInterceptor(parent=app)
+
+ global _download_manager
+ log.init.debug("Initializing QtWebEngine downloads...")
+ _download_manager = webenginedownloads.DownloadManager(parent=app)
+ objreg.register('webengine-download-manager', _download_manager)
+ from qutebrowser.misc import quitter
+ quitter.instance.shutting_down.connect(_download_manager.shutdown)
+
+ global _global_settings
+ _global_settings = WebEngineSettings(_SettingsWrapper())
+
_init_default_profile()
init_private_profile()
config.instance.changed.connect(_update_settings)
- global global_settings
- global_settings = WebEngineSettings(_SettingsWrapper())
- global_settings.init_settings()
-
_init_site_specific_quirks()
_init_devtools_settings()
diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py
index f026b7c23..98a6bf05d 100644
--- a/qutebrowser/browser/webengine/webenginetab.py
+++ b/qutebrowser/browser/webengine/webenginetab.py
@@ -27,68 +27,19 @@ from typing import cast, Union
from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QPoint, QPointF, QUrl, QObject
from PyQt5.QtNetwork import QAuthenticator
-from PyQt5.QtWidgets import QApplication, QWidget
+from PyQt5.QtWidgets import QWidget
from PyQt5.QtWebEngineWidgets import QWebEnginePage, QWebEngineScript, QWebEngineHistory
from qutebrowser.config import config
-from qutebrowser.browser import (browsertab, eventfilter, shared, webelem,
- history, greasemonkey)
+from qutebrowser.browser import browsertab, eventfilter, shared, webelem, greasemonkey
from qutebrowser.browser.webengine import (webview, webengineelem, tabhistory,
- interceptor, webenginequtescheme,
- cookies, webenginedownloads,
webenginesettings, certificateerror)
-from qutebrowser.misc import miscwidgets, objects, quitter
+from qutebrowser.misc import miscwidgets, objects
from qutebrowser.utils import (usertypes, qtutils, log, javascript, utils,
- message, objreg, jinja, debug)
+ message, jinja, debug)
from qutebrowser.qt import sip
-_qute_scheme_handler = None
-
-
-def init():
- """Initialize QtWebEngine-specific modules."""
- # For some reason we need to keep a reference, otherwise the scheme handler
- # won't work...
- # https://www.riverbankcomputing.com/pipermail/pyqt/2016-September/038075.html
- global _qute_scheme_handler
-
- app = QApplication.instance()
- log.init.debug("Initializing qute://* handler...")
- _qute_scheme_handler = webenginequtescheme.QuteSchemeHandler(parent=app)
- _qute_scheme_handler.install(webenginesettings.default_profile)
- if webenginesettings.private_profile:
- _qute_scheme_handler.install(webenginesettings.private_profile)
-
- log.init.debug("Initializing request interceptor...")
- req_interceptor = interceptor.RequestInterceptor(parent=app)
- req_interceptor.install(webenginesettings.default_profile)
- if webenginesettings.private_profile:
- req_interceptor.install(webenginesettings.private_profile)
-
- log.init.debug("Initializing QtWebEngine downloads...")
- download_manager = webenginedownloads.DownloadManager(parent=app)
- download_manager.install(webenginesettings.default_profile)
- if webenginesettings.private_profile:
- download_manager.install(webenginesettings.private_profile)
- objreg.register('webengine-download-manager', download_manager)
- quitter.instance.shutting_down.connect(download_manager.shutdown)
-
- log.init.debug("Initializing cookie filter...")
- cookies.install_filter(webenginesettings.default_profile)
- if webenginesettings.private_profile:
- cookies.install_filter(webenginesettings.private_profile)
-
- # Clear visited links on web history clear
- for p in [webenginesettings.default_profile,
- webenginesettings.private_profile]:
- if not p:
- continue
- history.web_history.history_cleared.connect(p.clearAllVisitedLinks)
- history.web_history.url_cleared.connect(
- lambda url, profile=p: profile.clearVisitedLinks([url]))
-
-
# Mapping worlds from usertypes.JsWorld to QWebEngineScript world IDs.
_JS_WORLD_MAP = {
usertypes.JsWorld.main: QWebEngineScript.MainWorld,
diff --git a/qutebrowser/commands/userscripts.py b/qutebrowser/commands/userscripts.py
index ce25d7d28..6d2c2f147 100644
--- a/qutebrowser/commands/userscripts.py
+++ b/qutebrowser/commands/userscripts.py
@@ -395,6 +395,7 @@ def _lookup_path(cmd):
directories = [
os.path.join(standarddir.data(), "userscripts"),
os.path.join(standarddir.data(system=True), "userscripts"),
+ os.path.join(standarddir.config(), "userscripts"),
]
for directory in directories:
cmd_path = os.path.join(directory, cmd)
diff --git a/qutebrowser/completion/models/listcategory.py b/qutebrowser/completion/models/listcategory.py
index 79dc0770a..d4193b6d8 100644
--- a/qutebrowser/completion/models/listcategory.py
+++ b/qutebrowser/completion/models/listcategory.py
@@ -22,7 +22,7 @@
import re
from typing import Iterable, Tuple
-from PyQt5.QtCore import Qt, QSortFilterProxyModel, QRegExp
+from PyQt5.QtCore import QSortFilterProxyModel, QRegularExpression
from PyQt5.QtGui import QStandardItem, QStandardItemModel
from PyQt5.QtWidgets import QWidget
@@ -63,9 +63,9 @@ class ListCategory(QSortFilterProxyModel):
val = re.sub(r' +', r' ', val) # See #1919
val = re.escape(val)
val = val.replace(r'\ ', '.*')
- rx = QRegExp(val, Qt.CaseInsensitive)
+ rx = QRegularExpression(val, QRegularExpression.CaseInsensitiveOption)
qtutils.ensure_valid(rx)
- self.setFilterRegExp(rx)
+ self.setFilterRegularExpression(rx)
self.invalidate()
sortcol = 0
self.sort(sortcol)
diff --git a/qutebrowser/config/qtargs.py b/qutebrowser/config/qtargs.py
index 8ab93c904..2136f7e7f 100644
--- a/qutebrowser/config/qtargs.py
+++ b/qutebrowser/config/qtargs.py
@@ -203,12 +203,18 @@ def _qtwebengine_settings_args() -> Iterator[str]:
}
}
- referrer_setting = settings['content.headers.referer']
- if qtutils.version_check('5.14', compiled=False):
+ if (qtutils.version_check('5.14', compiled=False) and
+ not qtutils.version_check('5.15.2', compiled=False)):
+ # In Qt 5.14 to 5.15.1, `--force-dark-mode` is used to set the
+ # preferred colorscheme. In Qt 5.15.2, this is handled by a
+ # blink-setting instead.
settings['colors.webpage.prefers_color_scheme_dark'] = {
True: '--force-dark-mode',
False: None,
}
+
+ referrer_setting = settings['content.headers.referer']
+ if qtutils.version_check('5.14', compiled=False):
# Starting with Qt 5.14, this is handled via --enable-features
referrer_setting['same-domain'] = None
else:
diff --git a/qutebrowser/misc/earlyinit.py b/qutebrowser/misc/earlyinit.py
index d1c57760e..d742a6706 100644
--- a/qutebrowser/misc/earlyinit.py
+++ b/qutebrowser/misc/earlyinit.py
@@ -211,6 +211,10 @@ def _check_modules(modules):
), log.py_warning_filter(
category=DeprecationWarning,
message=r'the imp module is deprecated',
+ ), log.py_warning_filter(
+ # WORKAROUND for https://github.com/pypa/setuptools/issues/2466
+ category=DeprecationWarning,
+ message=r'Creating a LegacyVersion has been deprecated',
):
# pylint: enable=bad-continuation
importlib.import_module(name)
diff --git a/qutebrowser/misc/utilcmds.py b/qutebrowser/misc/utilcmds.py
index 56138c798..fa327b772 100644
--- a/qutebrowser/misc/utilcmds.py
+++ b/qutebrowser/misc/utilcmds.py
@@ -42,15 +42,17 @@ from qutebrowser.qt import sip
@cmdutils.register(maxsplit=1, no_cmd_split=True, no_replace_variables=True)
@cmdutils.argument('win_id', value=cmdutils.Value.win_id)
-def later(ms: int, command: str, win_id: int) -> None:
+def later(duration: str, command: str, win_id: int) -> None:
"""Execute a command after some time.
Args:
- ms: How many milliseconds to wait.
+ duration: Duration to wait in format XhYmZs or a number for milliseconds.
command: The command to run, with optional args.
"""
- if ms < 0:
- raise cmdutils.CommandError("I can't run something in the past!")
+ try:
+ ms = utils.parse_duration(duration)
+ except ValueError as e:
+ raise cmdutils.CommandError(e)
commandrunner = runners.CommandRunner(win_id)
timer = usertypes.Timer(name='later', parent=QApplication.instance())
try:
diff --git a/qutebrowser/qutebrowser.py b/qutebrowser/qutebrowser.py
index bca1df020..7f36d4807 100644
--- a/qutebrowser/qutebrowser.py
+++ b/qutebrowser/qutebrowser.py
@@ -85,6 +85,11 @@ def get_argparser():
parser.add_argument('--json-args', help=argparse.SUPPRESS)
parser.add_argument('--temp-basedir-restarted', help=argparse.SUPPRESS)
+ parser.add_argument('--desktop-file-name',
+ default="org.qutebrowser.qutebrowser",
+ help="Set the base name of the desktop entry for this "
+ "application. Used to set the app_id under Wayland. See "
+ "https://doc.qt.io/qt-5/qguiapplication.html#desktopFileName-prop")
debug = parser.add_argument_group('debug arguments')
debug.add_argument('-l', '--loglevel', dest='loglevel',
diff --git a/qutebrowser/utils/jinja.py b/qutebrowser/utils/jinja.py
index f60c46bbc..c4ba703cc 100644
--- a/qutebrowser/utils/jinja.py
+++ b/qutebrowser/utils/jinja.py
@@ -118,8 +118,7 @@ class Environment(jinja2.Environment):
def _data_url(self, path: str) -> str:
"""Get a data: url for the broken qutebrowser logo."""
data = utils.read_file(path, binary=True)
- filename = utils.resource_filename(path)
- mimetype = utils.guess_mimetype(filename)
+ mimetype = utils.guess_mimetype(path)
return urlutils.data_url(mimetype, data).toString()
def getattr(self, obj: Any, attribute: str) -> Any:
diff --git a/qutebrowser/utils/utils.py b/qutebrowser/utils/utils.py
index 31ff5bf50..331bf5f96 100644
--- a/qutebrowser/utils/utils.py
+++ b/qutebrowser/utils/utils.py
@@ -42,6 +42,11 @@ from typing import (Any, Callable, IO, Iterator, Optional, Sequence, Tuple, Type
from PyQt5.QtCore import QUrl, QVersionNumber
from PyQt5.QtGui import QClipboard, QDesktopServices
from PyQt5.QtWidgets import QApplication
+# We cannot use the stdlib version on 3.7-3.8 because we need the files() API.
+if sys.version_info >= (3, 9):
+ import importlib.resources as importlib_resources
+else: # pragma: no cover
+ import importlib_resources
import pkg_resources
import yaml
try:
@@ -69,7 +74,7 @@ is_posix = os.name == 'posix'
try:
# Protocol was added in Python 3.8
- from typing import Protocol
+ from typing import Protocol # pylint: disable=ungrouped-imports
except ImportError: # pragma: no cover
if not TYPE_CHECKING:
class Protocol:
@@ -216,13 +221,12 @@ def read_file(filename: str, binary: bool = False) -> Any:
with open(fn, 'r', encoding='utf-8') as f:
return f.read()
else:
- data = pkg_resources.resource_string(
- qutebrowser.__name__, filename)
+ p = importlib_resources.files(qutebrowser) / filename
if binary:
- return data
+ return p.read_bytes()
- return data.decode('UTF-8')
+ return p.read_text()
def resource_filename(filename: str) -> str:
@@ -773,3 +777,30 @@ def libgl_workaround() -> None:
libgl = ctypes.util.find_library("GL")
if libgl is not None: # pragma: no branch
ctypes.CDLL(libgl, mode=ctypes.RTLD_GLOBAL)
+
+
+def parse_duration(duration: str) -> int:
+ """Parse duration in format XhYmZs into milliseconds duration."""
+ if duration.isdigit():
+ # For backward compatibility return milliseconds
+ return int(duration)
+
+ match = re.fullmatch(
+ r'(?P<hours>[0-9]+(\.[0-9])?h)?\s*'
+ r'(?P<minutes>[0-9]+(\.[0-9])?m)?\s*'
+ r'(?P<seconds>[0-9]+(\.[0-9])?s)?',
+ duration
+ )
+ if not match or not match.group(0):
+ raise ValueError(
+ f"Invalid duration: {duration} - "
+ "expected XhYmZs or a number of milliseconds"
+ )
+ seconds_string = match.group('seconds') if match.group('seconds') else '0'
+ seconds = float(seconds_string.rstrip('s'))
+ minutes_string = match.group('minutes') if match.group('minutes') else '0'
+ minutes = float(minutes_string.rstrip('m'))
+ hours_string = match.group('hours') if match.group('hours') else '0'
+ hours = float(hours_string.rstrip('h'))
+ milliseconds = int((seconds + minutes * 60 + hours * 3600) * 1000)
+ return milliseconds
diff --git a/qutebrowser/utils/version.py b/qutebrowser/utils/version.py
index 1dc623881..a31b4eae2 100644
--- a/qutebrowser/utils/version.py
+++ b/qutebrowser/utils/version.py
@@ -362,6 +362,7 @@ MODULE_INFO: Mapping[str, ModuleInfo] = collections.OrderedDict([
('yaml', ['__version__']),
('adblock', ['__version__'], "0.3.2"),
('attr', ['__version__']),
+ ('importlib_resources', []),
('PyQt5.QtWebEngineWidgets', []),
('PyQt5.QtWebEngine', ['PYQT_WEBENGINE_VERSION_STR']),
('PyQt5.QtWebKitWidgets', []),
diff --git a/requirements.txt b/requirements.txt
index fa1c5f31b..0d682f809 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -3,8 +3,9 @@
adblock==0.3.2
attrs==20.3.0
colorama==0.4.4
+importlib-resources==3.3.0 ; python_version<"3.9"
Jinja2==2.11.2
MarkupSafe==1.1.1
-Pygments==2.7.2
+Pygments==2.7.3
pyPEG2==2.15.2
PyYAML==5.3.1
diff --git a/scripts/dev/build_release.py b/scripts/dev/build_release.py
index 6044a1e18..479283a92 100755
--- a/scripts/dev/build_release.py
+++ b/scripts/dev/build_release.py
@@ -116,7 +116,13 @@ def smoke_test(executable):
(r'\[.*:ERROR:mach_port_broker.mm\(48\)\] bootstrap_look_up '
r'org\.chromium\.Chromium\.rohitfork\.1: Permission denied \(1100\)'),
(r'\[.*:ERROR:mach_port_broker.mm\(43\)\] bootstrap_look_up: '
- r'Unknown service name \(1102\)')
+ r'Unknown service name \(1102\)'),
+
+ # Windows N:
+ # https://github.com/microsoft/playwright/issues/2901
+ (r'\[.*:ERROR:dxva_video_decode_accelerator_win.cc\(\d+\)\] '
+ r'DXVAVDA fatal error: could not LoadLibrary: .*: The specified '
+ r'module could not be found. \(0x7E\)'),
]
proc = subprocess.run([executable, '--no-err-windows', '--nowindow',
diff --git a/scripts/dev/check_coverage.py b/scripts/dev/check_coverage.py
index 728a36873..aa88c97a3 100644
--- a/scripts/dev/check_coverage.py
+++ b/scripts/dev/check_coverage.py
@@ -339,7 +339,7 @@ def main_check():
print("or check https://codecov.io/github/qutebrowser/qutebrowser")
print()
- if 'CI' in os.environ:
+ if scriptutils.ON_CI:
print("Keeping coverage.xml on CI.")
else:
os.remove('coverage.xml')
diff --git a/scripts/dev/ci/docker/Dockerfile.j2 b/scripts/dev/ci/docker/Dockerfile.j2
index 412c42cf2..1835f0a2f 100644
--- a/scripts/dev/ci/docker/Dockerfile.j2
+++ b/scripts/dev/ci/docker/Dockerfile.j2
@@ -24,4 +24,4 @@ WORKDIR /home/user
CMD git clone /outside qutebrowser.git && \
cd qutebrowser.git && \
- tox -e py38
+ tox -e py
diff --git a/scripts/dev/ci/problemmatchers.py b/scripts/dev/ci/problemmatchers.py
index 3e804af05..cc423f922 100644
--- a/scripts/dev/ci/problemmatchers.py
+++ b/scripts/dev/ci/problemmatchers.py
@@ -188,7 +188,7 @@ MATCHERS = {
"severity": "error",
"pattern": [
{
- "regexp": r'^([^:]+):(\d+): (Found .*)',
+ "regexp": r'^([^:]+):(\d+): \033\[34m(Found .*)\033\[0m',
"file": 1,
"line": 2,
"message": 3,
diff --git a/scripts/dev/misc_checks.py b/scripts/dev/misc_checks.py
index 14373f94f..ad446412c 100644
--- a/scripts/dev/misc_checks.py
+++ b/scripts/dev/misc_checks.py
@@ -145,8 +145,8 @@ def _check_spelling_file(path, fobj, patterns):
for pattern, explanation in patterns:
if pattern.search(line):
ok = False
- print(f'{path}:{num}: Found "{pattern.pattern}" - ', end='')
- utils.print_col(explanation, 'blue')
+ print(f'{path}:{num}: ', end='')
+ utils.print_col(f'Found "{pattern.pattern}" - {explanation}', 'blue')
return ok
@@ -185,7 +185,7 @@ def check_spelling(args: argparse.Namespace) -> Optional[bool]:
"'type: ignore[error-code]' instead."),
),
(
- re.compile(r'# type: (?!ignore\[)'),
+ re.compile(r'# type: (?!ignore(\[|$))'),
"Don't use type comments, use type annotations instead.",
),
(
@@ -274,12 +274,35 @@ def check_userscripts_descriptions(_args: argparse.Namespace = None) -> bool:
return ok
+def check_userscript_shebangs(_args: argparse.Namespace) -> bool:
+ """Check that we're using /usr/bin/env in shebangs."""
+ ok = True
+ folder = pathlib.Path('misc/userscripts')
+
+ for sub in folder.iterdir():
+ if sub.is_dir() or sub.name == 'README.md':
+ continue
+
+ with sub.open('r', encoding='utf-8') as f:
+ shebang = f.readline()
+ assert shebang.startswith('#!'), shebang
+ binary = shebang.split()[0][2:]
+
+ if binary not in ['/bin/sh', '/usr/bin/env']:
+ bin_name = pathlib.Path(binary).name
+ print(f"In {sub}, use #!/usr/bin/env {bin_name} instead of #!{binary}")
+ ok = False
+
+ return ok
+
+
def main() -> int:
checkers = {
'git': check_git,
'vcs': check_vcs_conflict,
'spelling': check_spelling,
- 'userscripts': check_userscripts_descriptions,
+ 'userscript-descriptions': check_userscripts_descriptions,
+ 'userscript-shebangs': check_userscript_shebangs,
'changelog-urls': check_changelog_urls,
}
diff --git a/scripts/dev/recompile_requirements.py b/scripts/dev/recompile_requirements.py
index 3257b4734..5cda4b3d7 100644
--- a/scripts/dev/recompile_requirements.py
+++ b/scripts/dev/recompile_requirements.py
@@ -73,7 +73,7 @@ CHANGELOG_URLS = {
'pytest-bdd': 'https://github.com/pytest-dev/pytest-bdd/blob/master/CHANGES.rst',
'snowballstemmer': 'https://github.com/snowballstem/snowball/blob/master/NEWS',
'virtualenv': 'https://virtualenv.pypa.io/en/latest/changelog.html',
- 'packaging': 'https://pypi.org/project/packaging/',
+ 'packaging': 'https://packaging.pypa.io/en/latest/changelog.html',
'build': 'https://github.com/pypa/build/commits/master',
'attrs': 'http://www.attrs.org/en/stable/changelog.html',
'Jinja2': 'https://github.com/pallets/jinja/blob/master/CHANGES.rst',
@@ -130,7 +130,7 @@ CHANGELOG_URLS = {
'six': 'https://github.com/benjaminp/six/blob/master/CHANGES',
'altgraph': 'https://github.com/ronaldoussoren/altgraph/blob/master/doc/changelog.rst',
'urllib3': 'https://github.com/urllib3/urllib3/blob/master/CHANGES.rst',
- 'lxml': 'https://lxml.de/4.6/changes-4.6.0.html',
+ 'lxml': 'https://lxml.de/index.html#old-versions',
'jwcrypto': 'https://github.com/latchset/jwcrypto/commits/master',
'wrapt': 'https://github.com/GrahamDumpleton/wrapt/blob/develop/docs/changes.rst',
'pep517': 'https://github.com/pypa/pep517/blob/master/doc/changelog.rst',
@@ -175,6 +175,7 @@ CHANGELOG_URLS = {
'pyroma': 'https://github.com/regebro/pyroma/blob/master/HISTORY.txt',
'adblock': 'https://github.com/ArniDagur/python-adblock/blob/master/CHANGELOG.md',
'pyPEG2': None,
+ 'importlib-resources': 'https://importlib-resources.readthedocs.io/en/latest/changelog%20%28links%29.html',
}
@@ -262,13 +263,17 @@ def get_all_names():
yield basename[len('requirements-'):-len('.txt-raw')]
-def run_pip(venv_dir, *args, **kwargs):
+def run_pip(venv_dir, *args, quiet=False, **kwargs):
"""Run pip inside the virtualenv."""
+ args = list(args)
+ if quiet:
+ args.insert(1, '-q')
+
arg_str = ' '.join(str(arg) for arg in args)
utils.print_col('venv$ pip {}'.format(arg_str), 'blue')
+
venv_python = os.path.join(venv_dir, 'bin', 'python')
- return subprocess.run([venv_python, '-m', 'pip'] + list(args),
- check=True, **kwargs)
+ return subprocess.run([venv_python, '-m', 'pip'] + args, check=True, **kwargs)
def init_venv(host_python, venv_dir, requirements, pre=False):
@@ -277,8 +282,8 @@ def init_venv(host_python, venv_dir, requirements, pre=False):
utils.print_col('$ python3 -m venv {}'.format(venv_dir), 'blue')
subprocess.run([host_python, '-m', 'venv', venv_dir], check=True)
- run_pip(venv_dir, 'install', '-U', 'pip')
- run_pip(venv_dir, 'install', '-U', 'setuptools', 'wheel')
+ run_pip(venv_dir, 'install', '-U', 'pip', quiet=not utils.ON_CI)
+ run_pip(venv_dir, 'install', '-U', 'setuptools', 'wheel', quiet=not utils.ON_CI)
install_command = ['install', '-r', requirements]
if pre:
@@ -292,6 +297,8 @@ def init_venv(host_python, venv_dir, requirements, pre=False):
def parse_args():
"""Parse commandline arguments via argparse."""
parser = argparse.ArgumentParser()
+ parser.add_argument('--force-test', help="Force running environment tests",
+ action='store_true')
parser.add_argument('names', nargs='*')
return parser.parse_args()
@@ -358,6 +365,7 @@ def _get_changed_files():
def parse_versioned_line(line):
"""Parse a requirements.txt line into name/version."""
if '==' in line:
+ line = line.rsplit('#', maxsplit=1)[0] # Strip comments
name, version = line.split('==')
if ';' in version: # pip environment markers
version = version.split(';')[0].strip()
@@ -412,7 +420,7 @@ def print_changed_files():
utils.print_subtitle('Diff')
print(diff_text)
- if 'CI' in os.environ:
+ if utils.ON_CI:
print()
print('::set-output name=changed::' +
files_text.replace('\n', '%0A'))
@@ -481,7 +489,6 @@ def build_requirements(name):
def test_tox():
"""Test requirements via tox."""
- utils.print_title('Testing via tox')
host_python = get_host_python('tox')
req_path = os.path.join(REQ_DIR, 'requirements-tox.txt')
@@ -506,11 +513,15 @@ def test_tox():
check=True)
-def test_requirements(name, outfile):
+def test_requirements(name, outfile, *, force=False):
"""Test a resulting requirements file."""
print()
utils.print_subtitle("Testing")
+ if name not in _get_changed_files() and not force:
+ print(f"Skipping test as there were no changes for {name}.")
+ return
+
host_python = get_host_python(name)
with tempfile.TemporaryDirectory() as tmpdir:
init_venv(host_python, tmpdir, outfile)
@@ -528,11 +539,16 @@ def main():
for name in names:
utils.print_title(name)
outfile = build_requirements(name)
- test_requirements(name, outfile)
+ test_requirements(name, outfile, force=args.force_test)
- if not args.names:
+ utils.print_title('Testing via tox')
+ if args.names and not args.force_test:
# If we selected a subset, let's not go through the trouble of testing
# via tox.
+ print("Skipping: Selected a subset only")
+ elif not _get_changed_files() and not args.force_test:
+ print("Skipping: No changes")
+ else:
test_tox()
print_changed_files()
diff --git a/scripts/dev/update_version.py b/scripts/dev/update_version.py
index 5f7df5cae..1f744d392 100644
--- a/scripts/dev/update_version.py
+++ b/scripts/dev/update_version.py
@@ -82,7 +82,7 @@ if __name__ == "__main__":
.format(v=version))
print("* Windows: git fetch; git checkout v{v}; "
"py -3.7 -m tox -e build-release -- --asciidoc "
- "$env:userprofile\\bin\\asciidoc-9.0.2\\asciidoc.py --upload"
+ "$env:userprofile\\bin\\asciidoc-9.0.4\\asciidoc.py --upload"
.format(v=version))
print("* macOS: git fetch && git checkout v{v} && "
"tox -e build-release -- --upload"
diff --git a/scripts/mkvenv.py b/scripts/mkvenv.py
index ad5f2073e..897088539 100644
--- a/scripts/mkvenv.py
+++ b/scripts/mkvenv.py
@@ -87,6 +87,9 @@ def parse_args(argv: List[str] = None) -> argparse.Namespace:
parser.add_argument('--skip-docs',
action='store_true',
help="Skip doc generation.")
+ parser.add_argument('--skip-smoke-test',
+ action='store_true',
+ help="Skip Qt smoke test.")
parser.add_argument('--tox-error',
action='store_true',
help=argparse.SUPPRESS)
@@ -296,12 +299,19 @@ def apply_xcb_util_workaround(
def _find_libs() -> Dict[Tuple[str, str], List[str]]:
"""Find all system-wide .so libraries."""
all_libs: Dict[Tuple[str, str], List[str]] = {}
+
+ if pathlib.Path("/sbin/ldconfig").exists():
+ # /sbin might not be in PATH on e.g. Debian
+ ldconfig_bin = "/sbin/ldconfig"
+ else:
+ ldconfig_bin = "ldconfig"
ldconfig_proc = subprocess.run(
- ['ldconfig', '-p'],
+ [ldconfig_bin, '-p'],
check=True,
stdout=subprocess.PIPE,
encoding=sys.getfilesystemencoding(),
)
+
pattern = re.compile(r'(?P<name>\S+) \((?P<abi_type>[^)]+)\) => (?P<path>.*)')
for line in ldconfig_proc.stdout.splitlines():
match = pattern.fullmatch(line.strip())
@@ -421,7 +431,7 @@ def run(args) -> None:
raise AssertionError
apply_xcb_util_workaround(venv_dir, args.pyqt_type, args.pyqt_version)
- if args.pyqt_type != 'skip':
+ if args.pyqt_type != 'skip' and not args.skip_smoke_test:
run_qt_smoke_test(venv_dir)
install_requirements(venv_dir)
diff --git a/setup.py b/setup.py
index 1169eae81..137b514ca 100755
--- a/setup.py
+++ b/setup.py
@@ -71,7 +71,8 @@ try:
entry_points={'gui_scripts':
['qutebrowser = qutebrowser.qutebrowser:main']},
zip_safe=True,
- install_requires=['pypeg2', 'jinja2', 'pygments', 'PyYAML', 'attrs'],
+ install_requires=['pypeg2', 'jinja2', 'pygments', 'PyYAML', 'attrs',
+ 'importlib_resources>=1.1.0; python_version < "3.9"'],
python_requires='>=3.6',
name='qutebrowser',
version=_get_constant('version'),
diff --git a/tests/conftest.py b/tests/conftest.py
index 017c11ba8..fd317d6c4 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -282,7 +282,7 @@ def check_yaml_c_exts():
Not available yet with a nightly Python, see:
https://github.com/yaml/pyyaml/issues/416
"""
- if 'CI' in os.environ and sys.version_info[:2] != (3, 10):
+ if testutils.ON_CI and sys.version_info[:2] != (3, 10):
from yaml import CLoader
diff --git a/tests/end2end/data/adblock/external_logo.html b/tests/end2end/data/adblock/external_logo.html
new file mode 100644
index 000000000..7fa7e9ebb
--- /dev/null
+++ b/tests/end2end/data/adblock/external_logo.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>External logo</title>
+ </head>
+ <body>
+ <p>
+ <b>NOTE:</> This should never be used in a test where
+ qutebrowser.org isn't blocked, as no network requests should be
+ made while running the testsuite.
+ </p>
+ <img src="https://qutebrowser.org/icons/qutebrowser.svg">
+ </body>
+</html>
diff --git a/tests/end2end/data/adblock/qutebrowser b/tests/end2end/data/adblock/qutebrowser
new file mode 100644
index 000000000..d104c0104
--- /dev/null
+++ b/tests/end2end/data/adblock/qutebrowser
@@ -0,0 +1 @@
+qutebrowser.org
diff --git a/tests/end2end/features/private.feature b/tests/end2end/features/private.feature
index fe870dded..2698555ab 100644
--- a/tests/end2end/features/private.feature
+++ b/tests/end2end/features/private.feature
@@ -199,3 +199,49 @@ Feature: Using private browsing
- history:
- active: true
url: http://localhost:*/data/numbers/5.txt
+
+ # https://github.com/qutebrowser/qutebrowser/issues/5810
+
+ Scenario: Using qute:// scheme after reiniting private profile
+ When I open about:blank in a private window
+ And I run :close
+ And I open qute://version in a private window
+ Then the page should contain the plaintext "Version info"
+
+ Scenario: Downloading after reiniting private profile
+ When I open about:blank in a private window
+ And I run :close
+ And I open data/downloads/downloads.html in a private window
+ And I run :click-element id download
+ And I wait for "*PromptMode.download*" in the log
+ And I run :leave-mode
+ Then "Removed download *: download.bin *" should be logged
+
+ Scenario: Adblocking after reiniting private profile
+ When I open about:blank in a private window
+ And I run :close
+ And I set content.host_blocking.lists to ["http://localhost:(port)/data/adblock/qutebrowser"]
+ And I run :adblock-update
+ And I wait for the message "adblock: Read 1 hosts from 1 sources."
+ And I open data/adblock/external_logo.html in a private window
+ Then "Request to qutebrowser.org blocked by host blocker." should be logged
+
+ @pyqt!=5.15.0 # cookie filtering is broken on QtWebEngine 5.15.0
+ Scenario: Cookie filtering after reiniting private profile
+ When I open about:blank in a private window
+ And I run :close
+ And I set content.cookies.accept to never
+ And I open data/title.html in a private window
+ And I open cookies/set?unsuccessful-cookie=1 without waiting in a new tab
+ And I wait until cookies is loaded
+ And I open cookies
+ Then the cookie unsuccessful-cookie should not be set
+
+ Scenario: Disabling JS after reiniting private profile
+ When I open about:blank in a new window
+ And I run :window-only
+ And I set content.javascript.enabled to false
+ And I open about:blank in a private window
+ And I run :close
+ And I open data/javascript/enabled.html in a private window
+ Then the page should contain the plaintext "JavaScript is disabled"
diff --git a/tests/end2end/fixtures/quteprocess.py b/tests/end2end/fixtures/quteprocess.py
index 7ad739997..7d27e1166 100644
--- a/tests/end2end/fixtures/quteprocess.py
+++ b/tests/end2end/fixtures/quteprocess.py
@@ -691,7 +691,7 @@ class QuteProc(testprocess.Process):
is_dl_inconsistency = str(self.captured_log[-1]).endswith(
"_dl_allocate_tls_init: Assertion "
"`listp->slotinfo[cnt].gen <= GL(dl_tls_generation)' failed!")
- if 'CI' in os.environ and is_dl_inconsistency:
+ if testutils.ON_CI and is_dl_inconsistency:
# WORKAROUND for https://sourceware.org/bugzilla/show_bug.cgi?id=19329
self.captured_log = []
self._log("NOTE: Restarted after libc DL inconsistency!")
@@ -809,7 +809,7 @@ class QuteProc(testprocess.Process):
testprocess.WaitForTimeout))
if timeout is None:
- if 'CI' in os.environ:
+ if testutils.ON_CI:
timeout = 15000
else:
timeout = 5000
diff --git a/tests/end2end/fixtures/testprocess.py b/tests/end2end/fixtures/testprocess.py
index 51352c539..7a70e4de9 100644
--- a/tests/end2end/fixtures/testprocess.py
+++ b/tests/end2end/fixtures/testprocess.py
@@ -20,7 +20,6 @@
"""Base class for a subprocess run for tests."""
import re
-import os
import time
import warnings
@@ -234,7 +233,7 @@ class Process(QObject):
self._started = True
verbose = self.request.config.getoption('--verbose')
- timeout = 60 if 'CI' in os.environ else 20
+ timeout = 60 if utils.ON_CI else 20
for _ in range(timeout):
with self._wait_signal(self.ready, timeout=1000,
raising=False) as blocker:
@@ -476,7 +475,7 @@ class Process(QObject):
if timeout is None:
if do_skip:
timeout = 2000
- elif 'CI' in os.environ:
+ elif utils.ON_CI:
timeout = 15000
else:
timeout = 5000
diff --git a/tests/end2end/test_invocations.py b/tests/end2end/test_invocations.py
index e34bd912d..74805cec2 100644
--- a/tests/end2end/test_invocations.py
+++ b/tests/end2end/test_invocations.py
@@ -417,3 +417,17 @@ def test_referrer(quteproc_new, server, server2, request, value, expected):
expected = expected.replace(key, str(val))
assert headers.get('Referer') == expected
+
+
+@pytest.mark.qtwebkit_skip
+@utils.qt514
+def test_preferred_colorscheme(request, quteproc_new):
+ """Make sure the the preferred colorscheme is set."""
+ args = _base_args(request.config) + [
+ '--temp-basedir',
+ '-s', 'colors.webpage.prefers_color_scheme_dark', 'true',
+ ]
+ quteproc_new.start(args)
+
+ quteproc_new.send_cmd(':jseval matchMedia("(prefers-color-scheme: dark)").matches')
+ quteproc_new.wait_for(message='True')
diff --git a/tests/helpers/fixtures.py b/tests/helpers/fixtures.py
index 3c0623dfb..6f80099bb 100644
--- a/tests/helpers/fixtures.py
+++ b/tests/helpers/fixtures.py
@@ -166,7 +166,7 @@ def fake_web_tab(stubs, tab_registry, mode_manager, qapp):
@pytest.fixture
-def greasemonkey_manager(monkeypatch, data_tmpdir):
+def greasemonkey_manager(monkeypatch, data_tmpdir, config_tmpdir):
gm_manager = greasemonkey.GreasemonkeyManager()
monkeypatch.setattr(greasemonkey, 'gm_manager', gm_manager)
diff --git a/tests/unit/browser/webengine/test_darkmode.py b/tests/unit/browser/webengine/test_darkmode.py
index 3e62000d2..cd84526c3 100644
--- a/tests/unit/browser/webengine/test_darkmode.py
+++ b/tests/unit/browser/webengine/test_darkmode.py
@@ -32,6 +32,26 @@ def patch_backend(monkeypatch):
monkeypatch.setattr(objects, 'backend', usertypes.Backend.QtWebEngine)
+@pytest.mark.parametrize('qversion, enabled, expected', [
+ # Disabled or nothing set
+ ("5.14", False, []),
+ ("5.15.0", False, []),
+ ("5.15.1", False, []),
+ ("5.15.2", False, []),
+
+ # Enabled in configuration
+ ("5.14", True, []),
+ ("5.15.0", True, []),
+ ("5.15.1", True, []),
+ ("5.15.2", True, [("preferredColorScheme", "1")]),
+])
+@utils.qt514
+def test_colorscheme(config_stub, monkeypatch, qversion, enabled, expected):
+ monkeypatch.setattr(darkmode.qtutils, 'qVersion', lambda: qversion)
+ config_stub.val.colors.webpage.prefers_color_scheme_dark = enabled
+ assert list(darkmode.settings()) == expected
+
+
@pytest.mark.parametrize('settings, expected', [
# Disabled
({}, []),
diff --git a/tests/unit/browser/webengine/test_webenginesettings.py b/tests/unit/browser/webengine/test_webenginesettings.py
index 53f3e21b2..92ac8aca6 100644
--- a/tests/unit/browser/webengine/test_webenginesettings.py
+++ b/tests/unit/browser/webengine/test_webenginesettings.py
@@ -21,31 +21,51 @@ import logging
import pytest
-pytest.importorskip('PyQt5.QtWebEngineWidgets')
+QtWebEngineWidgets = pytest.importorskip('PyQt5.QtWebEngineWidgets')
from qutebrowser.browser.webengine import webenginesettings
from qutebrowser.utils import usertypes
-from qutebrowser.misc import objects
-@pytest.fixture(autouse=True)
-def init(qapp, config_stub, cache_tmpdir, data_tmpdir, monkeypatch):
- monkeypatch.setattr(webenginesettings.webenginequtescheme, 'init',
- lambda: None)
- monkeypatch.setattr(objects, 'backend', usertypes.Backend.QtWebEngine)
- webenginesettings.init()
- config_stub.changed.disconnect(webenginesettings._update_settings)
+@pytest.fixture
+def global_settings(monkeypatch, default_profile):
+ wrapper = webenginesettings._SettingsWrapper()
+ settings = webenginesettings.WebEngineSettings(wrapper)
+ settings.init_settings()
+ monkeypatch.setattr(webenginesettings, '_global_settings', settings)
-def test_big_cache_size(config_stub):
+@pytest.fixture
+def default_profile(monkeypatch):
+ """A profile to use which is set as default_profile.
+
+ Note we use a "private" profile here to avoid actually storing data during tests.
+ """
+ profile = QtWebEngineWidgets.QWebEngineProfile()
+ profile.setter = webenginesettings.ProfileSetter(profile)
+ monkeypatch.setattr(profile, 'isOffTheRecord', lambda: False)
+ monkeypatch.setattr(webenginesettings, 'default_profile', profile)
+ return profile
+
+
+@pytest.fixture
+def private_profile(monkeypatch):
+ """A profile to use which is set as private_profile."""
+ profile = QtWebEngineWidgets.QWebEngineProfile()
+ profile.setter = webenginesettings.ProfileSetter(profile)
+ monkeypatch.setattr(webenginesettings, 'private_profile', profile)
+ return profile
+
+
+def test_big_cache_size(config_stub, default_profile):
"""Make sure a too big cache size is handled correctly."""
config_stub.val.content.cache.size = 2 ** 63 - 1
- profile = webenginesettings.default_profile
- profile.setter.set_http_cache_size()
- assert profile.httpCacheMaximumSize() == 2 ** 31 - 1
+ default_profile.setter.set_http_cache_size()
+ assert default_profile.httpCacheMaximumSize() == 2 ** 31 - 1
-def test_non_existing_dict(config_stub, monkeypatch, message_mock, caplog):
+def test_non_existing_dict(config_stub, monkeypatch, message_mock, caplog,
+ global_settings):
monkeypatch.setattr(webenginesettings.spell, 'local_filename',
lambda _code: None)
config_stub.val.spellcheck.languages = ['af-ZA']
@@ -59,29 +79,25 @@ def test_non_existing_dict(config_stub, monkeypatch, message_mock, caplog):
assert msg.text == expected
-def test_existing_dict(config_stub, monkeypatch):
+def test_existing_dict(config_stub, monkeypatch, global_settings,
+ default_profile, private_profile):
monkeypatch.setattr(webenginesettings.spell, 'local_filename',
lambda _code: 'en-US-8-0')
config_stub.val.spellcheck.languages = ['en-US']
webenginesettings._update_settings('spellcheck.languages')
- for profile in [webenginesettings.default_profile,
- webenginesettings.private_profile]:
+ for profile in [default_profile, private_profile]:
assert profile.isSpellCheckEnabled()
assert profile.spellCheckLanguages() == ['en-US-8-0']
-def test_spell_check_disabled(config_stub, monkeypatch):
+def test_spell_check_disabled(config_stub, monkeypatch, global_settings,
+ default_profile, private_profile):
config_stub.val.spellcheck.languages = []
webenginesettings._update_settings('spellcheck.languages')
- for profile in [webenginesettings.default_profile,
- webenginesettings.private_profile]:
+ for profile in [default_profile, private_profile]:
assert not profile.isSpellCheckEnabled()
-def test_default_user_agent_saved():
- assert webenginesettings.parsed_user_agent is not None
-
-
def test_parsed_user_agent(qapp):
webenginesettings.init_user_agent()
parsed = webenginesettings.parsed_user_agent
diff --git a/tests/unit/config/test_qtargs.py b/tests/unit/config/test_qtargs.py
index 051956a00..b050113b4 100644
--- a/tests/unit/config/test_qtargs.py
+++ b/tests/unit/config/test_qtargs.py
@@ -289,20 +289,25 @@ class TestQtArgs:
else:
assert arg in args
- @pytest.mark.parametrize('dark, new_qt, added', [
- (True, True, True),
- (True, False, False),
- (False, True, False),
- (False, False, False),
+ @pytest.mark.parametrize('dark, qt_version, added', [
+ (True, "5.13", False), # not supported
+ (True, "5.14", True),
+ (True, "5.15.0", True),
+ (True, "5.15.1", True),
+ (True, "5.15.2", False), # handled via blink setting
+
+ (False, "5.13", False),
+ (False, "5.14", False),
+ (False, "5.15.0", False),
+ (False, "5.15.1", False),
+ (False, "5.15.2", False),
])
@utils.qt514
def test_prefers_color_scheme_dark(self, config_stub, monkeypatch, parser,
- dark, new_qt, added):
+ dark, qt_version, added):
monkeypatch.setattr(qtargs.objects, 'backend',
usertypes.Backend.QtWebEngine)
- monkeypatch.setattr(qtargs.qtutils, 'version_check',
- lambda version, exact=False, compiled=True:
- new_qt)
+ monkeypatch.setattr(qtargs.qtutils, 'qVersion', lambda: qt_version)
config_stub.val.colors.webpage.prefers_color_scheme_dark = dark
diff --git a/tests/unit/javascript/test_greasemonkey.py b/tests/unit/javascript/test_greasemonkey.py
index 5d549ca68..5f2c94b56 100644
--- a/tests/unit/javascript/test_greasemonkey.py
+++ b/tests/unit/javascript/test_greasemonkey.py
@@ -41,12 +41,15 @@ test_gm_script = r"""
console.log("Script is running.");
"""
-pytestmark = pytest.mark.usefixtures('data_tmpdir')
+pytestmark = [
+ pytest.mark.usefixtures('data_tmpdir'),
+ pytest.mark.usefixtures('config_tmpdir')
+]
def _save_script(script_text, filename):
# pylint: disable=no-member
- file_path = py.path.local(greasemonkey._scripts_dir()) / filename
+ file_path = py.path.local(greasemonkey._scripts_dirs()[0]) / filename
# pylint: enable=no-member
file_path.write_text(script_text, encoding='utf-8', ensure=True)
diff --git a/tests/unit/scripts/test_problemmatchers.py b/tests/unit/scripts/test_problemmatchers.py
new file mode 100644
index 000000000..98bd9c7a5
--- /dev/null
+++ b/tests/unit/scripts/test_problemmatchers.py
@@ -0,0 +1,38 @@
+# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
+
+# Copyright 2015-2020 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
+
+# This file is part of qutebrowser.
+#
+# qutebrowser is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# qutebrowser is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
+
+import re
+
+import pytest
+
+from scripts.dev.ci import problemmatchers
+
+
+@pytest.mark.parametrize('matcher_name', list(problemmatchers.MATCHERS))
+def test_patterns(matcher_name):
+ """Make sure all regexps are valid.
+
+ They aren't actually Python syntax, but hopefully close enough to it to compile with
+ Python's re anyways.
+ """
+ for matcher in problemmatchers.MATCHERS[matcher_name]:
+ for pattern in matcher['pattern']:
+ regexp = pattern['regexp']
+ print(regexp)
+ re.compile(regexp)
diff --git a/tests/unit/utils/test_utils.py b/tests/unit/utils/test_utils.py
index ac7ed5ce7..404185548 100644
--- a/tests/unit/utils/test_utils.py
+++ b/tests/unit/utils/test_utils.py
@@ -818,3 +818,52 @@ def test_libgl_workaround(monkeypatch, skip):
if skip:
monkeypatch.setenv('QUTE_SKIP_LIBGL_WORKAROUND', '1')
utils.libgl_workaround() # Just make sure it doesn't crash.
+
+
+@pytest.mark.parametrize('duration, out', [
+ ("0", 0),
+ ("0s", 0),
+ ("0.5s", 500),
+ ("59s", 59000),
+ ("60", 60),
+ ("60.4s", 60400),
+ ("1m1s", 61000),
+ ("1.5m", 90000),
+ ("1m", 60000),
+ ("1h", 3_600_000),
+ ("0.5h", 1_800_000),
+ ("1h1s", 3_601_000),
+ ("1h 1s", 3_601_000),
+ ("1h1m", 3_660_000),
+ ("1h1m1s", 3_661_000),
+ ("1h1m10s", 3_670_000),
+ ("10h1m10s", 36_070_000),
+])
+def test_parse_duration(duration, out):
+ assert utils.parse_duration(duration) == out
+
+
+@pytest.mark.parametrize('duration', [
+ "-1s", # No sense to wait for negative seconds
+ "-1",
+ "34ss",
+ "",
+ "h",
+ "1.s",
+ "1.1.1s",
+ ".1s",
+ ".s",
+ "10e5s",
+ "5s10m",
+])
+def test_parse_duration_invalid(duration):
+ with pytest.raises(ValueError, match='Invalid duration'):
+ utils.parse_duration(duration)
+
+
+@hypothesis.given(strategies.text())
+def test_parse_duration_hypothesis(duration):
+ try:
+ utils.parse_duration(duration)
+ except ValueError:
+ pass
diff --git a/tests/unit/utils/test_version.py b/tests/unit/utils/test_version.py
index a82514e08..c429effee 100644
--- a/tests/unit/utils/test_version.py
+++ b/tests/unit/utils/test_version.py
@@ -562,11 +562,13 @@ class ImportFake:
('yaml', True),
('adblock', True),
('attr', True),
+ ('importlib_resources', True),
('PyQt5.QtWebEngineWidgets', True),
('PyQt5.QtWebEngine', True),
('PyQt5.QtWebKitWidgets', True),
])
self.no_version_attribute = ['sip',
+ 'importlib_resources',
'PyQt5.QtWebEngineWidgets',
'PyQt5.QtWebKitWidgets',
'PyQt5.QtWebEngine']
diff --git a/tox.ini b/tox.ini
index 73baf89d7..abdce0b5b 100644
--- a/tox.ini
+++ b/tox.ini
@@ -12,11 +12,12 @@ minversion = 3.15
[testenv]
setenv =
PYTEST_QT_API=pyqt5
- pyqt{,512,513,514,515}: LINK_PYQT_SKIP=true
- pyqt{,512,513,514,515}: QUTE_BDD_WEBENGINE=true
+ pyqt{,512,513,514,515,5150}: LINK_PYQT_SKIP=true
+ pyqt{,512,513,514,515,5150}: QUTE_BDD_WEBENGINE=true
cov: PYTEST_ADDOPTS=--cov --cov-report xml --cov-report=html --cov-report=
passenv = PYTHON DISPLAY XAUTHORITY HOME USERNAME USER CI XDG_* QUTE_* DOCKER QT_QUICK_BACKEND PY_COLORS
basepython =
+ py: {env:PYTHON:python3}
py3: {env:PYTHON:python3}
py36: {env:PYTHON:python3.6}
py37: {env:PYTHON:python3.7}
@@ -30,6 +31,7 @@ deps =
pyqt513: -r{toxinidir}/misc/requirements/requirements-pyqt-5.13.txt
pyqt514: -r{toxinidir}/misc/requirements/requirements-pyqt-5.14.txt
pyqt515: -r{toxinidir}/misc/requirements/requirements-pyqt-5.15.txt
+ pyqt5150: -r{toxinidir}/misc/requirements/requirements-pyqt-5.15.0.txt
commands =
{envpython} scripts/link_pyqt.py --tox {envdir}
{envpython} -bb -m pytest {posargs:tests}
@@ -44,7 +46,7 @@ basepython = {env:PYTHON:python3}
passenv = HOME
deps =
commands =
- {envpython} scripts/dev/misc_checks.py all
+ {envpython} scripts/dev/misc_checks.py {posargs:all}
[testenv:vulture]
basepython = {env:PYTHON:python3}