Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Alexander Cogneau 2015-08-06 16:40:48 +02:00
commit 9a85b66452
11 changed files with 175 additions and 31 deletions

View File

@ -32,6 +32,8 @@ Added
- New setting `tabs -> show` which supersedes the old `tabs -> hide-*` options
and has an additional `switching` option which shows tab while switching
them. There's also a new `show-switching` option to configure the timeout.
- New setting `storage -> remember-download-directory` to remember the last
used download directory.
Changed
~~~~~~~
@ -48,7 +50,12 @@ Fixed
~~~~~
- `link_pyqt.py` now should work better on untested distributions.
- Fixed various corner-cases with crashes when reading invalid config values.
- Fixed various corner-cases with crashes when reading invalid config values
and the history file.
- Fixed various corner-cases when setting text via an external editor.
- Fixed potential crash when hinting a text field.
- Fixed entering of insert mode when certain disabled text fields were clicked.
- Fixed a crash when using `:set` with `-p` and `!` (invert value)
Removed
~~~~~~~

View File

@ -89,10 +89,10 @@ Requirements
The following software and libraries are required to run qutebrowser:
* http://www.python.org/[Python] 3.4
* http://qt.io/[Qt] 5.2.0 or newer (5.4.2 recommended)
* http://qt.io/[Qt] 5.2.0 or newer (5.5.0 recommended)
* QtWebKit
* http://www.riverbankcomputing.com/software/pyqt/intro[PyQt] 5.2.0 or newer
(5.4.2 recommended) for Python 3
(5.5.0 recommended) for Python 3
* https://pypi.python.org/pypi/setuptools/[pkg_resources/setuptools]
* http://fdik.org/pyPEG/[pyPEG2]
* http://jinja.pocoo.org/[jinja2]

View File

@ -473,10 +473,10 @@ Where to show the downloaded files.
Valid values:
* +north+
* +south+
* +top+
* +bottom+
Default: +pass:[north]+
Default: +pass:[top]+
[[ui-message-timeout]]
=== message-timeout
@ -1020,12 +1020,12 @@ The position of the tab bar.
Valid values:
* +north+
* +south+
* +east+
* +west+
* +top+
* +bottom+
* +left+
* +right+
Default: +pass:[north]+
Default: +pass:[top]+
[[tabs-show-favicons]]
=== show-favicons

View File

@ -307,6 +307,7 @@ def javascript_escape(text):
('"', r'\"'), # (note it won't hurt when we escape the wrong one).
('\n', r'\n'), # We also need to escape newlines for some reason.
('\r', r'\r'),
('\x00', r'\x00'),
)
for orig, repl in replacements:
text = text.replace(orig, repl)

View File

@ -488,6 +488,8 @@ class Completer(QObject):
"""Delete the current completion item."""
completion = objreg.get('completion', scope='window',
window=self._win_id)
if not completion.currentIndex().isValid():
raise cmdexc.CommandError("No item selected!")
try:
self.model().srcmodel.delete_cur_item(completion)
except NotImplementedError:

View File

@ -271,6 +271,20 @@ def _get_value_transformer(old, new):
return transformer
def _transform_position(val):
"""Transformer for position values."""
mapping = {
'north': 'top',
'south': 'bottom',
'west': 'left',
'east': 'right',
}
try:
return mapping[val]
except KeyError:
return val
class ConfigManager(QObject):
"""Configuration manager for qutebrowser.
@ -334,6 +348,8 @@ class ConfigManager(QObject):
CHANGED_OPTIONS = {
('content', 'cookies-accept'):
_get_value_transformer('default', 'no-3rdparty'),
('tabbar', 'position'): _transform_position,
('ui', 'downloads-position'): _transform_position,
}
changed = pyqtSignal(str, str)
@ -674,10 +690,11 @@ class ConfigManager(QObject):
else:
try:
if option.endswith('!') and value is None:
val = self.get(section_, option[:-1])
option = option[:-1]
val = self.get(section_, option)
layer = 'temp' if temp else 'conf'
if isinstance(val, bool):
self.set(layer, section_, option[:-1], str(not val))
self.set(layer, section_, option, str(not val))
else:
raise cmdexc.CommandError(
"set: Attempted inversion of non-boolean value.")

View File

@ -240,7 +240,7 @@ def data(readonly=False):
"The default zoom level."),
('downloads-position',
SettingValue(typ.VerticalPosition(), 'north'),
SettingValue(typ.VerticalPosition(), 'top'),
"Where to show the downloaded files."),
('message-timeout',
@ -501,7 +501,7 @@ def data(readonly=False):
"On which mouse button to close tabs."),
('position',
SettingValue(typ.Position(), 'north'),
SettingValue(typ.Position(), 'top'),
"The position of the tab bar."),
('show-favicons',

View File

@ -1243,13 +1243,13 @@ class Position(MappingType):
"""The position of the tab bar."""
valid_values = ValidValues('north', 'south', 'east', 'west')
valid_values = ValidValues('top', 'bottom', 'left', 'right')
MAPPING = {
'north': QTabWidget.North,
'south': QTabWidget.South,
'west': QTabWidget.West,
'east': QTabWidget.East,
'top': QTabWidget.North,
'bottom': QTabWidget.South,
'left': QTabWidget.West,
'right': QTabWidget.East,
}
@ -1257,7 +1257,7 @@ class VerticalPosition(BaseType):
"""The position of the download bar."""
valid_values = ValidValues('north', 'south')
valid_values = ValidValues('top', 'bottom')
class UrlList(List):

View File

@ -198,10 +198,10 @@ class MainWindow(QWidget):
self._vbox.removeWidget(self._downloadview)
self._vbox.removeWidget(self.status)
position = config.get('ui', 'downloads-position')
if position == 'north':
if position == 'top':
self._vbox.addWidget(self._downloadview)
self._vbox.addWidget(self.tabbed_browser)
elif position == 'south':
elif position == 'bottom':
self._vbox.addWidget(self.tabbed_browser)
self._vbox.addWidget(self._downloadview)
else:

View File

@ -25,10 +25,12 @@ from unittest import mock
import collections.abc
import operator
import itertools
import binascii
import os.path
import hypothesis
import hypothesis.strategies
from PyQt5.QtCore import QRect, QPoint
from PyQt5.QtCore import PYQT_VERSION, QRect, QPoint
from PyQt5.QtWebKit import QWebElement
import pytest
@ -513,6 +515,11 @@ class TestJavascriptEscape:
"foo'bar": r"foo\'bar",
'foo"bar': r'foo\"bar',
'one\\two\rthree\nfour\'five"six': r'one\\two\rthree\nfour\'five\"six',
'\x00': r'\x00',
'hellö': 'hellö',
'': '',
'\x80Ā': '\x80Ā',
'𐀀\x00𐀀\x00': r'𐀀\x00𐀀\x00',
}
@pytest.mark.parametrize('before, after', TESTS.items())
@ -520,21 +527,63 @@ class TestJavascriptEscape:
"""Test javascript escaping with some expected outcomes."""
assert webelem.javascript_escape(before) == after
def _test_escape(self, text, webframe):
def _test_escape(self, text, qtbot, webframe):
"""Helper function for test_real_escape*."""
try:
self._test_escape_simple(text, webframe)
except AssertionError:
# Try another method if the simple method failed.
#
# See _test_escape_hexlified documentation on why this is
# necessary.
self._test_escape_hexlified(text, qtbot, webframe)
def _test_escape_hexlified(self, text, qtbot, webframe):
"""Test conversion by hexlifying in javascript.
Since the conversion of QStrings to Python strings is broken in some
older PyQt versions in some corner cases, we load a HTML file which
generates an MD5 of the escaped text and use that for comparisons.
"""
escaped = webelem.javascript_escape(text)
webframe.evaluateJavaScript('window.foo = "{}";'.format(escaped))
assert webframe.evaluateJavaScript('window.foo') == text
path = os.path.join(os.path.dirname(__file__),
'test_webelem_jsescape.html')
with open(path, encoding='utf-8') as f:
html_source = f.read().replace('%INPUT%', escaped)
with qtbot.waitSignal(webframe.loadFinished, raising=True):
webframe.setHtml(html_source)
result = webframe.evaluateJavaScript('window.qute_test_result')
assert result is not None
assert '|' in result
result_md5, result_text = result.split('|', maxsplit=1)
text_md5 = binascii.hexlify(text.encode('utf-8')).decode('ascii')
assert result_md5 == text_md5, result_text
def _test_escape_simple(self, text, webframe):
"""Test conversion by using evaluateJavaScript."""
escaped = webelem.javascript_escape(text)
result = webframe.evaluateJavaScript('"{}";'.format(escaped))
assert result == text
@pytest.mark.parametrize('text', TESTS)
def test_real_escape(self, webframe, text):
def test_real_escape(self, webframe, qtbot, text):
"""Test javascript escaping with a real QWebPage."""
self._test_escape(text, webframe)
self._test_escape(text, qtbot, webframe)
@hypothesis.given(hypothesis.strategies.text())
def test_real_escape_hypothesis(self, webframe, text):
def test_real_escape_hypothesis(self, webframe, qtbot, text):
"""Test javascript escaping with a real QWebPage and hypothesis."""
self._test_escape(text, webframe)
# We can't simply use self._test_escape because of this:
# https://github.com/pytest-dev/pytest-qt/issues/69
# self._test_escape(text, qtbot, webframe)
try:
self._test_escape_simple(text, webframe)
except AssertionError:
if PYQT_VERSION >= 0x050300:
self._test_escape_hexlified(text, qtbot, webframe)
class TestGetChildFrames:

View File

@ -0,0 +1,68 @@
<!--
Helper file for test_javascript_escape() in test_webelem.py.
Since the conversion from QStrings to Python strings is broken in some corner
cases in PyQt < 5.4 we hexlify the string we got in javascript here and test
that in the test.
-->
<html>
<head>
<script type="text/javascript">
//<![CDATA[
/*
* hexlify() and str2rstr_utf8() are based on:
*
* JavaScript MD5 1.0.1
* https://github.com/blueimp/JavaScript-MD5
*
* Copyright 2011, Sebastian Tschan
* https://blueimp.net
*
* Licensed under the MIT license:
* http://www.opensource.org/licenses/MIT
*
* Based on
* A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
* Digest Algorithm, as defined in RFC 1321.
* Version 2.2 Copyright (C) Paul Johnston 1999 - 2009
* Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
* Distributed under the BSD License
* See http://pajhome.org.uk/crypt/md5 for more info.
*/
'use strict';
function hexlify(input) {
var hex_tab = '0123456789abcdef';
var output = '';
var x;
var i;
for (i = 0; i < input.length; i += 1) {
x = input.charCodeAt(i);
output += hex_tab.charAt((x >>> 4) & 0x0F) + hex_tab.charAt(x & 0x0F);
}
return output;
}
function encode_utf8(input) {
return unescape(encodeURIComponent(input));
}
function set_text() {
var elems = document.getElementsByTagName("p");
var hexlified = hexlify(encode_utf8("%INPUT%"));
var result = hexlified + "|" + "%INPUT%";
elems[0].innerHTML = result
window.qute_test_result = result;
}
//]]>
</script>
</head>
<body onload="set_text()">
<p>set_text() not called...</p>
</html>