Compare commits

...

31 Commits

Author SHA1 Message Date
Florian Bruhin
bad5005dee Release v0.10.1 2017-03-08 06:25:24 +01:00
Florian Bruhin
361e4e93ed Update changelog for v0.10.1 2017-03-08 05:08:54 +01:00
Florian Bruhin
d22125249c pylint: Disable too-many-boolean-expressions
(cherry picked from commit 6bdf8495aa)
2017-03-08 05:07:12 +01:00
pyup-bot
a9871df971 Update pyparsing from 2.1.10 to 2.2.0
(cherry picked from commit a19ebfb6d0)
2017-03-07 22:07:52 +01:00
Florian Bruhin
89eb5224b0 Fix compiled version check
Fixes #2412

(cherry picked from commit 7c9d004bbc)
2017-03-07 22:06:33 +01:00
Florian Bruhin
1f850b8de9 Refactor adblock parsing
(cherry picked from commit 4c3c86081f)
2017-03-07 22:06:29 +01:00
pyup-bot
40d3679073 Update appdirs from 1.4.2 to 1.4.3
(cherry picked from commit c50e652cc7)
2017-03-07 20:38:03 +01:00
Florian Bruhin
4d5dfb5ee0 Stringify py.path.local in adblock tests
(cherry picked from commit ace4006179)
2017-03-07 20:38:00 +01:00
Florian Bruhin
2619d9b83d Handle invalid UTF8 data in hostblock lists
Fixes #2301

(cherry picked from commit c45019f0a0)
2017-03-07 20:29:02 +01:00
Florian Bruhin
2725538155 Reorder initialization
marcos.init() really belongs into _init_modules, and we need to do _process_args
after everything has been initialized.

Fixes #2408.

(cherry picked from commit d42dff67f2)
2017-03-07 20:28:56 +01:00
Florian Bruhin
b1f3c19fc6 Stabilize some tests
(cherry picked from commit 5fb6d26465)
2017-03-07 20:28:52 +01:00
Florian Bruhin
88f2f9dfb5 Only call QApplication::sync() with QtWebEngine
(cherry picked from commit a1c7d179e3)
2017-03-07 20:26:51 +01:00
Florian Bruhin
57c424accd Stabilize session tests
(cherry picked from commit fa89fff668)
2017-03-07 20:26:49 +01:00
Florian Bruhin
2ab6fdab47 Remove unneeded deleted attribute for FakeSocket
(cherry picked from commit 199a2ffe27)
2017-03-07 20:26:44 +01:00
Florian Bruhin
10c64a9e52 Update changelog
(cherry picked from commit 27edc89d88)
2017-03-05 15:52:21 +01:00
Daniel Schadt
63954af9a7 return fast from DownloadItem.open_file
Fixes #2296

By using a singleshot timer, we return fast from DownloadItem.open_file,
which in turn closes the prompt fast, which in turn doesn't allow a
second Ctrl-x to be registered, which in turn doesn't want to set the
filename twice.

(cherry picked from commit 90f12a1d5a)
2017-03-05 15:52:13 +01:00
Florian Bruhin
6b423e15ae Make sure to process history after the rest of init is done
Otherwise, with 5ccafd62d4 the history starts
processing before the webview opened, and opening it is delayed until the whole
history is read.

Instead, call _process_args directly (I'm not even sure why it was using a 0ms
QTimer...) and schedule _init_late_modules after everything is really done.

(cherry picked from commit c422897abb)
2017-03-04 18:26:44 +01:00
Florian Bruhin
50c5a425c0 Fix initial keyboard focus with QtWebEngine
Fixes #2321.

(cherry picked from commit fa90668814c8fa448eb74fad171ce9878bcb5374)
2017-03-04 18:10:57 +01:00
Florian Bruhin
3de4a942e1 ipc: Delay deleting of QLocalSocket on disconnect
Fixes #2396.

See https://bugreports.qt.io/browse/QTBUG-59297 and
https://github.com/qutebrowser/qutebrowser/issues/2321#issuecomment-284024213

(cherry picked from commit b16f1952bb4c8b68f54040e7a2c95702054e624b)
2017-03-04 18:10:54 +01:00
Florian Bruhin
ec969e2da2 Set tab as parent for print dialogs
Fixes #2366

(cherry picked from commit 477539f4bab39122dbee82cfc77b583f871793fd)
2017-03-02 18:42:34 +01:00
Florian Bruhin
a5c3c49e8c Fix WebKitElement._move_text_cursor with old PyQt 2017-03-02 10:10:36 +01:00
Florian Bruhin
ac1095405c Fix lint 2017-03-02 10:10:36 +01:00
Florian Bruhin
18e008d9c0 Stabilize existing text test
(cherry picked from commit 65a701a180)
2017-03-02 08:38:30 +01:00
Florian Bruhin
68775a8cf6 Strip QtWebEngine download suffixes properly
Fixes #2386

(cherry picked from commit 81a36ffd7d)
2017-03-01 23:44:03 +01:00
Florian Bruhin
ca62acb219 Relax validation of QssColor values
Fixes #2370

(cherry picked from commit 1f12b4c1c1)
2017-03-01 18:14:59 +01:00
Florian Bruhin
dbdb48ff71 Move cursor to end with input elements on QtWebEngine
(cherry picked from commit bc0a9cd94d)
2017-03-01 17:55:30 +01:00
Florian Bruhin
b2088e711c Fix selecting text fields with QtWebKit
Using focus() in JS there means that existing text in the field gets selected.
Move the cursor to the end after focusing it to prevent that.

Fixes #2359

(cherry picked from commit 1e1ba34b60)
2017-03-01 17:55:24 +01:00
Florian Bruhin
4730c6fd6b Update changelog
(cherry picked from commit f9697f1ebe)
2017-03-01 14:37:58 +01:00
Florian Bruhin
bc19f138fb Don't strip info when loading PAC from a file
(cherry picked from commit deb59fc66e)
2017-03-01 14:37:57 +01:00
Florian Bruhin
7502d10fd9 Remove UserInfo and path/query for PAC URLs
(cherry picked from commit 9bb5c9fdab)
2017-03-01 14:37:55 +01:00
Florian Bruhin
0d5489395e Fix lint
(cherry picked from commit 1e42fd1319)
2017-03-01 14:35:54 +01:00
33 changed files with 350 additions and 99 deletions

View File

@@ -38,7 +38,8 @@ disable=no-self-use,
suppressed-message,
too-many-return-statements,
duplicate-code,
wrong-import-position
wrong-import-position,
too-many-boolean-expressions
[BASIC]
function-rgx=[a-z_][a-z0-9_]{2,50}$

View File

@@ -14,6 +14,27 @@ This project adheres to http://semver.org/[Semantic Versioning].
// `Fixed` for any bug fixes.
// `Security` to invite users to upgrade in case of vulnerabilities.
v0.10.1
-------
Changed
~~~~~~~
- `--qt-arg` and `--qt-flag` can now also be used to pass arguments to Chromium when using QtWebEngine.
Fixed
~~~~~
- URLs are now redacted properly (username/password, and path/query for HTTPS) when using Proxy Autoconfig with QtWebKit
- Crash when updating adblock lists with invalid UTF8-chars in them
- Fixed the web inspector with QtWebEngine
- Version checks when starting qutebrowser now also take the Qt version PyQt was compiled against into account
- Hinting a input now doesn't select existing text anymore with QtWebKit
- The cursor now moves to the end when input elements are selected with QtWebEngine
- Download suffixes like (1) are now correctly stripped with QtWebEngine
- Crash when trying to print a tab which was closed in the meantime
- Crash when trying to open a file twice on Windows
v0.10.0
-------

View File

@@ -1,8 +1,8 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
appdirs==1.4.2
appdirs==1.4.3
packaging==16.8
pyparsing==2.1.10
setuptools==34.3.0
pyparsing==2.2.0
setuptools==34.3.1
six==1.10.0
wheel==0.29.0

View File

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

View File

@@ -142,9 +142,6 @@ def init(args, crash_handler):
pre_text="Error while initializing")
sys.exit(usertypes.Exit.err_init)
QTimer.singleShot(0, functools.partial(_process_args, args))
QTimer.singleShot(10, functools.partial(_init_late_modules, args))
log.init.debug("Initializing eventfilter...")
event_filter = EventFilter(qApp)
qApp.installEventFilter(event_filter)
@@ -155,11 +152,13 @@ def init(args, crash_handler):
config_obj.style_changed.connect(style.get_stylesheet.cache_clear)
qApp.focusChanged.connect(on_focus_changed)
_process_args(args)
QDesktopServices.setUrlHandler('http', open_desktopservices_url)
QDesktopServices.setUrlHandler('https', open_desktopservices_url)
QDesktopServices.setUrlHandler('qute', open_desktopservices_url)
macros.init()
QTimer.singleShot(10, functools.partial(_init_late_modules, args))
log.init.debug("Init done!")
crash_handler.raise_crashdlg()
@@ -448,6 +447,7 @@ def _init_modules(args, crash_handler):
os.environ['QT_WAYLAND_DISABLE_WINDOWDECORATION'] = '1'
else:
os.environ.pop('QT_WAYLAND_DISABLE_WINDOWDECORATION', None)
macros.init()
# Init backend-specific stuff
browsertab.init()

View File

@@ -58,7 +58,7 @@ def get_fileobj(byte_io):
byte_io = zf.open(filename, mode='r')
else:
byte_io.seek(0) # rewind what zipfile.is_zipfile did
return io.TextIOWrapper(byte_io, encoding='utf-8')
return byte_io
def is_whitelisted_host(host):
@@ -147,7 +147,7 @@ class HostBlocker:
with open(filename, 'r', encoding='utf-8') as f:
for line in f:
target.add(line.strip())
except OSError:
except (OSError, UnicodeDecodeError):
log.misc.exception("Failed to read host blocklist!")
return True
@@ -205,6 +205,54 @@ class HostBlocker:
download.finished.connect(
functools.partial(self.on_download_finished, download))
def _parse_line(self, line):
"""Parse a line from a host file.
Args:
line: The bytes object to parse.
Returns:
True if parsing succeeded, False otherwise.
"""
if line.startswith(b'#'):
# Ignoring comments early so we don't have to care about
# encoding errors in them.
return True
try:
line = line.decode('utf-8')
except UnicodeDecodeError:
log.misc.error("Failed to decode: {!r}".format(line))
return False
# Remove comments
try:
hash_idx = line.index('#')
line = line[:hash_idx]
except ValueError:
pass
line = line.strip()
# Skip empty lines
if not line:
return True
parts = line.split()
if len(parts) == 1:
# "one host per line" format
host = parts[0]
elif len(parts) == 2:
# /etc/hosts format
host = parts[1]
else:
log.misc.error("Failed to parse: {!r}".format(line))
return False
if host not in self.WHITELISTED:
self._blocked_hosts.add(host)
return True
def _merge_file(self, byte_io):
"""Read and merge host files.
@@ -218,35 +266,18 @@ class HostBlocker:
line_count = 0
try:
f = get_fileobj(byte_io)
except (OSError, UnicodeDecodeError, zipfile.BadZipFile,
zipfile.LargeZipFile, LookupError) as e:
except (OSError, zipfile.BadZipFile, zipfile.LargeZipFile,
LookupError) as e:
message.error("adblock: Error while reading {}: {} - {}".format(
byte_io.name, e.__class__.__name__, e))
return
for line in f:
line_count += 1
# Remove comments
try:
hash_idx = line.index('#')
line = line[:hash_idx]
except ValueError:
pass
line = line.strip()
# Skip empty lines
if not line:
continue
parts = line.split()
if len(parts) == 1:
# "one host per line" format
host = parts[0]
elif len(parts) == 2:
# /etc/hosts format
host = parts[1]
else:
ok = self._parse_line(line)
if not ok:
error_count += 1
continue
if host not in self.WHITELISTED:
self._blocked_hosts.add(host)
log.misc.debug("{}: read {} lines".format(byte_io.name, line_count))
if error_count > 0:
message.error("adblock: {} read errors for {}".format(

View File

@@ -350,7 +350,7 @@ class CommandDispatcher:
message.error("Printing failed!")
tab.printing.check_preview_support()
diag = QPrintPreviewDialog()
diag = QPrintPreviewDialog(tab)
diag.setAttribute(Qt.WA_DeleteOnClose)
diag.setWindowFlags(diag.windowFlags() | Qt.WindowMaximizeButtonHint |
Qt.WindowMinimizeButtonHint)
@@ -376,7 +376,7 @@ class CommandDispatcher:
message.error("Printing failed!")
diag.deleteLater()
diag = QPrintDialog()
diag = QPrintDialog(tab)
diag.open(lambda: tab.printing.to_printer(diag.printer(),
print_callback))

View File

@@ -529,7 +529,11 @@ class AbstractDownloadItem(QObject):
if filename is None: # pragma: no cover
log.downloads.error("No filename to open the download!")
return
utils.open_file(filename, cmdline)
# By using a singleshot timer, we ensure that we return fast. This
# is important on systems where process creation takes long, as
# otherwise the prompt might hang around and cause bugs
# (see issue #2296)
QTimer.singleShot(0, lambda: utils.open_file(filename, cmdline))
def _ensure_can_set_filename(self, filename):
"""Make sure we can still set a filename."""

View File

@@ -22,7 +22,7 @@
import sys
import functools
from PyQt5.QtCore import (QObject, pyqtSignal, pyqtSlot)
from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot, QUrl
from PyQt5.QtNetwork import (QNetworkProxy, QNetworkRequest, QHostInfo,
QNetworkReply, QNetworkAccessManager,
QHostAddress)
@@ -199,16 +199,24 @@ class PACResolver:
err = "Cannot resolve FindProxyForURL function, got '{}' instead"
raise EvalProxyError(err.format(self._resolver.toString()))
def resolve(self, query):
def resolve(self, query, from_file=False):
"""Resolve a proxy via PAC.
Args:
query: QNetworkProxyQuery.
from_file: Whether the proxy info is coming from a file.
Return:
A list of QNetworkProxy objects in order of preference.
"""
result = self._resolver.call([query.url().toString(),
if from_file:
string_flags = QUrl.PrettyDecoded
else:
string_flags = QUrl.RemoveUserInfo
if query.url().scheme() == 'https':
string_flags |= QUrl.RemovePath | QUrl.RemoveQuery
result = self._resolver.call([query.url().toString(string_flags),
query.peerHostName()])
result_str = result.toString()
if not result.isString():
@@ -236,6 +244,7 @@ class PACFetcher(QObject):
assert url.scheme().startswith(pac_prefix)
url.setScheme(url.scheme()[len(pac_prefix):])
self._pac_url = url
self._manager = QNetworkAccessManager()
self._manager.setProxy(QNetworkProxy(QNetworkProxy.NoProxy))
self._reply = self._manager.get(QNetworkRequest(url))
@@ -292,8 +301,9 @@ class PACFetcher(QObject):
Return a list of QNetworkProxy objects in order of preference.
"""
self._wait()
from_file = self._pac_url.scheme() == 'file'
try:
return self._pac.resolve(query)
return self._pac.resolve(query, from_file=from_file)
except (EvalProxyError, ParseProxyError) as e:
log.network.exception("Error in PAC resolution: {}.".format(e))
# .invalid is guaranteed to be inaccessible in RFC 6761.

View File

@@ -326,6 +326,10 @@ class AbstractWebElement(collections.abc.MutableMapping):
raise Error("Element position is out of view!")
return pos
def _move_text_cursor(self):
"""Move cursor to end after clicking."""
raise NotImplementedError
def _click_fake_event(self, click_target):
"""Send a fake click event to the element."""
pos = self._mouse_pos()
@@ -356,11 +360,7 @@ class AbstractWebElement(collections.abc.MutableMapping):
for evt in events:
self._tab.send_event(evt)
def after_click():
"""Move cursor to end after clicking."""
if self.is_text_input() and self.is_editable():
self._tab.caret.move_to_end_of_document()
QTimer.singleShot(0, after_click)
QTimer.singleShot(0, self._move_text_cursor)
def _click_editable(self, click_target):
"""Fake a click on an editable input field."""

View File

@@ -144,7 +144,7 @@ def _get_suggested_filename(path):
See https://bugreports.qt.io/browse/QTBUG-56978
"""
filename = os.path.basename(path)
filename = re.sub(r'\([0-9]+\)$', '', filename)
filename = re.sub(r'\([0-9]+\)(?=\.|$)', '', filename)
if not qtutils.version_check('5.8.1'):
# https://bugreports.qt.io/browse/QTBUG-58155
filename = urllib.parse.unquote(filename)

View File

@@ -157,6 +157,12 @@ class WebEngineElement(webelem.AbstractWebElement):
self._id)
self._tab.run_js_async(js_code)
def _move_text_cursor(self):
if self.is_text_input() and self.is_editable():
js_code = javascript.assemble('webelem', 'move_cursor_to_end',
self._id)
self._tab.run_js_async(js_code)
def _click_editable(self, click_target):
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-58515
# pylint doesn't know about Qt.MouseEventSynthesizedBySystem
@@ -171,6 +177,7 @@ class WebEngineElement(webelem.AbstractWebElement):
# This actually "clicks" the element by calling focus() on it in JS.
js_code = javascript.assemble('webelem', 'focus', self._id)
self._tab.run_js_async(js_code)
self._move_text_cursor()
def _click_js(self, _click_target):
settings = QWebEngineSettings.globalSettings()

View File

@@ -300,9 +300,18 @@ class WebKitElement(webelem.AbstractWebElement):
break
elem = elem._parent() # pylint: disable=protected-access
def _move_text_cursor(self):
if self is None:
# old PyQt versions call the slot after the element is deleted.
return
if self.is_text_input() and self.is_editable():
self._tab.caret.move_to_end_of_document()
def _click_editable(self, click_target):
ok = self._elem.evaluateJavaScript('this.focus(); true;')
if not ok:
if ok:
self._move_text_cursor()
else:
log.webelem.debug("Failed to focus via JS, falling back to event")
self._click_fake_event(click_target)

View File

@@ -694,30 +694,17 @@ class CssColor(BaseType):
class QssColor(CssColor):
"""Base class for a color value.
Class attributes:
color_func_regexes: Valid function regexes.
"""
num = r'[0-9]{1,3}%?'
color_func_regexes = [
r'rgb\({num},\s*{num},\s*{num}\)'.format(num=num),
r'rgba\({num},\s*{num},\s*{num},\s*{num}\)'.format(num=num),
r'hsv\({num},\s*{num},\s*{num}\)'.format(num=num),
r'hsva\({num},\s*{num},\s*{num},\s*{num}\)'.format(num=num),
r'qlineargradient\(.*\)',
r'qradialgradient\(.*\)',
r'qconicalgradient\(.*\)',
]
"""Color used in a Qt stylesheet."""
def validate(self, value):
functions = ['rgb', 'rgba', 'hsv', 'hsva', 'qlineargradient',
'qradialgradient', 'qconicalgradient']
self._basic_validation(value)
if not value:
return
elif any(re.match(r, value) for r in self.color_func_regexes):
# QColor doesn't handle these, so we do the best we can easily
elif (any(value.startswith(func + '(') for func in functions) and
value.endswith(')')):
# QColor doesn't handle these
pass
elif QColor.isValidColor(value):
pass

View File

@@ -203,5 +203,11 @@ window._qutebrowser.webelem = (function() {
elem.focus();
};
funcs.move_cursor_to_end = function(id) {
var elem = elements[id];
elem.selectionStart = elem.value.length;
elem.selectionEnd = elem.value.length;
};
return funcs;
})();

View File

@@ -262,7 +262,7 @@ def get_backend(args):
def check_qt_version(backend):
"""Check if the Qt version is recent enough."""
from PyQt5.QtCore import qVersion, PYQT_VERSION, PYQT_VERSION_STR
from PyQt5.QtCore import PYQT_VERSION, PYQT_VERSION_STR
from qutebrowser.utils import qtutils, version
if (qtutils.version_check('5.2.0', operator.lt, strict=True) or
PYQT_VERSION < 0x050200):

View File

@@ -27,7 +27,7 @@ import getpass
import binascii
import hashlib
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, Qt
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, Qt, QTimer
from PyQt5.QtNetwork import QLocalSocket, QLocalServer, QAbstractSocket
import qutebrowser
@@ -281,7 +281,11 @@ class IPCServer(QObject):
if self._socket is None:
log.ipc.debug("In on_disconnected with None socket!")
else:
self._socket.deleteLater()
# For some reason Qt can still get delayed canReadNotifications
# internally, so if we call deleteLater() right away and then call
# QApplication::processEvents() somewhere in the code, we can get a
# segfault.
QTimer.singleShot(500, self._socket.deleteLater)
self._socket = None
# Maybe another connection is waiting.
self.handle_connection()

View File

@@ -19,13 +19,15 @@
"""Misc. widgets used at different places."""
import operator
from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt, QSize, QTimer
from PyQt5.QtWidgets import (QLineEdit, QWidget, QHBoxLayout, QLabel,
QStyleOption, QStyle, QLayout, QApplication)
from PyQt5.QtGui import QValidator, QPainter
from qutebrowser.utils import utils, objreg
from qutebrowser.misc import cmdhistory
from qutebrowser.utils import utils, objreg, qtutils, log, usertypes
from qutebrowser.misc import cmdhistory, objects
class MinimalLineEditMixin:
@@ -260,6 +262,16 @@ class WrapperLayout(QLayout):
self._widget = widget
container.setFocusProxy(widget)
widget.setParent(container)
if (qtutils.version_check('5.8.0', op=operator.eq) and
objects.backend == usertypes.Backend.QtWebEngine and
container.window() and
container.window().windowHandle() and
not container.window().windowHandle().isActive()):
log.misc.debug("Calling QApplication::sync...")
# WORKAROUND for:
# https://bugreports.qt.io/browse/QTBUG-56652
# https://codereview.qt-project.org/#/c/176113/5//ALL,unified
QApplication.sync()
def unwrap(self):
self._widget.setParent(None)

View File

@@ -88,9 +88,12 @@ def version_check(version, op=operator.ge, strict=False):
op: The operator to use for the check.
strict: If given, also check the compiled Qt version.
"""
if strict:
assert op in [operator.ge, operator.lt], op
parsed = pkg_resources.parse_version(version)
result = op(pkg_resources.parse_version(qVersion()), parsed)
if result and strict:
if ((strict and op == operator.ge and result) or
(strict and op == operator.lt and not result)):
result = op(pkg_resources.parse_version(QT_VERSION_STR), parsed)
return result

View File

@@ -4,10 +4,21 @@
<head>
<meta charset="utf-8">
<title>Simple input</title>
<script type="text/javascript">
function setup_event_listener() {
var elem = document.getElementById('qute-input-existing');
console.log(elem);
elem.addEventListener('input', function() {
console.log("contents: " + elem.value);
});
}
</script>
</head>
<body>
<body onload="setup_event_listener()">
<form><input id="qute-input"></input></form>
With padding:
<form><input type="text" style="padding-left: 20px;"></input></form>
With existing text (logs to JS)::
<form><input id="qute-input-existing" value="existing"></input></form>
</body>
</html>

View File

@@ -188,6 +188,14 @@ Feature: Using hints
And I run :hint
Then the error "No elements found." should be shown
Scenario: Clicking input with existing text
When I set general -> log-javascript-console to info
And I open data/hints/input.html
And I run :click-element id qute-input-existing
And I wait for "Entering mode KeyMode.insert *" in the log
And I run :fake-key new
Then the javascript message "contents: existingnew" should be logged
### iframes
@qtwebengine_todo: Hinting in iframes is not implemented yet

View File

@@ -70,7 +70,7 @@ Feature: Setting positional marks
And I run :jump-mark b
Then the error "Mark b is not set" should be shown
@qtwebengine_todo: Does not emit loaded signal for fragments?
@qtwebengine_todo: Does not emit loaded signal for fragments? @flaky
Scenario: Jumping to a local mark after changing fragments
When I open data/marks.html#top
And I run :scroll 'top'

View File

@@ -263,8 +263,10 @@ Feature: Saving and loading sessions
Then the error "No session loaded currently!" should be shown
Scenario: Saving current session after one is loaded
When I open data/numbers/1.txt
When I run :session-save current_session
And I run :session-load current_session
And I wait until data/numbers/1.txt is loaded
And I run :session-save --current
Then the message "Saved session current_session." should be shown
@@ -288,6 +290,7 @@ Feature: Saving and loading sessions
And I run :window-only
And I run :tab-only
And I run :session-load window_session_name
And I wait until data/numbers/5.txt is loaded
Then the session should look like:
windows:
- tabs:

View File

@@ -209,15 +209,22 @@ class QuteProc(testprocess.Process):
"load status for <qutebrowser.browser.* tab_id=0 "
"url='about:blank'>: LoadStatus.success")
start_okay_messages_focus = [
# QtWebKit
## QtWebKit
"Focus object changed: "
"<qutebrowser.browser.* tab_id=0 url='about:blank'>",
# QtWebEngine
# when calling QApplication::sync
"Focus object changed: "
"<qutebrowser.browser.webkit.webview.WebView tab_id=0 url=''>",
## QtWebEngine
"Focus object changed: "
"<PyQt5.QtWidgets.QOpenGLWidget object at *>",
# QtWebEngine with Qt >= 5.8
# with Qt >= 5.8
"Focus object changed: "
"<PyQt5.QtGui.QWindow object at *>",
# when calling QApplication::sync
"Focus object changed: "
"<PyQt5.QtWidgets.QWidget object at *>",
]
if (log_line.category == 'ipc' and

View File

@@ -48,12 +48,6 @@ def test_insert_mode(file_name, elem_id, source, input_text, auto_insert, zoom,
if source == 'keypress':
quteproc.press_keys(input_text)
elif source == 'clipboard':
if request.config.webengine:
pytest.xfail(reason="QtWebEngine TODO: caret mode is not "
"implemented")
# Note we actually run the keypress tests with QtWebEngine, as for
# some reason it selects all the text when clicking the field the
# second time.
quteproc.send_cmd(':debug-set-fake-clipboard "{}"'.format(input_text))
quteproc.send_cmd(':insert-text {clipboard}')
else:

View File

@@ -219,3 +219,45 @@ def test_webengine_inspector(request, quteproc_new):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('127.0.0.1', port))
s.close()
@pytest.mark.linux
def test_webengine_download_suffix(request, quteproc_new, tmpdir):
"""Make sure QtWebEngine does not add a suffix to downloads."""
if not request.config.webengine:
pytest.skip()
download_dir = tmpdir / 'downloads'
download_dir.ensure(dir=True)
(tmpdir / 'user-dirs.dirs').write(
'XDG_DOWNLOAD_DIR={}'.format(download_dir))
env = {'XDG_CONFIG_HOME': str(tmpdir)}
args = (['--temp-basedir'] + _base_args(request.config))
quteproc_new.start(args, env=env)
quteproc_new.set_setting('storage', 'prompt-download-directory', 'false')
quteproc_new.set_setting('storage', 'download-directory',
str(download_dir))
quteproc_new.open_path('data/downloads/download.bin', wait=False)
quteproc_new.wait_for(category='downloads', message='Download * finished')
quteproc_new.open_path('data/downloads/download.bin', wait=False)
quteproc_new.wait_for(message='Entering mode KeyMode.yesno *')
quteproc_new.send_cmd(':prompt-accept yes')
quteproc_new.wait_for(category='downloads', message='Download * finished')
files = download_dir.listdir()
assert len(files) == 1
assert files[0].basename == 'download.bin'
def test_command_on_start(request, quteproc_new):
"""Make sure passing a command on start works.
See https://github.com/qutebrowser/qutebrowser/issues/2408
"""
args = (['--temp-basedir'] + _base_args(request.config) +
[':quickmark-add https://www.example.com/ example'])
quteproc_new.start(args)
quteproc_new.send_cmd(':quit')
quteproc_new.wait_for_quit()

View File

@@ -312,6 +312,68 @@ def test_failed_dl_update(config_stub, basedir, download_stub,
assert_urls(host_blocker, whitelisted=[])
@pytest.mark.parametrize('location', ['content', 'comment'])
def test_invalid_utf8(config_stub, download_stub, tmpdir, caplog, location):
"""Make sure invalid UTF-8 is handled correctly.
See https://github.com/qutebrowser/qutebrowser/issues/2301
"""
blocklist = tmpdir / 'blocklist'
if location == 'comment':
blocklist.write_binary(b'# nbsp: \xa0\n')
else:
assert location == 'content'
blocklist.write_binary(b'https://www.example.org/\xa0')
for url in BLOCKLIST_HOSTS:
blocklist.write(url + '\n', mode='a')
config_stub.data = {
'content': {
'host-block-lists': [QUrl(str(blocklist))],
'host-blocking-enabled': True,
'host-blocking-whitelist': None,
}
}
host_blocker = adblock.HostBlocker()
host_blocker.adblock_update()
finished_signal = host_blocker._in_progress[0].finished
if location == 'content':
with caplog.at_level(logging.ERROR):
finished_signal.emit()
expected = (r"Failed to decode: "
r"b'https://www.example.org/\xa0localhost\n'")
assert caplog.records[-2].message == expected
else:
finished_signal.emit()
host_blocker.read_hosts()
assert_urls(host_blocker, whitelisted=[])
def test_invalid_utf8_compiled(config_stub, tmpdir, monkeypatch, caplog):
"""Make sure invalid UTF-8 in the compiled file is handled."""
data_dir = tmpdir / 'data'
config_dir = tmpdir / 'config'
monkeypatch.setattr(adblock.standarddir, 'data', lambda: str(data_dir))
monkeypatch.setattr(adblock.standarddir, 'config', lambda: str(config_dir))
config_stub.data = {
'content': {
'host-block-lists': [],
}
}
(config_dir / 'blocked-hosts').write_binary(
b'https://www.example.org/\xa0')
(data_dir / 'blocked-hosts').ensure()
host_blocker = adblock.HostBlocker()
with caplog.at_level(logging.ERROR):
host_blocker.read_hosts()
assert caplog.records[-1].message == "Failed to read host blocklist!"
def test_blocking_with_whitelist(config_stub, basedir, download_stub,
data_tmpdir, tmpdir):
"""Ensure hosts in host-blocking-whitelist are never blocked."""

View File

@@ -171,6 +171,41 @@ def test_fail_return():
res.resolve(QNetworkProxyQuery(QUrl("https://example.com/test")))
@pytest.mark.parametrize('url, has_secret', [
('http://example.com/secret', True), # path passed with HTTP
('http://example.com?secret=yes', True), # query passed with HTTP
('http://secret@example.com', False), # user stripped with HTTP
('http://user:secret@example.com', False), # password stripped with HTTP
('https://example.com/secret', False), # path stripped with HTTPS
('https://example.com?secret=yes', False), # query stripped with HTTPS
('https://secret@example.com', False), # user stripped with HTTPS
('https://user:secret@example.com', False), # password stripped with HTTPS
])
@pytest.mark.parametrize('from_file', [True, False])
def test_secret_url(url, has_secret, from_file):
"""Make sure secret parts in an URL are stripped correctly.
The following parts are considered secret:
- If the PAC info is loaded from a local file, nothing.
- If the URL to resolve is a HTTP URL, the username/password.
- If the URL to resolve is a HTTPS URL, the username/password, query
and path.
"""
test_str = """
function FindProxyForURL(domain, host) {{
has_secret = domain.indexOf("secret") !== -1;
expected_secret = {};
if (has_secret !== expected_secret) {{
throw new Error("Expected secret: " + expected_secret + ", found: " + has_secret + " in " + domain);
}}
return "DIRECT";
}}
""".format('true' if (has_secret or from_file) else 'false')
res = pac.PACResolver(test_str)
res.resolve(QNetworkProxyQuery(QUrl(url)), from_file=from_file)
# See https://github.com/qutebrowser/qutebrowser/pull/1891#issuecomment-259222615
try:

View File

@@ -408,7 +408,7 @@ class TestDefaultConfig:
If it did change, place a new qutebrowser-vx.y.z.conf in old_configs
and then increment the version.
"""
assert qutebrowser.__version__ == '0.10.0'
assert qutebrowser.__version__ == '0.10.1'
@pytest.mark.parametrize('filename',
os.listdir(os.path.join(os.path.dirname(__file__), 'old_configs')),

View File

@@ -880,19 +880,16 @@ class ColorTests:
('#12', []),
('foobar', []),
('42', []),
('rgb(1, 2, 3, 4)', []),
('foo(1, 2, 3)', []),
('rgb(1, 2, 3', []),
('rgb(0, 0, 0)', [configtypes.QssColor]),
('rgb(0,0,0)', [configtypes.QssColor]),
('-foobar(42)', [configtypes.CssColor]),
('rgba(255, 255, 255, 255)', [configtypes.QssColor]),
('rgba(255,255,255,255)', [configtypes.QssColor]),
('hsv(359, 255, 255)', [configtypes.QssColor]),
('hsva(359, 255, 255, 255)', [configtypes.QssColor]),
('hsv(10%, 10%, 10%)', [configtypes.QssColor]),
('rgba(255, 255, 255, 1.0)', [configtypes.QssColor]),
('hsv(10%,10%,10%)', [configtypes.QssColor]),
('qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 white, '
'stop: 0.4 gray, stop:1 green)', [configtypes.QssColor]),
('qconicalgradient(cx:0.5, cy:0.5, angle:30, stop:0 white, '

View File

@@ -205,8 +205,7 @@ def test_exit_unsuccessful_output(qtbot, proc, caplog, py_proc, stream):
print("test", file=sys.{})
sys.exit(1)
""".format(stream)))
assert len(caplog.records) == 2
assert caplog.records[1].msg == 'Process {}:\ntest'.format(stream)
assert caplog.records[-1].msg == 'Process {}:\ntest'.format(stream)
@pytest.mark.parametrize('stream', ['stdout', 'stderr'])

View File

@@ -101,7 +101,6 @@ class FakeSocket(QObject):
_error_val: The value returned for error().
_state_val: The value returned for state().
_connect_successful: The value returned for waitForConnected().
deleted: Set to True if deleteLater() was called.
"""
readyRead = pyqtSignal()
@@ -115,7 +114,6 @@ class FakeSocket(QObject):
self._data = data
self._connect_successful = connect_successful
self.error = stubs.FakeSignal('error', func=self._error)
self.deleted = False
def _error(self):
return self._error_val
@@ -131,9 +129,6 @@ class FakeSocket(QObject):
self._data = rest
return firstline + mid
def deleteLater(self):
self.deleted = True
def errorString(self):
return "Error string"

View File

@@ -52,6 +52,9 @@ import overflow_test_cases
# strict=True
('5.4.0', '5.3.0', '5.4.0', operator.ge, False),
('5.4.0', '5.4.0', '5.4.0', operator.ge, True),
('5.4.0', '5.3.0', '5.4.0', operator.lt, True),
('5.4.0', '5.4.0', '5.4.0', operator.lt, False),
])
def test_version_check(monkeypatch, qversion, compiled, version, op, expected):
"""Test for version_check().
@@ -66,7 +69,7 @@ def test_version_check(monkeypatch, qversion, compiled, version, op, expected):
"""
monkeypatch.setattr('qutebrowser.utils.qtutils.qVersion', lambda: qversion)
if compiled is not None:
monkeypatch.setattr('qutebrowser.utils.qtutils.QT_VERSION_STR', compiled)
monkeypatch.setattr(qtutils, 'QT_VERSION_STR', compiled)
strict = True
else:
strict = False