Compare commits

...

46 Commits

Author SHA1 Message Date
Florian Bruhin
ad9b50601c Release v1.3.3 2018-06-21 23:30:51 +02:00
Florian Bruhin
c88a94f1cc Update changelog
(cherry picked from commit 66fc3a30dd)
2018-06-21 23:30:38 +02:00
Florian Bruhin
13332cd2cf Fix shadowing of 'html' name
(cherry picked from commit 0864ad4069)
2018-06-21 22:34:05 +02:00
Florian Bruhin
48808e59cb Re-add waiting for QQuickWidget
Apparently this is still needed on some PyQt versions.

(cherry picked from commit 9a5439e5d0)
2018-06-21 22:23:58 +02:00
Florian Bruhin
5571ef4e62 Revert "Properly add QtQuickWidgets dependency"
Looks like FreeBSD doesn't have QtQuickWidgets packaged at all, so let's do the
same without requiring it...

This reverts commit e5405f0ae9.

(cherry picked from commit c87757a913)
2018-06-21 22:23:42 +02:00
Florian Bruhin
b4de889df9 Update changelog 2018-06-21 21:45:43 +02:00
Florian Bruhin
4c9360237f Fix XSS issue on qute://history
Fixes #4011

(cherry picked from commit 5a7869f2fe)
2018-06-21 21:43:53 +02:00
Florian Bruhin
10538738e0 Don't depend on PyQt5.QtQuickWidgets to get RWHV
Some distributions (at least FreeBSD) don't package that module, so let's not
rely on it.

(cherry picked from commit 62d8b5b574)
2018-06-21 21:43:47 +02:00
Florian Bruhin
bd5f84bddf Always clear searches between page loads
Looks like this wasn't properly fixed in Qt for some reason.
Fixes #3693
See #2728 and bef372e5f5

(cherry picked from commit 3399f2df96)
2018-06-21 21:43:30 +02:00
Florian Bruhin
93ae6ad592 Properly add QtQuickWidgets dependency
(cherry picked from commit e5405f0ae9)
2018-06-21 00:22:36 +02:00
Florian Bruhin
146f8e72ae Handle multiple visible children when finding lost focusProxy
When we click a QTBUG link (to open in a new tab) from Qt's codereview, we get
two RWHV objects which both are visible.

Experimenting with .setEnabled(False) it looks like it's (hopefully always...)
the last one which is the one to use.

(cherry picked from commit 67c67db230)
2018-06-11 21:45:09 +02:00
Florian Bruhin
f61662fa52 Only consider visible render widgets for lost focusProxy
Otherwise, when commenting out the focusProxy way above, and using "foo !npm"
with DuckDuckGo, we get two children (one visible, one invisible).

(cherry picked from commit b63e06561d)
2018-06-11 14:02:26 +02:00
Florian Bruhin
e9b4c2a66e Release v1.3.2 2018-06-10 15:58:54 +02:00
Florian Bruhin
6a7ab7edb3 Remove unused import
(cherry picked from commit 7949335a2b)
2018-06-09 21:45:13 +02:00
Florian Bruhin
f7f96484e8 Fix waiting for initial focus object with Qt 5.11 workarounds
This was broken in d32d541ac0 because now
apparently PyQt knows it's a QQuickWidget.

(cherry picked from commit ec88c15390)
2018-06-09 20:11:00 +02:00
Florian Bruhin
840d2e4423 Further simplify getting focusProxy children
(cherry picked from commit d32d541ac0)
2018-06-08 17:11:33 +02:00
Florian Bruhin
e54f2a090a Improve RWHV typecheck for focusProxy
(cherry picked from commit cc497bf2ea)
2018-06-08 15:20:33 +02:00
Florian Bruhin
55ce4b7ed2 Exclude QMenu when trying to find the missing focusProxy
(cherry picked from commit 9725d9ce33)
2018-06-08 15:20:31 +02:00
Florian Bruhin
3daf823da8 Show children in focusProxy workaround
(cherry picked from commit 1531961aeb)
2018-06-08 15:20:29 +02:00
Florian Bruhin
72feb2c19f Fix check for reloads on Qt < 5.11
Equivalent commit on master: 91b4106dcf
This was broken in 900efe4a36
2018-06-08 08:57:26 +02:00
Florian Bruhin
6d04508490 Remove unused import
(cherry picked from commit 4614ad5063)
2018-06-07 18:02:11 +02:00
Florian Bruhin
93ebd846ab Implement a better workaround for chrome-error:// URLs
It looks like chrome-error://chromewebdata/ triggers another invalid scheme
load which is why the endless loop happens. When we install a custom scheme
handler for chrome-error:// we can at least show an error page.

(cherry picked from commit b1506274c5)
2018-06-07 16:04:37 +02:00
Florian Bruhin
9ee473a54c Go back to using an invalid scheme for invalid_link.html
Otherwise, this breaks the tests on Qt 5.10

(cherry picked from commit 596041c40e)
2018-06-07 15:42:02 +02:00
Florian Bruhin
d1cced0da4 Make sure external schemes are clickable via hints
This issue was probably introduced in 545539f28d
- with JavaScript, we can't "click" on an external link.

There might be a better solution using
QWebEngineSettings::setUnknownUrlSchemePolicy(QWebEngineSettings::AllowAllUnknownUrlSchemes)
temporarily when using hints with PyQt 5.11.

Fixes #2833

(cherry picked from commit 89f4333df1)
2018-06-07 14:36:12 +02:00
Florian Bruhin
22644c41da Add a workaround for chrome-error:// loops on Qt 5.11
See #3661

(cherry picked from commit 0c0d204fd4)
2018-06-07 14:36:10 +02:00
Florian Bruhin
6b5857ef7d Skip invalid link tests on Qt 5.11
Qt 5.11 just loads about:blank and doesn't let us catch this in
acceptNavigationRequest, but the same happens in Chromium as well.

See #3661

(cherry picked from commit 999513d5d8)
2018-06-07 14:36:07 +02:00
Florian Bruhin
b0f4cc6924 Use a valid scheme in invalid_link.html
This is to avoid triggering QTBUG-63378 which fails differently with a custom
scheme.

See #3661

(cherry picked from commit d059197bc9)
2018-06-07 14:36:03 +02:00
Florian Bruhin
218637af84 Reenable Qt 5.11 tests on Travis
Fixes #3661

(cherry picked from commit eb6478dd3e)
2018-06-07 14:35:56 +02:00
Florian Bruhin
dd84845f01 Don't run test with failed download on Qt 5.11
Looks like we can't use an <a> tag with download-attribute to trigger a failed
download in the test on Qt 5.11...

See #2298, #3661

(cherry picked from commit 8cc3804119)
2018-06-06 21:14:20 +02:00
Florian Bruhin
cf53f9042a Only set PseudoLayout with Qt 5.11
(cherry picked from commit 456fdc55cc)
2018-06-06 20:30:42 +02:00
Florian Bruhin
f48266f72f Fix lint
(cherry picked from commit 7e31897dcc)
2018-06-06 20:30:39 +02:00
Florian Bruhin
5dac848968 Handle resizing via PseudoLayout
This fixes the scenario where we just get a grey view when opening a link in a
tab from DuckDuckGo.

(cherry picked from commit 5147fc832c)
2018-06-06 20:30:37 +02:00
Florian Bruhin
341aa1e700 Try harder to get the RenderWidgetHostViewQt
(cherry picked from commit ec6c5ebb69)
2018-06-06 20:30:34 +02:00
Florian Bruhin
ebf81c06ae Initial proof of concept for pseudo layout
Fixes #3920 - hopefully properly this time...

(cherry picked from commit cee88cd7ca)
2018-06-06 20:30:31 +02:00
Florian Bruhin
a3eb8d6561 travis: Allow Archlinux to fail for now
See #3661

(cherry picked from commit 6fc3546923)
2018-05-29 13:19:36 +02:00
Florian Bruhin
af4b02bf46 setup.py: Set long_description_content_type
Otherwise, Warehouse (new PyPI) refuses the upload...

(cherry picked from commit 52c44d3da6)
2018-05-29 11:23:48 +02:00
Florian Bruhin
ac29c579ff Release v1.3.1 2018-05-29 11:14:01 +02:00
Florian Bruhin
db5ec363cd Update changelog for v1.3.1 2018-05-29 11:12:33 +02:00
Florian Bruhin
f352c72d1d Fix lint
(cherry picked from commit 12e0edbcd0)
2018-05-28 07:44:49 +02:00
Florian Bruhin
b1f1a0cafa Add some more logging for #3920
(cherry picked from commit 17cfb0d39c)
2018-05-28 07:44:44 +02:00
Florian Bruhin
749056ff90 Use functools instead of a lambda for QTimer
It reads nicer, and this is also speculative fix for #3896 as PyQt5 is
hopefully better at disconnecting partial-objects from dead objects than it is
with lambdas.

(cherry picked from commit 7162f15348)
2018-05-28 07:44:36 +02:00
Florian Bruhin
e7b00ace73 Handle ² keypress correctly
Turns out str.isdigit() also handles ² as a digit, but int('²') causes a
ValueError.

Here we use `string.digits` instead, which is '0123456789'.

Fixes #3743

(cherry picked from commit 29ad252278)
2018-05-22 12:29:09 +02:00
Florian Bruhin
442bdd4a4f Properly work around Qt 5.11 keyboard focus issues
Please let this be the last attempt... :D

Fixes #3939
Supersedes #3921
Reverts ae295a7f65
See #3661

This should not regress #3872. Might affect #3834 in some way.

(cherry picked from commit 71ad8bdb47)
2018-05-22 09:41:13 +02:00
Florian Bruhin
900efe4a36 Fix reload for JavaScript support on Qt 5.11
(cherry picked from commit 6ccd69dad2)
2018-05-17 17:54:39 +02:00
Florian Bruhin
f8a78a0962 Add workaround for the "split page" Qt bug (QTBUG-68224)
Fixes #3920

(cherry picked from commit 44d26f77a5)
2018-05-17 14:25:38 +02:00
Florian Bruhin
60e8abaa89 Improve configuration docs
(cherry picked from commit 20efaeff19)
2018-05-17 14:25:33 +02:00
24 changed files with 327 additions and 99 deletions

View File

@@ -15,6 +15,56 @@ 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.
v1.3.3
------
Security
~~~~~~~~
- An XSS vulnerability on the `qute://history` page allowed websites to inject
HTML into the page via a crafted title tag. This could allow them to steal
your browsing history. If you're currently unable to upgrade, avoid using
`:history`. A CVE request for this issue is pending, see
https://github.com/qutebrowser/qutebrowser/issues/4011[#4011] for updates.
Fixed
~~~~~
- Crash in a workaround for a Qt 5.11 bug in rare circumstances.
- Workaround for a Qt bug which preserves searches between page loads.
- In v1.3.2 a dependency on the `PyQt5.QtQuickWidgets` module was accidentally
introduced. Since that module isn't packaged everywhere, it's been removed
again.
v1.3.2
------
Fixed
~~~~~
- QtWebEngine: Improved workaround for a bug in Qt 5.11 where only the
top/bottom half of the window is used.
- QtWebEngine: Work around a bug in Qt 5.11 where an endless loading-loop is
triggered when clicking a link with an unknown scheme.
- QtWebEngine: When switching between pages with changed settings, less
unnecessary reloads are done now.
- QtWebEngine: It's now possible to open external links such as `magnet://` or
`mailto:` via hints.
v1.3.1
------
Fixed
~~~~~
- Work around a bug in Qt 5.11 where only the top/bottom half of the window is used.
This workaround is incomplete, but fixes the majority of the cases where this happens.
- Work around keyboard focus issues with Qt 5.11.
- Work around an issue in Qt 5.11 where e.g. activating JavaScript per-domain
needed a manual reload in some cases.
- Don't crash when a ² key is pressed (e.g. on AZERTY keyboards).
- Don't crash when a tab is opened and quickly closed again.
v1.3.0
------

View File

@@ -3,44 +3,28 @@ Configuring qutebrowser
IMPORTANT: qutebrowser's configuration system was completely rewritten in
September 2017. This information is not applicable to older releases, and older
information elsewhere might be outdated. **If you had an old configuration
around and upgraded, this page will automatically open once**. To view it at a
later time, use the `:help` command.
information elsewhere might be outdated.
Migrating older configurations
------------------------------
qutebrowser's config files
--------------------------
qutebrowser does no automatic migration for the new configuration. However,
there's a special link:qute://configdiff/old[configdiff] page
(`qute://configdiff/old`) in qutebrowser, which will show you the changes you
did in your old configuration, compared to the old defaults.
qutebrowser releases before v1.0.0 had a `qutebrowser.conf` and `keys.conf`
file. Those are not used anymore since that release - see
<<migrating,"Migrating older configurations">> for information on how to
migrate to the new config.
Other changes in default settings:
When using `:set` and `:bind`, changes are saved to an `autoconfig.yml` file
automatically. If you don't want to have a config file which is curated by
hand, you can simply use those - see
<<autoconfig,"Configuring qutebrowser via the user interface">> for details.
- In v1.1.x and newer, `<Up>` and `<Down>` navigate through command history
if no text was entered yet.
With v1.0.x, they always navigate through command history instead of selecting
completion items. Use `<Tab>`/`<Shift-Tab>` to cycle through the completion
instead.
You can get back the old behavior by doing:
+
----
:bind -m command <Up> completion-item-focus prev
:bind -m command <Down> completion-item-focus next
----
+
or always navigate through command history with
+
----
:bind -m command <Up> command-history-prev
:bind -m command <Down> command-history-next
----
- The default for `completion.web_history_max_items` is now set to `-1`, showing
an unlimited number of items in the completion for `:open` as the new
sqlite-based completion is much faster. If the `:open` completion is too slow
on your machine, set an appropriate limit again.
For more advanced configuration, you can write a `config.py` file - see
<<configpy,"Configuring qutebrowser via config.py">>. As soon as a `config.py`
exists, the `autoconfig.yml` file **is not read anymore** by default. You need
to <<configpy-autoconfig,load it by hand>> if you want settings done via
`:set`/`:bind` to still persist.
[[autoconfig]]
Configuring qutebrowser via the user interface
----------------------------------------------
@@ -88,6 +72,7 @@ link:commands.html#config-clear[`:config-clear`] to reset the entire configurati
and link:commands.html#config-cycle[`:config-cycle`] to cycle a setting between
different values.
[[configpy]]
Configuring qutebrowser via config.py
-------------------------------------
@@ -239,6 +224,7 @@ config.bind(',v', 'spawn mpv {url}')
To suppress loading of any default keybindings, you can set
`c.bindings.default = {}`.
[[configpy-autoconfig]]
Loading `autoconfig.yml`
~~~~~~~~~~~~~~~~~~~~~~~~
@@ -429,3 +415,38 @@ from qutebrowser.config.config import ConfigContainer # noqa: F401
config = config # type: ConfigAPI # noqa: F821 pylint: disable=E0602,C0103
c = c # type: ConfigContainer # noqa: F821 pylint: disable=E0602,C0103
----
[[migrating]]
Migrating older configurations
------------------------------
qutebrowser does no automatic migration for the new configuration. However,
there's a special link:qute://configdiff/old[configdiff] page
(`qute://configdiff/old`) in qutebrowser, which will show you the changes you
did in your old configuration, compared to the old defaults.
Other changes in default settings:
- In v1.1.x and newer, `<Up>` and `<Down>` navigate through command history
if no text was entered yet.
With v1.0.x, they always navigate through command history instead of selecting
completion items. Use `<Tab>`/`<Shift-Tab>` to cycle through the completion
instead.
You can get back the old behavior by doing:
+
----
:bind -m command <Up> completion-item-focus prev
:bind -m command <Down> completion-item-focus next
----
+
or always navigate through command history with
+
----
:bind -m command <Up> command-history-prev
:bind -m command <Down> command-history-next
----
- The default for `completion.web_history_max_items` is now set to `-1`, showing
an unlimited number of items in the completion for `:open` as the new
sqlite-based completion is much faster. If the `:open` completion is too slow
on your machine, set an appropriate limit again.

View File

@@ -26,7 +26,7 @@ __copyright__ = "Copyright 2014-2018 Florian Bruhin (The Compiler)"
__license__ = "GPL"
__maintainer__ = __author__
__email__ = "mail@qutebrowser.org"
__version_info__ = (1, 3, 0)
__version_info__ = (1, 3, 3)
__version__ = '.'.join(str(e) for e in __version_info__)
__description__ = "A keyboard-driven, vim-like browser based on PyQt5."

View File

@@ -22,7 +22,7 @@
from PyQt5.QtCore import QObject, QEvent, Qt, QTimer
from qutebrowser.config import config
from qutebrowser.utils import message, log, usertypes
from qutebrowser.utils import message, log, usertypes, qtutils
from qutebrowser.keyinput import modeman
@@ -54,6 +54,14 @@ class ChildEventFilter(QObject):
obj, child))
assert obj is self._widget
child.installEventFilter(self._filter)
if qtutils.version_check('5.11', compiled=False, exact=True):
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-68076
QTimer.singleShot(0, self._widget.setFocus)
elif event.type() == QEvent.ChildRemoved:
child = event.child()
log.mouse.debug("{}: removed child {}".format(obj, child))
return False

View File

@@ -24,6 +24,7 @@ Module attributes:
_HANDLERS: The handlers registered via decorators.
"""
import html
import json
import os
import time
@@ -123,12 +124,12 @@ class add_handler: # noqa: N801,N806 pylint: disable=invalid-name
def wrong_backend_handler(self, url):
"""Show an error page about using the invalid backend."""
html = jinja.render('error.html',
title="Error while opening qute://url",
url=url.toDisplayString(),
error='{} is not available with this '
'backend'.format(url.toDisplayString()))
return 'text/html', html
src = jinja.render('error.html',
title="Error while opening qute://url",
url=url.toDisplayString(),
error='{} is not available with this '
'backend'.format(url.toDisplayString()))
return 'text/html', src
def data_for_url(url):
@@ -196,11 +197,11 @@ def qute_bookmarks(_url):
quickmarks = sorted(objreg.get('quickmark-manager').marks.items(),
key=lambda x: x[0]) # Sort by name
html = jinja.render('bookmarks.html',
title='Bookmarks',
bookmarks=bookmarks,
quickmarks=quickmarks)
return 'text/html', html
src = jinja.render('bookmarks.html',
title='Bookmarks',
bookmarks=bookmarks,
quickmarks=quickmarks)
return 'text/html', src
@add_handler('tabs')
@@ -218,10 +219,10 @@ def qute_tabs(_url):
urlstr = tab.url().toDisplayString()
tabs[str(win_id)].append((tab.title(), urlstr))
html = jinja.render('tabs.html',
title='Tabs',
tab_list_by_window=tabs)
return 'text/html', html
src = jinja.render('tabs.html',
title='Tabs',
tab_list_by_window=tabs)
return 'text/html', src
def history_data(start_time, offset=None):
@@ -241,8 +242,9 @@ def history_data(start_time, offset=None):
end_time = start_time - 24*60*60
entries = hist.entries_between(end_time, start_time)
return [{"url": e.url, "title": e.title or e.url, "time": e.atime}
for e in entries]
return [{"url": html.escape(e.url),
"title": html.escape(e.title) or html.escape(e.url),
"time": e.atime} for e in entries]
@add_handler('history')
@@ -287,25 +289,25 @@ def qute_javascript(url):
@add_handler('pyeval')
def qute_pyeval(_url):
"""Handler for qute://pyeval."""
html = jinja.render('pre.html', title='pyeval', content=pyeval_output)
return 'text/html', html
src = jinja.render('pre.html', title='pyeval', content=pyeval_output)
return 'text/html', src
@add_handler('spawn-output')
def qute_spawn_output(_url):
"""Handler for qute://spawn-output."""
html = jinja.render('pre.html', title='spawn output', content=spawn_output)
return 'text/html', html
src = jinja.render('pre.html', title='spawn output', content=spawn_output)
return 'text/html', src
@add_handler('version')
@add_handler('verizon')
def qute_version(_url):
"""Handler for qute://version."""
html = jinja.render('version.html', title='Version info',
version=version.version(),
copyright=qutebrowser.__copyright__)
return 'text/html', html
src = jinja.render('version.html', title='Version info',
version=version.version(),
copyright=qutebrowser.__copyright__)
return 'text/html', src
@add_handler('plainlog')
@@ -323,8 +325,8 @@ def qute_plainlog(url):
if not level:
level = 'vdebug'
text = log.ram_handler.dump_log(html=False, level=level)
html = jinja.render('pre.html', title='log', content=text)
return 'text/html', html
src = jinja.render('pre.html', title='log', content=text)
return 'text/html', src
@add_handler('log')
@@ -343,8 +345,8 @@ def qute_log(url):
level = 'vdebug'
html_log = log.ram_handler.dump_log(html=True, level=level)
html = jinja.render('log.html', title='log', content=html_log)
return 'text/html', html
src = jinja.render('log.html', title='log', content=html_log)
return 'text/html', src
@add_handler('gpl')
@@ -415,12 +417,12 @@ def qute_help(url):
@add_handler('backend-warning')
def qute_backend_warning(_url):
"""Handler for qute://backend-warning."""
html = jinja.render('backend-warning.html',
distribution=version.distribution(),
Distribution=version.Distribution,
version=pkg_resources.parse_version,
title="Legacy backend warning")
return 'text/html', html
src = jinja.render('backend-warning.html',
distribution=version.distribution(),
Distribution=version.Distribution,
version=pkg_resources.parse_version,
title="Legacy backend warning")
return 'text/html', src
def _qute_settings_set(url):
@@ -450,10 +452,10 @@ def qute_settings(url):
if url.path() == '/set':
return _qute_settings_set(url)
html = jinja.render('settings.html', title='settings',
configdata=configdata,
confget=config.instance.get_str)
return 'text/html', html
src = jinja.render('settings.html', title='settings',
configdata=configdata,
confget=config.instance.get_str)
return 'text/html', src
@add_handler('bindings')
@@ -467,9 +469,9 @@ def qute_bindings(_url):
for mode in modes:
bindings[mode] = config.key_instance.get_bindings_for(mode)
html = jinja.render('bindings.html', title='Bindings',
bindings=bindings)
return 'text/html', html
src = jinja.render('bindings.html', title='Bindings',
bindings=bindings)
return 'text/html', src
@add_handler('back')
@@ -478,10 +480,10 @@ def qute_back(url):
Simple page to free ram / lazy load a site, goes back on focusing the tab.
"""
html = jinja.render(
src = jinja.render(
'back.html',
title='Suspended: ' + urllib.parse.unquote(url.fragment()))
return 'text/html', html
return 'text/html', src
@add_handler('configdiff')

View File

@@ -307,6 +307,10 @@ class AbstractWebElement(collections.abc.MutableMapping):
href_tags = ['a', 'area', 'link']
return self.tag_name() in href_tags and 'href' in self
def _requires_user_interaction(self):
"""Return True if clicking this element needs user interaction."""
raise NotImplementedError
def _mouse_pos(self):
"""Get the position to click/hover."""
# Click the center of the largest square fitting into the top/left
@@ -405,7 +409,7 @@ class AbstractWebElement(collections.abc.MutableMapping):
return
if click_target == usertypes.ClickTarget.normal:
if self.is_link():
if self.is_link() and not self._requires_user_interaction():
log.webelem.debug("Clicking via JS click()")
self._click_js(click_target)
elif self.is_editable(strict=True):

View File

@@ -27,7 +27,7 @@ from PyQt5.QtGui import QMouseEvent
from PyQt5.QtWidgets import QApplication
from PyQt5.QtWebEngineWidgets import QWebEngineSettings
from qutebrowser.utils import log, javascript
from qutebrowser.utils import log, javascript, urlutils
from qutebrowser.browser import webelem
@@ -198,6 +198,13 @@ class WebEngineElement(webelem.AbstractWebElement):
if self.is_text_input() and self.is_editable():
self._js_call('move_cursor_to_end')
def _requires_user_interaction(self):
baseurl = self._tab.url()
url = self.resolve_url(baseurl)
if url is None:
return True
return url.scheme() not in urlutils.WEBENGINE_SCHEMES
def _click_editable(self, click_target):
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-58515
ev = QMouseEvent(QMouseEvent.MouseButtonPress, QPoint(0, 0),

View File

@@ -34,6 +34,9 @@ class QuteSchemeHandler(QWebEngineUrlSchemeHandler):
def install(self, profile):
"""Install the handler for qute:// URLs on the given profile."""
profile.installUrlSchemeHandler(b'qute', self)
if qtutils.version_check('5.11', compiled=False):
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-63378
profile.installUrlSchemeHandler(b'chrome-error', self)
def requestStarted(self, job):
"""Handle a request for a qute: scheme.
@@ -45,6 +48,12 @@ class QuteSchemeHandler(QWebEngineUrlSchemeHandler):
job: QWebEngineUrlRequestJob
"""
url = job.requestUrl()
if url.scheme() == 'chrome-error':
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-63378
job.fail(QWebEngineUrlRequestJob.UrlInvalid)
return
assert job.requestMethod() == b'GET'
assert url.scheme() == 'qute'
log.misc.debug("Got request for {}".format(url.toDisplayString()))

View File

@@ -780,8 +780,6 @@ class WebEngineTab(browsertab.AbstractTab):
url: The QUrl to open.
predict: If set to False, predicted_navigation is not emitted.
"""
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-68076
self._widget.setFocus()
self._saved_zoom = self.zoom.factor()
self._openurl_prepare(url, predict=predict)
self._widget.load(url)
@@ -952,11 +950,10 @@ class WebEngineTab(browsertab.AbstractTab):
@pyqtSlot()
def _on_load_started(self):
"""Clear search when a new load is started if needed."""
if (qtutils.version_check('5.9', compiled=False) and
not qtutils.version_check('5.9.2', compiled=False)):
# WORKAROUND for
# https://bugreports.qt.io/browse/QTBUG-61506
self.search.clear()
# WORKAROUND for
# https://bugreports.qt.io/browse/QTBUG-61506
# (seems to be back in later Qt versions as well)
self.search.clear()
super()._on_load_started()
self.data.netrc_used = False
@@ -1028,8 +1025,9 @@ class WebEngineTab(browsertab.AbstractTab):
log.config.debug(
"Loading {} again because of config change".format(
self._reload_url.toDisplayString()))
QTimer.singleShot(100, lambda url=self._reload_url:
self.openurl(url, predict=False))
QTimer.singleShot(100, functools.partial(self.openurl,
self._reload_url,
predict=False))
self._reload_url = None
if not qtutils.version_check('5.10', compiled=False):
@@ -1048,10 +1046,11 @@ class WebEngineTab(browsertab.AbstractTab):
@pyqtSlot(usertypes.NavigationRequest)
def _on_navigation_request(self, navigation):
super()._on_navigation_request(navigation)
if not navigation.accepted or not navigation.is_main_frame:
return
needs_reload = {
settings_needing_reload = {
'content.plugins',
'content.javascript.enabled',
'content.javascript.can_access_clipboard',
@@ -1060,11 +1059,20 @@ class WebEngineTab(browsertab.AbstractTab):
'input.spatial_navigation',
'input.spatial_navigation',
}
assert needs_reload.issubset(configdata.DATA)
assert settings_needing_reload.issubset(configdata.DATA)
changed = self.settings.update_for_url(navigation.url)
if (changed & needs_reload and navigation.navigation_type !=
navigation.Type.link_clicked):
reload_needed = changed & settings_needing_reload
# On Qt < 5.11, we don't don't need a reload when type == link_clicked.
# On Qt 5.11.0, we always need a reload.
# TODO on Qt > 5.11.0, we hopefully never need a reload:
# https://codereview.qt-project.org/#/c/229525/1
if not qtutils.version_check('5.11.0', exact=True, compiled=False):
if navigation.navigation_type == navigation.Type.link_clicked:
reload_needed = False
if reload_needed:
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-66656
self._reload_url = navigation.url
@@ -1106,4 +1114,4 @@ class WebEngineTab(browsertab.AbstractTab):
self.predicted_navigation.connect(self._on_predicted_navigation)
def event_target(self):
return self._widget.focusProxy()
return self._widget.render_widget()

View File

@@ -21,8 +21,10 @@
import functools
import sip
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QUrl, PYQT_VERSION
from PyQt5.QtGui import QPalette
from PyQt5.QtWidgets import QWidget
from PyQt5.QtWebEngineWidgets import (QWebEngineView, QWebEnginePage,
QWebEngineScript)
@@ -30,6 +32,7 @@ from qutebrowser.browser import shared
from qutebrowser.browser.webengine import certificateerror, webenginesettings
from qutebrowser.config import config
from qutebrowser.utils import log, debug, usertypes, jinja, objreg, qtutils
from qutebrowser.misc import miscwidgets
class WebEngineView(QWebEngineView):
@@ -51,6 +54,34 @@ class WebEngineView(QWebEngineView):
parent=self)
self.setPage(page)
if qtutils.version_check('5.11', compiled=False):
# Set a PseudoLayout as a WORKAROUND for
# https://bugreports.qt.io/browse/QTBUG-68224
# and other related issues.
sip.delete(self.layout())
self._layout = miscwidgets.PseudoLayout(self)
def render_widget(self):
"""Get the RenderWidgetHostViewQt for this view.
Normally, this would always be the focusProxy().
However, it sometimes isn't, so we use this as a WORKAROUND for
https://bugreports.qt.io/browse/QTBUG-68727
"""
proxy = self.focusProxy()
if proxy is not None:
return proxy
# We don't want e.g. a QMenu.
rwhv_class = 'QtWebEngineCore::RenderWidgetHostViewQtDelegateWidget'
children = [c for c in self.findChildren(QWidget)
if c.isVisible() and c.inherits(rwhv_class)]
log.webview.debug("Found possibly lost focusProxy: {}"
.format(children))
return children[-1] if children else None
def shutdown(self):
self.page().shutdown()

View File

@@ -305,6 +305,9 @@ class WebKitElement(webelem.AbstractWebElement):
if self.is_text_input() and self.is_editable():
self._tab.caret.move_to_end_of_document()
def _requires_user_interaction(self):
return False
def _click_editable(self, click_target):
ok = self._elem.evaluateJavaScript('this.focus(); true;')
if ok:

View File

@@ -74,7 +74,7 @@ function tryagain()
</td>
<td style="padding-left: 40px;">
<h1>Unable to load page</h1>
Error while opening {{ url }}<br>
Error while opening {{ url | default('page', true) }}<br>
<p id="error-message-text" style="color: #a31a1a;">{{ error }}</p><br><br>
<form name="bl">

View File

@@ -19,6 +19,8 @@
"""Base class for vim-like key sequence parser."""
import string
from PyQt5.QtCore import pyqtSignal, QObject
from PyQt5.QtGui import QKeySequence
@@ -136,7 +138,7 @@ class BaseKeyParser(QObject):
def _match_count(self, sequence, dry_run):
"""Try to match a key as count."""
txt = str(sequence[-1]) # To account for sequences changed above.
if (txt.isdigit() and self._supports_count and
if (txt in string.digits and self._supports_count and
not (not self._count and txt == '0')):
self._debug_log("Trying match as count")
assert len(txt) == 1, txt

View File

@@ -490,6 +490,7 @@ class TabbedBrowser(QWidget):
else:
self.widget.setCurrentWidget(tab)
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-68076
# Still seems to be needed with Qt 5.11.1
tab.setFocus()
tab.show()

View File

@@ -266,6 +266,47 @@ class WrapperLayout(QLayout):
self._widget.deleteLater()
class PseudoLayout(QLayout):
"""A layout which isn't actually a real layout.
This is used to replace QWebEngineView's internal layout, as a WORKAROUND
for https://bugreports.qt.io/browse/QTBUG-68224 and other related issues.
This is partly inspired by https://codereview.qt-project.org/#/c/230894/
which does something similar as part of Qt.
"""
def addItem(self, item):
assert self.parent() is not None
item.widget().setParent(self.parent())
def removeItem(self, item):
item.widget().setParent(None)
def count(self):
return 0
def itemAt(self, _pos):
return None
def widget(self):
return self.parent().render_widget()
def setGeometry(self, rect):
"""Resize the render widget when the view is resized."""
widget = self.widget()
if widget is not None:
widget.setGeometry(rect)
def sizeHint(self):
"""Make sure the view has the sizeHint of the render widget."""
widget = self.widget()
if widget is not None:
return widget.sizeHint()
return QSize()
class FullscreenNotification(QLabel):
"""A label telling the user this page is now fullscreen."""

View File

@@ -39,6 +39,21 @@ from qutebrowser.browser.network import pac
# https://github.com/qutebrowser/qutebrowser/issues/108
# URL schemes supported by QtWebEngine
WEBENGINE_SCHEMES = [
'about',
'data',
'file',
'filesystem',
'ftp',
'http',
'https',
'javascript',
'ws',
'wss',
]
class InvalidUrlError(ValueError):
"""Error raised if a function got an invalid URL.

View File

@@ -77,6 +77,7 @@ try:
version='.'.join(str(e) for e in _get_constant('version_info')),
description=_get_constant('description'),
long_description=read_file('README.asciidoc'),
long_description_content_type='text/plain',
url='https://www.qutebrowser.org/',
author=_get_constant('author'),
author_email=_get_constant('email'),

View File

@@ -7,5 +7,6 @@
</head>
<body>
<a href="what://::">I'm broken</a>
<a href="foo://bar">Unknown scheme</a>
</body>
</html>

View File

@@ -0,0 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>&lt;img src=&quot;x&quot; onerror=&quot;console.log('XSS')&quot;&gt;foo</title>
</head>
<body>
foo
</body>
</html>

View File

@@ -201,8 +201,8 @@ Feature: Downloading things from a website.
And I run :download-retry
Then the error "Retrying downloads is unsupported *" should be shown
@qtwebkit_skip @qt>=5.10
Scenario: Retrying a failed download with QtWebEngine (Qt >= 5.10)
@qtwebkit_skip @qt==5.10.1
Scenario: Retrying a failed download with QtWebEngine (Qt 5.10)
When I open data/downloads/issue2298.html
And I run :click-element id download
And I wait for "Download error: *" in the log

View File

@@ -156,11 +156,13 @@ Feature: Using hints
And I hint with args "all run message-info {hint-url}" and follow a
Then the message "http://localhost:(port)/data/hello.txt" should be shown
@qt!=5.11.0
Scenario: Clicking an invalid link
When I open data/invalid_link.html
And I hint with args "all" and follow a
Then the error "Invalid link clicked - *" should be shown
@qt!=5.11.0
Scenario: Clicking an invalid link opening in a new tab
When I open data/invalid_link.html
And I hint with args "all tab" and follow a

View File

@@ -111,3 +111,8 @@ Feature: Page history
And I wait until qute://history is loaded
Then the page should contain the plaintext "3.txt"
Then the page should contain the plaintext "4.txt"
Scenario: XSS in :history
When I open data/issue4011.html
And I open qute://history
Then the javascript message "XSS" should not be logged

View File

@@ -361,6 +361,9 @@ class QuteProc(testprocess.Process):
"Focus object changed: "
"<qutebrowser.browser.webengine.webview.WebEngineView object "
"at *>",
# Qt >= 5.11 with workarounds
"Focus object changed: "
"<PyQt5.QtQuickWidgets.QQuickWidget object at *>",
]
if (log_line.category == 'ipc' and

View File

@@ -320,6 +320,10 @@ class TestCount:
keyparser.execute.assert_called_once_with('message-info ccc', 23)
assert not keyparser._sequence
def test_superscript(self, handle_text, keyparser):
# https://github.com/qutebrowser/qutebrowser/issues/3743
handle_text(Qt.Key_twosuperior, Qt.Key_B, Qt.Key_A)
def test_count_keystring_update(self, qtbot, handle_text, keyparser):
"""Make sure the keystring is updated correctly when entering count."""
with qtbot.waitSignals([keyparser.keystring_updated,