The workaround added for #7820 seems to cause datalist dropdowns to lose focus
on Wayland. Let's just disable the old workaround on Qt versions that are not affected
by the original issue, which seems to be Qt 6.6.3+.
Fixes#8831.
libwayland-client.so is development symlink used during linking and there's no need to
have it installed (usually shipped in -devel/-dev packages) on user's machines. Instead
of hardcoding library file name, use same mechanism as in libX11 which let's Python
figure the details and share common logic between X11 and Wayland.
Fixes#8771
https://github.blog/changelog/2025-09-19-github-actions-macos-13-runner-image-is-closing-down/
- CI: Switch to macOS 15 Intel runner
(macOS 14 is still tested with Apple Silicon)
- Nightly: Use macOS 15 Intel runner for nightly releases
(macOS 14 would be better to align with actual Intel releases, but it is
a -large runner, thus possibly metered)
- Releases: Use macOS 14 for Intel releases
This is a -large runner, but releases don't happen often.
The docs show an example for adding domain filtering for configuration options. However the example only matches the root of a domain rather than all pages on a domain which is for example, the default case when using the `tsh` shortcut to disable/enable javascript on a page.
The fix for #8223 in 6f21accfae
was misguided: We don't really care about the statusbar being hidden,
controlled release of keyboard focus needs to happen in any case where
we're hiding the command widget (as that's when we lose keyboard focus).
Fixes#8750.
Equivalent to https://codereview.qt-project.org/c/qt/qtwebengine/+/663568
Specify native platform for ANGLE's EGL backend on Wayland
Set EGL_PLATFORM=wayland to force ANGLE to obtain EGL display connection
for wayland platform. Otherwise, the display connection for
EGL_DEFAULT_DISPLAY may belong to a platform which Nvidia's EGL driver
doesn't support. In case of unsupported platform, EGL may fallback to
Mesa software renderer (LLVMPipe) disabling hardware acceleration in
Chromium.
Fixes#8637
Qt 6.8.2 has a more fine-grained workaround:
https://codereview.qt-project.org/c/qt/qtwebengine-chromium/+/606122
This never seems to have it made to Qt 6.9+, but I can't seem to reproduce the
issue anymore (neither with PDF.js nor with Google Sheets), even on older
affected Qt versions, even on older Intel hardware. Maybe something else (mesa
etc.?) changed and this was fixed there?
Let's reenable this and find out if it breaks things again for someone.
Fixes#8346
See #7489, #8001, #8006
This is conceptually similar to the release_focus
signal added in 6aa19eb90f and
6f21accfae, but without having to thread the
signal through to TabbedBrowser and back.
Weirdly, doing self.setFocusPolicy(Qt.FocusPolicy.NoFocus) in
FullscreenNotification.__init__ did not help, so let's just set the focus
manually instead.
Fixes#8174.
Fixes#8625.
From knezi's analysis in #8722:
The problem was that in Qt, slots are called in the order of connection, so
even though there's a code that tries to set up the focus correctly, it's
run after the cmd widget is hidden and hence the focus is already moved and
it doesn't work as expected.
Follow-up for #2236/#8024.
Fixes#8223.
Supersedes and closes#8722.
Also see #8625 and #8174 (which are not fixed by this).
Qt < 6.9 doesn't crash in this situation, and seems to handle nested prompts
differently, causing the test to still wait for a prompt answer.
Follow-up to a13306a79f
For an unknown reason, if a download prompt is triple-nested, Qt segfaults here:
#0 add_lazy_attrs (td=0x7ffff52e0dd1 <QtPrivate::sizedFree(void*, unsigned long)+32>) at sip_core.c:6255
#1 sip_add_all_lazy_attrs (td=0x7ffff52e0dd1 <QtPrivate::sizedFree(void*, unsigned long)+32>) at sip_core.c:6304
#2 0x00007ffff636598e in sip_api_is_py_method_12_8 (gil=gil@entry=0x7fffffff8b8c, pymc=pymc@entry=0x7fffffff8b8b "", sipSelfp=sipSelfp@entry=0x7fffffff8ba8, cname=cname@entry=0x0, mname=mname@entry=0x7ffff636d57b "__dtor__") at sip_core.c:7402
#3 0x00007ffff6365c2c in sip_api_is_py_method_12_8 (mname=0x7ffff636d57b "__dtor__", cname=0x0, sipSelfp=0x7fffffff8ba8, pymc=0x7fffffff8b8b "", gil=0x7fffffff8b8c) at sip_core.c:7356
#4 callPyDtor (self=<optimized out>) at sip_core.c:5375
#5 sip_api_instance_destroyed_ex (sipSelfp=0x7fffffff8c40) at sip_core.c:5311
#6 0x00007ffff5fc9967 in sipQEventLoop::~sipQEventLoop() () from [...]/python3.13/site-packages/PyQt6/QtCore.abi3.so
#7 0x00007ffff0bcd749 in QFileInfoGatherer::getInfo (this=0x5555583f9bc0, fileInfo=...) at [...]/qt5/qtbase/src/gui/itemmodels/qfileinfogatherer.cpp:349
#8 0x00007ffff0be2629 in QFileSystemModelPrivate::fileSystemChanged (this=0x5555583f9870, path="/tmp/qbdl/download", updates=QList<std::pair<QString, QFileInfo>> (size = 3) = {...}) at [...]/qt5/qtbase/src/gui/itemmodels/qfilesystemmodel.cpp:1966
(full stacktrace has 183 frames)
After a lot of experimentation, I figured out that moving the `NullIconProvider`
to a class variable (or removing it entirely) seems to help. Note that making it
`global` (but keeping the instanciation inside `FilenamePrompt._init_fileview()`)
did not!
This is semi-blind fix: It's unclear to me if this is a proper fix, or if the
changed memory layout just causes the issue to temporary disappear.
However, given that `QFileInfoGatherer::getInfo()` calls into the icon provider,
it might very well be the real deal.
Closes#8674
window-latest switched to windows-2025, where GitHub doesn't preinstall NSIS:
https://github.com/actions/runner-images/issues/12677
Let's install it manually (untested, might need follow-up commits).
The release.yml workflow uses windows-2019 (and will switch to windows-2022 in a
follow-up commit), so it is unaffected for now.
With Python 3.14, since argparse enabled coloring by default:
17c5959aa3
running test_cmdutils.py failed with a lengthy traceback finishing in:
```
INTERNALERROR> pluggy.PluggyTeardownRaisedWarning: A plugin raised an exception during an old-style hookwrapper teardown.
INTERNALERROR> Plugin: /home/florian/proj/qutebrowser/git/tests/conftest.py, Hook: pytest_runtest_makereport
INTERNALERROR> PluggyTeardownRaisedWarning: A plugin raised an exception during an old-style hookwrapper teardown.
INTERNALERROR> Plugin: rerunfailures, Hook: pytest_runtest_makereport
INTERNALERROR> PluggyTeardownRaisedWarning: A plugin raised an exception during an old-style hookwrapper teardown.
INTERNALERROR> Plugin: hypothesispytest, Hook: pytest_runtest_makereport
INTERNALERROR> PluggyTeardownRaisedWarning: A plugin raised an exception during an old-style hookwrapper teardown.
INTERNALERROR> Plugin: pytest-bdd, Hook: pytest_runtest_makereport
INTERNALERROR> AttributeError: 'types.SimpleNamespace' object has no attribute 'verbose'
```
with one part pointing to Python internals:
```pytb
INTERNALERROR> File "/home/florian/proj/qutebrowser/git/.tox/py314-pyqt68/lib/python3.14/site-packages/_pytest/_code/code.py", line 669, in exconly
INTERNALERROR> lines = format_exception_only(self.type, self.value)
INTERNALERROR> File "/usr/lib/python3.14/traceback.py", line 180, in format_exception_only
INTERNALERROR> te = TracebackException(type(value), value, None, compact=True)
INTERNALERROR> File "/usr/lib/python3.14/traceback.py", line 1106, in __init__
INTERNALERROR> suggestion = _compute_suggestion_error(exc_value, exc_traceback, wrong_name)
INTERNALERROR> File "/usr/lib/python3.14/traceback.py", line 1653, in _compute_suggestion_error
INTERNALERROR> import _suggestions
INTERNALERROR> File "<frozen importlib._bootstrap>", line 1371, in _find_and_load
INTERNALERROR> File "<frozen importlib._bootstrap>", line 1342, in _find_and_load_unlocked
INTERNALERROR> File "<frozen importlib._bootstrap>", line 951, in _load_unlocked
INTERNALERROR> File "<frozen importlib._bootstrap>", line 496, in _verbose_message
INTERNALERROR> AttributeError: 'types.SimpleNamespace' object has no attribute 'verbose'
```
This happens when Python tries to access sys.flags.verbose, but we replaced it
with a fake object that does not have a .verbose attribute anymore (only
temporarily, but for some reason that's enough to trigger the issue anyways).
Since we can't mutate sys.flags, instead we create an object that copies all
sys.flags attributes, and then use that as a nicer replacement.
See #8529
This keeps our setup.py around for now, while still supporting a PEP-517
compliant build. It's the minimum required change to make modern pyroma stop
complaining, and hopefully to avoid deprecation warnings.
Partially duplicates #8560
See #3526
Unfortunately there is no way to get this information from Qt, so I had to
resort to some funny low-level C-like Python programming to directly use
libwayland-client and Xlib. Fun was had! Hopefully this avoids having to ask
for this information every time someone shows a bug/crash report, as there
are various subtleties that can be specific to the Wayland compositor in use.
Packages are slowly migrating to not having a __version__ attribute anymore,
instead relying on importlib.metadata to query the installed version.
jinja2 now shows a deprecation warning when accessing the __version__
attribute: https://github.com/pallets/jinja/pull/2098
For now we keep accessing __version__ for other packages (we still need the
logic for PyQt and its special version attributes anyways), but we fall back on
importlib.metadata.version if we can't get a version that way, and we stop
trying __version__ for jinja2.
With windows-2022 and windows-2025 on GitHub Actions,
we get:
qutescheme:data_for_url:128 url: qute://pdfjs/web/viewer.mjs, path: /web/viewer.mjs, host pdfjs
webenginequtescheme:requestStarted:105 Returning text/plain data
which causes:
JS: [qute://pdfjs/build/pdf.mjs:0] Failed to load module script: Expected a
JavaScript module script but the server responded with a MIME type of
"text/plain". Strict MIME type checking is enforced for module scripts per
HTML spec.
It's unclear why we get text/plain back there in the first place (can't
reproduce on a local Windows 11 install), but it's definitely wrong, so let's
just override that problematic case.
It's changed in this PR: https://github.com/mozilla/pdf.js/pull/19956
to have the version string as a comment in the file, instead of the
variable.
Making the regex more forgiving increases the chance of matching on the
wrong string on a past or future version. I've only tested with the
previous version (v5.2.133) and it seemed to still work there.
Changes I made to the regex:
* add the literal * to the match group of possible prefixes of pdfjsVersion
* make the quotes around the version optional
* make the semicolon at the end of the line optional
* add newline to the list of characters that can terminate the match
group (otherwise it was matching across the end of the line up to the
first string, kinda odd when there was a $ after the match group)
We only have to cast these variables on Qt5. If we cast them on Qt6 mypy
warns of a redundant cast. If we ignore that warning mypy complains of a
redundant ignore on Qt5.
Add a conditional on `machinery.IS_QT5` and only cast it required.
But also move that conditional out to a utility function to avoid
duplicating code in the function doing the actual logic.
I'm re-using the `_T` generic TypeVar defined above. Not sure if it's
proper to re-use them or not. Doesn't python have a better way of
defining generics now?
I added the `valid-type` ignore because mypy was complaining:
Variable "to_type" is not valid as a type
I'm not sure if there is some way to do it better, but `reveal_type()`
says that the call site is getting the right type back in the end.
With PyQtWebEngine-Qt5 5.15.17 (Qt 5.15.19), we seem to run into similar issues
like we already did with Qt 6.5:
https://github.com/qutebrowser/qutebrowser/issues/7624#issuecomment-14740084709cb54b2099
However, skipping the ELF test is not enough, as we also get warnings
at runtime (as we don't have any API to get the version at runtime).
We don't care much about Qt 5 at this stage, so let's just not output
warnings in that case (and nothing in the code should care about the exact
QtWebEngine patch level beyond 5.15.2 anyways).
For most user-facing things, we *can* get the exact version number from
the user-agent, so this should not actually affect much.
This sometimes fails with 'The data stream has read past the end of the data in
the underlying device', which turns up in crash reports around once a month.
This is somewhat similar to https://bugreports.qt.io/browse/QTBUG-117489 - but
without knowing the data and without being able to reproduce, it's unclear what
the culprit could be.
It's broken in weird ways since recently (`:version` not loading,
segfault in test_version.py). Since nobody should be using it anyways,
there is no point in spending time on debugging a tricky issue.
Next step is probably ripping it out completely, but that's a separate
can of worms.
See #4039
We create the DownloadManager with parent=qapp, which means they will stick
around forever after the test finished.
While we disconnect the QWebEngineProfile::downloadRequested() signal,
we keep the DownloadManager around, which also keeps around its download-update
timers.
Those will then result in tests/unit/utils/usertypes/test_timer.py::test_early_timeout_check
being flaky, as their _validity_check_handler slot keeps getting called in the
background. Due to the globally mocked time.monotonic(), this results in
nonsensical error messages such as:
Got logging message on logger misc with level WARNING:
Timer download-update (id ...) triggered too early:
interval 500 but only -1023.269s passed!
After this change, we now clean up those objects properly, thus fixing the
flakiness.
See #5390.
To fix a flaky tests/unit/utils/usertypes/test_timer.py::test_early_timeout_check
(where a download-update timer from an earlier test fails), I looked into what
usertypes.Timer instances where left over after a test finished.
It turns out that there were about 190 still existing "partial-match" and
"normal-inhibited" timers when breaking in test_timer.py, even when just running
tests in tests/unit/browser/ and tests/unit/utils/usertypes.
This is because we pass qapp as parent to the ModeManager we create, but that
means it will be forever alive if we don't take care of cleaning it up after a
test.
Perhaps our tests should have some sort of mechanism that checks whether there
are any "leftovers" after a test has finished (perhaps even as part of
pytest-qt?), but for now, let's just fix the issues we can directly see.
Skip this hypothesis version pending https://github.com/HypothesisWorks/hypothesis/issues/4375
Our test suite is currently failing due to running python with `-b` and
being configured to fail on warnings.
We'll pick it up on the next update run or so.
The previous fix in 3dc212a815 was insufficient,
as the inner `getattr(extract_result, "registered_domain")` was always evaluated
first (thus triggering the deprecation warning again).
We also cannot do:
getattr(extract_result, "top_domain_under_public_suffix", None) or extract_result.registered_domain
as `""` is a valid value for it.
Speculative fix for test_early_timeout_handler in
tests/unit/utils/usertypes/test_timer.py failing:
> assert len(caplog.messages) == 1
E AssertionError: assert 5 == 1
due to:
------------------------------ Captured log call -------------------------------
WARNING misc:usertypes.py:467 Timer download-update (id 620757000) triggered too early: interval 500 but only -609.805s passed
WARNING misc:usertypes.py:467 Timer download-update (id 922746881) triggered too early: interval 500 but only -609.429s passed
WARNING misc:usertypes.py:467 Timer download-update (id 1056964613) triggered too early: interval 500 but only -609.537s passed
WARNING misc:usertypes.py:467 Timer download-update (id 1912602631) triggered too early: interval 500 but only -609.671s passed
WARNING misc:usertypes.py:467 Timer t (id -1) triggered too early: interval 3 but only 0.001s passed
We sometimes tried to use hints before the page was fully rendered (?), thus
causing no elements to be found.
It also doesn't make much sense to test leaving insert mode if we aren't in
insert mode yet, so make sure we entered it first.
See #5390
My reproducer is this:
* open the browser with the auto insert mode JS primed, and two tabs:
`python3 -m qutebrowser -T -s input.insert_mode.auto_load true about:blank?1 about:blank?2`
* close the second tab: `d`
* re-open the closed tab then close it again real quick: `u` then `d`
If you have trouble reproducing, try increasing the 65ms delay in
`handle_auto_insert_mode` to be bigger (like 500ms).
Closes: #3895
This can be used to easily test a different PDF.js version manually (which is
installed via update_3rdparty.py), without having to uninstall the system-wide
one first.
Not yet quite sure what exactly is the culprit, but this seems to help for all
tests (!) to pass with Xvfb locally.
For now only scoped to Qt 6.9.0. Will probably already need to reevaluate with
the RC, but definitely with the final release.
See #8444
The DocumentPictureInPicture JS API added in Chromium 116 is not implemented in
QtWebEngine. This results in createWindow() being called with a window type with
random value, which then causes qutebrowser to bail out:
Traceback (most recent call last):
File ".../qutebrowser/browser/webengine/webview.py", line 123, in createWindow
raise ValueError("Invalid wintype {}".format(debug_type))
ValueError: Invalid wintype 843995690
Until this is fixed in Qt, we pass an argument to Chromium to disable the API
entirely, so that web pages hopefully fall back to something else.
In the case of the new Google Huddle feature, this results in them still working
with an on-page overlay instead.
Thanks to Joshua Cold and Vivia for helping to debug this!
Fixes#8449
See https://bugreports.qt.io/browse/QTBUG-132681
Ubuntu 20.04 will be EOL in April 2025, and PyQt 6.8 does not support being
installed on it anymore:
https://pyqt-builder.readthedocs.io/en/stable/releases.html
Other than for the oldest Qt 5 / Qt 6 envs, and for utility envs, let's use
Ubuntu 22.04 or 24.04.
The normal PDF.js build only officially supports the latest Chromium, so things
might break every once in a while with QtWebEngine (e.g. #8199, #7335).
Let's instead bundle and recommend the legacy build.
Closes#8332Closes#7721 (reworded)
Also see #7135
The test added in fbd148f983 was flaky because
reloading didn't wait for the page load to actually finish fully, and thus the
element not being found on the page.
Fix this by providing the page path to the step, and making sure it waits for it
to be fully loaded again.
Also undos some flaky tags done in 2018:
12e5375931c1c182d958
As this might have been the real culprit.
If not, those should be re-added.
See #8348, #5390
Turns out there are various places in the tests that somehow cause Qt to start
caching the dict location, not just actually accessing
.[is|set]SpellCheckEnabled() like I originally assumed.
Thus, move setting QTWEBENGINE_DICTIONARIES_PATH into conftest.py so it's run
before any tests. However, remove the sanity check after, as that requires
initializing a QWebEngineProfile which we might not always want to do when
running a subset of unit tests. If something goes wrong with this, chances are
we'll only notice later in the tests anyways.
Follow-up to db8e508530
See #8330
- Newest Linux/macOS/Windows environments (should be roughly same as release,
especially for Windows/macOS)
- Nightly binary builds
- Release automation
Closes#8205
Starting with Qt 6.8, Qt enforces that the spell checking dictionary path exists
when enabling spell checking (but it doesn't check whether there is actually
anything in there). This caused our test ensuring that spell checking gets
enabled properly to fail, as we never actually set a proper directory.
We now do, though we need to do so for the entire test session, as QtWebEngine
caches the directory.
Reverts 7475d38
See https://github.com/qutebrowser/qutebrowser/issues/8242#issuecomment-2333609589Fixes#8330
Due to a Qt bug, we get a misleading error even when trying to *disable*
spell checking: https://bugreports.qt.io/browse/QTBUG-131969
To avoid that from happening, we now only call setSpellCheckEnabled() if
the value actually changed.
See #8242, #8330 (not the same issue but related)
Similarly to #5998, XHR requests should be able to set their custom
Accept-Language values - and for some odd reason, stuff breaks on websites
sometimes when that's not respected.
There's no way for qutebrowser to know if a given header value is already set in
a request (i.e. whether we're adding or overriding). Thus we only really have
two options here:
1) Don't set any shared.custom_headers() for XHR requests at all.
2) Special-case Accept-Language here, because the issue usually is triggered by
the global override, but that already gets set just fine via QWebEngineProfile
anyways.
Given that 2) is the thing causing trouble in the wild and it's unclear what the
desired behavior for 1) is (e.g. for the DNT header), let's go for 2) here.
Was getting an import error when trying to import the webengine version.
Not going to skip the tests using this step since they seemed to be
working otherwise, probably this behavior isn't needed on webkit anyway.
A string comparison of version numbers relies on nice simple version
numbers with approximately the same amount of digits. Who knows what
various systems are running!
Switch to the integer format version number. It's harder to grep for
when dropping a Qt version, but hopefully we have enough "6.8"s around
it to compensate.
review feedback
Hopefully this is an okay header format for checkers to pick up, it's
the same as in qutebrowser/html/version.html except with the doctype
declaration above.
review feedback
I've mixed opinions on this. I'm not convinced that ternary expressions
are more readable than an if/else block.
Also if someone passes a string into this function it'll return
"access-paste" now.
Combine the `if exists` and `unlink` in one step and avoid any race
conditions with the file disappearing in between them.
Co-Authored-By: Florian Bruhin <me@the-compiler.org>
Previously it would have crashed with an AttributeError if a user had a
PyQt of 6.8 but Qt of 6.7.
Switch to catching any AttributeError which will hopefully cover
either or both of Qt and PyQt not being a new enough version, even if
it's a bit less of an explicit check.
This branch gets entered if the value of a setting linked to a feature
is anything other than True, False or "ask". I think this could only
happen due to a programming error, for example when you add an entry to
`_WebEnginePermissions` that was linked to a String type setting. So I
think putting an assert here instead of a warning should be fine and
more explicit. (Or should be be `utils.Unreachable`? Or `ValueError`?)
Relying on `hasattr()` made me feel a bit guilty. So I've moved these
conditionals to be backed by a class. The only class based alternative I
can think of is putting it on the base type and leaving all the other
config variables to raise errors. But that doesn't tell the type system
anything.
All the promptable feature permissions so far have been of type BoolAsk.
The prompt uses a "yesno" mode prompt and only results in a bool. The
persistence logic only supports bools.
Previously I made the shared prompt support the String type clipboard
permission setting by treating non "ask" values as False (you only get
prompted if the global setting is "none" anyway), but saving the prompt
results with `:prompt-accept --save` didn't work because the persistence
code only supported bools.
What we want to do when saving is convert `False` to "none" and `True`
to "access-paste". This mirrors the new webengine logic. It does mean we
can't let users choose to persist either none/access/access-paste, but
webengine doesn't prompt us if the page is already allowed "access" but
is trying to paste anyway. If it did we would have to use a non-yesno
prompt for this (perhaps it could be driven directly by the ConfigType).
For now I've added a new concept to the ConfigTypes to allow them to be
casted to and from bools, so that we can plumb this String type from the
boolean yesno prompt.
TODO:
* try to make it an interface so we don't have to use `hasattr` (even if
it means multiple inheritance)
* add test coverage to test_configtypes.py
The test page is using a JS API that is too new for qtwebkit:
23:07:54.898 DEBUG js shared:javascript_log_message:190 [http://localhost:37635/data/prompt/clipboard.html:97] TypeError: undefined is not an object (evaluating 'navigator.clipboard.readText')
This is failing with:
ERROR tests/end2end/test_invocations.py::test_permission_prompt_across_restart - PermissionError: [WinError 5] Access is denied: 'C:\\Users\\RUNNER~1\\AppData\\Local\\Temp\\tmpytep6gj0\\cache\\webengine\\Cache\\Cache_Data\\data_0'
In pytest teardown, while trying to clean up a temp dir, probably the
basedir. The test above waits for shutdown at the end of the test, maybe
that's what's needed here.
Otherwise maybe just `@pytest.mark.skipif(utils.is_windows)` 🤷
For tests that do the same prompt in the same session, they are failing
on Qt6.8 and PyQWebEnginet6.7 because we can't disable the new WebEngine
behaviour.
We can work around this by getting a fresh instance for each test, but I
don't want to make more tests slower if I don't have to. So move that
logic into a custom "fresh instance" prompt that will only create one
when needed.
WebEngine now supports prompting for permission when a web page tries to
access the clipboard. Previously we only supported fixed permissions that
applied globally.
This commit
1. adds support for permission requests with the new ClipboardReadWrite
permission
2. tweaks the generic JS prompt handler in browser/shared.py to handle
seeing a setting which isn't of type BoolAsk
3. adds end2end tests around clipboard permissions, both the existing
global ones and the new per-URL ones
1. the ClipboardReadWrite permission
I added this as an int because the relevant PyQt isn't out yet and users
with 6.8 running with PyQt6.7 are already seeing this.
I added an "ask" value to the existing String type
`content.javascript.clipboard` setting and set the
default/global/fallback permissions to False if ask is set globally.
Hmm, maybe we should change the default actually... I'll have to check
what the other prompt supporting settings default to.
2. tweaked prompt handler
This was treating the string values that weren't "ask" (like "none" and
"access") as truthy and allowing the action. I've changed the bool
checks to be exact checks to add a warning if this happens again.
Then I added an exception to the warning logging for known cases like
this. I did try looking at adding a new setting type. Something that was
descended from String but had an `__eq__` method that understood bools
and would treat `access-paste` as True and everything else as False. But
that didn't work out because it looks like config values are stored as
python values and the config classes are just static and don't actually
hold values. Oh well, maybe a better pattern will emerge with time.
3. tests
Apparently there were no tests around the clipboard settings. Perhaps
not a coincidence given how confusing they are (what does access-paste
mean?).
I copied a test file from some random test site, tweaked it a little bit
and got to work. For the paste test it's a bit awkward because I don't
know if we have a way to fill the clipboard in a nice way for the tests.
So I'm just matching on "Text pasted: *", which is usually an empty
string.
The prompt tests require running against Qt6.8 to get the new prompt
behaviour (don't need pyqt68 though).
There are some changes in this area in Qt6.8, so it would be good to
have some test coverage.
The "access permission - copy" one is broken in 6.8. Still need to raise
that upstream.
QtWebEngine has a new feature where it will remember what permissions
you have granted or denied. It has three options regarding permissions
storage:
AskEveryTime -- don't store
StoreInMemory -- store in memory only
StoreOnDisk -- store in memory and on disk
By default it does the StoreOnDisk behavior. Having webengine remember
whether you granted or denied a permission would make the qutebrowser UX
around that area inconsistent. For example the default y/n actions for a
permission prompt in qutebrowser will only accept that single
permission request, and you'll be re-prompted if you reload the page.
Users may be used to this and if webengine started remembering
permission grants the users may be surprised to find that the page was
accessing features without prompting them for permission anymore.
Additionally we already have our own permission storage machinery in
autoconfig.yml.
This commit will set the webengine feature to AskEveryTime, which disables
any storing of permission grants.
Also adjusts the skip marker of the affected tests so they'll be enabled
again on Qt versions with the appropriate permissions API.
This re-enables the pylint too-many-positional-arguments for the main
application code. It's still disabled for tests because that's how you pull in
pytlint fixtures, and I don't think we want to push people into being creative
with fixtures just to get around that.
When functions are called with many positional arguments the reader has to do
a bit of heavy lifting to figure out in what position a value is being passed,
and it's easier to make mistakes. So I would like to encourage using keyword
arguments for long argument lists.
I've set the `max-positional-arguments` to a completely arbitrary 7, from a
completely arbitrary 5, because there were many more violations under 7. If
like 99% of our functions fit under 7 it's probably fine.
Regarding the exceptions:
* objreg.register: I grepped it and it looks like everything is only passing
the first two args as positional already, lucky!
* `_get_color_percentage`: only one usage of it, but I was in "add directive
comment" mode
* update_3rdparty.py: only one usage, already using kwargs
* pyqtProperty: idk
* commands.py: "its complicated". Many methods in this file map to commands
used in qutebrowser's command mode. In that case it's usual for them to be
called as flags, rather than positional. But it could be complicated to wade
into that, and having one file excluded isn't so bad.
With the upgrade to MarkupSafe 3.0, something funny happened when trying to pass
the GUIProcess object to jinja after launching a userscript:
[...]
File "[...]/qutebrowser/browser/qutescheme.py", line 291, in qute_process
src = jinja.render('process.html', title=f'Process {pid}', proc=proc)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "[...]/qutebrowser/utils/jinja.py", line 123, in render
return environment.get_template(template).render(**kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[...]
File "html/process.html", line 11, in block 'content'
File "[...]/lib/python3.11/site-packages/markupsafe/__init__.py", line 42, in escape
if hasattr(s, "__html__"):
^^^^^^^^^^^^^^^^^^^^^^
RuntimeError: wrapped C/C++ object of type GUIProcess has been deleted
This can be reproduced with:
qutebrowser --temp-basedir ':cmd-later 0 spawn -u -o /bin/echo test'
We pass the `GUIProcess` to the Jinja template as `proc`, which then formats it as
`{{ proc }}`` (to stringify it). For some reason, with the newest MarkupSafe/Jinja
versions, this now triggers the `if hasattr(s, "__html__")` check in MarkupSafe
(which has been around for a while). That then presumably causes PyQt to try and
access the underlying C++ object for `GUIProcess``, but that has already been
deleted.
But why is it deleted in the first place, if we keep track of even completed
processes data ever since we added `:process` in a3adba81c? It looks like the Qt
parent-child relationship is the culprit here: When we pass a parent to the
`GUIProcess`` from the userscript runner, it will get deleted as soon as said
runner is cleaned up (which happens after the userscript has finished).
We probably never noticed this before because we only accessed data from the
Python wrapper and not from the C++ side, but it still seems like a good idea
to avoid passing a parent for a long-lived object (with time-based cleanup) in
the first place.
Added in 3.3.0:
https://pylint.pycqa.org/en/latest/whatsnew/3/3.3/index.html
Some of those arguments could probably indeed be keyword-only,
but for some of the functions shown by pylint, those are qutebrowser command
handlers where a positional argument has different semantics.
Did run with ruff pretending to use Python 3.10,
because otherwise it won't reformat those:
ruff check --select 'UP035' --fix --config 'target-version = "py310"' --unsafe-fixes
This is because collections.abc.Callable inside Optional[...] and Union[...] is
broken with Python 3.9.0 and 3.9.1:
https://github.com/asottile/pyupgrade/issues/677https://github.com/astral-sh/ruff/issues/2690https://github.com/python/cpython/issues/87131
However, pylint can detect problematic usages (of which we only have one),
so we might as well use the new thing everywhere possible for consistency.
Also see #7098
To avoid WebEngine remembering granted permissions across restarts,
remove their persistence file when we start up.
This is only technically required when Qt=>6.8 and PyQt<6.8. But we only
take action if the file exists anyway, so it's safe enough to run all
the time and that means less conditional code to test ;)
There are a few options for where we could do this cleanup, I'm choosing
to do it at the latest point possible, which is right before we set
`setPersistentStoragePath()`, since the permissions manager is
re-initialized after that, see https://bugreports.qt.io/browse/QTBUG-126595
TODO:
* call the new setPersistentPermissionsPolicy API when PyQt>=6.8
Qt 6.8 has its own permission grant persistence features. This means
that if you accept a permission prompt in qutebrowser, and don't save
it, it will be remembered by webengine anyway and you won't be
re-prompted again.
This test demonstrates that behaviour by temporarily granting a
permission, restarting the browser in the same basedir, then seeing if
we get prompted for the permission again or not. If not it fails on the
"Asking question" line.
We can't do much about re-prompting for a permission in the same browser
instance (Qt saves the permission grant in memory too) but we can clean
up the persisted permission files on browser starts so it doesn't
remember it forever. At that point the skip mark can be removed from
this test.
I just searched for qt5 and deleted stuff. EZ.
Will leave on a branch for a bit and see if I feel like testing this at
all, otherwise maybe leave this stuff in here and make it not called.
Not 100% sure that we need to remove all this stuff when we just want
the CI to go green. But tbh if we don't need to make Qt5 releases then
we don't need it. Better to be bold and pull it out than have to work
around it in the future. And we can always revert the commit.
They require maintenance, but we don't have a great need for Qt5 builds.
See https://github.com/qutebrowser/qutebrowser/issues/8260
All the Qt5 switches are still in tox, the build release script and the
nsis installer.
The previous way of obtaining the Bang's URL was using the
response's body, using the url atrribute of a meta tag. The way
this was handled was a bit problematic: some URLs were of the format
/l/?uddg=<URL>&<some-other-query-arguments>, while others (such as google)
is simply <URL>. This could have been fixed by using an if-else, but I
preferred another approach. Lite DuckDuckGo sends a 303 response when
using Bangs, instead of a 200 response which makes the redirect. The
location header of the 303 response is the search engine's URL, so it's
easy to implement this function, working for both afformentioned URL
templates.
2023-02-26 15:47:13 +01:00
311 changed files with 5291 additions and 1834 deletions
* Make sure there are no unstaged changes and the tests are green.
* Make sure there are no unstaged or unpushed changes.
* Make sure CI is reasonably green.
* Make sure all issues with the related milestone are closed.
* Mark the https://github.com/qutebrowser/qutebrowser/milestones[milestone] as closed.
* Consider updating the completions for `content.headers.user_agent` in `configdata.yml`.
* Consider updating the completions for `content.headers.user_agent` in `configdata.yml`
and the Firefox UA in `qutebrowser/browser/webengine/webenginesettings.py`.
* Minor release: Consider updating some files from main:
- `misc/requirements/` and `requirements.txt`
- `scripts/`
@ -802,7 +804,8 @@ qutebrowser release
**Automatic release via GitHub Actions (starting with v3.0.0):**
* Double check Python version in `.github/workflows/release.yml`
* Run the `release` workflow on the `main` branch, e.g. via `gh workflow run release -f release_type=major` (`release_type` can be `major`, `minor` or `patch`; you can also override `python_version`)
* Run the `release` workflow on the `main` branch, e.g. via `gh workflow run release -f release_type=minor` (`release_type` can be `major`, `minor` or `patch`; you can also override `python_version`)
* Consider running `gh run watch` or `gh run view --web` to watch the progress
|<<qt.force_software_rendering,qt.force_software_rendering>>|Force software rendering for QtWebEngine.
|<<qt.highdpi,qt.highdpi>>|Turn on Qt HighDPI scaling.
|<<qt.workarounds.disable_accelerated_2d_canvas,qt.workarounds.disable_accelerated_2d_canvas>>|Disable accelerated 2d canvas to avoid graphical glitches.
|<<qt.workarounds.disable_accessibility,qt.workarounds.disable_accessibility>>|Disable accessibility to avoid crashes on Qt 6.10.1.
|<<qt.workarounds.disable_hangouts_extension,qt.workarounds.disable_hangouts_extension>>|Disable the Hangouts extension.
|<<qt.workarounds.locale,qt.workarounds.locale>>|Work around locale parsing issues in QtWebEngine 5.15.3.
|<<qt.workarounds.remove_service_workers,qt.workarounds.remove_service_workers>>|Delete the QtWebEngine Service Worker directory on every start.
@ -2275,21 +2276,22 @@ The following placeholders are defined:
* `{upstream_browser_key}`: "Version" for QtWebKit, "Chrome" for
QtWebEngine.
* `{upstream_browser_version}`: The corresponding Safari/Chrome version.
* `{upstream_browser_version_short}`: The corresponding Safari/Chrome
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} 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
@ -2345,18 +2347,20 @@ Default: +pass:[false]+
=== content.javascript.clipboard
Allow JavaScript to read from or write to the clipboard.
With QtWebEngine, writing the clipboard as response to a user interaction is always allowed.
On Qt < 6.8, the `ask` setting is equivalent to `none` and permission needs to be granted manually via this setting.
This setting supports link:configuring{outfilesuffix}#patterns[URL patterns].
Type: <<types,String>>
Type: <<types,JSClipboardPermission>>
Valid values:
* +none+: Disable access to clipboard.
* +access+: Allow reading from and writing to the clipboard.
* +access-paste+: Allow accessing the clipboard and pasting clipboard content.
* +ask+: Prompt when requested (grants 'access-paste' permission).
Default: +pass:[none]+
Default: +pass:[ask]+
[[content.javascript.enabled]]
=== content.javascript.enabled
@ -2765,10 +2769,9 @@ Type: <<types,FlagList>>
Valid values:
* +ua-whatsapp+
* +ua-google+
* +ua-slack+
* +ua-googledocs+
* +ua-gnome-gitlab+
* +js-whatsapp-web+
* +js-discord+
* +js-string-replaceall+
@ -3891,7 +3894,7 @@ Chromium has various sandboxing layers, which should be enabled for normal brows
Open `chrome://sandbox` to see the current sandbox status.
Changing this setting is only recommended if you know what you're doing, as it **disables one of Chromium's security layers**. To avoid sandboxing being accidentally disabled persistently, this setting can only be set via `config.py`, not via `:set`.
^QObject::connect: Connecting from COMPAT signal \(QWebEnginePage::featurePermissionRequest(ed|Canceled)\(QUrl,QWebEnginePage::Feature\)\)
xfail_strict=true
filterwarnings=
error
default:Test process .* failed to terminate!:UserWarning
# Python 3.12: https://github.com/ionelmc/pytest-benchmark/issues/240 (fixed but not released)
ignore:(datetime\.)?datetime\.utcnow\(\) is deprecated and scheduled for removal in a future version\. Use timezone-aware objects to represent datetimes in UTC. (datetime\.)?datetime\.now\(datetime\.UTC\)\.:DeprecationWarning:pytest_benchmark\.utils