Merge remote-tracking branch 'upstream/main' into tree-tabs-integration

This commit is contained in:
toofar 2025-04-20 15:52:19 +12:00
commit e6524f7959
51 changed files with 368 additions and 165 deletions

View File

@ -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

View File

@ -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: |

View File

@ -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
`<permission>` 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)

View File

@ -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[].

View File

@ -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: <<types,FormatString>>
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: <<types,FlagList>>
Valid values:
* +ua-whatsapp+
* +ua-google+
* +ua-slack+
* +ua-googledocs+
* +js-whatsapp-web+
* +js-discord+

View File

@ -44,6 +44,7 @@
</content_rating>
<releases>
<!-- Add new releases here -->
<release version='3.5.0' date='2025-04-12'/>
<release version="3.4.0" date="2024-12-14"/>
<release version="3.3.1" date="2024-10-12"/>
<release version="3.3.0" date="2024-10-12"/>

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -2,4 +2,4 @@
pathspec==0.12.1
PyYAML==6.0.2
yamllint==1.35.1
yamllint==1.37.0

View File

@ -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,

View File

@ -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))

View File

@ -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:

View File

@ -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

View File

@ -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."

View File

@ -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)

View File

@ -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

View File

@ -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:

View File

@ -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

View File

@ -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

View File

@ -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')

View File

@ -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;
}
}
}
})();

View File

@ -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

View File

@ -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
}

View File

@ -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",

View File

@ -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',

View File

@ -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:

View File

@ -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

View File

@ -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)

View File

@ -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.

View File

@ -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 <function IOBase.__del__ at "
)
)
or (
unraisable.object is not None
and unraisable.object.__qualname__ == "IOBase.__del__"
)
)
):
# WORKAROUND for bogus exceptions with cheroot:
# https://github.com/cherrypy/cheroot/issues/734

View File

@ -56,9 +56,25 @@ def test_auto_load(quteproc, auto_load, background, insert_mode):
quteproc.ensure_not_logged(message=log_message)
def test_auto_load_delayed_tab_close(quteproc):
"""We shouldn't try to run JS on dead tabs async.
Triggering the bug is pretty timing-dependent, so this test might still pass
even if a bug is present. Howevber, with those timings, it triggers consistently
on my machine.
"""
quteproc.set_setting('input.insert_mode.auto_load', "true")
quteproc.send_cmd(":cmd-later 50 open -t about:blank")
quteproc.send_cmd(":cmd-later 110 tab-close")
quteproc.wait_for(message="command called: tab-close")
def test_auto_leave_insert_mode(quteproc):
quteproc.set_setting('input.insert_mode.auto_load', 'true')
url_path = 'data/insert_mode_settings/html/autofocus.html'
quteproc.open_path(url_path)
quteproc.wait_for(message='Entering mode KeyMode.insert (reason: *)')
quteproc.set_setting('input.insert_mode.auto_leave', 'true')
quteproc.send_cmd(':zoom 100')

View File

@ -277,7 +277,7 @@ DISABLE_SECCOMP_BPF_FLAG = "--disable-seccomp-filter-sandbox"
DISABLE_SECCOMP_BPF_ARGS = ["-s", "qt.chromium.sandboxing", "disable-seccomp-bpf"]
def _needs_map_discard_workaround(webengine_version: utils.VersionNumber) -> 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:

View File

@ -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):

View File

@ -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):

View File

@ -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)

View File

@ -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'),

View File

@ -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])

View File

@ -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

View File

@ -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."""

View File

@ -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}