Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
9a85b66452
@ -32,6 +32,8 @@ Added
|
|||||||
- New setting `tabs -> show` which supersedes the old `tabs -> hide-*` options
|
- New setting `tabs -> show` which supersedes the old `tabs -> hide-*` options
|
||||||
and has an additional `switching` option which shows tab while switching
|
and has an additional `switching` option which shows tab while switching
|
||||||
them. There's also a new `show-switching` option to configure the timeout.
|
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
|
Changed
|
||||||
~~~~~~~
|
~~~~~~~
|
||||||
@ -48,7 +50,12 @@ Fixed
|
|||||||
~~~~~
|
~~~~~
|
||||||
|
|
||||||
- `link_pyqt.py` now should work better on untested distributions.
|
- `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
|
Removed
|
||||||
~~~~~~~
|
~~~~~~~
|
||||||
|
@ -89,10 +89,10 @@ Requirements
|
|||||||
The following software and libraries are required to run qutebrowser:
|
The following software and libraries are required to run qutebrowser:
|
||||||
|
|
||||||
* http://www.python.org/[Python] 3.4
|
* 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
|
* QtWebKit
|
||||||
* http://www.riverbankcomputing.com/software/pyqt/intro[PyQt] 5.2.0 or newer
|
* 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]
|
* https://pypi.python.org/pypi/setuptools/[pkg_resources/setuptools]
|
||||||
* http://fdik.org/pyPEG/[pyPEG2]
|
* http://fdik.org/pyPEG/[pyPEG2]
|
||||||
* http://jinja.pocoo.org/[jinja2]
|
* http://jinja.pocoo.org/[jinja2]
|
||||||
|
@ -473,10 +473,10 @@ Where to show the downloaded files.
|
|||||||
|
|
||||||
Valid values:
|
Valid values:
|
||||||
|
|
||||||
* +north+
|
* +top+
|
||||||
* +south+
|
* +bottom+
|
||||||
|
|
||||||
Default: +pass:[north]+
|
Default: +pass:[top]+
|
||||||
|
|
||||||
[[ui-message-timeout]]
|
[[ui-message-timeout]]
|
||||||
=== message-timeout
|
=== message-timeout
|
||||||
@ -1020,12 +1020,12 @@ The position of the tab bar.
|
|||||||
|
|
||||||
Valid values:
|
Valid values:
|
||||||
|
|
||||||
* +north+
|
* +top+
|
||||||
* +south+
|
* +bottom+
|
||||||
* +east+
|
* +left+
|
||||||
* +west+
|
* +right+
|
||||||
|
|
||||||
Default: +pass:[north]+
|
Default: +pass:[top]+
|
||||||
|
|
||||||
[[tabs-show-favicons]]
|
[[tabs-show-favicons]]
|
||||||
=== show-favicons
|
=== show-favicons
|
||||||
|
@ -307,6 +307,7 @@ def javascript_escape(text):
|
|||||||
('"', r'\"'), # (note it won't hurt when we escape the wrong one).
|
('"', r'\"'), # (note it won't hurt when we escape the wrong one).
|
||||||
('\n', r'\n'), # We also need to escape newlines for some reason.
|
('\n', r'\n'), # We also need to escape newlines for some reason.
|
||||||
('\r', r'\r'),
|
('\r', r'\r'),
|
||||||
|
('\x00', r'\x00'),
|
||||||
)
|
)
|
||||||
for orig, repl in replacements:
|
for orig, repl in replacements:
|
||||||
text = text.replace(orig, repl)
|
text = text.replace(orig, repl)
|
||||||
|
@ -488,6 +488,8 @@ class Completer(QObject):
|
|||||||
"""Delete the current completion item."""
|
"""Delete the current completion item."""
|
||||||
completion = objreg.get('completion', scope='window',
|
completion = objreg.get('completion', scope='window',
|
||||||
window=self._win_id)
|
window=self._win_id)
|
||||||
|
if not completion.currentIndex().isValid():
|
||||||
|
raise cmdexc.CommandError("No item selected!")
|
||||||
try:
|
try:
|
||||||
self.model().srcmodel.delete_cur_item(completion)
|
self.model().srcmodel.delete_cur_item(completion)
|
||||||
except NotImplementedError:
|
except NotImplementedError:
|
||||||
|
@ -271,6 +271,20 @@ def _get_value_transformer(old, new):
|
|||||||
return transformer
|
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):
|
class ConfigManager(QObject):
|
||||||
|
|
||||||
"""Configuration manager for qutebrowser.
|
"""Configuration manager for qutebrowser.
|
||||||
@ -334,6 +348,8 @@ class ConfigManager(QObject):
|
|||||||
CHANGED_OPTIONS = {
|
CHANGED_OPTIONS = {
|
||||||
('content', 'cookies-accept'):
|
('content', 'cookies-accept'):
|
||||||
_get_value_transformer('default', 'no-3rdparty'),
|
_get_value_transformer('default', 'no-3rdparty'),
|
||||||
|
('tabbar', 'position'): _transform_position,
|
||||||
|
('ui', 'downloads-position'): _transform_position,
|
||||||
}
|
}
|
||||||
|
|
||||||
changed = pyqtSignal(str, str)
|
changed = pyqtSignal(str, str)
|
||||||
@ -674,10 +690,11 @@ class ConfigManager(QObject):
|
|||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
if option.endswith('!') and value is None:
|
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'
|
layer = 'temp' if temp else 'conf'
|
||||||
if isinstance(val, bool):
|
if isinstance(val, bool):
|
||||||
self.set(layer, section_, option[:-1], str(not val))
|
self.set(layer, section_, option, str(not val))
|
||||||
else:
|
else:
|
||||||
raise cmdexc.CommandError(
|
raise cmdexc.CommandError(
|
||||||
"set: Attempted inversion of non-boolean value.")
|
"set: Attempted inversion of non-boolean value.")
|
||||||
|
@ -240,7 +240,7 @@ def data(readonly=False):
|
|||||||
"The default zoom level."),
|
"The default zoom level."),
|
||||||
|
|
||||||
('downloads-position',
|
('downloads-position',
|
||||||
SettingValue(typ.VerticalPosition(), 'north'),
|
SettingValue(typ.VerticalPosition(), 'top'),
|
||||||
"Where to show the downloaded files."),
|
"Where to show the downloaded files."),
|
||||||
|
|
||||||
('message-timeout',
|
('message-timeout',
|
||||||
@ -501,7 +501,7 @@ def data(readonly=False):
|
|||||||
"On which mouse button to close tabs."),
|
"On which mouse button to close tabs."),
|
||||||
|
|
||||||
('position',
|
('position',
|
||||||
SettingValue(typ.Position(), 'north'),
|
SettingValue(typ.Position(), 'top'),
|
||||||
"The position of the tab bar."),
|
"The position of the tab bar."),
|
||||||
|
|
||||||
('show-favicons',
|
('show-favicons',
|
||||||
|
@ -1243,13 +1243,13 @@ class Position(MappingType):
|
|||||||
|
|
||||||
"""The position of the tab bar."""
|
"""The position of the tab bar."""
|
||||||
|
|
||||||
valid_values = ValidValues('north', 'south', 'east', 'west')
|
valid_values = ValidValues('top', 'bottom', 'left', 'right')
|
||||||
|
|
||||||
MAPPING = {
|
MAPPING = {
|
||||||
'north': QTabWidget.North,
|
'top': QTabWidget.North,
|
||||||
'south': QTabWidget.South,
|
'bottom': QTabWidget.South,
|
||||||
'west': QTabWidget.West,
|
'left': QTabWidget.West,
|
||||||
'east': QTabWidget.East,
|
'right': QTabWidget.East,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -1257,7 +1257,7 @@ class VerticalPosition(BaseType):
|
|||||||
|
|
||||||
"""The position of the download bar."""
|
"""The position of the download bar."""
|
||||||
|
|
||||||
valid_values = ValidValues('north', 'south')
|
valid_values = ValidValues('top', 'bottom')
|
||||||
|
|
||||||
|
|
||||||
class UrlList(List):
|
class UrlList(List):
|
||||||
|
@ -198,10 +198,10 @@ class MainWindow(QWidget):
|
|||||||
self._vbox.removeWidget(self._downloadview)
|
self._vbox.removeWidget(self._downloadview)
|
||||||
self._vbox.removeWidget(self.status)
|
self._vbox.removeWidget(self.status)
|
||||||
position = config.get('ui', 'downloads-position')
|
position = config.get('ui', 'downloads-position')
|
||||||
if position == 'north':
|
if position == 'top':
|
||||||
self._vbox.addWidget(self._downloadview)
|
self._vbox.addWidget(self._downloadview)
|
||||||
self._vbox.addWidget(self.tabbed_browser)
|
self._vbox.addWidget(self.tabbed_browser)
|
||||||
elif position == 'south':
|
elif position == 'bottom':
|
||||||
self._vbox.addWidget(self.tabbed_browser)
|
self._vbox.addWidget(self.tabbed_browser)
|
||||||
self._vbox.addWidget(self._downloadview)
|
self._vbox.addWidget(self._downloadview)
|
||||||
else:
|
else:
|
||||||
|
@ -25,10 +25,12 @@ from unittest import mock
|
|||||||
import collections.abc
|
import collections.abc
|
||||||
import operator
|
import operator
|
||||||
import itertools
|
import itertools
|
||||||
|
import binascii
|
||||||
|
import os.path
|
||||||
|
|
||||||
import hypothesis
|
import hypothesis
|
||||||
import hypothesis.strategies
|
import hypothesis.strategies
|
||||||
from PyQt5.QtCore import QRect, QPoint
|
from PyQt5.QtCore import PYQT_VERSION, QRect, QPoint
|
||||||
from PyQt5.QtWebKit import QWebElement
|
from PyQt5.QtWebKit import QWebElement
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
@ -513,6 +515,11 @@ class TestJavascriptEscape:
|
|||||||
"foo'bar": r"foo\'bar",
|
"foo'bar": r"foo\'bar",
|
||||||
'foo"bar': r'foo\"bar',
|
'foo"bar': r'foo\"bar',
|
||||||
'one\\two\rthree\nfour\'five"six': r'one\\two\rthree\nfour\'five\"six',
|
'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())
|
@pytest.mark.parametrize('before, after', TESTS.items())
|
||||||
@ -520,21 +527,63 @@ class TestJavascriptEscape:
|
|||||||
"""Test javascript escaping with some expected outcomes."""
|
"""Test javascript escaping with some expected outcomes."""
|
||||||
assert webelem.javascript_escape(before) == after
|
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*."""
|
"""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)
|
escaped = webelem.javascript_escape(text)
|
||||||
webframe.evaluateJavaScript('window.foo = "{}";'.format(escaped))
|
path = os.path.join(os.path.dirname(__file__),
|
||||||
assert webframe.evaluateJavaScript('window.foo') == text
|
'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)
|
@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."""
|
"""Test javascript escaping with a real QWebPage."""
|
||||||
self._test_escape(text, webframe)
|
self._test_escape(text, qtbot, webframe)
|
||||||
|
|
||||||
@hypothesis.given(hypothesis.strategies.text())
|
@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."""
|
"""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:
|
class TestGetChildFrames:
|
||||||
|
68
tests/browser/test_webelem_jsescape.html
Normal file
68
tests/browser/test_webelem_jsescape.html
Normal 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>
|
Loading…
Reference in New Issue
Block a user