diff --git a/.bumpversion.toml b/.bumpversion.toml index 51524959d..a2e463db9 100644 --- a/.bumpversion.toml +++ b/.bumpversion.toml @@ -1,5 +1,5 @@ [tool.bumpversion] -current_version = "3.4.0" +current_version = "3.5.0" commit = true message = "Release v{new_version}" tag = true diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0ec73024f..b293dc338 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -193,6 +193,14 @@ jobs: - testenv: py313-pyqt68 os: ubuntu-24.04 python: "3.13" + ### PyQt 6.8 (Python 3.14) + - testenv: py314-pyqt68 + os: ubuntu-24.04 + python: "3.14-dev" + ### PyQt 6.9 (Python 3.13) + - testenv: py313-pyqt69 + os: ubuntu-24.04 + python: "3.13" ### macOS Ventura - testenv: py313-pyqt68 os: macos-13 @@ -228,7 +236,7 @@ jobs: - name: Install apt dependencies run: | sudo apt-get update - sudo apt-get install --no-install-recommends libyaml-dev libegl1 libxkbcommon-x11-0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-xinerama0 libxcb-shape0 libxcb-cursor0 + sudo apt-get install --no-install-recommends libyaml-dev libegl1 libxkbcommon-x11-0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-xinerama0 libxcb-shape0 libxcb-cursor0 libjpeg-dev if: "startsWith(matrix.os, 'ubuntu-')" - name: Install dependencies run: | diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index ded12a6b9..1081bbc40 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -15,16 +15,55 @@ breaking changes (such as renamed commands) can happen in minor releases. // `Fixed` for any bug fixes. // `Security` to invite users to upgrade in case of vulnerabilities. -[[v3.4.1]] -v3.4.1 (unreleased) +[[v3.5.1]] +v3.5.1 (unreleased) +------------------- + +Fixed +~~~~~ + +- Resolved issues in userscripts: + * `qute-bitwarden`, `-lastpass` and `-pass` now properly avoid a + `DeprecationWarning` from the upcoming 6.0 release of `tldextract`. + The previous fix in v3.5.1 was insufficient. + +[[v3.5.0]] +v3.5.0 (2025-04-12) ------------------- Changed ~~~~~~~ +- Windows/macOS releases are now built with Qt 6.9.0 + * Based on Chromium 130.0.6723.192 + * Security fixes up to Chromium 133.0.6943.141 + * Also fixes issues with opening links on macOS - The `content.headers.user_agent` setting now has a new `{upstream_browser_version_short}` template field, which is the upstream/Chromium version but shortened to only major version. +- The default user agent now uses the shortened Chromium version and doesn't + expose the `QtWebEngine/...` part anymore, thus making it equal to the + corresponding Chromium user agent. This increases compatibilty due to various + overzealous "security" products used by a variety of websites that block + QtWebEngine, presumably as a bot (known issues existed with Whatsapp Web, UPS, + Digitec Galaxus). +- Changed features in userscripts: + * `qute-bitwarden` now passes your password to the subprocess in an + environment variable when unlocking your vault, instead of as a command + line argument. (#7781) +- New `-D no-system-pdfjs` debug flag to ignore system-wide PDF.js installations + for testing. +- Polyfill for missing `URL.parse` with PDF.js v5 and QtWebEngine < 6.9. Note + this is a "best effort" fix and you should be using the "older browsers" + ("legacy") build of PDF.js instead. + +Removed +~~~~~~~ + +- The `ua-slack` site-specific quirk, as things seem to work better nowadays + without a quirk needed. +- The `ua-whatsapp` site-specific quirk, as it's unneeded with the default UA + change described above. Fixed ~~~~~ @@ -34,8 +73,18 @@ Fixed QtWebEngine and now correctly disabled on the JS side. (#8449) - Crash when a buggy notification presenter returns a duplicate ID (now an error is shown instead). -- The default user agent now only contains the shortened Chromium version - number, which fixes overzealous blocking on ScienceDirect. +- Crashes when running `:tab-move` or `:yank title` at startup, before a tab is + available. +- Crash with `input.insert_mode.auto_load`, when closing a new tab quickly after + opening it, but before it was fully loaded. (#3895, #8400) +- Workaround for microphone/camera permissions not being requested with + QtWebEngine 6.9.0 on Google Meet, Zoom, or other pages using the new + `` element. (#8539) +- Resolved issues in userscripts: + * `qute-bitwarden` will now prompt a re-login if its cached session has + been invalidated since last used. (#8456) + * `qute-bitwarden`, `-lastpass` and `-pass` now avoid a + `DeprecationWarning` from the upcoming 6.0 release of `tldextract` [[v3.4.0]] v3.4.0 (2024-12-14) diff --git a/doc/help/index.asciidoc b/doc/help/index.asciidoc index 4f2b009c7..b52d1c82c 100644 --- a/doc/help/index.asciidoc +++ b/doc/help/index.asciidoc @@ -26,7 +26,7 @@ Getting help You can get help in the IRC channel link:ircs://irc.libera.chat:6697/#qutebrowser[`#qutebrowser`] on https://libera.chat/[Libera Chat] -(https://web.libera.chat/#qutebrowser[webchat], https://matrix.to/#qutebrowser:libera.chat[via Matrix]), +(https://web.libera.chat/#qutebrowser[webchat]), or by writing a message to the https://listi.jpberlin.de/mailman/listinfo/qutebrowser[mailinglist] at mailto:qutebrowser@lists.qutebrowser.org[]. diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc index 7f8775bf5..66696e96f 100644 --- a/doc/help/settings.asciidoc +++ b/doc/help/settings.asciidoc @@ -2296,19 +2296,18 @@ The following placeholders are defined: version, but only with its major version. * `{qutebrowser_version}`: The currently running qutebrowser version. -The default value is equal to the unchanged user agent of -QtWebKit/QtWebEngine. +The default value is equal to the default user agent of +QtWebKit/QtWebEngine, but with the `QtWebEngine/...` part removed for +increased compatibility. -Note that the value read from JavaScript is always the global value. With -QtWebEngine between 5.12 and 5.14 (inclusive), changing the value exposed -to JavaScript requires a restart. +Note that the value read from JavaScript is always the global value. This setting supports link:configuring{outfilesuffix}#patterns[URL patterns]. Type: <> -Default: +pass:[Mozilla/5.0 ({os_info}) AppleWebKit/{webkit_version} (KHTML, like Gecko) {qt_key}/{qt_version} {upstream_browser_key}/{upstream_browser_version_short} Safari/{webkit_version}]+ +Default: +pass:[Mozilla/5.0 ({os_info}) AppleWebKit/{webkit_version} (KHTML, like Gecko) {upstream_browser_key}/{upstream_browser_version_short} Safari/{webkit_version}]+ [[content.hyperlink_auditing]] === content.hyperlink_auditing @@ -2786,9 +2785,7 @@ Type: <> Valid values: - * +ua-whatsapp+ * +ua-google+ - * +ua-slack+ * +ua-googledocs+ * +js-whatsapp-web+ * +js-discord+ diff --git a/misc/org.qutebrowser.qutebrowser.appdata.xml b/misc/org.qutebrowser.qutebrowser.appdata.xml index 1fcc18cbe..636ceae1d 100644 --- a/misc/org.qutebrowser.qutebrowser.appdata.xml +++ b/misc/org.qutebrowser.qutebrowser.appdata.xml @@ -44,6 +44,7 @@ + diff --git a/misc/requirements/requirements-dev.txt b/misc/requirements/requirements-dev.txt index fc290a9f6..832923a83 100644 --- a/misc/requirements/requirements-dev.txt +++ b/misc/requirements/requirements-dev.txt @@ -1,12 +1,12 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py annotated-types==0.7.0 -anyio==4.8.0 +anyio==4.9.0 autocommand==2.2.2 backports.tarfile==1.2.0 bracex==2.5.post1 build==1.2.2.post1 -bump-my-version==1.0.2 +bump-my-version==1.1.2 certifi==2025.1.31 cffi==1.17.1 charset-normalizer==3.4.1 @@ -16,7 +16,7 @@ docutils==0.21.2 exceptiongroup==1.2.2 github3.py==4.0.1 h11==0.14.0 -httpcore==1.0.7 +httpcore==1.0.8 httpx==0.28.1 hunter==3.7.0 id==1.5.0 @@ -37,25 +37,25 @@ mdurl==0.1.2 more-itertools==10.6.0 nh3==0.2.21 packaging==24.2 -platformdirs==4.3.6 +platformdirs==4.3.7 prompt_toolkit==3.0.50 pycparser==2.22 -pydantic==2.10.6 +pydantic==2.11.3 pydantic-settings==2.8.1 -pydantic_core==2.27.2 +pydantic_core==2.33.1 Pygments==2.19.1 PyJWT==2.10.1 Pympler==1.1 pyproject_hooks==1.2.0 PyQt-builder==1.18.1 python-dateutil==2.9.0.post0 -python-dotenv==1.0.1 +python-dotenv==1.1.0 questionary==2.1.0 readme_renderer==44.0 requests==2.32.3 requests-toolbelt==1.0.0 rfc3986==2.0.0 -rich==13.9.4 +rich==14.0.0 rich-click==1.8.8 SecretStorage==3.3.3 sip==6.10.0 @@ -65,9 +65,10 @@ tomli==2.2.1 tomlkit==0.13.2 twine==6.1.0 typeguard==4.3.0 -typing_extensions==4.12.2 +typing-inspection==0.4.0 +typing_extensions==4.13.2 uritemplate==4.1.1 -# urllib3==2.3.0 +# urllib3==2.4.0 wcmatch==10.0 wcwidth==0.2.13 zipp==3.21.0 diff --git a/misc/requirements/requirements-flake8.txt b/misc/requirements/requirements-flake8.txt index 5f41db35f..d421edb84 100644 --- a/misc/requirements/requirements-flake8.txt +++ b/misc/requirements/requirements-flake8.txt @@ -1,7 +1,7 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -attrs==25.1.0 -flake8==7.1.2 +attrs==25.3.0 +flake8==7.2.0 flake8-bugbear==24.12.12 flake8-builtins==2.5.0 flake8-comprehensions==3.16.0 @@ -16,8 +16,8 @@ flake8-tidy-imports==4.11.0 flake8-tuple==0.4.1 mccabe==0.7.0 pep8-naming==0.14.1 -pycodestyle==2.12.1 +pycodestyle==2.13.0 pydocstyle==6.3.0 -pyflakes==3.2.0 +pyflakes==3.3.2 six==1.17.0 snowballstemmer==2.2.0 diff --git a/misc/requirements/requirements-mypy.txt b/misc/requirements/requirements-mypy.txt index de5f4a05f..475554363 100644 --- a/misc/requirements/requirements-mypy.txt +++ b/misc/requirements/requirements-mypy.txt @@ -3,7 +3,7 @@ chardet==5.2.0 diff_cover==9.2.4 Jinja2==3.1.6 -lxml==5.3.1 +lxml==5.3.2 MarkupSafe==3.0.2 mypy==1.15.0 mypy-extensions==1.0.0 @@ -14,5 +14,5 @@ tomli==2.2.1 types-colorama==0.4.15.20240311 types-docutils==0.21.0.20241128 types-Pygments==2.19.0.20250305 -types-PyYAML==6.0.12.20241230 -typing_extensions==4.12.2 +types-PyYAML==6.0.12.20250402 +typing_extensions==4.13.2 diff --git a/misc/requirements/requirements-pyinstaller.txt b/misc/requirements/requirements-pyinstaller.txt index e2439cd84..b309a56d9 100644 --- a/misc/requirements/requirements-pyinstaller.txt +++ b/misc/requirements/requirements-pyinstaller.txt @@ -4,5 +4,5 @@ altgraph==0.17.4 importlib_metadata==8.6.1 packaging==24.2 pyinstaller==6.12.0 -pyinstaller-hooks-contrib==2025.1 +pyinstaller-hooks-contrib==2025.2 zipp==3.21.0 diff --git a/misc/requirements/requirements-pylint.txt b/misc/requirements/requirements-pylint.txt index fb6f3f090..299c2f58d 100644 --- a/misc/requirements/requirements-pylint.txt +++ b/misc/requirements/requirements-pylint.txt @@ -11,16 +11,16 @@ idna==3.10 isort==6.0.1 mccabe==0.7.0 pefile==2024.8.26 -platformdirs==4.3.6 +platformdirs==4.3.7 pycparser==2.22 PyJWT==2.10.1 -pylint==3.3.5 +pylint==3.3.6 python-dateutil==2.9.0.post0 ./scripts/dev/pylint_checkers requests==2.32.3 six==1.17.0 tomli==2.2.1 tomlkit==0.13.2 -typing_extensions==4.12.2 +typing_extensions==4.13.2 uritemplate==4.1.1 -# urllib3==2.3.0 +# urllib3==2.4.0 diff --git a/misc/requirements/requirements-pyqt-6.9.txt b/misc/requirements/requirements-pyqt-6.9.txt new file mode 100644 index 000000000..a70577da1 --- /dev/null +++ b/misc/requirements/requirements-pyqt-6.9.txt @@ -0,0 +1,7 @@ +# This file is automatically generated by scripts/dev/recompile_requirements.py + +PyQt6==6.9.0 +PyQt6-Qt6==6.9.0 +PyQt6-WebEngine==6.9.0 +PyQt6-WebEngine-Qt6==6.9.0 +PyQt6_sip==13.10.0 diff --git a/misc/requirements/requirements-pyqt-6.9.txt-raw b/misc/requirements/requirements-pyqt-6.9.txt-raw new file mode 100644 index 000000000..edeae013a --- /dev/null +++ b/misc/requirements/requirements-pyqt-6.9.txt-raw @@ -0,0 +1,4 @@ +PyQt6 >= 6.9, < 6.10 +PyQt6-Qt6 >= 6.9, < 6.10 +PyQt6-WebEngine >= 6.9, < 6.10 +PyQt6-WebEngine-Qt6 >= 6.9, < 6.10 diff --git a/misc/requirements/requirements-pyqt-6.txt b/misc/requirements/requirements-pyqt-6.txt index 84c0d21f7..a70577da1 100644 --- a/misc/requirements/requirements-pyqt-6.txt +++ b/misc/requirements/requirements-pyqt-6.txt @@ -1,7 +1,7 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -PyQt6==6.8.1 -PyQt6-Qt6==6.8.2 -PyQt6-WebEngine==6.8.0 -PyQt6-WebEngine-Qt6==6.8.2 +PyQt6==6.9.0 +PyQt6-Qt6==6.9.0 +PyQt6-WebEngine==6.9.0 +PyQt6-WebEngine-Qt6==6.9.0 PyQt6_sip==13.10.0 diff --git a/misc/requirements/requirements-pyqt.txt b/misc/requirements/requirements-pyqt.txt index 84c0d21f7..a70577da1 100644 --- a/misc/requirements/requirements-pyqt.txt +++ b/misc/requirements/requirements-pyqt.txt @@ -1,7 +1,7 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -PyQt6==6.8.1 -PyQt6-Qt6==6.8.2 -PyQt6-WebEngine==6.8.0 -PyQt6-WebEngine-Qt6==6.8.2 +PyQt6==6.9.0 +PyQt6-Qt6==6.9.0 +PyQt6-WebEngine==6.9.0 +PyQt6-WebEngine-Qt6==6.9.0 PyQt6_sip==13.10.0 diff --git a/misc/requirements/requirements-pyroma.txt b/misc/requirements/requirements-pyroma.txt index 7224c4cd5..b2beabb54 100644 --- a/misc/requirements/requirements-pyroma.txt +++ b/misc/requirements/requirements-pyroma.txt @@ -12,6 +12,6 @@ pyproject_hooks==1.2.0 pyroma==4.2 requests==2.32.3 tomli==2.2.1 -trove-classifiers==2025.3.3.18 -urllib3==2.3.0 +trove-classifiers==2025.4.11.15 +urllib3==2.4.0 zipp==3.21.0 diff --git a/misc/requirements/requirements-sphinx.txt b/misc/requirements/requirements-sphinx.txt index abe6d9c69..332583170 100644 --- a/misc/requirements/requirements-sphinx.txt +++ b/misc/requirements/requirements-sphinx.txt @@ -22,5 +22,5 @@ sphinxcontrib-jsmath==1.0.1 sphinxcontrib-qthelp==2.0.0 sphinxcontrib-serializinghtml==2.0.0 tomli==2.2.1 -urllib3==2.3.0 +urllib3==2.4.0 zipp==3.21.0 diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index 7646a5970..f6d7c2b77 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -1,6 +1,6 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -attrs==25.1.0 +attrs==25.3.0 autocommand==2.2.2 backports.tarfile==1.2.0 beautifulsoup4==4.13.3 @@ -9,45 +9,45 @@ certifi==2025.1.31 charset-normalizer==3.4.1 cheroot==10.0.1 click==8.1.8 -coverage==7.6.12 +coverage==7.8.0 exceptiongroup==1.2.2 execnet==2.1.1 -filelock==3.17.0 +filelock==3.18.0 Flask==3.1.0 gherkin-official==29.0.0 hunter==3.7.0 -hypothesis==6.128.1 +hypothesis==6.131.0 idna==3.10 importlib_metadata==8.6.1 importlib_resources==6.5.2 inflect==7.3.1 -iniconfig==2.0.0 +iniconfig==2.1.0 itsdangerous==2.2.0 jaraco.collections==5.1.0 jaraco.context==6.0.1 jaraco.functools==4.1.0 jaraco.text==3.12.1 # Jinja2==3.1.6 -Mako==1.3.9 +Mako==1.3.10 manhole==1.8.1 # MarkupSafe==3.0.2 more-itertools==10.6.0 packaging==24.2 parse==1.20.2 parse_type==0.6.4 -pillow==11.1.0 -platformdirs==4.3.6 +pillow==11.2.1 +platformdirs==4.3.7 pluggy==1.5.0 py-cpuinfo==9.0.0 Pygments==2.19.1 pytest==8.3.5 pytest-bdd==8.1.0 pytest-benchmark==5.1.0 -pytest-cov==6.0.0 +pytest-cov==6.1.1 pytest-instafail==0.5.0 pytest-mock==3.14.0 pytest-qt==4.4.0 -pytest-repeat==0.9.3 +pytest-repeat==0.9.4 pytest-rerunfailures==15.0 pytest-xdist==3.6.1 pytest-xvfb==3.1.1 @@ -57,11 +57,11 @@ requests-file==2.1.0 six==1.17.0 sortedcontainers==2.4.0 soupsieve==2.6 -tldextract==5.1.3 +tldextract==5.2.0 tomli==2.2.1 typeguard==4.3.0 -typing_extensions==4.12.2 -urllib3==2.3.0 +typing_extensions==4.13.2 +urllib3==2.4.0 vulture==2.14 Werkzeug==3.1.3 zipp==3.21.0 diff --git a/misc/requirements/requirements-tox.txt b/misc/requirements/requirements-tox.txt index 169109940..6b1a9f51b 100644 --- a/misc/requirements/requirements-tox.txt +++ b/misc/requirements/requirements-tox.txt @@ -4,15 +4,15 @@ cachetools==5.5.2 chardet==5.2.0 colorama==0.4.6 distlib==0.3.9 -filelock==3.17.0 +filelock==3.18.0 packaging==24.2 pip==25.0.1 -platformdirs==4.3.6 +platformdirs==4.3.7 pluggy==1.5.0 pyproject-api==1.9.0 -setuptools==76.0.0 +setuptools==78.1.0 tomli==2.2.1 -tox==4.24.2 -typing_extensions==4.12.2 -virtualenv==20.29.3 +tox==4.25.0 +typing_extensions==4.13.2 +virtualenv==20.30.0 wheel==0.45.1 diff --git a/misc/requirements/requirements-yamllint.txt b/misc/requirements/requirements-yamllint.txt index a501b1d56..67862705e 100644 --- a/misc/requirements/requirements-yamllint.txt +++ b/misc/requirements/requirements-yamllint.txt @@ -2,4 +2,4 @@ pathspec==0.12.1 PyYAML==6.0.2 -yamllint==1.35.1 +yamllint==1.37.0 diff --git a/misc/userscripts/qute-bitwarden b/misc/userscripts/qute-bitwarden index ad17ec08f..4e7557727 100755 --- a/misc/userscripts/qute-bitwarden +++ b/misc/userscripts/qute-bitwarden @@ -133,7 +133,7 @@ def get_session_key(auto_lock, password_prompt_invocation): def pass_(domain, encoding, auto_lock, password_prompt_invocation): session_key = get_session_key(auto_lock, password_prompt_invocation) process = subprocess.run( - ['bw', 'list', 'items', '--session', session_key, '--url', domain], + ['bw', 'list', 'items', '--nointeraction', '--session', session_key, '--url', domain], capture_output=True, ) @@ -142,6 +142,10 @@ def pass_(domain, encoding, auto_lock, password_prompt_invocation): msg = 'Bitwarden CLI returned for {:s} - {:s}'.format(domain, err) stderr(msg) + if "Vault is locked" in err: + stderr("Bitwarden Vault got locked, trying again with clean session") + return pass_(domain, encoding, 0, password_prompt_invocation) + if process.returncode: return '[]' @@ -153,7 +157,7 @@ def pass_(domain, encoding, auto_lock, password_prompt_invocation): def get_totp_code(selection_id, domain_name, encoding, auto_lock, password_prompt_invocation): session_key = get_session_key(auto_lock, password_prompt_invocation) process = subprocess.run( - ['bw', 'get', 'totp', '--session', session_key, selection_id], + ['bw', 'get', 'totp', '--nointeraction', '--session', session_key, selection_id], capture_output=True, ) @@ -163,6 +167,10 @@ def get_totp_code(selection_id, domain_name, encoding, auto_lock, password_promp msg = 'Bitwarden CLI returned for {:s} - {:s}'.format(domain_name, err) stderr(msg) + if "Vault is locked" in err: + stderr("Bitwarden Vault got locked, trying again with clean session") + return get_totp_code(selection_id, domain_name, encoding, 0, password_prompt_invocation) + if process.returncode: return '[]' @@ -196,12 +204,20 @@ def main(arguments): # the registered domain name and finally: the IPv4 address if that's what # the URL represents candidates = [] - for target in filter(None, [ - extract_result.fqdn, - extract_result.registered_domain, - extract_result.subdomain + '.' + extract_result.domain, - extract_result.domain, - extract_result.ipv4]): + for target in filter( + None, + [ + extract_result.fqdn, + ( + extract_result.top_domain_under_public_suffix + if hasattr(extract_result, "top_domain_under_public_suffix") + else extract_result.registered_domain + ), + extract_result.subdomain + "." + extract_result.domain, + extract_result.domain, + extract_result.ipv4, + ], + ): target_candidates = json.loads( pass_( target, diff --git a/misc/userscripts/qute-lastpass b/misc/userscripts/qute-lastpass index d79ef658a..5a7658699 100755 --- a/misc/userscripts/qute-lastpass +++ b/misc/userscripts/qute-lastpass @@ -117,7 +117,20 @@ def main(arguments): # the URL represents candidates = [] seen_id = set() - for target in filter(None, [extract_result.fqdn, extract_result.registered_domain, extract_result.subdomain + extract_result.domain, extract_result.domain, extract_result.ipv4]): + for target in filter( + None, + [ + extract_result.fqdn, + ( + extract_result.top_domain_under_public_suffix + if hasattr(extract_result, "top_domain_under_public_suffix") + else extract_result.registered_domain + ), + extract_result.subdomain + extract_result.domain, + extract_result.domain, + extract_result.ipv4, + ], + ): target_candidates, err = pass_(target, arguments.io_encoding) if err: stderr("LastPass CLI returned for {:s} - {:s}".format(target, err)) diff --git a/misc/userscripts/qute-pass b/misc/userscripts/qute-pass index 0b483c0e2..902f785fd 100755 --- a/misc/userscripts/qute-pass +++ b/misc/userscripts/qute-pass @@ -243,7 +243,20 @@ def main(arguments): netloc = urlparse(arguments.url).netloc - for target in filter(None, [extract_result.fqdn, extract_result.registered_domain, extract_result.ipv4, private_domain, netloc]): + for target in filter( + None, + [ + extract_result.fqdn, + ( + extract_result.top_domain_under_public_suffix + if hasattr(extract_result, "top_domain_under_public_suffix") + else extract_result.registered_domain + ), + extract_result.ipv4, + private_domain, + netloc, + ], + ): attempted_targets.append(target) target_candidates = find_pass_candidates(target, unfiltered=arguments.unfiltered) if not target_candidates: diff --git a/pytest.ini b/pytest.ini index 4b7649c13..54df9528c 100644 --- a/pytest.ini +++ b/pytest.ini @@ -43,6 +43,7 @@ markers = qt5_xfail: Tests which fail with Qt 5 qt6_xfail: Tests which fail with Qt 6 qt69_ci_flaky: Tests which are flaky with Qt 6.9 on CI + qt69_ci_skip: Tests which should be skipped with Qt 6.9 on CI qt_log_level_fail = WARNING qt_log_ignore = # GitHub Actions @@ -95,5 +96,9 @@ filterwarnings = # https://github.com/cucumber/gherkin/commit/2f4830093149eae7ff7bd82f683b3d3bb7320d39 # https://github.com/pytest-dev/pytest-bdd/issues/752 ignore:'maxsplit' is passed as positional argument:DeprecationWarning:gherkin.gherkin_line + # https://github.com/pytest-dev/pytest-mock/issues/468 + ignore:'asyncio\.iscoroutinefunction' is deprecated and slated for removal:DeprecationWarning:pytest_mock.plugin + # https://github.com/ionelmc/pytest-benchmark/issues/283 + ignore:FileType is deprecated\. Simply open files after parsing arguments\.:PendingDeprecationWarning:pytest_benchmark.plugin faulthandler_timeout = 90 xvfb_colordepth = 24 diff --git a/qutebrowser/__init__.py b/qutebrowser/__init__.py index c2dc1c775..85b5db5db 100644 --- a/qutebrowser/__init__.py +++ b/qutebrowser/__init__.py @@ -11,10 +11,10 @@ _year = datetime.date.today().year __author__ = "Florian Bruhin" __copyright__ = "Copyright 2013-{} Florian Bruhin (The Compiler)".format(_year) -__license__ = "GPL" +__license__ = "GPL-3.0-or-later" __maintainer__ = __author__ __email__ = "mail@qutebrowser.org" -__version__ = "3.4.0" +__version__ = "3.5.0" __version_info__ = tuple(int(part) for part in __version__.split('.')) __description__ = "A keyboard-driven, vim-like browser based on Python and Qt." diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 61574b0fb..293950d65 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -73,7 +73,10 @@ class CommandDispatcher: def _current_index(self): """Convenience method to get the current widget index.""" - return self._tabbed_browser.widget.currentIndex() + current_index = self._tabbed_browser.widget.currentIndex() + if current_index == -1: + raise cmdutils.CommandError("No WebView available yet!") + return current_index def _current_url(self): """Convenience method to get the current url.""" @@ -1315,7 +1318,7 @@ class CommandDispatcher: if count is not None: env['QUTE_COUNT'] = str(count) - idx = self._current_index() + idx = self._tabbed_browser.widget.currentIndex() if idx != -1: env['QUTE_TAB_INDEX'] = str(idx + 1) env['QUTE_TITLE'] = self._tabbed_browser.widget.page_title(idx) diff --git a/qutebrowser/browser/pdfjs.py b/qutebrowser/browser/pdfjs.py index 78982d98d..e999a503e 100644 --- a/qutebrowser/browser/pdfjs.py +++ b/qutebrowser/browser/pdfjs.py @@ -11,6 +11,7 @@ from qutebrowser.qt.core import QUrl, QUrlQuery from qutebrowser.utils import resources, javascript, jinja, standarddir, log, urlutils from qutebrowser.config import config +from qutebrowser.misc import objects _SYSTEM_PATHS = [ @@ -127,7 +128,12 @@ def get_pdfjs_res_and_path(path): content = None file_path = None - system_paths = _SYSTEM_PATHS + [ + if 'no-system-pdfjs' in objects.debug_flags: + system_paths = [] + else: + system_paths = _SYSTEM_PATHS[:] + + system_paths += [ # fallback os.path.join(standarddir.data(), 'pdfjs'), # hardcoded fallback for --temp-basedir diff --git a/qutebrowser/browser/webengine/webenginesettings.py b/qutebrowser/browser/webengine/webenginesettings.py index 721360f46..2d2d3c188 100644 --- a/qutebrowser/browser/webengine/webenginesettings.py +++ b/qutebrowser/browser/webengine/webenginesettings.py @@ -26,7 +26,7 @@ from qutebrowser.config import config, websettings from qutebrowser.config.websettings import AttributeInfo as Attr from qutebrowser.misc import pakjoy from qutebrowser.utils import (standarddir, qtutils, message, log, - urlmatch, usertypes, objreg, version) + urlmatch, usertypes, objreg, version, utils) if TYPE_CHECKING: from qutebrowser.browser.webengine import interceptor @@ -495,10 +495,6 @@ def _init_site_specific_quirks(): # "{qt_key}/{qt_version} " # "{upstream_browser_key}/{upstream_browser_version_short} " # "Safari/{webkit_version}") - no_qtwe_ua = ("Mozilla/5.0 ({os_info}) " - "AppleWebKit/{webkit_version} (KHTML, like Gecko) " - "{upstream_browser_key}/{upstream_browser_version_short} " - "Safari/{webkit_version}") firefox_ua = "Mozilla/5.0 ({os_info}; rv:136.0) Gecko/20100101 Firefox/136.0" def maybe_newer_chrome_ua(at_least_version): @@ -514,23 +510,13 @@ def _init_site_specific_quirks(): "Safari/537.36" ) - user_agents = [ - # Needed to avoid a ""WhatsApp works with Google Chrome 36+" error - # page which doesn't allow to use WhatsApp Web at all. Also see the - # additional JS quirk: qutebrowser/javascript/quirks/whatsapp_web.user.js - # https://github.com/qutebrowser/qutebrowser/issues/4445 - ("ua-whatsapp", 'https://web.whatsapp.com/', no_qtwe_ua), + utils.unused(maybe_newer_chrome_ua) + user_agents = [ # Needed to avoid a "you're using a browser [...] that doesn't allow us # to keep your account secure" error. # https://github.com/qutebrowser/qutebrowser/issues/5182 - ("ua-google", 'https://accounts.google.com/*', firefox_ua), - - # Needed because Slack adds an error which prevents using it relatively - # aggressively, despite things actually working fine. - # October 2023: Slack claims they only support 112+. On #7951 at least - # one user claims it still works fine on 108 based Qt versions. - ("ua-slack", 'https://*.slack.com/*', maybe_newer_chrome_ua(112)), + ("ua-google", "https://accounts.google.com/*", firefox_ua), ] for name, pattern, ua in user_agents: diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index 8142c071e..cebc77767 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -1363,6 +1363,11 @@ class WebEngineTab(browsertab.AbstractTab): self._widget.page().toHtml(callback) def run_js_async(self, code, callback=None, *, world=None): + if sip.isdeleted(self._widget): + # https://github.com/qutebrowser/qutebrowser/issues/3895 + log.misc.debug("run_js_async called on deleted tab") + return + world_id_type = Union[QWebEngineScript.ScriptWorldId, int] if world is None: world_id: world_id_type = QWebEngineScript.ScriptWorldId.ApplicationWorld diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml index 641a8d274..bb1de4249 100644 --- a/qutebrowser/config/configdata.yml +++ b/qutebrowser/config/configdata.yml @@ -656,9 +656,7 @@ content.site_specific_quirks.skip: type: name: FlagList valid_values: - - ua-whatsapp - ua-google - - ua-slack - ua-googledocs - js-whatsapp-web - js-discord @@ -752,7 +750,7 @@ content.headers.referer: content.headers.user_agent: default: 'Mozilla/5.0 ({os_info}) AppleWebKit/{webkit_version} (KHTML, like Gecko) - {qt_key}/{qt_version} {upstream_browser_key}/{upstream_browser_version_short} + {upstream_browser_key}/{upstream_browser_version_short} Safari/{webkit_version}' type: name: FormatString @@ -800,12 +798,11 @@ content.headers.user_agent: version, but only with its major version. * `{qutebrowser_version}`: The currently running qutebrowser version. - The default value is equal to the unchanged user agent of - QtWebKit/QtWebEngine. + The default value is equal to the default user agent of + QtWebKit/QtWebEngine, but with the `QtWebEngine/...` part removed for + increased compatibility. - Note that the value read from JavaScript is always the global value. With - QtWebEngine between 5.12 and 5.14 (inclusive), changing the value exposed - to JavaScript requires a restart. + Note that the value read from JavaScript is always the global value. content.host_blocking.enabled: renamed: content.blocking.enabled diff --git a/qutebrowser/config/qtargs.py b/qutebrowser/config/qtargs.py index b02899e28..da0050b0d 100644 --- a/qutebrowser/config/qtargs.py +++ b/qutebrowser/config/qtargs.py @@ -78,7 +78,7 @@ def qt_args(namespace: argparse.Namespace) -> list[str]: return argv -def _qtwebengine_features( +def _qtwebengine_features( # noqa: C901 versions: version.WebEngineVersions, special_flags: Sequence[str], ) -> tuple[Sequence[str], Sequence[str]]: @@ -159,6 +159,11 @@ def _qtwebengine_features( # TODO adjust if fixed in Qt 6.8.2/.3 or 6.9.0/.1 disabled_features.append('DocumentPictureInPictureAPI') + if versions.webengine == utils.VersionNumber(6, 9): + # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-135787 + # TODO adjust if still present in 6.9.1 + disabled_features.append('PermissionElement') + if not config.val.input.media_keys: disabled_features.append('HardwareMediaKeyHandling') diff --git a/qutebrowser/javascript/pdfjs_polyfills.js b/qutebrowser/javascript/pdfjs_polyfills.js index 54e71652d..880642bbe 100644 --- a/qutebrowser/javascript/pdfjs_polyfills.js +++ b/qutebrowser/javascript/pdfjs_polyfills.js @@ -19,4 +19,16 @@ SPDX-License-Identifier: GPL-3.0-or-later return { promise, resolve, reject } } } + + // Chromium 126 / QtWebEngine 6.9 + // https://caniuse.com/mdn-api_url_parse_static + if (typeof URL.parse === "undefined") { + URL.parse = function(url, base) { + try { + return new URL(url, base); + } catch (ex) { + return null; + } + } + } })(); diff --git a/qutebrowser/qutebrowser.py b/qutebrowser/qutebrowser.py index e68156759..044a6cceb 100644 --- a/qutebrowser/qutebrowser.py +++ b/qutebrowser/qutebrowser.py @@ -172,12 +172,14 @@ def debug_flag_error(flag): werror: Turn Python warnings into errors. test-notification-service: Use the testing libnotify service. caret: Enable debug logging for caret.js. + no-system-pdfjs: Ignore system-wide PDF.js installations """ valid_flags = ['debug-exit', 'pdb-postmortem', 'no-sql-history', 'no-scroll-filtering', 'log-requests', 'log-cookies', 'log-scroll-pos', 'log-sensitive-keys', 'stack', 'chromium', 'wait-renderer-process', 'avoid-chromium-init', 'werror', - 'test-notification-service', 'log-qt-events', 'caret'] + 'test-notification-service', 'log-qt-events', 'caret', + 'no-system-pdfjs'] if flag in valid_flags: return flag diff --git a/qutebrowser/utils/version.py b/qutebrowser/utils/version.py index 5a88b8c2b..eaa3662be 100644 --- a/qutebrowser/utils/version.py +++ b/qutebrowser/utils/version.py @@ -639,8 +639,9 @@ class WebEngineVersions: utils.VersionNumber(6, 8): (_BASES[122], '129.0.6668.70'), # 2024-09-24 utils.VersionNumber(6, 8, 1): (_BASES[122], '131.0.6778.70'), # 2024-11-12 utils.VersionNumber(6, 8, 2): (_BASES[122], '132.0.6834.111'), # 2025-01-22 + utils.VersionNumber(6, 8, 3): (_BASES[122], '134.0.6998.89'), # 2025-03-10 - ## Qt 6.9 (Beta 3) + ## Qt 6.9 utils.VersionNumber(6, 9): (_BASES[130], '133.0.6943.141'), # 2025-02-25 } diff --git a/scripts/dev/changelog_urls.json b/scripts/dev/changelog_urls.json index d225919d3..5029eb9c1 100644 --- a/scripts/dev/changelog_urls.json +++ b/scripts/dev/changelog_urls.json @@ -28,7 +28,7 @@ "types-docutils": "https://github.com/python/typeshed/commits/main/stubs/docutils", "types-Pygments": "https://github.com/python/typeshed/commits/main/stubs/Pygments", "pytest": "https://docs.pytest.org/en/latest/changelog.html", - "iniconfig": "https://github.com/pytest-dev/iniconfig/blob/master/CHANGELOG", + "iniconfig": "https://github.com/pytest-dev/iniconfig/blob/main/CHANGELOG", "tox": "https://tox.readthedocs.io/en/latest/changelog.html", "cachetools": "https://github.com/tkem/cachetools/blob/master/CHANGELOG.rst", "pyproject-api": "https://github.com/tox-dev/pyproject-api/releases", @@ -92,7 +92,7 @@ "pydantic": "https://docs.pydantic.dev/latest/changelog/", "pydantic-settings": "https://github.com/pydantic/pydantic-settings/releases", "pydantic_core": "https://github.com/pydantic/pydantic-core/releases", - "python-dotenv": "https://saurabh-kumar.com/python-dotenv/changelog/", + "python-dotenv": "https://github.com/theskumar/python-dotenv/blob/main/CHANGELOG.md", "questionary": "https://github.com/tmbo/questionary/blob/master/docs/pages/changelog.rst", "rich-click": "https://ewels.github.io/rich-click/changelog/", "wcmatch": "https://github.com/facelessuser/wcmatch/releases", @@ -133,6 +133,7 @@ "idna": "https://github.com/kjd/idna/blob/master/HISTORY.rst", "tldextract": "https://github.com/john-kurkowski/tldextract/blob/master/CHANGELOG.md", "typing_extensions": "https://github.com/python/typing_extensions/blob/main/CHANGELOG.md", + "typing-inspection": "https://github.com/pydantic/typing-inspection/blob/main/HISTORY.md", "diff_cover": "https://github.com/Bachmann1234/diff_cover/blob/main/CHANGELOG", "beautifulsoup4": "https://git.launchpad.net/beautifulsoup/tree/CHANGELOG", "check-manifest": "https://github.com/mgedmin/check-manifest/blob/master/CHANGES.rst", diff --git a/setup.py b/setup.py index 97a87a65c..baba8f749 100755 --- a/setup.py +++ b/setup.py @@ -72,8 +72,6 @@ try: 'Development Status :: 5 - Production/Stable', 'Environment :: X11 Applications :: Qt', 'Intended Audience :: End Users/Desktop', - 'License :: OSI Approved :: GNU General Public License v3 or later ' - '(GPLv3+)', 'Natural Language :: English', 'Operating System :: Microsoft :: Windows', 'Operating System :: POSIX :: Linux', diff --git a/tests/conftest.py b/tests/conftest.py index 210f23fad..bf8691033 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -128,7 +128,7 @@ def _apply_platform_markers(config, item): "Failing due to cheroot: https://github.com/cherrypy/cheroot/issues/346"), ( "qt69_ci_flaky", # WORKAROUND: https://github.com/qutebrowser/qutebrowser/issues/8444#issuecomment-2569610110 - pytest.mark.flaky, + pytest.mark.flaky(reruns=3), ( config.webengine and version.qtwebengine_versions(avoid_init=True).webengine @@ -137,6 +137,17 @@ def _apply_platform_markers(config, item): ), "Flaky with QtWebEngine 6.9 on CI", ), + ( + "qt69_ci_skip", # WORKAROUND: https://github.com/qutebrowser/qutebrowser/issues/8444#issuecomment-2569610110 + pytest.mark.skipif, + ( + config.webengine + and version.qtwebengine_versions(avoid_init=True).webengine + == utils.VersionNumber(6, 9) + and testutils.ON_CI + ), + "Skipped with QtWebEngine 6.9 on CI", + ), ] for searched_marker, new_marker_kind, condition, default_reason in markers: diff --git a/tests/end2end/features/qutescheme.feature b/tests/end2end/features/qutescheme.feature index 3e84c39fd..7c6a17e21 100644 --- a/tests/end2end/features/qutescheme.feature +++ b/tests/end2end/features/qutescheme.feature @@ -303,14 +303,14 @@ Feature: Special qute:// pages # :version - @qt69_ci_flaky + @qt69_ci_skip Scenario: Open qute://version When I open qute://version Then the page should contain the plaintext "Version info" # qute://gpl - @qt69_ci_flaky + @qt69_ci_skip Scenario: Open qute://gpl When I open qute://gpl Then the page should contain the plaintext "GNU GENERAL PUBLIC LICENSE" @@ -318,7 +318,7 @@ Feature: Special qute:// pages # qute://start # QtWebKit doesn't support formaction; unknown Qt 6.9 renderer process crashes - @qtwebkit_skip @qt69_ci_flaky + @qtwebkit_skip @qt69_ci_skip Scenario: Searching on qute://start When I set url.searchengines to {"DEFAULT": "http://localhost:(port)/data/title.html?q={}"} And I open qute://start diff --git a/tests/end2end/features/tabs.feature b/tests/end2end/features/tabs.feature index bc4297753..cb9c9702f 100644 --- a/tests/end2end/features/tabs.feature +++ b/tests/end2end/features/tabs.feature @@ -1659,14 +1659,16 @@ Feature: Tab management When I set tabs.last_close to close And I run :tab-only And I run :tab-close ;; tab-next - Then qutebrowser should quit + Then the error "No WebView available yet!" should be shown + And qutebrowser should quit And no crash should happen Scenario: Using :tab-prev after closing last tab (#1448) When I set tabs.last_close to close And I run :tab-only And I run :tab-close ;; tab-prev - Then qutebrowser should quit + Then the error "No WebView available yet!" should be shown + And qutebrowser should quit And no crash should happen Scenario: Opening link with tabs_are_windows set (#2162) diff --git a/tests/end2end/fixtures/quteprocess.py b/tests/end2end/fixtures/quteprocess.py index 9d41d9e3e..3d4ad6127 100644 --- a/tests/end2end/fixtures/quteprocess.py +++ b/tests/end2end/fixtures/quteprocess.py @@ -131,6 +131,8 @@ def is_ignored_chromium_message(line): # Qt 6.2: # [503633:503650:0509/185222.442798:ERROR:ssl_client_socket_impl.cc(959)] handshake failed; returned -1, SSL error code 1, net_error -202 'handshake failed; returned -1, SSL error code 1, net_error -202', + # Qt 6.8 + Python 3.14 + 'handshake failed; returned -1, SSL error code 1, net_error -101', # Qt 6.2: # [2432160:7:0429/195800.168435:ERROR:command_buffer_proxy_impl.cc(140)] ContextResult::kTransientFailure: Failed to send GpuChannelMsg_CreateCommandBuffer. diff --git a/tests/end2end/fixtures/webserver_sub.py b/tests/end2end/fixtures/webserver_sub.py index 117232068..fc1cc5264 100644 --- a/tests/end2end/fixtures/webserver_sub.py +++ b/tests/end2end/fixtures/webserver_sub.py @@ -341,7 +341,7 @@ class WSGIServer(cheroot.wsgi.Server): def unraisable_hook(unraisable: "sys.UnraisableHookArgs") -> None: if ( - sys.version_info[:2] == (3, 13) + sys.version_info[:2] >= (3, 13) and isinstance(unraisable.exc_value, OSError) and ( unraisable.exc_value.errno == errno.EBADF @@ -351,7 +351,19 @@ def unraisable_hook(unraisable: "sys.UnraisableHookArgs") -> None: and unraisable.exc_value.winerror == errno.WSAENOTSOCK ) ) - and unraisable.object.__qualname__ == "IOBase.__del__" + and ( + ( + # Python 3.14 + unraisable.object is None + and unraisable.err_msg.startswith( + "Exception ignored while calling deallocator bool: +def _needs_map_discard_workaround(qtwe_version: utils.VersionNumber) -> bool: """Check if this system needs the glibc 2.41+ MAP_DISCARD workaround. WORKAROUND for https://bugreports.qt.io/browse/QTBUG-134631 @@ -286,9 +286,6 @@ def _needs_map_discard_workaround(webengine_version: utils.VersionNumber) -> boo if not utils.is_posix: return False - # Not fixed yet as of Qt 6.9 Beta 3 - utils.unused(webengine_version) - libc_name, libc_version_str = platform.libc_ver() if libc_name != "glibc": return False @@ -300,7 +297,21 @@ def _needs_map_discard_workaround(webengine_version: utils.VersionNumber) -> boo affected_glibc = utils.VersionNumber(2, 41) affected_kernel = utils.VersionNumber(6, 11) - return libc_version >= affected_glibc and kernel_version >= affected_kernel + return ( + libc_version >= affected_glibc + and kernel_version >= affected_kernel + and not ( + # https://codereview.qt-project.org/c/qt/qtwebengine-chromium/+/631749 + # -> Fixed in QtWebEngine 5.15.9 + utils.VersionNumber(5, 15, 19) <= qtwe_version < utils.VersionNumber(6) + # https://codereview.qt-project.org/c/qt/qtwebengine-chromium/+/631750 + # -> Fixed in QtWebEngine 6.8.4 + or utils.VersionNumber(6, 8, 4) <= qtwe_version < utils.VersionNumber(6, 9) + # https://codereview.qt-project.org/c/qt/qtwebengine-chromium/+/631348 + # -> Fixed in QtWebEngine 6.9.1 + or utils.VersionNumber(6, 9, 1) <= qtwe_version + ) + ) def disable_seccomp_bpf_sandbox() -> bool: diff --git a/tests/unit/browser/test_downloads.py b/tests/unit/browser/test_downloads.py index 8dd4f0c31..75b9a27cc 100644 --- a/tests/unit/browser/test_downloads.py +++ b/tests/unit/browser/test_downloads.py @@ -10,7 +10,9 @@ from qutebrowser.browser import downloads, qtnetworkdownloads @pytest.fixture def manager(config_stub, cookiejar_and_cache): """A QtNetwork download manager.""" - return qtnetworkdownloads.DownloadManager() + dl_manager = qtnetworkdownloads.DownloadManager() + yield dl_manager + dl_manager.deleteLater() def test_download_model(qapp, qtmodeltester, manager): diff --git a/tests/unit/browser/test_pdfjs.py b/tests/unit/browser/test_pdfjs.py index 867e8e1de..b13e5b356 100644 --- a/tests/unit/browser/test_pdfjs.py +++ b/tests/unit/browser/test_pdfjs.py @@ -3,6 +3,8 @@ # SPDX-License-Identifier: GPL-3.0-or-later import logging +import pathlib +import unittest.mock import os.path import pytest @@ -10,6 +12,7 @@ from qutebrowser.qt.core import QUrl from qutebrowser.browser import pdfjs from qutebrowser.utils import urlmatch, utils +from qutebrowser.misc import objects pytestmark = [pytest.mark.usefixtures('data_tmpdir')] @@ -73,20 +76,32 @@ class TestResources: read_system_mock.assert_called_with('/usr/share/pdf.js/', ['web/test', 'test']) - def test_get_pdfjs_res_bundled(self, read_system_mock, read_file_mock, - tmpdir): + @pytest.mark.parametrize("with_system", [True, False]) + def test_get_pdfjs_res_bundled( + self, + read_system_mock: unittest.mock.Mock, + read_file_mock: unittest.mock.Mock, + tmp_path: pathlib.Path, + monkeypatch: pytest.MonkeyPatch, + with_system: bool, + ) -> None: read_system_mock.return_value = (None, None) - read_file_mock.return_value = b'content' + if not with_system: + monkeypatch.setattr(objects, 'debug_flags', {'no-system-pdfjs'}) assert pdfjs.get_pdfjs_res_and_path('web/test') == (b'content', None) assert pdfjs.get_pdfjs_res('web/test') == b'content' - for path in ['/usr/share/pdf.js/', - str(tmpdir / 'data' / 'pdfjs'), - # hardcoded for --temp-basedir - os.path.expanduser('~/.local/share/qutebrowser/pdfjs/')]: - read_system_mock.assert_any_call(path, ['web/test', 'test']) + paths = {call.args[0] for call in read_system_mock.mock_calls} + expected_paths = { + str(tmp_path / 'data' / 'pdfjs'), + # hardcoded for --temp-basedir + os.path.expanduser('~/.local/share/qutebrowser/pdfjs/') + } + assert expected_paths.issubset(paths) + if not with_system: + assert '/usr/share/pdf.js/' not in paths def test_get_pdfjs_res_not_found(self, read_system_mock, read_file_mock, caplog): diff --git a/tests/unit/components/test_braveadblock.py b/tests/unit/components/test_braveadblock.py index dab842139..b23827827 100644 --- a/tests/unit/components/test_braveadblock.py +++ b/tests/unit/components/test_braveadblock.py @@ -105,7 +105,6 @@ def assert_none_blocked(ad_blocker): assert_urls(ad_blocker, NOT_OKAY_URLS + OKAY_URLS, False) def assert_not_blocked(url, source_url, resource_type): - nonlocal ad_blocker assert not ad_blocker._is_blocked(url, source_url, resource_type) run_function_on_dataset(assert_not_blocked) diff --git a/tests/unit/config/test_configfiles.py b/tests/unit/config/test_configfiles.py index 25e2d7e50..d01a0e721 100644 --- a/tests/unit/config/test_configfiles.py +++ b/tests/unit/config/test_configfiles.py @@ -787,7 +787,6 @@ class TestYamlMigrations: @pytest.mark.parametrize('old, new', [ (None, ('Mozilla/5.0 ({os_info}) ' 'AppleWebKit/{webkit_version} (KHTML, like Gecko) ' - '{qt_key}/{qt_version} ' '{upstream_browser_key}/{upstream_browser_version_short} ' 'Safari/{webkit_version}')), ('toaster', 'toaster'), diff --git a/tests/unit/config/test_qtargs.py b/tests/unit/config/test_qtargs.py index ab06aeb76..ed2d6ae45 100644 --- a/tests/unit/config/test_qtargs.py +++ b/tests/unit/config/test_qtargs.py @@ -448,25 +448,25 @@ class TestWebEngineArgs: expected = ['--disable-features=InstalledApp'] if has_workaround else [] assert disable_features_args == expected - @pytest.mark.parametrize('qt_version, has_workaround', [ + @pytest.mark.parametrize('qt_version, disabled', [ # Qt 6.6 - ('6.6.3', False), + ('6.6.3', None), # Qt 6.7 - ('6.7.0', True), - ('6.7.1', True), - ('6.7.2', True), - ('6.7.3', True), + ('6.7.0', "DocumentPictureInPictureAPI"), + ('6.7.1', "DocumentPictureInPictureAPI"), + ('6.7.2', "DocumentPictureInPictureAPI"), + ('6.7.3', "DocumentPictureInPictureAPI"), # Qt 6.8 - ('6.8.0', True), - ('6.8.1', True), - ('6.8.2', True), # tbd - ('6.8.3', True), # tbd + ('6.8.0', "DocumentPictureInPictureAPI"), + ('6.8.1', "DocumentPictureInPictureAPI"), + ('6.8.2', "DocumentPictureInPictureAPI"), + ('6.8.3', "DocumentPictureInPictureAPI"), # Qt 6.9 - ('6.9.0', True), # tbd - ('6.9.1', True), # tbd + ('6.9.0', "DocumentPictureInPictureAPI,PermissionElement"), + ('6.9.1', "DocumentPictureInPictureAPI"), # tbd ]) - def test_document_pip_workaround( - self, parser, version_patcher, qt_version, has_workaround + def test_disble_feature_workaround( + self, parser, version_patcher, qt_version, disabled ): version_patcher(qt_version) @@ -477,8 +477,7 @@ class TestWebEngineArgs: if arg.startswith(qtargs._DISABLE_FEATURES) ] - flag = "--disable-features=DocumentPictureInPictureAPI" - expected = [flag] if has_workaround else [] + expected = [f"--disable-features={disabled}"] if disabled else [] assert disable_features_args == expected @pytest.mark.parametrize('enabled', [True, False]) diff --git a/tests/unit/javascript/test_js_quirks.py b/tests/unit/javascript/test_js_quirks.py index 981f9d9e8..b7760b980 100644 --- a/tests/unit/javascript/test_js_quirks.py +++ b/tests/unit/javascript/test_js_quirks.py @@ -61,17 +61,17 @@ def test_js_quirks_match_files(webengine_tab): def test_js_quirks_match_settings(webengine_tab, configdata_init): + quirks_code = {q.name for q in webengine_tab._scripts._get_quirks()} + opt = configdata.DATA["content.site_specific_quirks.skip"] - prefix = "js-" valid_values = opt.typ.get_valid_values() assert valid_values is not None quirks_config = { - val.removeprefix(prefix).replace("-", "_") + val for val in valid_values - if val.startswith(prefix) + # some JS quirks are actually only setting the user agent, so we include + # those as well. + if val.startswith("js-") or (val.startswith("ua-") and val in quirks_code) } - quirks_code = {q.filename for q in webengine_tab._scripts._get_quirks()} - quirks_code -= {"googledocs"} # special case, UA quirk - assert quirks_code == quirks_config diff --git a/tests/unit/utils/test_qtutils.py b/tests/unit/utils/test_qtutils.py index 0a3afa416..240cc42dd 100644 --- a/tests/unit/utils/test_qtutils.py +++ b/tests/unit/utils/test_qtutils.py @@ -530,7 +530,7 @@ class TestSavefileOpen: assert data == b'foo\nbar\nbaz' -if test_file is not None: +if test_file is not None: # noqa: C901 # If we were able to import Python's test_file module, we run some code # here which defines unittest TestCases to run the python tests over # PyQIODevice. @@ -577,7 +577,7 @@ if test_file is not None: qiodev.name = test_file.TESTFN qiodev.mode = mode # Create empty TESTFN file because the Python tests try to unlink - # it.after the test. + # it after the test. with open(test_file.TESTFN, 'w', encoding='utf-8'): pass return qiodev @@ -598,6 +598,9 @@ if test_file is not None: def testSetBufferSize(self): """Skip this test as setting buffer size is unsupported.""" + def testDefaultBufferSize(self): + """Skip this test as getting buffer size is unsupported.""" + def testTruncateOnWindows(self): """Skip this test truncating is unsupported.""" diff --git a/tox.ini b/tox.ini index 92f61e55b..18da47b7f 100644 --- a/tox.ini +++ b/tox.ini @@ -41,6 +41,7 @@ basepython = py311: {env:PYTHON:python3.11} py312: {env:PYTHON:python3.12} py313: {env:PYTHON:python3.13} + py314: {env:PYTHON:python3.14} deps = -r{toxinidir}/requirements.txt -r{toxinidir}/misc/requirements/requirements-tests.txt @@ -55,8 +56,9 @@ deps = pyqt66: -r{toxinidir}/misc/requirements/requirements-pyqt-6.6.txt pyqt67: -r{toxinidir}/misc/requirements/requirements-pyqt-6.7.txt pyqt68: -r{toxinidir}/misc/requirements/requirements-pyqt-6.8.txt + pyqt69: -r{toxinidir}/misc/requirements/requirements-pyqt-6.9.txt commands = - !pyqt-!pyqt515-!pyqt5152-!pyqt62-!pyqt63-!pyqt64-!pyqt65-!pyqt66-!pyqt67-!pyqt68: {envpython} scripts/link_pyqt.py --tox {envdir} + !pyqt-!pyqt515-!pyqt5152-!pyqt62-!pyqt63-!pyqt64-!pyqt65-!pyqt66-!pyqt67-!pyqt68-!pyqt69: {envpython} scripts/link_pyqt.py --tox {envdir} {envpython} -bb -m pytest {posargs:tests} cov: {envpython} scripts/dev/check_coverage.py {posargs}