Compare commits

...

43 Commits
nsis ... v0.8.3

Author SHA1 Message Date
Florian Bruhin
17cdf2eab6 Release v0.8.3 2016-11-05 23:06:56 +01:00
Florian Bruhin
38f7f63689 Update changelog for v0.8.3 2016-11-05 23:02:49 +01:00
Daniel Schadt
3b0e7a07f3 pdfjs: compatibility for v1.6.210
They renamed PDFView to PDFViewerApplication, which we need to account
for in our pdfjs scripts.

Also, it seems like the actual viewer is now only created when the DOM
has been loaded. This means that at the time when our script is
executed, the viewer does not yet exist. Thus we need to delay the open
request too by registering a DOMContentLoaded handler.
2016-11-05 22:41:10 +01:00
arza
5f3922d1d6 Fix sk-keybinding. 2016-11-05 22:20:33 +01:00
Rok Mandeljc
6cd159246c TabWidget: a possible fix for #1693 - grey area under custom tabbar
Attempt to fix the issue #1693 by:
- setting the TabBarStyle to TabWidget in addition to TabBar
- chain up SE_TabWidgetTabBar requests in TabBarStyle.subElementRect
  to the super() rather than self._style, in order to avoid getting
  adwaita-specific rect sizes instead of default ones that are also
  used in rendering.
2016-11-05 22:20:10 +01:00
Michael Hoang
552198ac31 Give error when trying to detach a single tab 2016-11-05 22:19:17 +01:00
Ryan Roden-Corrent
1222394efd Update command completion on setting an alias.
Wire up the config change event to update command completion on
changing aliases, so the new aliases will be included.

Fixes #1814.

Currently we do not have tests at a high enough level to test whether
signals are wired up correctly to update completions.
2016-11-05 22:18:56 +01:00
Ryan Roden-Corrent
a7ff4075de Allow binding to an alias.
Fix #1813: Cannot :bind to alias
2016-11-05 22:17:43 +01:00
Florian Bruhin
a876f4a199 Fix handling of typing.Union with newer Python 3.5 versions 2016-11-05 22:13:03 +01:00
Florian Bruhin
661945c61b Fix :restart deleting the given --basedir 2016-08-16 21:28:18 +02:00
Florian Bruhin
496e5a38fa Add "# pragma: no branch" 2016-08-16 11:55:07 +02:00
Florian Bruhin
a43aa52c81 Fix #1641 for v0.8.x
This is the equivalent of 56515321dd
2016-08-16 11:15:32 +02:00
Daniel Schadt
4abbe50289 tests: fix tests for misc.editor
Due to the change to NamedTemporaryFile, the _filename attribute was
removed. We now have to use _file.name.
2016-08-07 11:21:33 +02:00
Daniel Schadt
363d4f4654 os.unlink -> os.remove
The functions do the same, but remove sounds better.
2016-08-07 11:21:33 +02:00
Daniel Schadt
34e184ebf5 editor: fix external editor on windows
On windows, only one process can open a file in write mode at once. We
didn't close the handle we got (self._oshandle) before _cleanup, which
means that we had the file open the whole time, which means that the
external editor couldn't write back the changes.

This patch closes the file while the external editor is running and only
opens it once the editor is closed. We re-opened the file anyway, so
this shouldn't be a huge change. Additionally, tempfile.NamedTemporaryFile
is used instead of mkstemp, as we don't have to deal with os-level file
handles that way.
2016-08-07 11:21:33 +02:00
Florian Bruhin
17840d06da keyconfig: Validate commands *after* transforming
Otherwise we'd still get an error when e.g. transforming :yank-selected
to :yank selection as :yank-selected got removed.
2016-08-07 11:21:33 +02:00
Florian Bruhin
e9e2adb9a7 Fix crash when doing :<space><enter>
Introduced in #1577.
Fixes #1773.
2016-08-07 11:21:05 +02:00
Florian Bruhin
113675a0b5 Release v0.8.2 2016-08-02 18:33:59 +02:00
Florian Bruhin
c4d8a767f9 Update changelog for 0.8.2 2016-08-02 18:33:36 +02:00
Florian Bruhin
de3867fe95 Bump up filename length limit to 50
The usual limit seems to be 255 bytes, so even when assuming 5-byte
UTF-8 chars for every letter, 50 should be fine.

http://serverfault.com/questions/9546/filename-length-limits-on-linux/9548#9548
2016-08-02 16:17:05 +02:00
Daniel Schadt
6ac3940264 open-download: don't crash on download cancel
Fixes #1728.
2016-08-02 16:17:05 +02:00
Daniel Schadt
5e9eafd5a3 open-download: force encoding for filename
Fixes #1726.
2016-08-02 16:17:05 +02:00
Daniel Schadt
5d4b9e815c open-download: make sure the name is not too long
Fixes #1725.

Make sure that the temporary filename is not too long by restricting the
suggested part to 20 characters.
2016-08-02 16:17:05 +02:00
Daniel Schadt
3eba7fc314 downloads: don't crash on OSError in open-download
Fixes the crash in #1725, but does not provide a solution. The browser
won't crash, but the file won't be downloaded and opened either.
2016-08-02 16:17:05 +02:00
Florian Bruhin
11d7486f97 freeze.py: Copy plugin folders on Windows
This makes HTML5 video work.
Fixes #1068.
2016-08-02 15:56:40 +02:00
Florian Bruhin
8e7a1d3d97 Use HTML content for localstorage test
JS logging is disabled by QtWebKit in private browsing mode
2016-08-02 15:18:58 +02:00
Florian Bruhin
98704c0471 Fix deleting of quickmarks with ctrl-d 2016-08-02 14:58:12 +02:00
Florian Bruhin
c2bf595b79 Don't use QSignalSpy in IPC test
Fixes #1727.

For another testcase in the same file we still need to use it until
pytest-qt has a MultiSignalBlocker.args.
2016-08-02 14:26:11 +02:00
Florian Bruhin
f73f3a2001 Tunnel private-browsing to QtWebKit correctly 2016-08-02 14:24:34 +02:00
Florian Bruhin
afde5bbc79 Fix using a relative path with --basedir 2016-08-02 14:23:38 +02:00
Florian Bruhin
66a76a4504 travis: Remove testing on Ubuntu Wily 2016-08-02 11:20:12 +02:00
Florian Bruhin
feb73f06c5 Fix ;o/;O default bindings 2016-08-02 10:52:45 +02:00
Florian Bruhin
e32fbe9013 QtWebEngine: Fix crash when closing/reopening tabs 2016-08-02 10:52:24 +02:00
Florian Bruhin
776a16bf65 Fix crash when opening http://foo%40bar@baz 2016-08-02 10:51:54 +02:00
Florian Bruhin
225c860452 Fix <input /> test in test_webelem 2016-08-02 10:48:18 +02:00
Florian Bruhin
f982402526 Consider input elements without type for hinting 2016-08-02 10:46:23 +02:00
Florian Bruhin
6a6e7ecb38 travis: Switch bot to #qutebrowser-dev 2016-07-27 13:05:09 +02:00
Florian Bruhin
c94ed93f13 build_release: Fix call_tox with no python on Win 2016-07-27 12:36:43 +02:00
Florian Bruhin
95d1721f01 Release v0.8.1 2016-07-27 12:31:30 +02:00
Florian Bruhin
410be07f54 Hide harfbuzz warning if frozen 2016-07-27 12:15:37 +02:00
Florian Bruhin
01de52c23a Improve error message on OS X without QtWebEngine 2016-07-27 12:13:45 +02:00
Florian Bruhin
a84807ed05 Handle empty command in CommandRunner.parse_all
Sicne we now call self._get_alias there, we also need to make sure it's
not an empty string before that.

Introduced in #1577. Fixes #1690.
2016-07-27 11:23:30 +02:00
Florian Bruhin
2795ae9478 Update build scripts from master 2016-07-26 17:01:08 +02:00
38 changed files with 351 additions and 142 deletions

View File

@@ -12,9 +12,6 @@ matrix:
- os: linux
env: DOCKER=archlinux
services: docker
- os: linux
env: DOCKER=ubuntu-wily
services: docker
- os: linux
env: DOCKER=ubuntu-xenial
services: docker
@@ -63,8 +60,8 @@ notifications:
- https://buildtimetrend.herokuapp.com/travis
irc:
channels:
- "chat.freenode.net#qutebrowser"
on_success: change
- "chat.freenode.net#qutebrowser-dev"
on_success: always
on_failure: always
skip_join: true
template:

View File

@@ -14,6 +14,60 @@ 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.8.3
------
Fixed
~~~~~
- Fixed crash when doing `:<space><enter>`, another corner-case introduced in v0.8.0
- Fixed `:open-editor` (`<Ctrl-e>`) on Windows
- Fixed crash when setting `general -> auto-save-interval` to a too big value.
- Fixed crash when using hints on Void Linux.
- Fixed compatibility with Python 3.5.2+ on Debian unstable
- Compatibility with pdfjs v1.6.210
- `:bind` can now be used to bind to an alias (binding by editing `keys.conf`
already worked before)
- The command completion now updates correctly when changing aliases
- The tabbar now displays correctly with the Adwaita Qt theme
- The default `sk` keybinding now sets the commandline to `:bind` correctly
- Fixed crash when closing a window without focusing it
- Userscripts now can access QUTE_FIFO correctly on Windows
v0.8.2
------
Fixed
~~~~~
- Fixed `general -> private-browsing` not being set correctly until a restart
(which caused e.g. local storage to be enabled).
- When hinting input fields (`:t`), also consider input elements without a type.
- Fixed crash when opening an invalid URL with a percent-encoded and a real @ in it
- Fixed default `;o` and `;O` bindings
- Fixed local storage not working (and possible other bugs) when using a
relative path with `--basedir`.
- Fixed crash when deleting a quickmark with Ctrl-D
- Fixed HTML5 video playback on Windows
- Fixed crash when using `:prompt-open-download` with a file with chars not
encodable with the OS' filesystem encoding (e.g. with `LC_ALL=C`)
- Fixed `:prompt-open-download` with a too long filename (> 255 bytes)
- Fixed crash when cancelling a download after doing `:prompt-open-download`
- Fixed crash when writing a download to disk fails with
`:prompt-open-download`.
- Fixed `:restart` deleting the basedir when it was given with `--basedir`.
v0.8.1
------
Fixed
~~~~~
- Fix crash when pressing enter without a command
- Adjust error message to point out QtWebEngine is unsupported with the OS
X .app currently.
- Hide Harfbuzz warning with the OS X .app
v0.8.0
------
@@ -33,7 +87,7 @@ Added
`$QUTE_DOWNLOAD_DIR` available for userscripts.
- New option `ui` -> `status-position` to configure the position of the
status bar (top/bottom).
- New `--pdf <filename>` argument for `:print` which can be used to generate a
- New `--pdf <filename>` argument for `:print` WHICH can be used to generate a
PDF without a dialog.
Changed
@@ -108,7 +162,6 @@ Changed
- `:navigate` now clears the URL fragment
- `:completion-item-del` (`Ctrl-D`) can now be used in `:buffer` completion to
close a tab
- Counts can now be used with special keybindings (e.g. with modifiers)
- Various SSL ciphers are now disabled by default. With recent Qt/OpenSSL
versions those already all are disabled, but with older versions they might
not be.

View File

@@ -1,37 +0,0 @@
FROM ubuntu:wily
MAINTAINER Florian Bruhin <me@the-compiler.org>
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get -y update && \
apt-get -y dist-upgrade && \
apt-get -y install --no-install-recommends \
python3-pyqt5 \
python3-pyqt5.qtwebkit \
python-tox \
python3-sip \
xvfb \
git \
python3-setuptools \
wget \
herbstluftwm \
language-pack-en \
libjs-pdf \
dbus
RUN dbus-uuidgen --ensure
RUN useradd user && mkdir /home/user && chown -R user:users /home/user
USER user
WORKDIR /home/user
ENV DISPLAY=:0
ENV LC_ALL=en_US.UTF-8
ENV LANG=en_US.UTF-8
CMD Xvfb -screen 0 800x600x24 :0 & \
sleep 2 && \
herbstluftwm & \
git clone /outside qutebrowser.git && \
cd qutebrowser.git && \
tox -e py34

View File

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

View File

@@ -551,8 +551,9 @@ class Quitter:
argdict['session'] = session
argdict['override_restore'] = False
# Ensure :restart works with --temp-basedir
argdict['temp_basedir'] = False
argdict['temp_basedir_restarted'] = True
if self._args.temp_basedir:
argdict['temp_basedir'] = False
argdict['temp_basedir_restarted'] = True
# Dump the data
data = json.dumps(argdict)

View File

@@ -377,6 +377,8 @@ class CommandDispatcher:
@cmdutils.register(instance='command-dispatcher', scope='window')
def tab_detach(self):
"""Detach the current tab to its own window."""
if self._count() < 2:
raise cmdexc.CommandError("Cannot detach one tab.")
url = self._current_url()
self._open(url, window=True)
cur_widget = self._current_widget()

View File

@@ -63,8 +63,10 @@ def _generate_pdfjs_script(url):
url: The url of the pdf page as QUrl.
"""
return (
'PDFJS.verbosity = PDFJS.VERBOSITY_LEVELS.info;\n'
'PDFView.open("{url}");\n'
'document.addEventListener("DOMContentLoaded", function() {{\n'
' PDFJS.verbosity = PDFJS.VERBOSITY_LEVELS.info;\n'
' (window.PDFView || window.PDFViewerApplication).open("{url}");\n'
'}});\n'
).format(url=webelem.javascript_escape(url.toString(QUrl.FullyEncoded)))

View File

@@ -211,7 +211,11 @@ class WebEngineScroller(browsertab.AbstractScroller):
"""Update the scroll position attributes when it changed."""
def update_scroll_pos(jsret):
"""Callback after getting scroll position via JS."""
assert isinstance(jsret, dict)
if jsret is None:
# This can happen when the callback would get called after
# shutting down a tab
return
assert isinstance(jsret, dict), jsret
self._pos_perc = (jsret['perc']['x'], jsret['perc']['y'])
self._pos_px = QPoint(jsret['px']['x'], jsret['px']['y'])
self.perc_changed.emit(*self._pos_perc)

View File

@@ -947,13 +947,28 @@ class DownloadManager(QAbstractListModel):
download.set_filename(target.filename)
elif isinstance(target, usertypes.OpenFileDownloadTarget):
tmp_manager = objreg.get('temporary-downloads')
fobj = tmp_manager.get_tmpfile(suggested_filename)
download.finished.connect(download.open_file)
try:
fobj = tmp_manager.get_tmpfile(suggested_filename)
except OSError as exc:
msg = "Download error: {}".format(exc)
message.error(self._win_id, msg)
download.cancel()
return
download.finished.connect(
functools.partial(self._open_download, download))
download.autoclose = True
download.set_fileobj(fobj)
else:
log.downloads.error("Unknown download target: {}".format(target))
def _open_download(self, download):
"""Open the given download but only if it was successful."""
if download.successful:
download.open_file()
else:
log.downloads.debug("{} finished but not successful, not opening!"
.format(download))
def raise_no_download(self, count):
"""Raise an exception that the download doesn't exist.
@@ -1316,6 +1331,11 @@ class TempDownloadManager(QObject):
A tempfile.NamedTemporaryFile that should be used to save the file.
"""
tmpdir = self._get_tmpdir()
encoding = sys.getfilesystemencoding()
suggested_name = utils.force_encoding(suggested_name, encoding)
# Make sure that the filename is not too long
if len(suggested_name) > 50:
suggested_name = suggested_name[:25] + '...' + suggested_name[-25:]
fobj = tempfile.NamedTemporaryFile(dir=tmpdir.name, delete=False,
suffix=suggested_name)
self.files.append(fobj)

View File

@@ -295,6 +295,10 @@ class WebHistory(QObject):
"""
if config.get('general', 'private-browsing'):
return
if not url.isValid():
log.misc.warning("Ignoring invalid URL being added to history")
return
if atime is None:
atime = time.time()
entry = Entry(atime, url, title, redirect=redirect)

View File

@@ -52,7 +52,8 @@ SELECTORS = {
Group.focus: '*:focus',
Group.inputs: ('input[type=text], input[type=email], input[type=url], '
'input[type=tel], input[type=number], '
'input[type=password], input[type=search], textarea'),
'input[type=password], input[type=search], '
'input:not([type]), textarea'),
}
@@ -398,26 +399,28 @@ def rect_on_view(elem, *, elem_geometry=None, adjust_zoom=True, no_js=False):
text = utils.compact_text(elem.toOuterXml(), 500)
log.hints.vdebug("Client rectangles of element '{}': {}".format(text,
rects))
for i in range(int(rects.get("length", 0))):
rect = rects[str(i)]
width = rect.get("width", 0)
height = rect.get("height", 0)
if width > 1 and height > 1:
# fix coordinates according to zoom level
zoom = elem.webFrame().zoomFactor()
if not config.get('ui', 'zoom-text-only') and adjust_zoom:
rect["left"] *= zoom
rect["top"] *= zoom
width *= zoom
height *= zoom
rect = QRect(rect["left"], rect["top"], width, height)
frame = elem.webFrame()
while frame is not None:
# Translate to parent frames' position
# (scroll position is taken care of inside getClientRects)
rect.translate(frame.geometry().topLeft())
frame = frame.parentFrame()
return rect
if rects is not None: # pragma: no branch
for i in range(int(rects.get("length", 0))):
rect = rects[str(i)]
width = rect.get("width", 0)
height = rect.get("height", 0)
if width > 1 and height > 1:
# fix coordinates according to zoom level
zoom = elem.webFrame().zoomFactor()
if not config.get('ui', 'zoom-text-only') and adjust_zoom:
rect["left"] *= zoom
rect["top"] *= zoom
width *= zoom
height *= zoom
rect = QRect(rect["left"], rect["top"], width, height)
frame = elem.webFrame()
while frame is not None:
# Translate to parent frames' position
# (scroll position is taken care of inside
# getClientRects)
rect.translate(frame.geometry().topLeft())
frame = frame.parentFrame()
return rect
# No suitable rects found via JS, try via the QWebElement API
if elem_geometry is None:

View File

@@ -419,9 +419,16 @@ class Command:
if isinstance(typ, tuple):
raise TypeError("{}: Legacy tuple type annotation!".format(
self.name))
elif issubclass(typ, typing.Union):
elif type(typ) is type(typing.Union): # flake8: disable=E721
# this is... slightly evil, I know
types = list(typ.__union_params__)
# We also can't use isinstance here because typing.Union doesn't
# support that.
# pylint: disable=no-member,useless-suppression
try:
types = list(typ.__union_params__)
except AttributeError:
types = list(typ.__args__)
# pylint: enable=no-member,useless-suppression
if param.default is not inspect.Parameter.empty:
types.append(type(param.default))
choices = self.get_arg_info(param).choices

View File

@@ -119,6 +119,9 @@ class CommandRunner(QObject):
Yields:
ParseResult tuples.
"""
if not text.strip():
raise cmdexc.NoSuchCommandError("No command given")
if aliases:
text = self._get_alias(text, text)

View File

@@ -30,7 +30,7 @@ import functools
from qutebrowser.completion.models import (miscmodels, urlmodel, configmodel,
base)
from qutebrowser.utils import objreg, usertypes, log, debug
from qutebrowser.config import configdata
from qutebrowser.config import configdata, config
_instances = {}
@@ -164,6 +164,12 @@ def update(completions):
did_run.append(func)
@config.change_filter('aliases', function=True)
def _update_aliases():
"""Update completions that include command aliases."""
update([usertypes.Completion.command])
def init():
"""Initialize completions. Note this only connects signals."""
quickmark_manager = objreg.get('quickmark-manager')
@@ -185,3 +191,5 @@ def init():
keyconf = objreg.get('key-config')
keyconf.changed.connect(
functools.partial(update, [usertypes.Completion.command]))
objreg.get('config').changed.connect(_update_aliases)

View File

@@ -192,4 +192,4 @@ class UrlCompletionModel(base.BaseCompletionModel):
sibling = index.sibling(index.row(), self.TEXT_COLUMN)
qtutils.ensure_valid(sibling)
name = sibling.data()
quickmark_manager.quickmark_del(name)
quickmark_manager.delete(name)

View File

@@ -1458,8 +1458,8 @@ KEY_DATA = collections.OrderedDict([
('hint all hover', [';h']),
('hint images', [';i']),
('hint images tab', [';I']),
('hint links fill ":open {hint-url}"', [';o']),
('hint links fill ":open -t {hint-url}"', [';O']),
('hint links fill :open {hint-url}', [';o']),
('hint links fill :open -t {hint-url}', [';O']),
('hint links yank', [';y']),
('hint links yank-primary', [';Y']),
('hint --rapid links tab-bg', [';r']),
@@ -1504,7 +1504,7 @@ KEY_DATA = collections.OrderedDict([
('save', ['sf']),
('set-cmd-text -s :set', ['ss']),
('set-cmd-text -s :set -t', ['sl']),
('set-cmd-text -s :set keybind', ['sk']),
('set-cmd-text -s :bind', ['sk']),
('zoom-out', ['-']),
('zoom-in', ['+']),
('zoom', ['=']),
@@ -1649,4 +1649,6 @@ CHANGED_KEY_COMMANDS = [
(re.compile(r'^leave-mode$'), r'clear-keychain ;; leave-mode'),
(re.compile(r'^download-remove --all$'), r'download-clear'),
(re.compile(r'^hint links fill "([^"]*)"$'), r'hint links fill \1'),
]

View File

@@ -25,7 +25,7 @@ import itertools
from PyQt5.QtCore import pyqtSignal, QObject
from qutebrowser.config import configdata, textwrapper
from qutebrowser.config import configdata, textwrapper, config
from qutebrowser.commands import cmdutils, cmdexc
from qutebrowser.utils import log, utils, qtutils, message, usertypes
@@ -352,7 +352,8 @@ class KeyConfigParser(QObject):
line))
commands = [c.split(maxsplit=1)[0].strip() for c in commands]
for cmd in commands:
if cmd not in cmdutils.cmd_dict:
aliases = config.section('aliases')
if cmd not in cmdutils.cmd_dict and cmd not in aliases:
raise KeyConfigError("Invalid command '{}'!".format(cmd))
def _read_command(self, line):
@@ -361,12 +362,12 @@ class KeyConfigParser(QObject):
raise KeyConfigError("Got command '{}' without getting a "
"section!".format(line))
else:
self._validate_command(line)
for rgx, repl in configdata.CHANGED_KEY_COMMANDS:
if rgx.match(line):
line = rgx.sub(repl, line)
self._mark_config_dirty()
break
self._validate_command(line)
self._cur_command = line
def _read_keybinding(self, line):

View File

@@ -433,10 +433,10 @@ def update_settings(section, option):
QWebSettings.setIconDatabasePath('')
else:
QWebSettings.setIconDatabasePath(cache_path)
else:
try:
mapping = MAPPINGS[section][option]
except KeyError:
return
value = config.get(section, option)
mapping.set(value)
try:
mapping = MAPPINGS[section][option]
except KeyError:
return
value = config.get(section, option)
mapping.set(value)

View File

@@ -50,6 +50,7 @@ class TabWidget(QTabWidget):
def __init__(self, win_id, parent=None):
super().__init__(parent)
bar = TabBar(win_id)
self.setStyle(TabBarStyle(self.style()))
self.setTabBar(bar)
bar.tabCloseRequested.connect(self.tabCloseRequested)
bar.tabMoved.connect(functools.partial(
@@ -652,6 +653,12 @@ class TabBarStyle(QCommonStyle):
if sr == QStyle.SE_TabBarTabText:
layouts = self._tab_layout(opt)
return layouts.text
elif sr == QStyle.SE_TabWidgetTabBar:
# Need to use super() because we also use super() to render
# element in drawControl(); otherwise, we may get bit by
# style differences...
rct = super().subElementRect(sr, opt, widget)
return rct
else:
return self._style.subElementRect(sr, opt, widget)

View File

@@ -59,9 +59,13 @@ def _missing_str(name, *, windows=None, pip=None, webengine=False):
'distributions packages, or install it via pip.'.format(name)]
blocks.append('<br />'.join(lines))
if webengine:
lines = ['Note QtWebEngine is not available for some distributions '
'(like Debian/Ubuntu), so you need to start without '
'--backend webengine there.']
lines = [
'Note QtWebEngine is not available for some distributions '
'(like Debian/Ubuntu), so you need to start without '
'--backend webengine there.',
'QtWebEngine is currently unsupported with the OS X .app, see '
'https://github.com/The-Compiler/qutebrowser/issues/1692',
]
else:
lines = ['<b>If you installed a qutebrowser package for your '
'distribution, please report this as a bug.</b>']
@@ -166,8 +170,11 @@ def fix_harfbuzz(args):
from qutebrowser.utils import log
from PyQt5.QtCore import qVersion
if 'PyQt5.QtWidgets' in sys.modules:
log.init.warning("Harfbuzz fix attempted but QtWidgets is already "
"imported!")
msg = "Harfbuzz fix attempted but QtWidgets is already imported!"
if getattr(sys, 'frozen', False):
log.init.debug(msg)
else:
log.init.warning(msg)
if sys.platform.startswith('linux') and args.harfbuzz == 'auto':
if qVersion() == '5.3.0':
log.init.debug("Using new harfbuzz engine (auto)")

View File

@@ -35,8 +35,8 @@ class ExternalEditor(QObject):
Attributes:
_text: The current text before the editor is opened.
_oshandle: The OS level handle to the tmpfile.
_filehandle: The file handle to the tmpfile.
_file: The file handle as tempfile.NamedTemporaryFile. Note that this
handle will be closed after the initial file has been created.
_proc: The GUIProcess of the editor.
_win_id: The window ID the ExternalEditor is associated with.
"""
@@ -46,20 +46,18 @@ class ExternalEditor(QObject):
def __init__(self, win_id, parent=None):
super().__init__(parent)
self._text = None
self._oshandle = None
self._filename = None
self._file = None
self._proc = None
self._win_id = win_id
def _cleanup(self):
"""Clean up temporary files after the editor closed."""
if self._oshandle is None or self._filename is None:
if self._file is None:
# Could not create initial file.
return
try:
os.close(self._oshandle)
if self._proc.exit_status() != QProcess.CrashExit:
os.remove(self._filename)
os.remove(self._file.name)
except OSError as e:
# NOTE: Do not replace this with "raise CommandError" as it's
# executed async.
@@ -82,7 +80,7 @@ class ExternalEditor(QObject):
return
encoding = config.get('general', 'editor-encoding')
try:
with open(self._filename, 'r', encoding=encoding) as f:
with open(self._file.name, 'r', encoding=encoding) as f:
text = f.read()
except OSError as e:
# NOTE: Do not replace this with "raise CommandError" as it's
@@ -108,13 +106,18 @@ class ExternalEditor(QObject):
if self._text is not None:
raise ValueError("Already editing a file!")
self._text = text
encoding = config.get('general', 'editor-encoding')
try:
self._oshandle, self._filename = tempfile.mkstemp(
text=True, prefix='qutebrowser-editor-')
if text:
encoding = config.get('general', 'editor-encoding')
with open(self._filename, 'w', encoding=encoding) as f:
f.write(text)
# Close while the external process is running, as otherwise systems
# with exclusive write access (e.g. Windows) may fail to update
# the file from the external editor, see
# https://github.com/The-Compiler/qutebrowser/issues/1767
with tempfile.NamedTemporaryFile(
mode='w', prefix='qutebrowser-editor-', encoding=encoding,
delete=False) as fobj:
if text:
fobj.write(text)
self._file = fobj
except OSError as e:
message.error(self._win_id, "Failed to create initial file: "
"{}".format(e))
@@ -125,6 +128,6 @@ class ExternalEditor(QObject):
self._proc.error.connect(self.on_proc_error)
editor = config.get('general', 'editor')
executable = editor[0]
args = [arg.replace('{}', self._filename) for arg in editor[1:]]
args = [arg.replace('{}', self._file.name) for arg in editor[1:]]
log.procs.debug("Calling \"{}\" with args {}".format(executable, args))
self._proc.start(executable, args)

View File

@@ -168,7 +168,7 @@ def _from_args(typ, args):
suffix = basedir_suffix[typ]
except KeyError: # pragma: no cover
return (False, None)
return (True, os.path.join(basedir, suffix))
return (True, os.path.abspath(os.path.join(basedir, suffix)))
try:
argname = typ_to_argparse_arg[typ]

View File

@@ -24,7 +24,7 @@ SOURCE_DIR ?= .
SOURCE_FILES ?= dist/qutebrowser.app COPYING
TEMPLATE_DMG ?= template.dmg
TEMPLATE_SIZE ?= 120m
TEMPLATE_SIZE ?= 300m
################################################################################
# DMG building. No editing should be needed beyond this point.
@@ -37,17 +37,12 @@ WC_DIR=wc
.PHONY: all
all: $(MASTER_DMG)
$(TEMPLATE_DMG): $(TEMPLATE_DMG).bz2
bunzip2 -k $<
$(TEMPLATE_DMG).bz2:
$(TEMPLATE_DMG):
@echo
@echo --------------------- Generating empty template --------------------
mkdir template
hdiutil create -fs HFSX -layout SPUD -size $(TEMPLATE_SIZE) "$(TEMPLATE_DMG)" -srcfolder template -format UDRW -volname "$(NAME)" -quiet
rmdir template
bzip2 "$(TEMPLATE_DMG)"
@echo
$(WC_DMG): $(TEMPLATE_DMG)
cp $< $@

View File

@@ -28,6 +28,7 @@ import shutil
import subprocess
import argparse
import tarfile
import tempfile
import collections
sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir,
@@ -50,7 +51,7 @@ def call_script(name, *args, python=sys.executable):
subprocess.check_call([python, path] + list(args))
def call_tox(toxenv, *args, python=sys.executable):
def call_tox(toxenv, *args, python=os.path.dirname(sys.executable)):
"""Call tox.
Args:
@@ -94,7 +95,7 @@ def build_osx():
utils.print_title("Updating 3rdparty content")
update_3rdparty.update_pdfjs()
utils.print_title("Building .app via pyinstaller")
call_tox('pyinstaller')
call_tox('pyinstaller', '-r')
utils.print_title("Building .dmg")
subprocess.check_call(['make', '-f', 'scripts/dev/Makefile-dmg'])
utils.print_title("Cleaning up...")
@@ -103,6 +104,17 @@ def build_osx():
for d in ['dist', 'build']:
shutil.rmtree(d)
utils.print_title("Running smoke test")
with tempfile.TemporaryDirectory() as tmpdir:
subprocess.check_call(['hdiutil', 'attach', 'qutebrowser.dmg',
'-mountpoint', tmpdir])
try:
binary = os.path.join(tmpdir, 'qutebrowser.app', 'Contents',
'MacOS', 'qutebrowser')
smoke_test(binary)
finally:
subprocess.check_call(['hdiutil', 'detach', tmpdir])
def build_windows():
"""Build windows executables/setups."""
@@ -116,6 +128,8 @@ def build_windows():
python_x86 = r'C:\Python{}_x32'.format(ver)
python_x64 = r'C:\Python{}'.format(ver)
utils.print_title("Rebuilding tox environment")
call_tox('cxfreeze-windows', '-r', '--notest')
utils.print_title("Running 32bit freeze.py build_exe")
call_tox('cxfreeze-windows', 'build_exe', python=python_x86)
utils.print_title("Running 32bit freeze.py bdist_msi")

View File

@@ -49,6 +49,16 @@ def get_egl_path():
r'PyQt5\libEGL.dll')
def get_plugin_folders():
"""Get the plugin folders to copy to the output."""
if not sys.platform.startswith('win'):
return []
plugin_dir = os.path.join(distutils.sysconfig.get_python_lib(),
'PyQt5', 'plugins')
folders = ['audio', 'iconengines', 'mediaservice', 'printsupport']
return [os.path.join(plugin_dir, folder) for folder in folders]
def get_build_exe_options(skip_html=False):
"""Get the options passed as build_exe_options to cx_Freeze.
@@ -81,6 +91,8 @@ def get_build_exe_options(skip_html=False):
if egl_path is not None:
include_files.append((egl_path, 'libEGL.dll'))
include_files += get_plugin_folders()
return {
'include_files': include_files,
'include_msvcr': True,

View File

@@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Simple input</title>
</head>
<body>
<form><input></input></form>
</body>
</html>

View File

@@ -0,0 +1,19 @@
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript">
function check_storage() {
var elem = document.getElementById("status");
try {
localStorage.qute_test = "foo";
elem.innerHTML = "working";
} catch (e) {
elem.innerHTML = "not working";
}
}
</script>
</head>
<body onload="check_storage()">
<p>Local storage status: <span id="status">checking...</span></p>
</body>
</html>

View File

@@ -119,6 +119,15 @@ Feature: Using hints
And I run :follow-hint a
Then the error "Invalid link clicked - *" should be shown
Scenario: Hinting inputs without type
When I open data/hints/input.html
And I run :hint inputs
And I run :follow-hint a
And I wait for "Entering mode KeyMode.insert (reason: click)" in the log
And I run :leave-mode
# The actual check is already done above
Then no crash should happen
### iframes
Scenario: Using :follow-hint inside an iframe

View File

@@ -51,3 +51,12 @@ Feature: Page history
When I open data/title.html
And I run :history-clear
Then the history file should be empty
## Bugs
Scenario: Opening a valid URL which turns out invalid
When I set general -> auto-search to true
And I run :open http://foo%40bar@baz
Then "QFSFileEngine::open: No file name specified" should be logged
And "Error while loading : Host not found" should be logged
And "Ignoring invalid URL being added to history" should be logged

View File

@@ -61,6 +61,18 @@ Feature: Keyboard input
And I run :bind <ctrl-test23>
Then the message "<ctrl-test23> is bound to 'message-info bar' in normal mode" should be shown
Scenario: Binding to an alias
When I run :set aliases 'mib' 'message-info baz'
And I run :bind test25 mib
And I press the keys "test25"
Then the message "baz" should be shown
Scenario: Printing a bound alias
When I run :set aliases 'mib' 'message-info baz'
And I run :bind <test26> mib
And I run :bind <test26>
Then the message "<test26> is bound to 'mib' in normal mode" should be shown
# :unbind
Scenario: Binding and unbinding a keychain

View File

@@ -471,6 +471,13 @@ Feature: Various utility commands.
And I open cookies in a new tab
Then the cookie qute-test should be set to 42
## https://github.com/The-Compiler/qutebrowser/issues/1742
Scenario: Private browsing is activated in QtWebKit without restart
When I set general -> private-browsing to true
And I open data/javascript/localstorage.html
Then the page should contain the plaintext "Local storage status: not working"
Scenario: :repeat-command
Given I open data/scroll.html
And I run :tab-only

View File

@@ -562,6 +562,12 @@ Feature: Tab management
- history:
- url: http://localhost:*/data/numbers/2.txt
Scenario: Detach tab from window with only one tab
Given I have a fresh instance
When I open data/hello.txt
And I run :tab-detach
Then the error "Cannot detach one tab." should be shown
# :undo
Scenario: Undo without any closed tabs

View File

@@ -162,7 +162,7 @@ class SelectionAndFilterTests:
('<textarea />', [webelem.Group.all, webelem.Group.inputs]),
('<select />', [webelem.Group.all]),
('<input />', [webelem.Group.all]),
('<input />', [webelem.Group.all, webelem.Group.inputs]),
('<input type="hidden" />', []),
('<input type="text" />', [webelem.Group.inputs, webelem.Group.all]),
('<input type="email" />', [webelem.Group.inputs, webelem.Group.all]),

View File

@@ -53,6 +53,17 @@ class TestCommandRunner:
with pytest.raises(cmdexc.NoSuchCommandError):
list(cr.parse_all("alias_name"))
@pytest.mark.parametrize('command', ['', ' '])
def test_parse_empty_with_alias(self, command):
"""An empty command should not crash.
See https://github.com/The-Compiler/qutebrowser/issues/1690
and https://github.com/The-Compiler/qutebrowser/issues/1773
"""
cr = runners.CommandRunner(0)
with pytest.raises(cmdexc.NoSuchCommandError):
list(cr.parse_all(command))
def test_parse_with_count(self):
"""Test parsing of commands with a count."""
cr = runners.CommandRunner(0)

View File

@@ -214,7 +214,7 @@ class TestKeyConfigParser:
"""Test config.parsers.keyconf.KeyConfigParser."""
def test_cmd_binding(self, cmdline_test):
def test_cmd_binding(self, cmdline_test, config_stub):
"""Test various command bindings.
See https://github.com/The-Compiler/qutebrowser/issues/615
@@ -222,6 +222,7 @@ class TestKeyConfigParser:
Args:
cmdline_test: A pytest fixture which provides testcases.
"""
config_stub.data = {'aliases': []}
kcp = keyconf.KeyConfigParser(None, None)
kcp._cur_section = 'normal'
if cmdline_test.valid:
@@ -281,6 +282,11 @@ class TestKeyConfigParser:
('leave-mode ;; foo', None),
('download-remove --all', 'download-clear'),
('hint links fill ":open {hint-url}"',
'hint links fill :open {hint-url}'),
('hint links fill ":open -t {hint-url}"',
'hint links fill :open -t {hint-url}'),
]
)
def test_migrations(self, old, new_expected):
@@ -329,7 +335,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.8.0'
assert qutebrowser.__version__ == '0.8.3'
@pytest.mark.parametrize('filename',
os.listdir(os.path.join(os.path.dirname(__file__), 'old_configs')),

View File

@@ -69,14 +69,14 @@ class TestArg:
config_stub.data['general']['editor'] = ['bin', 'foo', '{}', 'bar']
editor.edit("")
editor._proc._proc.start.assert_called_with(
"bin", ["foo", editor._filename, "bar"])
"bin", ["foo", editor._file.name, "bar"])
def test_placeholder_inline(self, config_stub, editor):
"""Test starting editor with placeholder arg inside of another arg."""
config_stub.data['general']['editor'] = ['bin', 'foo{}', 'bar']
editor.edit("")
editor._proc._proc.start.assert_called_with(
"bin", ["foo" + editor._filename, "bar"])
"bin", ["foo" + editor._file.name, "bar"])
class TestFileHandling:
@@ -86,7 +86,7 @@ class TestFileHandling:
def test_ok(self, editor):
"""Test file handling when closing with an exit status == 0."""
editor.edit("")
filename = editor._filename
filename = editor._file.name
assert os.path.exists(filename)
assert os.path.basename(filename).startswith('qutebrowser-editor-')
editor._proc.finished.emit(0, QProcess.NormalExit)
@@ -95,7 +95,7 @@ class TestFileHandling:
def test_error(self, editor):
"""Test file handling when closing with an exit status != 0."""
editor.edit("")
filename = editor._filename
filename = editor._file.name
assert os.path.exists(filename)
editor._proc._proc.exitStatus = mock.Mock(
@@ -109,7 +109,7 @@ class TestFileHandling:
def test_crash(self, editor):
"""Test file handling when closing with a crash."""
editor.edit("")
filename = editor._filename
filename = editor._file.name
assert os.path.exists(filename)
editor._proc._proc.exitStatus = mock.Mock(
@@ -125,7 +125,7 @@ class TestFileHandling:
def test_unreadable(self, message_mock, editor):
"""Test file handling when closing with an unreadable file."""
editor.edit("")
filename = editor._filename
filename = editor._file.name
assert os.path.exists(filename)
os.chmod(filename, 0o077)
editor._proc.finished.emit(0, QProcess.NormalExit)
@@ -160,10 +160,10 @@ def test_modify(editor, initial_text, edited_text):
"""Test if inputs get modified correctly."""
editor.edit(initial_text)
with open(editor._filename, 'r', encoding='utf-8') as f:
with open(editor._file.name, 'r', encoding='utf-8') as f:
assert f.read() == initial_text
with open(editor._filename, 'w', encoding='utf-8') as f:
with open(editor._file.name, 'w', encoding='utf-8') as f:
f.write(edited_text)
editor._proc.finished.emit(0, QProcess.NormalExit)

View File

@@ -532,32 +532,33 @@ class TestSendToRunningInstance:
@pytest.mark.linux(reason="Causes random trouble on Windows and OS X")
def test_normal(self, qtbot, tmpdir, ipc_server, mocker, has_cwd):
ipc_server.listen()
raw_spy = QSignalSpy(ipc_server.got_raw)
with qtbot.assertNotEmitted(ipc_server.got_invalid_data):
with qtbot.waitSignal(ipc_server.got_args,
timeout=5000) as blocker:
with tmpdir.as_cwd():
if not has_cwd:
m = mocker.patch('qutebrowser.misc.ipc.os')
m.getcwd.side_effect = OSError
sent = ipc.send_to_running_instance('qute-test', ['foo'],
None)
with qtbot.waitSignal(ipc_server.got_raw,
timeout=5000) as raw_blocker:
with tmpdir.as_cwd():
if not has_cwd:
m = mocker.patch('qutebrowser.misc.ipc.os')
m.getcwd.side_effect = OSError
sent = ipc.send_to_running_instance(
'qute-test', ['foo'], None)
assert sent
assert sent
expected_cwd = str(tmpdir) if has_cwd else ''
assert blocker.args == [['foo'], '', expected_cwd]
assert len(raw_spy) == 1
assert len(raw_spy[0]) == 1
raw_expected = {'args': ['foo'], 'target_arg': None,
'version': qutebrowser.__version__,
'protocol_version': ipc.PROTOCOL_VERSION}
if has_cwd:
raw_expected['cwd'] = str(tmpdir)
parsed = json.loads(raw_spy[0][0].decode('utf-8'))
assert len(raw_blocker.args) == 1
parsed = json.loads(raw_blocker.args[0].decode('utf-8'))
assert parsed == raw_expected
def test_socket_error(self):

View File

@@ -225,6 +225,15 @@ class TestArguments:
func = getattr(standarddir, typ)
assert func() == expected
def test_basedir_relative(self, tmpdir):
"""Test --basedir with a relative path."""
basedir = (tmpdir / 'basedir')
basedir.ensure(dir=True)
with tmpdir.as_cwd():
args = types.SimpleNamespace(basedir='basedir')
standarddir.init(args)
assert standarddir.config() == str(basedir / 'config')
class TestInitCacheDirTag:
@@ -311,6 +320,7 @@ class TestCreatingDir:
m.sep = os.sep
m.path.join = os.path.join
m.path.exists.return_value = False
m.path.abspath = lambda x: x
args = types.SimpleNamespace(basedir=str(tmpdir))
standarddir.init(args)