Compare commits
49 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
718f73be2e | ||
|
|
da4865d408 | ||
|
|
fd4ff3c9ce | ||
|
|
ad9b50601c | ||
|
|
c88a94f1cc | ||
|
|
13332cd2cf | ||
|
|
48808e59cb | ||
|
|
5571ef4e62 | ||
|
|
b4de889df9 | ||
|
|
4c9360237f | ||
|
|
10538738e0 | ||
|
|
bd5f84bddf | ||
|
|
93ae6ad592 | ||
|
|
146f8e72ae | ||
|
|
f61662fa52 | ||
|
|
e9b4c2a66e | ||
|
|
6a7ab7edb3 | ||
|
|
f7f96484e8 | ||
|
|
840d2e4423 | ||
|
|
e54f2a090a | ||
|
|
55ce4b7ed2 | ||
|
|
3daf823da8 | ||
|
|
72feb2c19f | ||
|
|
6d04508490 | ||
|
|
93ebd846ab | ||
|
|
9ee473a54c | ||
|
|
d1cced0da4 | ||
|
|
22644c41da | ||
|
|
6b5857ef7d | ||
|
|
b0f4cc6924 | ||
|
|
218637af84 | ||
|
|
dd84845f01 | ||
|
|
cf53f9042a | ||
|
|
f48266f72f | ||
|
|
5dac848968 | ||
|
|
341aa1e700 | ||
|
|
ebf81c06ae | ||
|
|
a3eb8d6561 | ||
|
|
af4b02bf46 | ||
|
|
ac29c579ff | ||
|
|
db5ec363cd | ||
|
|
f352c72d1d | ||
|
|
b1f1a0cafa | ||
|
|
749056ff90 | ||
|
|
e7b00ace73 | ||
|
|
442bdd4a4f | ||
|
|
900efe4a36 | ||
|
|
f8a78a0962 | ||
|
|
60e8abaa89 |
@@ -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
|
||||
------
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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."
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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()))
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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."""
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -34,6 +34,11 @@ import tarfile
|
||||
import tempfile
|
||||
import collections
|
||||
|
||||
try:
|
||||
import winreg
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir,
|
||||
os.pardir))
|
||||
|
||||
@@ -222,8 +227,25 @@ def build_windows():
|
||||
utils.print_title("Building Windows binaries")
|
||||
parts = str(sys.version_info.major), str(sys.version_info.minor)
|
||||
ver = ''.join(parts)
|
||||
python_x86 = r'C:\Python{}-32\python.exe'.format(ver)
|
||||
python_x64 = r'C:\Python{}\python.exe'.format(ver)
|
||||
dot_ver = '.'.join(parts)
|
||||
|
||||
# Get python path from registry if possible
|
||||
try:
|
||||
reg64_key = winreg.OpenKeyEx(winreg.HKEY_LOCAL_MACHINE,
|
||||
r'SOFTWARE\Python\PythonCore'
|
||||
r'\{}\InstallPath'.format(dot_ver))
|
||||
python_x64 = winreg.QueryValueEx(reg64_key, 'ExecutablePath')[0]
|
||||
except FileNotFoundError:
|
||||
python_x64 = r'C:\Python{}\python.exe'.format(ver)
|
||||
|
||||
try:
|
||||
reg32_key = winreg.OpenKeyEx(winreg.HKEY_LOCAL_MACHINE,
|
||||
r'SOFTWARE\WOW6432Node\Python\PythonCore'
|
||||
r'\{}-32\InstallPath'.format(dot_ver))
|
||||
python_x86 = winreg.QueryValueEx(reg32_key, 'ExecutablePath')[0]
|
||||
except FileNotFoundError:
|
||||
python_x86 = r'C:\Python{}-32\python.exe'.format(ver)
|
||||
|
||||
out_pyinstaller = os.path.join('dist', 'qutebrowser')
|
||||
out_32 = os.path.join('dist',
|
||||
'qutebrowser-{}-x86'.format(qutebrowser.__version__))
|
||||
|
||||
1
setup.py
1
setup.py
@@ -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'),
|
||||
|
||||
@@ -7,5 +7,6 @@
|
||||
</head>
|
||||
<body>
|
||||
<a href="what://::">I'm broken</a>
|
||||
<a href="foo://bar">Unknown scheme</a>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
10
tests/end2end/data/issue4011.html
Normal file
10
tests/end2end/data/issue4011.html
Normal file
@@ -0,0 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title><img src="x" onerror="console.log('XSS')">foo</title>
|
||||
</head>
|
||||
<body>
|
||||
foo
|
||||
</body>
|
||||
</html>
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user