From 3692c86a7e4ae4c529669861c74074c646036798 Mon Sep 17 00:00:00 2001 From: Cosmin Popescu Date: Tue, 1 Nov 2016 19:43:37 +0100 Subject: [PATCH 001/561] auto save session --- qutebrowser/browser/browsertab.py | 3 +++ qutebrowser/config/configdata.py | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/qutebrowser/browser/browsertab.py b/qutebrowser/browser/browsertab.py index 29b82d969..cc27c335f 100644 --- a/qutebrowser/browser/browsertab.py +++ b/qutebrowser/browser/browsertab.py @@ -685,6 +685,9 @@ class AbstractTab(QWidget): @pyqtSlot(bool) def _on_load_finished(self, ok): + if config.get('general', 'auto-save-session'): + sess_manager = objreg.get('session-manager') + sess_manager.session_save() if ok and not self._has_ssl_errors: if self.url().scheme() == 'https': self._set_load_status(usertypes.LoadStatus.success_https) diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index ea3939a44..3d13bf563 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -160,6 +160,11 @@ def data(readonly=False): SettingValue(typ.Bool(), 'true'), "Whether to save the config automatically on quit."), + ('auto-save-session', + SettingValue(typ.Bool(), 'false'), + "Whether to save the session automatically when a page finishes " + "loading."), + ('auto-save-interval', SettingValue(typ.Int(minval=0, maxval=MAXVALS['int']), '15000'), "How often (in milliseconds) to auto-save config/cookies/etc."), From c590a37043488d147ce2bf82debf00e6e486b604 Mon Sep 17 00:00:00 2001 From: Cosmin Popescu Date: Mon, 7 Nov 2016 20:45:46 +0100 Subject: [PATCH 002/561] changed to using an internal _autosave session --- qutebrowser/app.py | 8 ++++++-- qutebrowser/browser/browsertab.py | 5 ++--- qutebrowser/config/configdata.py | 5 ----- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/qutebrowser/app.py b/qutebrowser/app.py index 68397bbd3..49f06c764 100644 --- a/qutebrowser/app.py +++ b/qutebrowser/app.py @@ -210,14 +210,17 @@ def _load_session(name): name: The name of the session to load, or None to read state file. """ state_config = objreg.get('state-config') + session_manager = objreg.get('session-manager') if name is None: try: name = state_config['general']['session'] except KeyError: # No session given as argument and none in the session file -> # start without loading a session - return - session_manager = objreg.get('session-manager') + if session_manager.exists('_autosave'): + name = '_autosave' + else: + return try: session_manager.load(name) except sessions.SessionNotFoundError: @@ -723,6 +726,7 @@ class Quitter: # Now we can hopefully quit without segfaults log.destroy.debug("Deferring QApplication::exit...") objreg.get('signal-handler').deactivate() + objreg.get('session-manager').session_delete('_autosave', force = True) # We use a singleshot timer to exit here to minimize the likelihood of # segfaults. QTimer.singleShot(0, functools.partial(qApp.exit, status)) diff --git a/qutebrowser/browser/browsertab.py b/qutebrowser/browser/browsertab.py index cc27c335f..193f1695e 100644 --- a/qutebrowser/browser/browsertab.py +++ b/qutebrowser/browser/browsertab.py @@ -685,9 +685,8 @@ class AbstractTab(QWidget): @pyqtSlot(bool) def _on_load_finished(self, ok): - if config.get('general', 'auto-save-session'): - sess_manager = objreg.get('session-manager') - sess_manager.session_save() + sess_manager = objreg.get('session-manager') + sess_manager.session_save('_autosave', quiet = True, force = True) if ok and not self._has_ssl_errors: if self.url().scheme() == 'https': self._set_load_status(usertypes.LoadStatus.success_https) diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index 3d13bf563..ea3939a44 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -160,11 +160,6 @@ def data(readonly=False): SettingValue(typ.Bool(), 'true'), "Whether to save the config automatically on quit."), - ('auto-save-session', - SettingValue(typ.Bool(), 'false'), - "Whether to save the session automatically when a page finishes " - "loading."), - ('auto-save-interval', SettingValue(typ.Int(minval=0, maxval=MAXVALS['int']), '15000'), "How often (in milliseconds) to auto-save config/cookies/etc."), From 89e6ff6599cbaadcd1c06f58d272e9a0b035ea7d Mon Sep 17 00:00:00 2001 From: Cosmin Popescu Date: Wed, 9 Nov 2016 19:27:48 +0100 Subject: [PATCH 003/561] fix wrong spaces and priority for session restore --- qutebrowser/app.py | 12 ++++++------ qutebrowser/browser/browsertab.py | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/qutebrowser/app.py b/qutebrowser/app.py index 49f06c764..da0d3cd55 100644 --- a/qutebrowser/app.py +++ b/qutebrowser/app.py @@ -211,16 +211,16 @@ def _load_session(name): """ state_config = objreg.get('state-config') session_manager = objreg.get('session-manager') - if name is None: + if name is None and session_manager.exists('_autosave'): + name = '_autosave' + elif name is None: try: name = state_config['general']['session'] except KeyError: # No session given as argument and none in the session file -> # start without loading a session - if session_manager.exists('_autosave'): - name = '_autosave' - else: - return + return + try: session_manager.load(name) except sessions.SessionNotFoundError: @@ -726,7 +726,7 @@ class Quitter: # Now we can hopefully quit without segfaults log.destroy.debug("Deferring QApplication::exit...") objreg.get('signal-handler').deactivate() - objreg.get('session-manager').session_delete('_autosave', force = True) + objreg.get('session-manager').session_delete('_autosave', force=True) # We use a singleshot timer to exit here to minimize the likelihood of # segfaults. QTimer.singleShot(0, functools.partial(qApp.exit, status)) diff --git a/qutebrowser/browser/browsertab.py b/qutebrowser/browser/browsertab.py index 193f1695e..dc88efefc 100644 --- a/qutebrowser/browser/browsertab.py +++ b/qutebrowser/browser/browsertab.py @@ -686,7 +686,7 @@ class AbstractTab(QWidget): @pyqtSlot(bool) def _on_load_finished(self, ok): sess_manager = objreg.get('session-manager') - sess_manager.session_save('_autosave', quiet = True, force = True) + sess_manager.session_save('_autosave', quiet=True, force=True) if ok and not self._has_ssl_errors: if self.url().scheme() == 'https': self._set_load_status(usertypes.LoadStatus.success_https) From 75c996c13ee52eaedc57c5c67a5b9fd8e67d21e7 Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Fri, 11 Nov 2016 02:45:59 +0100 Subject: [PATCH 004/561] Cancel register key input with escape --- qutebrowser/keyinput/modeparsers.py | 4 ++++ tests/end2end/features/keyinput.feature | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/qutebrowser/keyinput/modeparsers.py b/qutebrowser/keyinput/modeparsers.py index 6fa881ad7..25243b1be 100644 --- a/qutebrowser/keyinput/modeparsers.py +++ b/qutebrowser/keyinput/modeparsers.py @@ -290,6 +290,10 @@ class RegisterKeyParser(keyparser.BaseKeyParser): Return: True if event has been handled, False otherwise. """ + if e.key() == Qt.Key_Escape: + self.request_leave.emit(self._mode, "register key cancelled", True) + return True + if utils.keyevent_to_string(e) is None: # this is a modifier key, let it pass and keep going return False diff --git a/tests/end2end/features/keyinput.feature b/tests/end2end/features/keyinput.feature index 6b671825d..1e94b10ac 100644 --- a/tests/end2end/features/keyinput.feature +++ b/tests/end2end/features/keyinput.feature @@ -255,3 +255,8 @@ Feature: Keyboard input And I press the key "a" And I wait for "hints: *" in the log Then no crash should happen + + Scenario: Cancelling key input + When I run :record-macro + And I press the key "" + Then "Leaving mode KeyMode.record_macro (reason: register key cancelled)" should be logged From f0d215e07a6508d421beb4ed3e404912ae2e423a Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Mon, 14 Nov 2016 23:28:38 +0100 Subject: [PATCH 005/561] Change hard-coded escape to leave-mode binding --- qutebrowser/keyinput/modeparsers.py | 11 ++++------- tests/end2end/features/keyinput.feature | 2 +- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/qutebrowser/keyinput/modeparsers.py b/qutebrowser/keyinput/modeparsers.py index 25243b1be..050f0dc50 100644 --- a/qutebrowser/keyinput/modeparsers.py +++ b/qutebrowser/keyinput/modeparsers.py @@ -267,7 +267,7 @@ class CaretKeyParser(keyparser.CommandKeyParser): self.read_config('caret') -class RegisterKeyParser(keyparser.BaseKeyParser): +class RegisterKeyParser(keyparser.CommandKeyParser): """KeyParser for modes that record a register key. @@ -280,6 +280,7 @@ class RegisterKeyParser(keyparser.BaseKeyParser): super().__init__(win_id, parent, supports_count=False, supports_chains=False) self._mode = mode + self.read_config('register') def handle(self, e): """Override handle to always match the next key and use the register. @@ -290,8 +291,8 @@ class RegisterKeyParser(keyparser.BaseKeyParser): Return: True if event has been handled, False otherwise. """ - if e.key() == Qt.Key_Escape: - self.request_leave.emit(self._mode, "register key cancelled", True) + + if super().handle(e): return True if utils.keyevent_to_string(e) is None: @@ -327,7 +328,3 @@ class RegisterKeyParser(keyparser.BaseKeyParser): def on_keyconfig_changed(self, mode): """RegisterKeyParser has no config section (no bindable keys).""" pass - - def execute(self, cmdstr, _keytype, count=None): - """Should never be called on RegisterKeyParser.""" - assert False diff --git a/tests/end2end/features/keyinput.feature b/tests/end2end/features/keyinput.feature index 1e94b10ac..3b4248e83 100644 --- a/tests/end2end/features/keyinput.feature +++ b/tests/end2end/features/keyinput.feature @@ -259,4 +259,4 @@ Feature: Keyboard input Scenario: Cancelling key input When I run :record-macro And I press the key "" - Then "Leaving mode KeyMode.record_macro (reason: register key cancelled)" should be logged + Then "Leaving mode KeyMode.record_macro (reason: leave current)" should be logged From fc79349af68ea52a9e2208d1265df637e2155fc8 Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Tue, 15 Nov 2016 00:30:54 +0100 Subject: [PATCH 006/561] Remove misplaced blank line --- qutebrowser/keyinput/modeparsers.py | 1 - 1 file changed, 1 deletion(-) diff --git a/qutebrowser/keyinput/modeparsers.py b/qutebrowser/keyinput/modeparsers.py index 050f0dc50..257937852 100644 --- a/qutebrowser/keyinput/modeparsers.py +++ b/qutebrowser/keyinput/modeparsers.py @@ -291,7 +291,6 @@ class RegisterKeyParser(keyparser.CommandKeyParser): Return: True if event has been handled, False otherwise. """ - if super().handle(e): return True From d99a7bd7ad1be61568641663924cc09fb8d47628 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 23 Nov 2016 07:57:35 +0100 Subject: [PATCH 007/561] Pass arguments to websettings init functions --- qutebrowser/app.py | 2 +- qutebrowser/browser/webengine/webenginesettings.py | 2 +- qutebrowser/browser/webkit/webkitsettings.py | 2 +- qutebrowser/config/websettings.py | 8 ++++---- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/qutebrowser/app.py b/qutebrowser/app.py index edc19931c..de8a131ad 100644 --- a/qutebrowser/app.py +++ b/qutebrowser/app.py @@ -405,7 +405,7 @@ def _init_modules(args, crash_handler): sessions.init(qApp) log.init.debug("Initializing websettings...") - websettings.init() + websettings.init(args) log.init.debug("Initializing adblock...") host_blocker = adblock.HostBlocker() diff --git a/qutebrowser/browser/webengine/webenginesettings.py b/qutebrowser/browser/webengine/webenginesettings.py index 1568abad5..3a0a90c32 100644 --- a/qutebrowser/browser/webengine/webenginesettings.py +++ b/qutebrowser/browser/webengine/webenginesettings.py @@ -106,7 +106,7 @@ def update_settings(section, option): _init_stylesheet(profile) -def init(): +def init(_args): """Initialize the global QWebSettings.""" if config.get('general', 'developer-extras'): # FIXME:qtwebengine Make sure we call globalSettings *after* this... diff --git a/qutebrowser/browser/webkit/webkitsettings.py b/qutebrowser/browser/webkit/webkitsettings.py index 5fe27c48b..b6b2f7ddf 100644 --- a/qutebrowser/browser/webkit/webkitsettings.py +++ b/qutebrowser/browser/webkit/webkitsettings.py @@ -102,7 +102,7 @@ def update_settings(section, option): websettings.update_mappings(MAPPINGS, section, option) -def init(): +def init(_args): """Initialize the global QWebSettings.""" cache_path = standarddir.cache() data_path = standarddir.data() diff --git a/qutebrowser/config/websettings.py b/qutebrowser/config/websettings.py index 6f1304057..3942189eb 100644 --- a/qutebrowser/config/websettings.py +++ b/qutebrowser/config/websettings.py @@ -259,14 +259,14 @@ def update_mappings(mappings, section, option): mapping.set(value) -def init(): +def init(args): """Initialize all QWeb(Engine)Settings.""" - if objreg.get('args').backend == 'webengine': + if args.backend == 'webengine': from qutebrowser.browser.webengine import webenginesettings - webenginesettings.init() + webenginesettings.init(args) else: from qutebrowser.browser.webkit import webkitsettings - webkitsettings.init() + webkitsettings.init(args) def shutdown(): From 946e3f93f95c22a881f37539f9da86e8f7dd7225 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 23 Nov 2016 08:12:13 +0100 Subject: [PATCH 008/561] Add --enable-webengine-inspector option Since the inspector can be a security risk, it's now not linked to developer-extras anymore until QtWebEngine provides a better way to access it. See: https://bugreports.qt.io/browse/QTBUG-50725 http://bouk.co/blog/hacking-developers/ --- doc/help/settings.asciidoc | 4 +++- doc/qutebrowser.1.asciidoc | 3 +++ qutebrowser/browser/inspector.py | 8 -------- qutebrowser/browser/webengine/webengineinspector.py | 5 ++--- qutebrowser/browser/webengine/webenginesettings.py | 5 ++--- qutebrowser/browser/webkit/webkitinspector.py | 6 +++++- qutebrowser/config/configdata.py | 6 ++++-- qutebrowser/qutebrowser.py | 6 ++++++ tests/end2end/features/misc.feature | 7 +++++++ 9 files changed, 32 insertions(+), 18 deletions(-) diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc index 6e2d3d67c..0ee5f5769 100644 --- a/doc/help/settings.asciidoc +++ b/doc/help/settings.asciidoc @@ -397,7 +397,7 @@ This setting is only available with the QtWebKit backend. === developer-extras Enable extra tools for Web developers. -This needs to be enabled for `:inspector` to work and also adds an _Inspect_ entry to the context menu. +This needs to be enabled for `:inspector` to work and also adds an _Inspect_ entry to the context menu. For QtWebEngine, see 'qutebrowser --help' instead. Valid values: @@ -406,6 +406,8 @@ Valid values: Default: +pass:[false]+ +This setting is only available with the QtWebKit backend. + [[general-print-element-backgrounds]] === print-element-backgrounds Whether the background color and images are also drawn when the page is printed. diff --git a/doc/qutebrowser.1.asciidoc b/doc/qutebrowser.1.asciidoc index d76c4480c..93e632678 100644 --- a/doc/qutebrowser.1.asciidoc +++ b/doc/qutebrowser.1.asciidoc @@ -59,6 +59,9 @@ show it. *--backend* '{webkit,webengine}':: Which backend to use (webengine backend is EXPERIMENTAL!). +*--enable-webengine-inspector*:: + Enable the web inspector for QtWebEngine. Note that this is a SECURITY RISK and you should not visit untrusted websites with the inspector turned on. See https://bugreports.qt.io/browse/QTBUG-50725 for more details. + === debug arguments *-l* '{critical,error,warning,info,debug,vdebug}', *--loglevel* '{critical,error,warning,info,debug,vdebug}':: Set loglevel diff --git a/qutebrowser/browser/inspector.py b/qutebrowser/browser/inspector.py index 00225d091..e605cfd8a 100644 --- a/qutebrowser/browser/inspector.py +++ b/qutebrowser/browser/inspector.py @@ -26,7 +26,6 @@ from PyQt5.QtWidgets import QWidget from qutebrowser.utils import log, objreg from qutebrowser.misc import miscwidgets -from qutebrowser.config import config def create(parent=None): @@ -91,13 +90,6 @@ class AbstractWebInspector(QWidget): state_config['geometry']['inspector'] = geom super().closeEvent(e) - def _check_developer_extras(self): - """Check if developer-extras are enabled.""" - if not config.get('general', 'developer-extras'): - raise WebInspectorError( - "Please enable developer-extras before using the " - "webinspector!") - def inspect(self, page): """Inspect the given QWeb(Engine)Page.""" raise NotImplementedError diff --git a/qutebrowser/browser/webengine/webengineinspector.py b/qutebrowser/browser/webengine/webengineinspector.py index b0c0a25bf..b822bd253 100644 --- a/qutebrowser/browser/webengine/webengineinspector.py +++ b/qutebrowser/browser/webengine/webengineinspector.py @@ -41,13 +41,12 @@ class WebEngineInspector(inspector.AbstractWebInspector): def inspect(self, _page): """Set up the inspector.""" - self._check_developer_extras() try: port = int(os.environ['QTWEBENGINE_REMOTE_DEBUGGING']) except KeyError: raise inspector.WebInspectorError( - "Debugging is not set up correctly. Did you restart after " - "setting developer-extras?") + "Debugging is not enabled. See 'qutebrowser --help' for " + "details.") url = QUrl('http://localhost:{}/'.format(port)) self._widget.load(url) self.show() diff --git a/qutebrowser/browser/webengine/webenginesettings.py b/qutebrowser/browser/webengine/webenginesettings.py index 3a0a90c32..bdc0f046a 100644 --- a/qutebrowser/browser/webengine/webenginesettings.py +++ b/qutebrowser/browser/webengine/webenginesettings.py @@ -106,10 +106,9 @@ def update_settings(section, option): _init_stylesheet(profile) -def init(_args): +def init(args): """Initialize the global QWebSettings.""" - if config.get('general', 'developer-extras'): - # FIXME:qtwebengine Make sure we call globalSettings *after* this... + if args.enable_webengine_inspector: os.environ['QTWEBENGINE_REMOTE_DEBUGGING'] = str(utils.random_port()) profile = QWebEngineProfile.defaultProfile() diff --git a/qutebrowser/browser/webkit/webkitinspector.py b/qutebrowser/browser/webkit/webkitinspector.py index 1a54ec784..9e612b28e 100644 --- a/qutebrowser/browser/webkit/webkitinspector.py +++ b/qutebrowser/browser/webkit/webkitinspector.py @@ -23,6 +23,7 @@ from PyQt5.QtWebKitWidgets import QWebInspector from qutebrowser.browser import inspector +from qutebrowser.config import config class WebKitInspector(inspector.AbstractWebInspector): @@ -35,6 +36,9 @@ class WebKitInspector(inspector.AbstractWebInspector): self._set_widget(qwebinspector) def inspect(self, page): - self._check_developer_extras() + if not config.get('general', 'developer-extras'): + raise inspector.WebInspectorError( + "Please enable developer-extras before using the " + "webinspector!") self._widget.setPage(page) self.show() diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index a99205902..da0fea5e3 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -184,10 +184,12 @@ def data(readonly=False): "icons."), ('developer-extras', - SettingValue(typ.Bool(), 'false'), + SettingValue(typ.Bool(), 'false', + backends=[usertypes.Backend.QtWebKit]), "Enable extra tools for Web developers.\n\n" "This needs to be enabled for `:inspector` to work and also adds " - "an _Inspect_ entry to the context menu."), + "an _Inspect_ entry to the context menu. For QtWebEngine, see " + "'qutebrowser --help' instead."), ('print-element-backgrounds', SettingValue(typ.Bool(), 'true', diff --git a/qutebrowser/qutebrowser.py b/qutebrowser/qutebrowser.py index 8665d4592..1b07296d6 100644 --- a/qutebrowser/qutebrowser.py +++ b/qutebrowser/qutebrowser.py @@ -66,6 +66,12 @@ def get_argparser(): parser.add_argument('--backend', choices=['webkit', 'webengine'], help="Which backend to use (webengine backend is " "EXPERIMENTAL!).", default='webkit') + parser.add_argument('--enable-webengine-inspector', action='store_true', + help="Enable the web inspector for QtWebEngine. Note " + "that this is a SECURITY RISK and you should not visit " + "untrusted websites with the inspector turned on. See " + "https://bugreports.qt.io/browse/QTBUG-50725 for more " + "details.") parser.add_argument('--json-args', help=argparse.SUPPRESS) parser.add_argument('--temp-basedir-restarted', help=argparse.SUPPRESS) diff --git a/tests/end2end/features/misc.feature b/tests/end2end/features/misc.feature index 6d3862713..20ace1959 100644 --- a/tests/end2end/features/misc.feature +++ b/tests/end2end/features/misc.feature @@ -130,11 +130,17 @@ Feature: Various utility commands. # :inspect + @qtwebengine_skip Scenario: Inspector without developer extras When I set general -> developer-extras to false And I run :inspector Then the error "Please enable developer-extras before using the webinspector!" should be shown + @qtwebkit_skip + Scenario: Inspector without --enable-webengine-inspector + When I run :inspector + Then the error "Debugging is not enabled. See 'qutebrowser --help' for details." should be shown + @no_xvfb @posix @qtwebengine_skip Scenario: Inspector smoke test When I set general -> developer-extras to true @@ -145,6 +151,7 @@ Feature: Various utility commands. Then no crash should happen # Different code path as an inspector got created now + @qtwebengine_skip Scenario: Inspector without developer extras (after smoke) When I set general -> developer-extras to false And I run :inspector From 1507cfcae714ee4683624473afeac3f903dbcbc4 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 23 Nov 2016 08:37:02 +0100 Subject: [PATCH 009/561] Fix lint --- qutebrowser/browser/webengine/webenginesettings.py | 2 +- qutebrowser/qutebrowser.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/qutebrowser/browser/webengine/webenginesettings.py b/qutebrowser/browser/webengine/webenginesettings.py index bdc0f046a..061da4146 100644 --- a/qutebrowser/browser/webengine/webenginesettings.py +++ b/qutebrowser/browser/webengine/webenginesettings.py @@ -32,7 +32,7 @@ from PyQt5.QtWebEngineWidgets import (QWebEngineSettings, QWebEngineProfile, # pylint: enable=no-name-in-module,import-error,useless-suppression from qutebrowser.browser import shared -from qutebrowser.config import websettings, config +from qutebrowser.config import websettings from qutebrowser.utils import objreg, utils, standarddir, javascript diff --git a/qutebrowser/qutebrowser.py b/qutebrowser/qutebrowser.py index 1b07296d6..dd434eb3d 100644 --- a/qutebrowser/qutebrowser.py +++ b/qutebrowser/qutebrowser.py @@ -68,10 +68,10 @@ def get_argparser(): "EXPERIMENTAL!).", default='webkit') parser.add_argument('--enable-webengine-inspector', action='store_true', help="Enable the web inspector for QtWebEngine. Note " - "that this is a SECURITY RISK and you should not visit " - "untrusted websites with the inspector turned on. See " - "https://bugreports.qt.io/browse/QTBUG-50725 for more " - "details.") + "that this is a SECURITY RISK and you should not " + "visit untrusted websites with the inspector turned " + "on. See https://bugreports.qt.io/browse/QTBUG-50725 " + "for more details.") parser.add_argument('--json-args', help=argparse.SUPPRESS) parser.add_argument('--temp-basedir-restarted', help=argparse.SUPPRESS) From 552e0551af9e13c0abf90bb6b39998edd0b150ac Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 23 Nov 2016 08:34:21 +0100 Subject: [PATCH 010/561] Revert zero counts This initially seemed like a nice feature, but it means 0 can't be bound as a separate key anymore, and 0 gives weird error messages... Reverts #1953. Fixes #2032. --- doc/help/commands.asciidoc | 3 +-- qutebrowser/browser/commands.py | 8 +++----- qutebrowser/commands/command.py | 20 +++----------------- qutebrowser/keyinput/basekeyparser.py | 3 +++ tests/end2end/features/misc.feature | 4 ---- tests/end2end/features/tabs.feature | 2 +- tests/unit/commands/test_cmdutils.py | 10 ---------- tests/unit/keyinput/test_basekeyparser.py | 11 +++++++++-- 8 files changed, 20 insertions(+), 41 deletions(-) diff --git a/doc/help/commands.asciidoc b/doc/help/commands.asciidoc index 4151eb892..5d9207dff 100644 --- a/doc/help/commands.asciidoc +++ b/doc/help/commands.asciidoc @@ -837,8 +837,7 @@ If neither count nor index are given, it behaves like tab-next. If both are give ==== count -The tab index to focus, starting with 1. The special value 0 focuses the rightmost tab. - +The tab index to focus, starting with 1. [[tab-move]] === tab-move diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 0458e18ed..c13cde990 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -959,7 +959,7 @@ class CommandDispatcher: @cmdutils.register(instance='command-dispatcher', scope='window') @cmdutils.argument('index', choices=['last']) - @cmdutils.argument('count', count=True, zero_count=True) + @cmdutils.argument('count', count=True) def tab_focus(self, index: typing.Union[str, int]=None, count=None): """Select the tab given as argument/[count]. @@ -972,7 +972,6 @@ class CommandDispatcher: Negative indices count from the end, such that -1 is the last tab. count: The tab index to focus, starting with 1. - The special value 0 focuses the rightmost tab. """ if index == 'last': self._tab_focus_last() @@ -982,9 +981,8 @@ class CommandDispatcher: if index is None: self.tab_next() return - elif index == 0: - index = self._count() - elif index < 0: + + if index < 0: index = self._count() + index + 1 if 1 <= index <= self._count(): diff --git a/qutebrowser/commands/command.py b/qutebrowser/commands/command.py index a2bc3b50d..a7404fb3a 100644 --- a/qutebrowser/commands/command.py +++ b/qutebrowser/commands/command.py @@ -34,14 +34,11 @@ class ArgInfo: """Information about an argument.""" def __init__(self, win_id=False, count=False, hide=False, metavar=None, - zero_count=False, flag=None, completion=None, choices=None): + flag=None, completion=None, choices=None): if win_id and count: raise TypeError("Argument marked as both count/win_id!") - if zero_count and not count: - raise TypeError("zero_count argument cannot exist without count!") self.win_id = win_id self.count = count - self.zero_count = zero_count self.flag = flag self.hide = hide self.metavar = metavar @@ -51,7 +48,6 @@ class ArgInfo: def __eq__(self, other): return (self.win_id == other.win_id and self.count == other.count and - self.zero_count == other.zero_count and self.flag == other.flag and self.hide == other.hide and self.metavar == other.metavar and @@ -61,7 +57,6 @@ class ArgInfo: def __repr__(self): return utils.get_repr(self, win_id=self.win_id, count=self.count, flag=self.flag, hide=self.hide, - zero_count=self.zero_count, metavar=self.metavar, completion=self.completion, choices=self.choices, constructor=True) @@ -142,7 +137,6 @@ class Command: self.opt_args = collections.OrderedDict() self.namespace = None self._count = None - self._zero_count = None self.pos_args = [] self.desc = None self.flags_with_args = [] @@ -154,7 +148,7 @@ class Command: self._inspect_func() - def _check_prerequisites(self, win_id, count): + def _check_prerequisites(self, win_id): """Check if the command is permitted to run currently. Args: @@ -170,11 +164,6 @@ class Command: "{}: Only available with {} " "backend.".format(self.name, self.backend.name)) - if count == 0 and not self._zero_count: - raise cmdexc.PrerequisitesError( - "{}: A zero count is not allowed for this command!" - .format(self.name)) - if self.deprecated: message.warning('{} is deprecated - {}'.format(self.name, self.deprecated)) @@ -246,9 +235,6 @@ class Command: assert param.kind != inspect.Parameter.POSITIONAL_ONLY if param.name == 'self': continue - arg_info = self.get_arg_info(param) - if arg_info.count: - self._zero_count = arg_info.zero_count if self._inspect_special_param(param): continue if (param.kind == inspect.Parameter.KEYWORD_ONLY and @@ -532,7 +518,7 @@ class Command: e.status, e)) return self._count = count - self._check_prerequisites(win_id, count) + self._check_prerequisites(win_id) posargs, kwargs = self._get_call_args(win_id) log.commands.debug('Calling {}'.format( debug_utils.format_call(self.handler, posargs, kwargs))) diff --git a/qutebrowser/keyinput/basekeyparser.py b/qutebrowser/keyinput/basekeyparser.py index 3114f4663..7325223a3 100644 --- a/qutebrowser/keyinput/basekeyparser.py +++ b/qutebrowser/keyinput/basekeyparser.py @@ -147,6 +147,9 @@ class BaseKeyParser(QObject): (countstr, cmd_input) = re.match(r'^(\d*)(.*)', self._keystring).groups() count = int(countstr) if countstr else None + if count == 0 and not cmd_input: + cmd_input = self._keystring + count = None else: cmd_input = self._keystring count = None diff --git a/tests/end2end/features/misc.feature b/tests/end2end/features/misc.feature index 20ace1959..143988c64 100644 --- a/tests/end2end/features/misc.feature +++ b/tests/end2end/features/misc.feature @@ -529,10 +529,6 @@ Feature: Various utility commands. And I set general -> private-browsing to false Then the page should contain the plaintext "Local storage status: not working" - Scenario: Using 0 as count - When I run :scroll down with count 0 - Then the error "scroll: A zero count is not allowed for this command!" should be shown - @no_xvfb Scenario: :window-only Given I run :tab-only diff --git a/tests/end2end/features/tabs.feature b/tests/end2end/features/tabs.feature index 1663dd8d7..143e9f2d4 100644 --- a/tests/end2end/features/tabs.feature +++ b/tests/end2end/features/tabs.feature @@ -255,6 +255,7 @@ Feature: Tab management - data/numbers/2.txt (active) - data/numbers/3.txt + Scenario: :tab-focus with invalid negative index Scenario: :tab-focus with count 0 When I open data/numbers/1.txt And I open data/numbers/2.txt in a new tab @@ -266,7 +267,6 @@ Feature: Tab management - data/numbers/2.txt - data/numbers/3.txt (active) - Scenario: :tab-focus with invalid negative index When I open data/numbers/1.txt And I open data/numbers/2.txt in a new tab And I open data/numbers/3.txt in a new tab diff --git a/tests/unit/commands/test_cmdutils.py b/tests/unit/commands/test_cmdutils.py index aaf63014e..28e7d5fed 100644 --- a/tests/unit/commands/test_cmdutils.py +++ b/tests/unit/commands/test_cmdutils.py @@ -423,16 +423,6 @@ class TestArgument: assert str(excinfo.value) == "Argument marked as both count/win_id!" - def test_count_and_zero_count_arg(self): - with pytest.raises(TypeError) as excinfo: - @cmdutils.argument('arg', count=False, zero_count=True) - def fun(arg=0): - """Blah.""" - pass - - expected = "zero_count argument cannot exist without count!" - assert str(excinfo.value) == expected - def test_no_docstring(self, caplog): with caplog.at_level(logging.WARNING): @cmdutils.register() diff --git a/tests/unit/keyinput/test_basekeyparser.py b/tests/unit/keyinput/test_basekeyparser.py index 3e55d0366..da1ecfbdf 100644 --- a/tests/unit/keyinput/test_basekeyparser.py +++ b/tests/unit/keyinput/test_basekeyparser.py @@ -245,6 +245,12 @@ class TestKeyChain: 'ba', keyparser.Type.chain, None) assert keyparser._keystring == '' + def test_0_press(self, handle_text, keyparser): + handle_text((Qt.Key_0, '0')) + keyparser.execute.assert_called_once_with( + '0', keyparser.Type.chain, None) + assert keyparser._keystring == '' + def test_ambiguous_keychain(self, qapp, handle_text, config_stub, keyparser): config_stub.data = CONFIG @@ -308,8 +314,9 @@ class TestCount: def test_count_0(self, handle_text, keyparser): handle_text((Qt.Key_0, '0'), (Qt.Key_B, 'b'), (Qt.Key_A, 'a')) - keyparser.execute.assert_called_once_with( - 'ba', keyparser.Type.chain, 0) + calls = [mock.call('0', keyparser.Type.chain, None), + mock.call('ba', keyparser.Type.chain, None)] + keyparser.execute.assert_has_calls(calls) assert keyparser._keystring == '' def test_count_42(self, handle_text, keyparser): From a3482a89793484537c0902c2e3599be6ca1a006b Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 23 Nov 2016 12:37:19 +0100 Subject: [PATCH 011/561] Lowercase special keys when checking if they're new Between v0.8.4 and now, the default keybinding for Ctrl-PgDown changed: - ('tab-focus', ['J', '']), + ('tab-next', ['J', '']), The existing keybinding was lower-cased in the config, but _is_new didn't lowercase the new one, causing a conflict to show up. Fixes #1835 See #1958 Supersedes #1986 --- qutebrowser/config/parsers/keyconf.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/qutebrowser/config/parsers/keyconf.py b/qutebrowser/config/parsers/keyconf.py index e602c33d5..3a658c4b1 100644 --- a/qutebrowser/config/parsers/keyconf.py +++ b/qutebrowser/config/parsers/keyconf.py @@ -280,6 +280,9 @@ class KeyConfigParser(QObject): A binding is considered new if both the command is not bound to any key yet, and the key isn't used anywhere else in the same section. """ + if utils.is_special_key(keychain): + keychain = keychain.lower() + try: bindings = self.keybindings[sectname] except KeyError: From 371e779d986f82b68df687029bb36fc967308d87 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 23 Nov 2016 13:14:44 +0100 Subject: [PATCH 012/561] Fix tabs.feature --- tests/end2end/features/tabs.feature | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/tests/end2end/features/tabs.feature b/tests/end2end/features/tabs.feature index 143e9f2d4..8066a6518 100644 --- a/tests/end2end/features/tabs.feature +++ b/tests/end2end/features/tabs.feature @@ -256,17 +256,6 @@ Feature: Tab management - data/numbers/3.txt Scenario: :tab-focus with invalid negative index - Scenario: :tab-focus with count 0 - When I open data/numbers/1.txt - And I open data/numbers/2.txt in a new tab - And I open data/numbers/3.txt in a new tab - And I run :tab-focus with count 1 - And I run :tab-focus with count 0 - Then the following tabs should be open: - - data/numbers/1.txt - - data/numbers/2.txt - - data/numbers/3.txt (active) - When I open data/numbers/1.txt And I open data/numbers/2.txt in a new tab And I open data/numbers/3.txt in a new tab From c57ad91e0476a807bb3997f4daa4158aa30b7a63 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 24 Nov 2016 06:43:33 +0100 Subject: [PATCH 013/561] Update comment for ignored Qt warning --- qutebrowser/utils/log.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutebrowser/utils/log.py b/qutebrowser/utils/log.py index 1a3db19be..05163577c 100644 --- a/qutebrowser/utils/log.py +++ b/qutebrowser/utils/log.py @@ -409,7 +409,7 @@ def qt_message_handler(msg_type, context, msg): "QXcbClipboard: SelectionRequest too old", # https://github.com/The-Compiler/qutebrowser/issues/2071 'QXcbWindow: Unhandled client message: ""', - # No idea where this comes from... + # https://codereview.qt-project.org/176831 "QObject::disconnect: Unexpected null parameter", ] if sys.platform == 'darwin': From 1b5664b72fffd683a3a92aec81af4500f4ae78d0 Mon Sep 17 00:00:00 2001 From: Niklas Haas Date: Thu, 11 Aug 2016 04:31:42 +0200 Subject: [PATCH 014/561] Add --cycle flag to :set Chooses the next value from the provided list of values (string-wise comparison). Technically, the 'option!' syntax for toggling bools is now redundant, but a translation from 'option!' to '--cycle option false true' is kept for backwards compatibility. The '--cycle' flag could also be technically optionally, since the only thing that depends on it is preserving the error message for specifying multiple values. (But I think it's best to keep it explicit, as a principle-of-least-surprise thing) Note: The business logic of picking the next value and setting it was moved out to a separate function to avoid tripping pylint's too-many-branches detector. Fixes #47 --- CHANGELOG.asciidoc | 1 + qutebrowser/config/config.py | 46 ++++++++++++++++++++++-------- tests/end2end/features/set.feature | 24 ++++++++++++++++ 3 files changed, 59 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index d0e412ced..4c3ebec9d 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -149,6 +149,7 @@ Changed - `ui -> window-title-format` now has a new `{backend} ` replacement - `:hint` has a new `--add-history` argument to add the URL to the history for yank/spawn targets. +- `:set` now has a `--cycle` flag which lets you cycle through multiple options. Deprecated ~~~~~~~~~~ diff --git a/qutebrowser/config/config.py b/qutebrowser/config/config.py index 57c498400..3bf559e66 100644 --- a/qutebrowser/config/config.py +++ b/qutebrowser/config/config.py @@ -772,13 +772,13 @@ class ConfigManager(QObject): raise cmdexc.CommandError("set: {} - {}".format( e.__class__.__name__, e)) - @cmdutils.register(name='set', instance='config') + @cmdutils.register(name='set', instance='config', star_args_optional=True) @cmdutils.argument('section_', completion=Completion.section) @cmdutils.argument('option', completion=Completion.option) - @cmdutils.argument('value', completion=Completion.value) + @cmdutils.argument('values', completion=Completion.value) @cmdutils.argument('win_id', win_id=True) - def set_command(self, win_id, section_=None, option=None, value=None, - temp=False, print_=False): + def set_command(self, win_id, section_=None, option=None, *values, + temp=False, print_=False, cycle=False): """Set an option. If the option name ends with '?', the value of the option is shown @@ -793,9 +793,10 @@ class ConfigManager(QObject): Args: section_: The section where the option is in. option: The name of the option. - value: The value to set. + values: The value to set, or the values to cycle through. temp: Set value temporarily. print_: Print the value after setting. + cycle: Cycle through multiple provided values. """ if section_ is not None and option is None: raise cmdexc.CommandError( @@ -812,27 +813,48 @@ class ConfigManager(QObject): print_ = True else: with self._handle_config_error(): - if option.endswith('!') and option != '!' and value is None: + if option.endswith('!') and option != '!' and not values: + # Handle inversion as special cases of the cycle code path option = option[:-1] val = self.get(section_, option) - layer = 'temp' if temp else 'conf' if isinstance(val, bool): - self.set(layer, section_, option, str(not val).lower()) + values = ['false', 'true'] else: raise cmdexc.CommandError( "set: Attempted inversion of non-boolean value.") - elif value is not None: - layer = 'temp' if temp else 'conf' - self.set(layer, section_, option, value) - else: + elif not values: raise cmdexc.CommandError("set: The following arguments " "are required: value") + elif not cycle and len(values) > 1: + raise cmdexc.CommandError("set: Too many values provided") + + layer = 'temp' if temp else 'conf' + self._set_next(layer, section_, option, values) if print_: with self._handle_config_error(): val = self.get(section_, option, transformed=False) message.info("{} {} = {}".format(section_, option, val)) + def _set_next(self, layer, section_, option, values): + """Set the next value out of a list of values.""" + if len(values) == 1: + # If we have only one value, just set it directly (avoid + # breaking stuff like aliases or other pseudo-settings) + self.set(layer, section_, option, values[0]) + else: + # Otherwise, use the next valid value from values, or the + # first if the current value does not appear in the list + assert len(values) > 1 + val = self.get(section_, option, transformed=False) + try: + idx = values.index(str(val)) + idx = (idx + 1) % len(values) + value = values[idx] + except ValueError: + value = values[0] + self.set(layer, section_, option, value) + def set(self, layer, sectname, optname, value, validate=True): """Set an option. diff --git a/tests/end2end/features/set.feature b/tests/end2end/features/set.feature index 1b1185a5c..76d92793b 100644 --- a/tests/end2end/features/set.feature +++ b/tests/end2end/features/set.feature @@ -15,6 +15,10 @@ Feature: Setting settings. When I run :set colors statusbar.bg Then the error "set: The following arguments are required: value" should be shown + Scenario: With too many values + When I run :set colors statusbar.bg green blue + Then the error "set: Too many values provided" should be shown + Scenario: Invalid section When I run :set blah blub foo Then the error "set: Section 'blah' does not exist!" should be shown @@ -32,6 +36,26 @@ Feature: Setting settings. When I run :set colors statusbar.bg! Then the error "set: Attempted inversion of non-boolean value." should be shown + Scenario: Cycling an option + When I run :set colors statusbar.bg magenta + And I run :set --cycle colors statusbar.bg green magenta blue yellow + Then colors -> statusbar.bg should be blue + + Scenario: Cycling an option through the end of the list + When I run :set colors statusbar.bg yellow + And I run :set --cycle colors statusbar.bg green magenta blue yellow + Then colors -> statusbar.bg should be green + + Scenario: Cycling an option that's not on the list + When I run :set colors statusbar.bg red + And I run :set --cycle colors statusbar.bg green magenta blue yellow + Then colors -> statusbar.bg should be green + + Scenario: Cycling through a single option + When I run :set colors statusbar.bg red + And I run :set --cycle colors statusbar.bg red + Then colors -> statusbar.bg should be red + Scenario: Getting an option When I run :set colors statusbar.bg magenta And I run :set colors statusbar.bg? From 3d140a1353b82e06b795a6354903067489c3258b Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 24 Nov 2016 07:23:37 +0100 Subject: [PATCH 015/561] Get rid of --cycle for :set with multiple values See https://github.com/The-Compiler/qutebrowser/pull/1840#issuecomment-258714784 --- CHANGELOG.asciidoc | 2 +- qutebrowser/config/config.py | 5 +---- tests/end2end/features/set.feature | 12 ++++-------- 3 files changed, 6 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 4c3ebec9d..26b0b5552 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -149,7 +149,7 @@ Changed - `ui -> window-title-format` now has a new `{backend} ` replacement - `:hint` has a new `--add-history` argument to add the URL to the history for yank/spawn targets. -- `:set` now has a `--cycle` flag which lets you cycle through multiple options. +- `:set` now cycles through values if more than one argument is given. Deprecated ~~~~~~~~~~ diff --git a/qutebrowser/config/config.py b/qutebrowser/config/config.py index 3bf559e66..9e8f14621 100644 --- a/qutebrowser/config/config.py +++ b/qutebrowser/config/config.py @@ -778,7 +778,7 @@ class ConfigManager(QObject): @cmdutils.argument('values', completion=Completion.value) @cmdutils.argument('win_id', win_id=True) def set_command(self, win_id, section_=None, option=None, *values, - temp=False, print_=False, cycle=False): + temp=False, print_=False): """Set an option. If the option name ends with '?', the value of the option is shown @@ -796,7 +796,6 @@ class ConfigManager(QObject): values: The value to set, or the values to cycle through. temp: Set value temporarily. print_: Print the value after setting. - cycle: Cycle through multiple provided values. """ if section_ is not None and option is None: raise cmdexc.CommandError( @@ -825,8 +824,6 @@ class ConfigManager(QObject): elif not values: raise cmdexc.CommandError("set: The following arguments " "are required: value") - elif not cycle and len(values) > 1: - raise cmdexc.CommandError("set: Too many values provided") layer = 'temp' if temp else 'conf' self._set_next(layer, section_, option, values) diff --git a/tests/end2end/features/set.feature b/tests/end2end/features/set.feature index 76d92793b..769605c3e 100644 --- a/tests/end2end/features/set.feature +++ b/tests/end2end/features/set.feature @@ -15,10 +15,6 @@ Feature: Setting settings. When I run :set colors statusbar.bg Then the error "set: The following arguments are required: value" should be shown - Scenario: With too many values - When I run :set colors statusbar.bg green blue - Then the error "set: Too many values provided" should be shown - Scenario: Invalid section When I run :set blah blub foo Then the error "set: Section 'blah' does not exist!" should be shown @@ -38,22 +34,22 @@ Feature: Setting settings. Scenario: Cycling an option When I run :set colors statusbar.bg magenta - And I run :set --cycle colors statusbar.bg green magenta blue yellow + And I run :set colors statusbar.bg green magenta blue yellow Then colors -> statusbar.bg should be blue Scenario: Cycling an option through the end of the list When I run :set colors statusbar.bg yellow - And I run :set --cycle colors statusbar.bg green magenta blue yellow + And I run :set colors statusbar.bg green magenta blue yellow Then colors -> statusbar.bg should be green Scenario: Cycling an option that's not on the list When I run :set colors statusbar.bg red - And I run :set --cycle colors statusbar.bg green magenta blue yellow + And I run :set colors statusbar.bg green magenta blue yellow Then colors -> statusbar.bg should be green Scenario: Cycling through a single option When I run :set colors statusbar.bg red - And I run :set --cycle colors statusbar.bg red + And I run :set colors statusbar.bg red Then colors -> statusbar.bg should be red Scenario: Getting an option From 26e7ec848f027010994d654d2ce719d23296fb68 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 24 Nov 2016 07:24:26 +0100 Subject: [PATCH 016/561] Update docs --- README.asciidoc | 2 +- doc/help/commands.asciidoc | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.asciidoc b/README.asciidoc index fea1101b6..7a1ef8bf2 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -171,8 +171,8 @@ Contributors, sorted by the number of commits in descending order: * Thorsten Wißmann * Austin Anderson * Jimmy -* Spreadyy * Niklas Haas +* Spreadyy * Alexey "Averrin" Nabrodov * nanjekyejoannah * avk diff --git a/doc/help/commands.asciidoc b/doc/help/commands.asciidoc index 5d9207dff..283acdf9f 100644 --- a/doc/help/commands.asciidoc +++ b/doc/help/commands.asciidoc @@ -733,7 +733,7 @@ Save a session. [[set]] === set -Syntax: +:set [*--temp*] [*--print*] ['section'] ['option'] ['value']+ +Syntax: +:set [*--temp*] [*--print*] ['section'] ['option'] ['values' ['values' ...]]+ Set an option. @@ -742,7 +742,7 @@ If the option name ends with '?', the value of the option is shown instead. If t ==== positional arguments * +'section'+: The section where the option is in. * +'option'+: The name of the option. -* +'value'+: The value to set. +* +'values'+: The value to set, or the values to cycle through. ==== optional arguments * +*-t*+, +*--temp*+: Set value temporarily. From 32cc6bea1d1ca97db97d68b96dd3ac4f50c3685f Mon Sep 17 00:00:00 2001 From: Niklas Haas Date: Thu, 11 Aug 2016 05:15:08 +0200 Subject: [PATCH 017/561] Add tests for the completion engine I tested everything that I thought was interesting enough to warrant a test: especially the ability to test multiple parameters deep, as well as testing :set --cycle and some involved example to make sure completion actually works and updates in realtime --- tests/end2end/features/completion.feature | 81 ++++++++++++++++++- tests/end2end/features/conftest.py | 26 +++++- tests/end2end/features/test_completion_bdd.py | 10 ++- tests/end2end/features/test_set_bdd.py | 6 -- tests/end2end/fixtures/quteprocess.py | 15 ++-- 5 files changed, 122 insertions(+), 16 deletions(-) diff --git a/tests/end2end/features/completion.feature b/tests/end2end/features/completion.feature index 4fec2d81b..1d39e6bff 100644 --- a/tests/end2end/features/completion.feature +++ b/tests/end2end/features/completion.feature @@ -1,4 +1,4 @@ -Feature: Command bar completion +Feature: Using completion Scenario: No warnings when completing with one entry (#1600) Given I open about:blank @@ -27,3 +27,82 @@ Feature: Command bar completion Given I open about:blank When I run :set-cmd-text -s :🌀 Then no crash should happen + + Scenario: Using command completion + When I run :set-cmd-text : + Then the completion model should be CommandCompletionModel + + Scenario: Using help completion + When I run :set-cmd-text -s :help + Then the completion model should be HelpCompletionModel + + Scenario: Using quickmark completion + When I run :set-cmd-text -s :quickmark-load + Then the completion model should be QuickmarkCompletionModel + + Scenario: Using bookmark completion + When I run :set-cmd-text -s :bookmark-load + Then the completion model should be BookmarkCompletionModel + + Scenario: Using bind completion + When I run :set-cmd-text -s :bind X + Then the completion model should be BindCompletionModel + + Scenario: Using session completion + Given I open data/hello.txt + And I run :session-save hello + When I run :set-cmd-text -s :session-load + And I run :completion-item-focus next + And I run :completion-item-focus next + And I run :session-delete hello + And I run :command-accept + Then the error "Session hello not found!" should be shown + + Scenario: Using option completion + When I run :set-cmd-text -s :set colors + Then the completion model should be SettingOptionCompletionModel + + Scenario: Using value completion + When I run :set-cmd-text -s :set colors statusbar.bg + Then the completion model should be SettingValueCompletionModel + + Scenario: Using value completion multiple times + When I run :set-cmd-text -s :set --cycle colors statusbar.bg black + Then the completion model should be SettingValueCompletionModel + + Scenario: Updating the completion in realtime + Given I have a fresh instance + And I set completion -> quick-complete to false + When I open data/hello.txt + And I run :set-cmd-text -s :buffer + And I run :completion-item-focus next + And I open data/hello2.txt in a new background tab + And I run :completion-item-focus next + And I open data/hello3.txt in a new background tab + And I run :completion-item-focus next + And I run :command-accept + Then the following tabs should be open: + - data/hello.txt + - data/hello2.txt + - data/hello3.txt (active) + + Scenario: Updating the value completion in realtime + Given I set colors -> statusbar.bg to green + When I run :set-cmd-text -s :set colors statusbar.bg + And I set colors -> statusbar.bg to yellow + And I run :completion-item-focus next + And I run :completion-item-focus next + And I set colors -> statusbar.bg to red + And I run :command-accept + Then colors -> statusbar.bg should be yellow + + Scenario: Deleting an open tab via the completion + Given I have a fresh instance + When I open data/hello.txt + And I open data/hello2.txt in a new tab + And I run :set-cmd-text -s :buffer + And I run :completion-item-focus next + And I run :completion-item-focus next + And I run :completion-item-del + Then the following tabs should be open: + - data/hello.txt (active) diff --git a/tests/end2end/features/conftest.py b/tests/end2end/features/conftest.py index 7dd42c2d0..ea6b96bbf 100644 --- a/tests/end2end/features/conftest.py +++ b/tests/end2end/features/conftest.py @@ -181,11 +181,13 @@ def open_path(quteproc, path): "... as a URL", it's opened according to new-instance-open-target. """ new_tab = False + new_bg_tab = False new_window = False as_url = False wait = True new_tab_suffix = ' in a new tab' + new_bg_tab_suffix = ' in a new background tab' new_window_suffix = ' in a new window' do_not_wait_suffix = ' without waiting' as_url_suffix = ' as a URL' @@ -206,8 +208,22 @@ def open_path(quteproc, path): else: break - quteproc.open_path(path, new_tab=new_tab, new_window=new_window, - as_url=as_url, wait=wait) + if path.endswith(new_tab_suffix): + path = path[:-len(new_tab_suffix)] + new_tab = True + elif path.endswith(new_bg_tab_suffix): + path = path[:-len(new_bg_tab_suffix)] + new_bg_tab = True + elif path.endswith(new_window_suffix): + path = path[:-len(new_window_suffix)] + new_window = True + + if path.endswith(do_not_wait_suffix): + path = path[:-len(do_not_wait_suffix)] + wait = False + + quteproc.open_path(path, new_tab=new_tab, new_bg_tab=new_bg_tab, + new_window=new_window, as_url=as_url, wait=wait) @bdd.when(bdd.parsers.parse("I set {sect} -> {opt} to {value}")) @@ -595,3 +611,9 @@ def check_not_scrolled(request, quteproc): x, y = _get_scroll_values(quteproc) assert x == 0 assert y == 0 + + +@bdd.then(bdd.parsers.parse("{section} -> {option} should be {value}")) +def check_option(quteproc, section, option, value): + actual_value = quteproc.get_setting(section, option) + assert actual_value == value diff --git a/tests/end2end/features/test_completion_bdd.py b/tests/end2end/features/test_completion_bdd.py index 4c2b43c8f..c8470441a 100644 --- a/tests/end2end/features/test_completion_bdd.py +++ b/tests/end2end/features/test_completion_bdd.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016 Ryan Roden-Corrent (rcorre) +# Copyright 2015-2016 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # @@ -17,5 +17,13 @@ # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see . +import re import pytest_bdd as bdd bdd.scenarios('completion.feature') + + +@bdd.then(bdd.parsers.parse("the completion model should be {model}")) +def check_model(quteproc, model): + """Make sure the completion model was set to something.""" + pattern = "New completion [^:]*: {}".format(model) + quteproc.wait_for(message=re.compile(pattern)) diff --git a/tests/end2end/features/test_set_bdd.py b/tests/end2end/features/test_set_bdd.py index 30b33aedb..2eabd8c56 100644 --- a/tests/end2end/features/test_set_bdd.py +++ b/tests/end2end/features/test_set_bdd.py @@ -19,9 +19,3 @@ import pytest_bdd as bdd bdd.scenarios('set.feature') - - -@bdd.then(bdd.parsers.parse("{section} -> {option} should be {value}")) -def check_option(quteproc, section, option, value): - actual_value = quteproc.get_setting(section, option) - assert actual_value == value diff --git a/tests/end2end/fixtures/quteprocess.py b/tests/end2end/fixtures/quteprocess.py index 53f3bee88..cabc16954 100644 --- a/tests/end2end/fixtures/quteprocess.py +++ b/tests/end2end/fixtures/quteprocess.py @@ -475,15 +475,16 @@ class QuteProc(testprocess.Process): yield self.set_setting(sect, opt, old_value) - def open_path(self, path, *, new_tab=False, new_window=False, as_url=False, - port=None, https=False, wait=True): + def open_path(self, path, *, new_tab=False, new_bg_tab=False, + new_window=False, as_url=False, port=None, https=False, + wait=True): """Open the given path on the local webserver in qutebrowser.""" url = self.path_to_url(path, port=port, https=https) - self.open_url(url, new_tab=new_tab, new_window=new_window, - as_url=as_url, wait=wait) + self.open_url(url, new_tab=new_tab, new_bg_tab=new_bg_tab, + new_window=new_window, wait=wait) - def open_url(self, url, *, new_tab=False, new_window=False, as_url=False, - wait=True): + def open_url(self, url, *, new_tab=False, new_bg_tab=False, + new_window=False, as_url=False, wait=True): """Open the given url in qutebrowser.""" if new_tab and new_window: raise ValueError("new_tab and new_window given!") @@ -492,6 +493,8 @@ class QuteProc(testprocess.Process): self.send_cmd(url, invalid=True) elif new_tab: self.send_cmd(':open -t ' + url) + elif new_bg_tab: + self.send_cmd(':open -b ' + url) elif new_window: self.send_cmd(':open -w ' + url) else: From 8ac16c0c4c156d8a2489fd039cb91077c71427da Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 24 Nov 2016 07:33:50 +0100 Subject: [PATCH 018/561] Update pattern for completion tests --- tests/end2end/features/test_completion_bdd.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/end2end/features/test_completion_bdd.py b/tests/end2end/features/test_completion_bdd.py index c8470441a..8927797ca 100644 --- a/tests/end2end/features/test_completion_bdd.py +++ b/tests/end2end/features/test_completion_bdd.py @@ -25,5 +25,5 @@ bdd.scenarios('completion.feature') @bdd.then(bdd.parsers.parse("the completion model should be {model}")) def check_model(quteproc, model): """Make sure the completion model was set to something.""" - pattern = "New completion [^:]*: {}".format(model) - quteproc.wait_for(message=re.compile(pattern)) + pattern = "Setting completion model to {} with pattern *".format(model) + quteproc.wait_for(message=pattern) From 002e30a6ca3a2e74850de4008532537fdc2e0d82 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 24 Nov 2016 07:34:07 +0100 Subject: [PATCH 019/561] Remove :set --cycle completion test --- tests/end2end/features/completion.feature | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/end2end/features/completion.feature b/tests/end2end/features/completion.feature index 1d39e6bff..0cc23a215 100644 --- a/tests/end2end/features/completion.feature +++ b/tests/end2end/features/completion.feature @@ -66,10 +66,6 @@ Feature: Using completion When I run :set-cmd-text -s :set colors statusbar.bg Then the completion model should be SettingValueCompletionModel - Scenario: Using value completion multiple times - When I run :set-cmd-text -s :set --cycle colors statusbar.bg black - Then the completion model should be SettingValueCompletionModel - Scenario: Updating the completion in realtime Given I have a fresh instance And I set completion -> quick-complete to false From 34dd30e9844c775e98ea96d498cab6ddd21b8830 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 24 Nov 2016 08:20:16 +0100 Subject: [PATCH 020/561] Fix bad merge --- tests/end2end/features/conftest.py | 17 +++-------------- tests/end2end/fixtures/quteprocess.py | 2 +- 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/tests/end2end/features/conftest.py b/tests/end2end/features/conftest.py index ea6b96bbf..789fda108 100644 --- a/tests/end2end/features/conftest.py +++ b/tests/end2end/features/conftest.py @@ -196,6 +196,9 @@ def open_path(quteproc, path): if path.endswith(new_tab_suffix): path = path[:-len(new_tab_suffix)] new_tab = True + elif path.endswith(new_bg_tab_suffix): + path = path[:-len(new_bg_tab_suffix)] + new_bg_tab = True elif path.endswith(new_window_suffix): path = path[:-len(new_window_suffix)] new_window = True @@ -208,20 +211,6 @@ def open_path(quteproc, path): else: break - if path.endswith(new_tab_suffix): - path = path[:-len(new_tab_suffix)] - new_tab = True - elif path.endswith(new_bg_tab_suffix): - path = path[:-len(new_bg_tab_suffix)] - new_bg_tab = True - elif path.endswith(new_window_suffix): - path = path[:-len(new_window_suffix)] - new_window = True - - if path.endswith(do_not_wait_suffix): - path = path[:-len(do_not_wait_suffix)] - wait = False - quteproc.open_path(path, new_tab=new_tab, new_bg_tab=new_bg_tab, new_window=new_window, as_url=as_url, wait=wait) diff --git a/tests/end2end/fixtures/quteprocess.py b/tests/end2end/fixtures/quteprocess.py index cabc16954..8b2134769 100644 --- a/tests/end2end/fixtures/quteprocess.py +++ b/tests/end2end/fixtures/quteprocess.py @@ -481,7 +481,7 @@ class QuteProc(testprocess.Process): """Open the given path on the local webserver in qutebrowser.""" url = self.path_to_url(path, port=port, https=https) self.open_url(url, new_tab=new_tab, new_bg_tab=new_bg_tab, - new_window=new_window, wait=wait) + new_window=new_window, as_url=as_url, wait=wait) def open_url(self, url, *, new_tab=False, new_bg_tab=False, new_window=False, as_url=False, wait=True): From 98dc92fe16f529d6862ab540e524785db755ca93 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 24 Nov 2016 08:50:54 +0100 Subject: [PATCH 021/561] Remove unused import --- tests/end2end/features/test_completion_bdd.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/end2end/features/test_completion_bdd.py b/tests/end2end/features/test_completion_bdd.py index 8927797ca..030a16ffc 100644 --- a/tests/end2end/features/test_completion_bdd.py +++ b/tests/end2end/features/test_completion_bdd.py @@ -17,7 +17,6 @@ # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see . -import re import pytest_bdd as bdd bdd.scenarios('completion.feature') From 64b47fc3a2f4a58aefc6ec34b01a95f4cc1ceb58 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 24 Nov 2016 10:14:02 +0100 Subject: [PATCH 022/561] Handle an invalid $XDG_RUNTIME_DIR gracefully This is probably against the XDG basedir spec, but some people have a Linux without anything setting $XDG_RUNTIME_DIR correctly. Fixes #971. --- CHANGELOG.asciidoc | 2 ++ qutebrowser/utils/standarddir.py | 18 ++++++++++++++++-- tests/unit/utils/test_standarddir.py | 19 ++++++++++++++++++- 3 files changed, 36 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 26b0b5552..52e466018 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -187,6 +187,8 @@ Fixed - `:tab-detach` now fails correctly when there's only one tab open. - Various small issues with the command completion - Fixed hang when using multiple spaces in a row with the URL completion +- qutebrowser now still starts with an incorrectly configured + `$XDG_RUNTIME_DIR`. v0.8.3 ------ diff --git a/qutebrowser/utils/standarddir.py b/qutebrowser/utils/standarddir.py index 05fe7ce27..7c756b805 100644 --- a/qutebrowser/utils/standarddir.py +++ b/qutebrowser/utils/standarddir.py @@ -33,6 +33,11 @@ from qutebrowser.utils import log, qtutils, debug _args = None +class EmptyValueError(Exception): + + """Error raised when QStandardPaths returns an empty value.""" + + def config(): """Get a location for configs.""" typ = QStandardPaths.ConfigLocation @@ -104,9 +109,18 @@ def runtime(): else: # pragma: no cover # RuntimeLocation is a weird path on OS X and Windows. typ = QStandardPaths.TempLocation + overridden, path = _from_args(typ, _args) + if not overridden: - path = _writable_location(typ) + try: + path = _writable_location(typ) + except EmptyValueError: + # Fall back to TempLocation when RuntimeLocation is misconfigured + if typ == QStandardPaths.TempLocation: + raise + path = _writable_location(QStandardPaths.TempLocation) + # This is generic, but per-user. # # For TempLocation: @@ -128,7 +142,7 @@ def _writable_location(typ): typ_str = debug.qenum_key(QStandardPaths, typ) log.misc.debug("writable location for {}: {}".format(typ_str, path)) if not path: - raise ValueError("QStandardPaths returned an empty value!") + raise EmptyValueError("QStandardPaths returned an empty value!") # Qt seems to use '/' as path separator even on Windows... path = path.replace('/', os.sep) return path diff --git a/tests/unit/utils/test_standarddir.py b/tests/unit/utils/test_standarddir.py index eb348132b..aa7d59f0b 100644 --- a/tests/unit/utils/test_standarddir.py +++ b/tests/unit/utils/test_standarddir.py @@ -90,7 +90,7 @@ class TestWritableLocation: monkeypatch.setattr( 'qutebrowser.utils.standarddir.QStandardPaths.writableLocation', lambda typ: '') - with pytest.raises(ValueError): + with pytest.raises(standarddir.EmptyValueError): standarddir._writable_location(QStandardPaths.DataLocation) def test_sep(self, monkeypatch): @@ -137,6 +137,23 @@ class TestStandardDir: monkeypatch.delenv('XDG_{}_HOME'.format(var), raising=False) assert func() == str(tmpdir.join(*subdirs)) + @pytest.mark.linux + @pytest.mark.qt_log_ignore(r'^QStandardPaths: XDG_RUNTIME_DIR points to ' + r'non-existing path') + def test_linux_invalid_runtimedir(self, monkeypatch, tmpdir): + """With invalid XDG_RUNTIME_DIR, fall back to TempLocation.""" + monkeypatch.setenv('XDG_RUNTIME_DIR', str(tmpdir / 'does-not-exist')) + monkeypatch.setenv('TMPDIR', str(tmpdir / 'temp')) + assert standarddir.runtime() == str(tmpdir / 'temp' / 'qute_test') + + def test_runtimedir_empty_tempdir(self, monkeypatch, tmpdir): + """With an empty tempdir on non-Linux, we should raise.""" + monkeypatch.setattr(standarddir.sys, 'platform', 'nt') + monkeypatch.setattr(standarddir.QStandardPaths, 'writableLocation', + lambda typ: '') + with pytest.raises(standarddir.EmptyValueError): + standarddir.runtime() + @pytest.mark.parametrize('func, elems, expected', [ (standarddir.data, 2, ['qute_test', 'data']), (standarddir.config, 1, ['qute_test']), From 3ca4916b7642a25a5907dbb74e03ee8385e50401 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 24 Nov 2016 10:37:56 +0100 Subject: [PATCH 023/561] Relax qt_log_ignore pattern Earlier Qt versions show this: QStandardPaths: wrong ownership on runtime directory /tmp/pytest-of-user/pytest-0/test_linux_invalid_runtimedir0/does-not-exist, -2 instead of 1000 --- tests/unit/utils/test_standarddir.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/unit/utils/test_standarddir.py b/tests/unit/utils/test_standarddir.py index aa7d59f0b..02ded14c7 100644 --- a/tests/unit/utils/test_standarddir.py +++ b/tests/unit/utils/test_standarddir.py @@ -138,8 +138,7 @@ class TestStandardDir: assert func() == str(tmpdir.join(*subdirs)) @pytest.mark.linux - @pytest.mark.qt_log_ignore(r'^QStandardPaths: XDG_RUNTIME_DIR points to ' - r'non-existing path') + @pytest.mark.qt_log_ignore(r'^QStandardPaths: ') def test_linux_invalid_runtimedir(self, monkeypatch, tmpdir): """With invalid XDG_RUNTIME_DIR, fall back to TempLocation.""" monkeypatch.setenv('XDG_RUNTIME_DIR', str(tmpdir / 'does-not-exist')) From ea44c6d4b99cf3661620317047f8565ef8535237 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 26 Nov 2016 11:48:57 +0100 Subject: [PATCH 024/561] eslint: Turn off capitalized-comments --- qutebrowser/javascript/.eslintrc.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/qutebrowser/javascript/.eslintrc.yaml b/qutebrowser/javascript/.eslintrc.yaml index de3f9e6ef..756cc362e 100644 --- a/qutebrowser/javascript/.eslintrc.yaml +++ b/qutebrowser/javascript/.eslintrc.yaml @@ -36,3 +36,4 @@ rules: sort-keys: "off" no-warning-comments: "off" max-len: ["error", {"ignoreUrls": true}] + capitalized-comments: "off" From 73c608dce7ba30932368815834152c3724d92cee Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 26 Nov 2016 12:17:23 +0100 Subject: [PATCH 025/561] Allow :open without URL with -t/-b/-w --- CHANGELOG.asciidoc | 1 + qutebrowser/browser/commands.py | 7 ++----- tests/end2end/features/open.feature | 7 ++++--- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 52e466018..51301965d 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -150,6 +150,7 @@ Changed - `:hint` has a new `--add-history` argument to add the URL to the history for yank/spawn targets. - `:set` now cycles through values if more than one argument is given. +- `:open` now opens `default-page` without an URL even without `-t`/`-b`/`-w` given. Deprecated ~~~~~~~~~~ diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index c13cde990..97fc8c186 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -257,13 +257,10 @@ class CommandDispatcher: count: The tab index to open the URL in, or None. """ if url is None: - if tab or bg or window: - urls = [config.get('general', 'default-page')] - else: - raise cmdexc.CommandError("No URL given, but -t/-b/-w is not " - "set!") + urls = [config.get('general', 'default-page')] else: urls = self._parse_url_input(url) + for i, cur_url in enumerate(urls): if not window and i > 0: tab = False diff --git a/tests/end2end/features/open.feature b/tests/end2end/features/open.feature index 3f6715e6a..89d6c9aa2 100644 --- a/tests/end2end/features/open.feature +++ b/tests/end2end/features/open.feature @@ -14,9 +14,10 @@ Feature: Opening pages - active: true url: http://localhost:*/data/numbers/1.txt - Scenario: :open without URL and no -t/-b/-w - When I run :open - Then the error "No URL given, but -t/-b/-w is not set!" should be shown + Scenario: :open without URL + When I set general -> default-page to http://localhost:(port)/data/numbers/11.txt + And I run :open + Then data/numbers/11.txt should be loaded Scenario: :open without URL and -t When I set general -> default-page to http://localhost:(port)/data/numbers/2.txt From c17bbd47b818cf40165d51204eb4df6acc24e7df Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 26 Nov 2016 13:32:37 +0100 Subject: [PATCH 026/561] Fix QTBUG-54419 workaround for invalid URLs --- qutebrowser/mainwindow/tabbedbrowser.py | 2 +- tests/end2end/features/hints.feature | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py index 4198c35c6..c5f0897ac 100644 --- a/qutebrowser/mainwindow/tabbedbrowser.py +++ b/qutebrowser/mainwindow/tabbedbrowser.py @@ -527,7 +527,7 @@ class TabbedBrowser(tabwidget.TabWidget): background = self.currentIndex() != idx if (tab.backend == usertypes.Backend.QtWebEngine and - tab.needs_qtbug54419_workaround): + tab.needs_qtbug54419_workaround and url.isValid()): log.misc.debug("Doing QTBUG-54419 workaround for {}, " "url {}".format(tab, url)) self.setUpdatesEnabled(False) diff --git a/tests/end2end/features/hints.feature b/tests/end2end/features/hints.feature index b6db8b47a..8d23d0199 100644 --- a/tests/end2end/features/hints.feature +++ b/tests/end2end/features/hints.feature @@ -151,6 +151,11 @@ Feature: Using hints And I hint with args "all" and follow a Then the error "Invalid link clicked - *" should be shown + Scenario: Clicking an invalid link opening in a new tab + When I open data/invalid_link.html + And I hint with args "all tab" and follow a + Then the error "Invalid link clicked - *" should be shown + Scenario: Hinting inputs without type When I open data/hints/input.html And I hint with args "inputs" and follow a From c83c9d96ae2780cc44373fb6c951753f32d059e1 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 26 Nov 2016 13:33:38 +0100 Subject: [PATCH 027/561] Small cleanup --- qutebrowser/mainwindow/tabbedbrowser.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py index c5f0897ac..e613d1d9b 100644 --- a/qutebrowser/mainwindow/tabbedbrowser.py +++ b/qutebrowser/mainwindow/tabbedbrowser.py @@ -524,12 +524,11 @@ class TabbedBrowser(tabwidget.TabWidget): # If needed, re-open the tab as a workaround for QTBUG-54419. # See https://bugreports.qt.io/browse/QTBUG-54419 - background = self.currentIndex() != idx - if (tab.backend == usertypes.Backend.QtWebEngine and tab.needs_qtbug54419_workaround and url.isValid()): log.misc.debug("Doing QTBUG-54419 workaround for {}, " "url {}".format(tab, url)) + background = self.currentIndex() != idx self.setUpdatesEnabled(False) try: self.tabopen(url, background=background, idx=idx) From 59d16efbef95932963555b75c2bdd6460b7df3b3 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 26 Nov 2016 13:35:49 +0100 Subject: [PATCH 028/561] Don't set title from URL twice browsertab already does that before emitting url_changed. --- qutebrowser/mainwindow/tabbedbrowser.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py index e613d1d9b..eb3b97e2c 100644 --- a/qutebrowser/mainwindow/tabbedbrowser.py +++ b/qutebrowser/mainwindow/tabbedbrowser.py @@ -519,9 +519,6 @@ class TabbedBrowser(tabwidget.TabWidget): # We can get signals for tabs we already deleted... return - if not self.page_title(idx): - self.set_page_title(idx, url.toDisplayString()) - # If needed, re-open the tab as a workaround for QTBUG-54419. # See https://bugreports.qt.io/browse/QTBUG-54419 if (tab.backend == usertypes.Backend.QtWebEngine and From 729cc61152335674b970e641c9afd4f168f9cf05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Wo=C5=82czyk?= Date: Tue, 29 Nov 2016 04:03:35 +0100 Subject: [PATCH 029/561] Fixed mhtml quopri encoding with spaces and tabs (see: #2063) --- qutebrowser/browser/webkit/mhtml.py | 19 +- .../data/downloads/mhtml/complex/complex.mht | 298 ++++++++---------- .../data/downloads/mhtml/simple/simple.mht | 15 +- 3 files changed, 164 insertions(+), 168 deletions(-) diff --git a/qutebrowser/browser/webkit/mhtml.py b/qutebrowser/browser/webkit/mhtml.py index 237898809..2e828dd3b 100644 --- a/qutebrowser/browser/webkit/mhtml.py +++ b/qutebrowser/browser/webkit/mhtml.py @@ -32,6 +32,7 @@ import email.generator import email.encoders import email.mime.multipart import email.message +from quopri import encodestring from PyQt5.QtCore import QUrl @@ -138,6 +139,22 @@ def _check_rel(element): return any(rel in rels for rel in must_have) +def _encode_quopri_mhtml(msg): + """Encode the message's payload in quoted-printable. + + Substitute for quopri's default 'encode_quopri' method, which needlessly + encodes all spaces and tabs, instead of only those at the end on the + line. + + Args: + msg: Email message to quote. + """ + orig = msg.get_payload(decode=True) + encdata = encodestring(orig, quotetabs=False) + msg.set_payload(encdata) + msg['Content-Transfer-Encoding'] = 'quoted-printable' + + MHTMLPolicy = email.policy.default.clone(linesep='\r\n', max_line_length=0) @@ -146,7 +163,7 @@ E_BASE64 = email.encoders.encode_base64 # Encode the file using MIME quoted-printable encoding. -E_QUOPRI = email.encoders.encode_quopri +E_QUOPRI = _encode_quopri_mhtml class MHTMLWriter: diff --git a/tests/end2end/data/downloads/mhtml/complex/complex.mht b/tests/end2end/data/downloads/mhtml/complex/complex.mht index d7988cb63..f4cfb4649 100644 --- a/tests/end2end/data/downloads/mhtml/complex/complex.mht +++ b/tests/end2end/data/downloads/mhtml/complex/complex.mht @@ -8,143 +8,125 @@ Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: quoted-printable -=20=20=20=20=20=20=20=20 -=20=20=20=20=20=20=20=20qutebrowser=20mhtml=20test -=20=20=20=20 -=20=20=20=20=20=20=20=20 -=20=20=20=20=20=20=20=20 -=20=20=20=20=20=20=20=20 -=20=20=20=20=20=20=20=20 -=20=20=20=20=20=20=20=20 -=20=20=20=20=20=20=20=20 -=20=20=20=20=20=20=20=20 -=20=20=20=20=20=20=20=20 -=20=20=20=20=20=20=20=20@import=20"actually-it's-css"; -=20=20=20=20=20=20=20=20 -=20=20=20=20=20=20=20=20 -=20=20=20=20=20=20=20=20 -=20=20=20=20=20=20=20=20 -=20=20=20=20=20=20=20=20 -=20=20=20=20=20=20=20=20 -=20=20=20=20=20=20=20=20 -=20=20=20=20=20=20=20=20 -=20=20=20=20=20=20=20=20 -=20=20=20=20=20=20=20=20 -=20=20=20=20=20=20=20=20 -=20=20=20=20=20=20=20=20=20 -=20=20=20=20=20=20=20=20 -=20=20=20=20 -=20=20=20=20 -=20=20=20=20=20=20=20=20 -=20=20=20=20=20=20=20=20 -=20=20=20=20=20=20=20=20 -=20=20=20=20=20=20=20=20

Welcome=20to=20the=20qutebrowser=20mhtml=20test= -=20page

-=20=20=20=20=20=20=20=20 -=20=20=20=20=20=20=20=20 -=20=20=20=20=20=20=20=20...that=20the=20word=20qutebrowser=20is=20= -a=20word=20play=20on=20Qt,=20the -=20=20=20=20=20=20=20=20framework=20the=20browser=20is=20built=20with? -=20=20=20=20=20=20=20=20 -=20=20=20=20=20=20=20=20 -=20=20=20=20=20=20=20=20

What=20is=20this=20page?

-=20=20=20=20=20=20=20=20 -=20=20=20=20=20=20=20=20

This=20page=20is=20a=20test-case=20for=20the=20m= -html=20download=20feature=20of -=20=20=20=20=20=20=20=20qutebrowser.=20Under=20normal=20circumstances,=20yo= -u=20won't=20see=20this=20page,=20except -=20=20=20=20=20=20=20=20if=20you're=20a=20qutebrowser=20developer=20or<= -/em>=20you're=20attending=20one=20of -=20=20=20=20=20=20=20=20The-Compiler's=20pytest=20demos.

-=20=20=20=20=20=20=20=20 -=20=20=20=20=20=20=20=20 -=20=20=20=20=20=20=20=20...that=20this=20page=20was=20once=20a=20monstrosit= -y=20with=20"this=20weird=20pixelated -=20=20=20=20=20=20=20=20globe=20with=20the=20geocities-like=20background"?=20You=20can=20find=20the=20old -=20=20=20=20=20=20=20=20page=20in=20the=20old=20commits=20and=20indeed,=20i= -t=20was=20quite=20atrocious.=20But=20hey, -=20=20=20=20=20=20=20=20every=20browser=20needs=20a=20globe... -=20=20=20=20=20=20=20=20 -=20=20=20=20=20=20=20=20 -=20=20=20=20=20=20=20=20

This=20page=20references=20other=20assets=20and= -=20when=20the=20page=20is=20downloaded, -=20=20=20=20=20=20=20=20qutebrowser=20checks=20if=20each=20asset=20was=20do= -wnloaded.=20If=20some=20assets=20are -=20=20=20=20=20=20=20=20missing,=20the=20test=20fails=20and=20the=20poor=20= -developers=20have=20to=20search=20for=20the -=20=20=20=20=20=20=20=20error.

-=20=20=20=20=20=20=20=20 -=20=20=20=20=20=20=20=20

Can=20I=20contribute=20to=20qutebrowser?

-=20=20=20=20=20=20=20=20 -=20=20=20=20=20=20=20=20

Yes!

-=20=20=20=20=20=20=20=20 -=20=20=20=20=20=20=20=20 -=20=20=20=20=20=20=20=20...that=20qutebrowser=20is=20free=20software?=20Fre= -e=20as=20in=20free=20beer=20and -=20=20=20=20=20=20=20=20free=20speech!=20Isn't=20that=20great? -=20=20=20=20=20=20=20=20 -=20=20=20=20=20=20=20=20 -=20=20=20=20=20=20=20=20

...and=20how?

-=20=20=20=20=20=20=20=20 -=20=20=20=20=20=20=20=20

See=20 -=20=20=20=20=20=20=20=20here=20for=20more=20information.

-=20=20=20=20=20=20=20=20 -=20=20=20=20=20=20=20=20

More=20useless=20trivia!

-=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20 -=20=20=20=20=20=20=20=20 -=20=20=20=20=20=20=20=20...that=20the=20font=20in=20the=20header=20is=20Com= -ic=20Sans? -=20=20=20=20=20=20=20=20 -=20=20=20=20=20=20=20=20 -=20=20=20=20=20=20=20=20 -=20=20=20=20=20=20=20=20...the=20IRC=20channel=20for=20qutebrowser=20is=20<= -code>#qutebrowser=20on -=20=20=20=20=20=20=20=20irc.freenode.net -=20=20=20=20=20=20=20=20 -=20=20=20=20=20=20=20=20 -=20=20=20=20=20=20=20=20 -=20=20=20=20=20=20=20=20...the=20area=20of=20a=20circle=20is=20=CF=80*r2? -=20=20=20=20=20=20=20=20 -=20=20=20=20=20=20=20=20 -=20=20=20=20=20=20=20=20

To=20make=20this=20page=20a=20bit=20useful,=20I'= -ve=20included=20a=20chessboard,=20so=20you -=20=20=20=20=20=20=20=20can=20play=20chess.=20Just=20turn=20your=20screen= -=2090=20degrees,=20such=20that=20it=20forms=20a -=20=20=20=20=20=20=20=20flat,=20horizontal=20surface=20(you=20can=20skip=20= -this=20step=20if=20you're=20using=20a -=20=20=20=20=20=20=20=20tablet).=20Next,=20zoom=20the=20page=20until=20it= -=20fits=20your=20needs.=20Enjoy=20your=20round -=20=20=20=20=20=20=20=20of=20chess!

-=20=20=20=20=20=20=20=20 -=20=20=20=20=20=20=20=20 -=20=20=20=20 + + qutebrowser mhtml test + =20 + + + =20 + + + =20 + + + =20 + + + =20 + + + =20 + + + =20 + + + + + + + =20 +

Welcome to the qutebrowser mhtml test page

+ =20 +
+ ...that the word qutebrowser is a word play on Qt, the + framework the browser is built with? +
+ =20 +

What is this page?

+ =20 +

This page is a test-case for the mhtml download feature of + qutebrowser. Under normal circumstances, you won't see this page, e= +xcept + if you're a qutebrowser developer or you're attending one = +of + The-Compiler's pytest demos.

+ =20 +
+ ...that this page was once a monstrosity with "this weird pixel= +ated + globe with the geocities-like background"? You can find the old + page in the old commits and indeed, it was quite atrocious. But hey, + every browser needs a globe... +
+ =20 +

This page references other assets and when the page is downloade= +d, + qutebrowser checks if each asset was downloaded. If some assets are + missing, the test fails and the poor developers have to search for = +the + error.

+ =20 +

Can I contribute to qutebrowser?

+ =20 +

Yes!

+ =20 +
+ ...that qutebrowser is free software? Free as in free beer= + and + free speech! Isn't that great? +
+ =20 +

...and how?

+ =20 +

See + here for more information.

+ =20 +

More useless trivia!

+ =20 +
+ ...that the font in the header is Comic Sans? +
+ =20 +
+ ...the IRC channel for qutebrowser is #qutebrowser on + irc.freenode.net +
+ =20 +
+ ...the area of a circle is =CF=80*r2? +
+ =20 +

To make this page a bit useful, I've included a chessboard, so y= +ou + can play chess. Just turn your screen 90 degrees, such that it form= +s a + flat, horizontal surface (you can skip this step if you're using a + tablet). Next, zoom the page until it fits your needs. Enjoy your r= +ound + of chess!

+ +
+ =20 -----=_qute-5314618b-e51d-46e1-9598-103536e86b59 @@ -709,35 +691,33 @@ MIME-Version: 1.0 Content-Type: text/css; charset=utf-8 Content-Transfer-Encoding: quoted-printable -@import=20'external-in-extern.css'; -/*=20We=20want=20to=20make=20sure=20that=20assets=20referenced=20in=20exter= -nal=20css=20files=20are -=20*=20properly=20included -=20*/ -div.dyk=20{ -=20=20=20=20/*=20Did=20you=20know?=20*/ -=20=20=20=20background-image:=20url('DYK.png'); -=20=20=20=20background-repeat:=20no-repeat; -=20=20=20=20/*=20Image=20is=20128px=20wide=20*/ -=20=20=20=20min-height:=20128px; -=20=20=20=20padding-left:=20148px; -=20=20=20=20margin-top:=2010px; -=20=20=20=20margin-bottom:=2010px; -=20=20=20=20border:=202px=20solid=20#474747; -=20=20=20=20border-radius:=2064px; +@import 'external-in-extern.css'; +/* We want to make sure that assets referenced in external css files are + * properly included + */ +div.dyk { + /* Did you know? */ + background-image: url('DYK.png'); + background-repeat: no-repeat; + /* Image is 128px wide */ + min-height: 128px; + padding-left: 148px; + margin-top: 10px; + margin-bottom: 10px; + border: 2px solid #474747; + border-radius: 64px; } -=20=20=20=20 + =20 -----=_qute-5314618b-e51d-46e1-9598-103536e86b59 Content-Location: http://localhost:1337/data/downloads/mhtml/complex/external-in-extern.css MIME-Version: 1.0 Content-Type: text/css; charset=utf-8 Content-Transfer-Encoding: quoted-printable -/*=20Just=20making=20sure=20that=20more=20than=20one=20level=20of=20externa= -l=20css=20is=20included=20*/ -h1,=20h2,=20h3,=20h4,=20h5,=20h6=20{ -=20=20=20=20color:=20#0A396E; -=20=20=20=20border-bottom:=201px=20dotted=20#474747; +/* Just making sure that more than one level of external css is included */ +h1, h2, h3, h4, h5, h6 { + color: #0A396E; + border-bottom: 1px dotted #474747; } -----=_qute-5314618b-e51d-46e1-9598-103536e86b59 Content-Location: http://localhost:1337/data/downloads/mhtml/complex/favicon.png diff --git a/tests/end2end/data/downloads/mhtml/simple/simple.mht b/tests/end2end/data/downloads/mhtml/simple/simple.mht index d0b7a7c48..ef4431362 100644 --- a/tests/end2end/data/downloads/mhtml/simple/simple.mht +++ b/tests/end2end/data/downloads/mhtml/simple/simple.mht @@ -7,14 +7,13 @@ MIME-Version: 1.0 Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: quoted-printable - -=20=20=20=20=20=20=20=20 -=20=20=20=20=20=20=20=20Simple=20MHTML=20test -=20=20=20=20 -=20=20=20=20 -=20=20=20=20=20=20=20=20normal=20link=20to=20another=20page= - -=20=20=20=20 + + + Simple MHTML test + + + normal link to another page + =20 -----=_qute-6d584056-b1e4-4882-91e6-d4a6d23adb67-- From 856ae39673514d4270bf01dd685599efb0c1cab5 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 29 Nov 2016 07:29:47 +0100 Subject: [PATCH 030/561] flake8 requirements: Update flake8-pep1301 to 1.0 --- misc/requirements/requirements-flake8.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-flake8.txt b/misc/requirements/requirements-flake8.txt index 7fa392d0d..51e45a808 100644 --- a/misc/requirements/requirements-flake8.txt +++ b/misc/requirements/requirements-flake8.txt @@ -7,7 +7,7 @@ flake8-deprecated==1.1 flake8-docstrings==1.0.2 flake8-future-import==0.4.3 flake8-mock==0.3 -flake8-pep3101==0.6 +flake8-pep3101==1.0 flake8-putty==0.4.0 flake8-string-format==0.2.3 flake8-tidy-imports==1.0.3 From 99fed7100174f772fb8c94389d896eb0b4604bac Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 29 Nov 2016 09:23:00 +0100 Subject: [PATCH 031/561] test/vulture requirements: Update vulture to 0.11 --- misc/requirements/requirements-tests.txt | 2 +- misc/requirements/requirements-vulture.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index 9a6588eb9..53645e278 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -30,5 +30,5 @@ pytest-travis-fold==1.2.0 pytest-warnings==0.2.0 pytest-xvfb==0.3.0 six==1.10.0 -vulture==0.10 +vulture==0.11 Werkzeug==0.11.11 diff --git a/misc/requirements/requirements-vulture.txt b/misc/requirements/requirements-vulture.txt index 4b19ae298..88a7b8eed 100644 --- a/misc/requirements/requirements-vulture.txt +++ b/misc/requirements/requirements-vulture.txt @@ -1,3 +1,3 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -vulture==0.10 +vulture==0.11 From 6340eee4488d9a2d51a5cb29cc9d34e6f984aa23 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 29 Nov 2016 09:33:27 +0100 Subject: [PATCH 032/561] Handle invalid text layout when drawing tab --- qutebrowser/mainwindow/tabwidget.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/qutebrowser/mainwindow/tabwidget.py b/qutebrowser/mainwindow/tabwidget.py index c43b805c6..de65fa280 100644 --- a/qutebrowser/mainwindow/tabwidget.py +++ b/qutebrowser/mainwindow/tabwidget.py @@ -27,7 +27,7 @@ from PyQt5.QtWidgets import (QTabWidget, QTabBar, QSizePolicy, QCommonStyle, QStyle, QStylePainter, QStyleOptionTab) from PyQt5.QtGui import QIcon, QPalette, QColor -from qutebrowser.utils import qtutils, objreg, utils, usertypes +from qutebrowser.utils import qtutils, objreg, utils, usertypes, log from qutebrowser.config import config @@ -607,6 +607,10 @@ class TabBarStyle(QCommonStyle): widget: QWidget """ layouts = self._tab_layout(opt) + if layouts is None: + log.misc.warning("Could not get layouts for tab!") + return + if element == QStyle.CE_TabBarTab: # We override this so we can control TabBarTabShape/TabBarTabLabel. self.drawControl(QStyle.CE_TabBarTabShape, opt, p, widget) @@ -692,8 +696,11 @@ class TabBarStyle(QCommonStyle): indicator_padding = config.get('tabs', 'indicator-padding') text_rect = QRect(opt.rect) + if not text_rect.isValid(): + # This happens sometimes according to crash reports, but no idea + # why... + return None - qtutils.ensure_valid(text_rect) text_rect.adjust(padding.left, padding.top, -padding.right, -padding.bottom) From 5f22affa36f9f01fc6191afbf54661df9a11e1d6 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 29 Nov 2016 11:12:04 +0100 Subject: [PATCH 033/561] Show longer debug string for elems got from JS See #2150 --- qutebrowser/browser/webengine/webenginetab.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index ce84a10ba..22a3d5462 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -414,7 +414,7 @@ class WebEngineElements(browsertab.AbstractElements): js_elem: The element serialized from javascript. """ debug_str = ('None' if js_elem is None - else utils.elide(repr(js_elem), 100)) + else utils.elide(repr(js_elem), 1000)) log.webview.debug("Got element from JS: {}".format(debug_str)) if js_elem is None: From 9f5f362533f0d7e5deb8709966ae191a45d00aa6 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 29 Nov 2016 11:31:03 +0100 Subject: [PATCH 034/561] Don't crash if we can't get an element's value for editing Fixes #2150 --- qutebrowser/browser/commands.py | 4 ++++ qutebrowser/browser/webelem.py | 2 +- qutebrowser/browser/webengine/webengineelem.py | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 97fc8c186..0d977a30f 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -1484,6 +1484,10 @@ class CommandDispatcher: return text = elem.value() + if text is None: + message.error("Could not get text from the focused element.") + return + ed = editor.ExternalEditor(self._tabbed_browser) ed.editing_finished.connect(functools.partial( self.on_editing_finished, elem)) diff --git a/qutebrowser/browser/webelem.py b/qutebrowser/browser/webelem.py index 74304040c..0ddd4d265 100644 --- a/qutebrowser/browser/webelem.py +++ b/qutebrowser/browser/webelem.py @@ -139,7 +139,7 @@ class AbstractWebElement(collections.abc.MutableMapping): raise NotImplementedError def value(self): - """Get the value attribute for this element.""" + """Get the value attribute for this element, or None.""" raise NotImplementedError def set_value(self, value): diff --git a/qutebrowser/browser/webengine/webengineelem.py b/qutebrowser/browser/webengine/webengineelem.py index e045d22ed..bf8d06474 100644 --- a/qutebrowser/browser/webengine/webengineelem.py +++ b/qutebrowser/browser/webengine/webengineelem.py @@ -91,7 +91,7 @@ class WebEngineElement(webelem.AbstractWebElement): return self._js_dict['outer_xml'] def value(self): - return self._js_dict['value'] + return self._js_dict.get('value', None) def set_value(self, value): js_code = javascript.assemble('webelem', 'set_value', self._id, value) From 2cbea5015246b7d4f05bace313ff71ccd83f8cee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Wo=C5=82czyk?= Date: Tue, 29 Nov 2016 19:10:36 +0100 Subject: [PATCH 035/561] Fixed style issues, corrected unit test --- qutebrowser/browser/webkit/mhtml.py | 10 +++++----- tests/unit/browser/webkit/test_mhtml.py | 22 +++++++++++----------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/qutebrowser/browser/webkit/mhtml.py b/qutebrowser/browser/webkit/mhtml.py index 2e828dd3b..fbfeafef4 100644 --- a/qutebrowser/browser/webkit/mhtml.py +++ b/qutebrowser/browser/webkit/mhtml.py @@ -32,7 +32,7 @@ import email.generator import email.encoders import email.mime.multipart import email.message -from quopri import encodestring +import quopri from PyQt5.QtCore import QUrl @@ -142,15 +142,15 @@ def _check_rel(element): def _encode_quopri_mhtml(msg): """Encode the message's payload in quoted-printable. - Substitute for quopri's default 'encode_quopri' method, which needlessly - encodes all spaces and tabs, instead of only those at the end on the - line. + Substitute for quopri's default 'encode_quopri' method, which needlessly + encodes all spaces and tabs, instead of only those at the end on the + line. Args: msg: Email message to quote. """ orig = msg.get_payload(decode=True) - encdata = encodestring(orig, quotetabs=False) + encdata = quopri.encodestring(orig, quotetabs=False) msg.set_payload(encdata) msg['Content-Transfer-Encoding'] = 'quoted-printable' diff --git a/tests/unit/browser/webkit/test_mhtml.py b/tests/unit/browser/webkit/test_mhtml.py index 3b33857e9..fd642b46f 100644 --- a/tests/unit/browser/webkit/test_mhtml.py +++ b/tests/unit/browser/webkit/test_mhtml.py @@ -87,7 +87,7 @@ def test_quoted_printable_umlauts(checker): Content-Type: text/plain Content-Transfer-Encoding: quoted-printable - Die=20s=FC=DFe=20H=FCndin=20l=E4uft=20in=20die=20H=F6hle=20des=20B=E4ren + Die s=FC=DFe H=FCndin l=E4uft in die H=F6hle des B=E4ren -----=_qute-UUID-- """) @@ -128,7 +128,7 @@ def test_file_encoded_as_base64(checker): Content-Type: text/plain Content-Transfer-Encoding: quoted-printable - Image=20file=20attached + Image file attached -----=_qute-UUID Content-Location: http://a.example.com/image.png MIME-Version: 1.0 @@ -175,56 +175,56 @@ def test_files_appear_sorted(checker): Content-Type: text/plain Content-Transfer-Encoding: quoted-printable - root=20file + root file -----=_qute-UUID Content-Location: http://a.example.com/ MIME-Version: 1.0 Content-Type: text/plain Content-Transfer-Encoding: quoted-printable - file=20a + file a -----=_qute-UUID Content-Location: http://b.example.com/ MIME-Version: 1.0 Content-Type: text/plain Content-Transfer-Encoding: quoted-printable - file=20b + file b -----=_qute-UUID Content-Location: http://g.example.com/ MIME-Version: 1.0 Content-Type: text/plain Content-Transfer-Encoding: quoted-printable - file=20g + file g -----=_qute-UUID Content-Location: http://h.example.com/ MIME-Version: 1.0 Content-Type: text/plain Content-Transfer-Encoding: quoted-printable - file=20h + file h -----=_qute-UUID Content-Location: http://i.example.com/ MIME-Version: 1.0 Content-Type: text/plain Content-Transfer-Encoding: quoted-printable - file=20i + file i -----=_qute-UUID Content-Location: http://t.example.com/ MIME-Version: 1.0 Content-Type: text/plain Content-Transfer-Encoding: quoted-printable - file=20t + file t -----=_qute-UUID Content-Location: http://z.example.com/ MIME-Version: 1.0 Content-Type: text/plain Content-Transfer-Encoding: quoted-printable - file=20z + file z -----=_qute-UUID-- """) @@ -251,7 +251,7 @@ def test_empty_content_type(checker): Content-Location: http://example.com/file Content-Transfer-Encoding: quoted-printable - file=20content + file content -----=_qute-UUID-- """) From e613d01263a4d49fb19144094cbf49447b172012 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Wo=C5=82czyk?= Date: Wed, 30 Nov 2016 13:35:08 +0100 Subject: [PATCH 036/561] Fixed problems with tab completion after moving tabs (#2141) --- qutebrowser/browser/commands.py | 6 +----- qutebrowser/completion/models/miscmodels.py | 1 + tests/end2end/features/completion.feature | 12 ++++++++++++ tests/helpers/stubs.py | 6 +++++- 4 files changed, 19 insertions(+), 6 deletions(-) diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 97fc8c186..c3038ee71 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -1028,17 +1028,13 @@ class CommandDispatcher: raise cmdexc.CommandError("Can't move tab to position {}!".format( new_idx + 1)) - tab = self._current_widget() cur_idx = self._current_index() - icon = self._tabbed_browser.tabIcon(cur_idx) - label = self._tabbed_browser.page_title(cur_idx) cmdutils.check_overflow(cur_idx, 'int') cmdutils.check_overflow(new_idx, 'int') self._tabbed_browser.setUpdatesEnabled(False) try: color = self._tabbed_browser.tab_indicator_color(cur_idx) - self._tabbed_browser.removeTab(cur_idx) - self._tabbed_browser.insertTab(new_idx, tab, icon, label) + self._tabbed_browser.tabBar().moveTab(cur_idx, new_idx) self._set_current_index(new_idx) self._tabbed_browser.set_tab_indicator_color(new_idx, color) finally: diff --git a/qutebrowser/completion/models/miscmodels.py b/qutebrowser/completion/models/miscmodels.py index 14a48c399..450549057 100644 --- a/qutebrowser/completion/models/miscmodels.py +++ b/qutebrowser/completion/models/miscmodels.py @@ -160,6 +160,7 @@ class TabCompletionModel(base.BaseCompletionModel): tab.title_changed.connect(self.rebuild) tab.shutting_down.connect(self.delayed_rebuild) tabbed_browser.new_tab.connect(self.on_new_tab) + tabbed_browser.tabBar().tabMoved.connect(self.rebuild) objreg.get("app").new_window.connect(self.on_new_window) self.rebuild() diff --git a/tests/end2end/features/completion.feature b/tests/end2end/features/completion.feature index 0cc23a215..87db94e8d 100644 --- a/tests/end2end/features/completion.feature +++ b/tests/end2end/features/completion.feature @@ -102,3 +102,15 @@ Feature: Using completion And I run :completion-item-del Then the following tabs should be open: - data/hello.txt (active) + + Scenario: Go to tab after moving a tab + Given I have a fresh instance + When I open data/hello.txt + And I open data/hello2.txt in a new tab + # Tricking completer into not updating tabs + And I run :set-cmd-text -s :buffer + And I run :tab-move 1 + And I run :buffer hello2.txt + Then the following tabs should be open: + - data/hello2.txt (active) + - data/hello.txt diff --git a/tests/helpers/stubs.py b/tests/helpers/stubs.py index 50f4e545e..627d77d62 100644 --- a/tests/helpers/stubs.py +++ b/tests/helpers/stubs.py @@ -27,7 +27,7 @@ from unittest import mock from PyQt5.QtCore import pyqtSignal, QPoint, QProcess, QObject from PyQt5.QtNetwork import (QNetworkRequest, QAbstractNetworkCache, QNetworkCacheMetaData) -from PyQt5.QtWidgets import QCommonStyle, QLineEdit, QWidget +from PyQt5.QtWidgets import QCommonStyle, QLineEdit, QWidget, QTabBar from qutebrowser.browser import browsertab, history from qutebrowser.config import configexc @@ -571,6 +571,7 @@ class TabbedBrowserStub(QObject): super().__init__(parent) self.tabs = [] self.shutting_down = False + self._qtabbar = QTabBar() def count(self): return len(self.tabs) @@ -584,6 +585,9 @@ class TabbedBrowserStub(QObject): def on_tab_close_requested(self, idx): del self.tabs[idx] + def tabBar(self): + return QTabBar(self._qtabbar) + class ApplicationStub(QObject): From 1ecda5a993822d868cf7dd89dc761e23ad6d0429 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 30 Nov 2016 15:20:47 +0100 Subject: [PATCH 037/561] test requirements: Update pytest-xvfb to 1.0.0 --- misc/requirements/requirements-tests.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index 53645e278..21bd23d00 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -28,7 +28,7 @@ pytest-repeat==0.4.1 pytest-rerunfailures==2.1.0 pytest-travis-fold==1.2.0 pytest-warnings==0.2.0 -pytest-xvfb==0.3.0 +pytest-xvfb==1.0.0 six==1.10.0 vulture==0.11 Werkzeug==0.11.11 From 2fca442892ceece2fd6630f49e4551a298c5d8f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Wo=C5=82czyk?= Date: Wed, 30 Nov 2016 15:23:35 +0100 Subject: [PATCH 038/561] Fixed TabbedBrowserStub's tabBar, no longer toggling updates while moving tabs --- qutebrowser/browser/commands.py | 12 ++++-------- tests/helpers/stubs.py | 2 +- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index c3038ee71..fa010f9e2 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -1031,14 +1031,10 @@ class CommandDispatcher: cur_idx = self._current_index() cmdutils.check_overflow(cur_idx, 'int') cmdutils.check_overflow(new_idx, 'int') - self._tabbed_browser.setUpdatesEnabled(False) - try: - color = self._tabbed_browser.tab_indicator_color(cur_idx) - self._tabbed_browser.tabBar().moveTab(cur_idx, new_idx) - self._set_current_index(new_idx) - self._tabbed_browser.set_tab_indicator_color(new_idx, color) - finally: - self._tabbed_browser.setUpdatesEnabled(True) + color = self._tabbed_browser.tab_indicator_color(cur_idx) + self._tabbed_browser.tabBar().moveTab(cur_idx, new_idx) + self._set_current_index(new_idx) + self._tabbed_browser.set_tab_indicator_color(new_idx, color) @cmdutils.register(instance='command-dispatcher', scope='window', maxsplit=0, no_replace_variables=True) diff --git a/tests/helpers/stubs.py b/tests/helpers/stubs.py index 627d77d62..dfbcc550d 100644 --- a/tests/helpers/stubs.py +++ b/tests/helpers/stubs.py @@ -586,7 +586,7 @@ class TabbedBrowserStub(QObject): del self.tabs[idx] def tabBar(self): - return QTabBar(self._qtabbar) + return self._qtabbar class ApplicationStub(QObject): From 506fe429577e1a254d1bbbb46d06dae79d8ced74 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 30 Nov 2016 20:30:58 +0100 Subject: [PATCH 039/561] requirements: Update requests to 2.12.2 --- misc/requirements/requirements-codecov.txt | 2 +- misc/requirements/requirements-pylint-master.txt | 2 +- misc/requirements/requirements-pylint.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/misc/requirements/requirements-codecov.txt b/misc/requirements/requirements-codecov.txt index 4f9fc5b12..7d86f774b 100644 --- a/misc/requirements/requirements-codecov.txt +++ b/misc/requirements/requirements-codecov.txt @@ -2,4 +2,4 @@ codecov==2.0.5 coverage==4.2 -requests==2.12.1 +requests==2.12.2 diff --git a/misc/requirements/requirements-pylint-master.txt b/misc/requirements/requirements-pylint-master.txt index 862e0abca..c2c45f65c 100644 --- a/misc/requirements/requirements-pylint-master.txt +++ b/misc/requirements/requirements-pylint-master.txt @@ -7,6 +7,6 @@ lazy-object-proxy==1.2.2 mccabe==0.5.2 -e git+https://github.com/PyCQA/pylint.git#egg=pylint ./scripts/dev/pylint_checkers -requests==2.12.1 +requests==2.12.2 six==1.10.0 wrapt==1.10.8 diff --git a/misc/requirements/requirements-pylint.txt b/misc/requirements/requirements-pylint.txt index 0aafe2388..fad117853 100644 --- a/misc/requirements/requirements-pylint.txt +++ b/misc/requirements/requirements-pylint.txt @@ -7,7 +7,7 @@ lazy-object-proxy==1.2.2 mccabe==0.5.2 pylint==1.6.4 ./scripts/dev/pylint_checkers -requests==2.12.1 +requests==2.12.2 six==1.10.0 uritemplate==3.0.0 uritemplate.py==3.0.2 From 18ac35e7b886c2035950dcdcb7775a6ad54178ff Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 30 Nov 2016 20:31:34 +0100 Subject: [PATCH 040/561] test requirements: Add new pytest-xvfb deps --- misc/requirements/requirements-tests.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index 21bd23d00..0df0fb726 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -5,6 +5,7 @@ CherryPy==8.1.2 click==6.6 coverage==4.2 decorator==4.0.10 +EasyProcess==0.2.3 Flask==0.11.1 glob2==0.5 httpbin==0.5.0 @@ -29,6 +30,7 @@ pytest-rerunfailures==2.1.0 pytest-travis-fold==1.2.0 pytest-warnings==0.2.0 pytest-xvfb==1.0.0 +PyVirtualDisplay==0.2.1 six==1.10.0 vulture==0.11 Werkzeug==0.11.11 From 63808fdb98d7a7549f6f8769f9826c5ceaf0b9df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Wo=C5=82czyk?= Date: Thu, 1 Dec 2016 00:44:07 +0100 Subject: [PATCH 041/561] Removed handling of tab's position and color since it's done by QTabBar --- qutebrowser/browser/commands.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index fa010f9e2..0c86431b7 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -1031,10 +1031,7 @@ class CommandDispatcher: cur_idx = self._current_index() cmdutils.check_overflow(cur_idx, 'int') cmdutils.check_overflow(new_idx, 'int') - color = self._tabbed_browser.tab_indicator_color(cur_idx) self._tabbed_browser.tabBar().moveTab(cur_idx, new_idx) - self._set_current_index(new_idx) - self._tabbed_browser.set_tab_indicator_color(new_idx, color) @cmdutils.register(instance='command-dispatcher', scope='window', maxsplit=0, no_replace_variables=True) From f6802272c7c50574cfddd9c23511179299fcfa8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Wo=C5=82czyk?= Date: Thu, 1 Dec 2016 01:17:00 +0100 Subject: [PATCH 042/561] Added a test for quopri with spaces at the end of the line --- tests/unit/browser/webkit/test_mhtml.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/unit/browser/webkit/test_mhtml.py b/tests/unit/browser/webkit/test_mhtml.py index fd642b46f..e5fdf4ffd 100644 --- a/tests/unit/browser/webkit/test_mhtml.py +++ b/tests/unit/browser/webkit/test_mhtml.py @@ -283,6 +283,28 @@ def test_css_url_scanner(monkeypatch, has_cssutils, inline, style, assert urls == expected_urls +def test_quoted_printable_spaces(checker): + content = b' ' * 100 + writer = mhtml.MHTMLWriter(root_content=content, + content_location='localhost', + content_type='text/plain') + writer.write_to(checker.fp) + checker.expect(""" + Content-Type: multipart/related; boundary="---=_qute-UUID" + MIME-Version: 1.0 + + -----=_qute-UUID + Content-Location: localhost + MIME-Version: 1.0 + Content-Type: text/plain + Content-Transfer-Encoding: quoted-printable + + {}= + {}=20 + -----=_qute-UUID-- + """.format(' ' * 75, ' ' * 24)) + + class TestNoCloseBytesIO: def test_fake_close(self): From 08f537546f1c19f0c6b4c57d95166e08c5fe71ca Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 1 Dec 2016 13:23:17 +0100 Subject: [PATCH 043/561] Regenerate authors --- README.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.asciidoc b/README.asciidoc index 7a1ef8bf2..0f28676ac 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -181,6 +181,7 @@ Contributors, sorted by the number of commits in descending order: * John ShaggyTwoDope Jenkins * Clayton Craft * Peter Vilim +* Maciej Wołczyk * knaggita * Oliver Caldwell * Julian Weigt @@ -188,7 +189,6 @@ Contributors, sorted by the number of commits in descending order: * Jonas Schürmann * error800 * Michael Hoang -* Maciej Wołczyk * Liam BEGUIN * Julie Engel * skinnay From 568b2560560d3c536dc9ee7da599974546c7ba20 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 1 Dec 2016 13:25:15 +0100 Subject: [PATCH 044/561] Regenerate authors --- README.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.asciidoc b/README.asciidoc index 0f28676ac..272828b37 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -172,6 +172,7 @@ Contributors, sorted by the number of commits in descending order: * Austin Anderson * Jimmy * Niklas Haas +* Maciej Wołczyk * Spreadyy * Alexey "Averrin" Nabrodov * nanjekyejoannah @@ -181,7 +182,6 @@ Contributors, sorted by the number of commits in descending order: * John ShaggyTwoDope Jenkins * Clayton Craft * Peter Vilim -* Maciej Wołczyk * knaggita * Oliver Caldwell * Julian Weigt From af766ade4853ed0d0a85c7bdc3e1254b17a30f11 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 2 Dec 2016 09:43:05 +0100 Subject: [PATCH 045/561] requirements: Update requests to 2.12.3 --- misc/requirements/requirements-codecov.txt | 2 +- misc/requirements/requirements-pylint-master.txt | 2 +- misc/requirements/requirements-pylint.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/misc/requirements/requirements-codecov.txt b/misc/requirements/requirements-codecov.txt index 7d86f774b..deb399503 100644 --- a/misc/requirements/requirements-codecov.txt +++ b/misc/requirements/requirements-codecov.txt @@ -2,4 +2,4 @@ codecov==2.0.5 coverage==4.2 -requests==2.12.2 +requests==2.12.3 diff --git a/misc/requirements/requirements-pylint-master.txt b/misc/requirements/requirements-pylint-master.txt index c2c45f65c..63ea698a6 100644 --- a/misc/requirements/requirements-pylint-master.txt +++ b/misc/requirements/requirements-pylint-master.txt @@ -7,6 +7,6 @@ lazy-object-proxy==1.2.2 mccabe==0.5.2 -e git+https://github.com/PyCQA/pylint.git#egg=pylint ./scripts/dev/pylint_checkers -requests==2.12.2 +requests==2.12.3 six==1.10.0 wrapt==1.10.8 diff --git a/misc/requirements/requirements-pylint.txt b/misc/requirements/requirements-pylint.txt index fad117853..b18c26b02 100644 --- a/misc/requirements/requirements-pylint.txt +++ b/misc/requirements/requirements-pylint.txt @@ -7,7 +7,7 @@ lazy-object-proxy==1.2.2 mccabe==0.5.2 pylint==1.6.4 ./scripts/dev/pylint_checkers -requests==2.12.2 +requests==2.12.3 six==1.10.0 uritemplate==3.0.0 uritemplate.py==3.0.2 From 6f90d93306d8dfbe1bc3a8cc9b411593e319674d Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 5 Dec 2016 16:17:51 +0100 Subject: [PATCH 046/561] test requirements: Update pytest to 3.0.5 --- misc/requirements/requirements-tests.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index 0df0fb726..c388e535f 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -17,7 +17,7 @@ Mako==1.0.6 parse==1.6.6 parse-type==0.3.4 py==1.4.31 -pytest==3.0.4 +pytest==3.0.5 pytest-bdd==2.18.1 pytest-catchlog==1.2.2 pytest-cov==2.4.0 From 587e782738ec7bf61987a437ca8869b0e68b3819 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 5 Dec 2016 22:40:09 +0100 Subject: [PATCH 047/561] Blacklist pytest 3.0.5 See https://github.com/pytest-dev/pytest/issues/2118 --- misc/requirements/requirements-tests.txt | 2 +- misc/requirements/requirements-tests.txt-raw | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index c388e535f..b086bcb6a 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -17,7 +17,7 @@ Mako==1.0.6 parse==1.6.6 parse-type==0.3.4 py==1.4.31 -pytest==3.0.5 +pytest==3.0.4 # rq.filter: != 3.0.5 pytest-bdd==2.18.1 pytest-catchlog==1.2.2 pytest-cov==2.4.0 diff --git a/misc/requirements/requirements-tests.txt-raw b/misc/requirements/requirements-tests.txt-raw index aaba28da6..523d8bfe4 100644 --- a/misc/requirements/requirements-tests.txt-raw +++ b/misc/requirements/requirements-tests.txt-raw @@ -4,7 +4,7 @@ coverage Flask httpbin hypothesis -pytest +pytest!=3.0.5 pytest-bdd pytest-catchlog pytest-cov @@ -20,3 +20,4 @@ pytest-xvfb vulture #@ ignore: Jinja2, MarkupSafe +#@ filter: pytest != 3.0.5 \ No newline at end of file From 7b8af9ebbff6cb8547b9d21c80737d85ce5a7ebc Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 6 Dec 2016 06:32:51 +0100 Subject: [PATCH 048/561] Try to stabilize history tests Sometimes :history-clear would run before about:blank finished loading, causing the test for :spawn with a history flag to fail. --- tests/end2end/features/history.feature | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/end2end/features/history.feature b/tests/end2end/features/history.feature index f4f57dcb7..7e9c55c99 100644 --- a/tests/end2end/features/history.feature +++ b/tests/end2end/features/history.feature @@ -3,7 +3,8 @@ Feature: Page history Make sure the global page history is saved correctly. Background: - Given I run :history-clear + Given I open about:blank + And I run :history-clear Scenario: Simple history saving When I open data/numbers/1.txt @@ -49,7 +50,8 @@ Feature: Page history http://localhost:(port)/status/404 Error loading page: http://localhost:(port)/status/404 Scenario: History with invalid URL - When I open data/javascript/window_open.html + When I run :tab-only + And I open data/javascript/window_open.html And I run :click-element id open-invalid Then "Changing title for idx 1 to 'about:blank'" should be logged From 0ab23a74fb76e05ea4dcb0ca51123e13c3079978 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 6 Dec 2016 06:47:42 +0100 Subject: [PATCH 049/561] Allow --qt-arg and --qt-flag to be given multiple times Fixes #2151 --- qutebrowser/qutebrowser.py | 5 +++-- qutebrowser/utils/qtutils.py | 9 ++++++--- tests/unit/utils/test_qtutils.py | 6 ++++++ 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/qutebrowser/qutebrowser.py b/qutebrowser/qutebrowser.py index dd434eb3d..f0b8e4f06 100644 --- a/qutebrowser/qutebrowser.py +++ b/qutebrowser/qutebrowser.py @@ -114,9 +114,10 @@ def get_argparser(): debug.add_argument('--qt-arg', help="Pass an argument with a value to Qt. " "For example, you can do " "`--qt-arg geometry 650x555+200+300` to set the window " - "geometry.", nargs=2, metavar=('NAME', 'VALUE')) + "geometry.", nargs=2, metavar=('NAME', 'VALUE'), + action='append') debug.add_argument('--qt-flag', help="Pass an argument to Qt as flag.", - nargs=1) + nargs=1, action='append') parser.add_argument('command', nargs='*', help="Commands to execute on " "startup.", metavar=':command') # URLs will actually be in command diff --git a/qutebrowser/utils/qtutils.py b/qutebrowser/utils/qtutils.py index f8509ae96..c573628e1 100644 --- a/qutebrowser/utils/qtutils.py +++ b/qutebrowser/utils/qtutils.py @@ -129,11 +129,14 @@ def get_args(namespace): The argv list to be passed to Qt. """ argv = [sys.argv[0]] + if namespace.qt_flag is not None: - argv.append('-' + namespace.qt_flag[0]) + argv += ['-' + flag[0] for flag in namespace.qt_flag] + if namespace.qt_arg is not None: - argv.append('-' + namespace.qt_arg[0]) - argv.append(namespace.qt_arg[1]) + for name, value in namespace.qt_arg: + argv += ['-' + name, value] + return argv diff --git a/tests/unit/utils/test_qtutils.py b/tests/unit/utils/test_qtutils.py index 8ac783505..b577e14e9 100644 --- a/tests/unit/utils/test_qtutils.py +++ b/tests/unit/utils/test_qtutils.py @@ -112,6 +112,12 @@ class TestGetQtArgs: # Qt argument with value (['--qt-arg', 'stylesheet', 'foo'], [sys.argv[0], '-stylesheet', 'foo']), + # --qt-arg given twice + (['--qt-arg', 'stylesheet', 'foo', '--qt-arg', 'geometry', 'bar'], + [sys.argv[0], '-stylesheet', 'foo', '-geometry', 'bar']), + # --qt-flag given twice + (['--qt-flag', 'foo', '--qt-flag', 'bar'], + [sys.argv[0], '-foo', '-bar']), ]) def test_qt_args(self, args, expected, parser): """Test commandline with no Qt arguments given.""" From 717fc97f05e009c1fe18cd56af410e5e4c5152ae Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 6 Dec 2016 07:16:41 +0100 Subject: [PATCH 050/561] Make tab.set_html work without base_url --- qutebrowser/browser/webengine/webenginetab.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index 22a3d5462..34423888a 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -566,12 +566,14 @@ class WebEngineTab(browsertab.AbstractTab): log.stub('on Qt < 5.7') return QIcon() - def set_html(self, html, base_url): + def set_html(self, html, base_url=None): # FIXME:qtwebengine # check this and raise an exception if too big: # Warning: The content will be percent encoded before being sent to the # renderer via IPC. This may increase its size. The maximum size of the # percent encoded content is 2 megabytes minus 30 bytes. + if base_url is None: + base_url = QUrl() self._widget.setHtml(html, base_url) def networkaccessmanager(self): From df5ce12ed86bcb6748d73599e081d800e103b56f Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 6 Dec 2016 07:45:03 +0100 Subject: [PATCH 051/561] Make it possible to cancel authentication with QtWebEngine Fixes #2156 --- qutebrowser/browser/shared.py | 1 + qutebrowser/browser/webengine/webenginetab.py | 17 +++++++++++++---- tests/end2end/features/prompts.feature | 16 ++++++++++++++++ 3 files changed, 30 insertions(+), 4 deletions(-) diff --git a/qutebrowser/browser/shared.py b/qutebrowser/browser/shared.py index 8536ea837..3d171c696 100644 --- a/qutebrowser/browser/shared.py +++ b/qutebrowser/browser/shared.py @@ -66,6 +66,7 @@ def authentication_required(url, authenticator, abort_on): if answer is not None: authenticator.setUser(answer.user) authenticator.setPassword(answer.password) + return answer def javascript_confirm(url, js_msg, abort_on): diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index 34423888a..e954edd76 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -37,7 +37,7 @@ from qutebrowser.browser.webengine import (webview, webengineelem, tabhistory, interceptor, webenginequtescheme, webenginedownloads) from qutebrowser.utils import (usertypes, qtutils, log, javascript, utils, - objreg) + objreg, jinja) _qute_scheme_handler = None @@ -604,9 +604,18 @@ class WebEngineTab(browsertab.AbstractTab): @pyqtSlot(QUrl, 'QAuthenticator*') def _on_authentication_required(self, url, authenticator): # FIXME:qtwebengine support .netrc - shared.authentication_required(url, authenticator, - abort_on=[self.shutting_down, - self.load_started]) + answer = shared.authentication_required( + url, authenticator, abort_on=[self.shutting_down, + self.load_started]) + if answer is None: + # WORKAROUND for + # https://www.riverbankcomputing.com/pipermail/pyqt/2016-December/038400.html + url_string = url.toDisplayString() + error_page = jinja.render( + 'error.html', + title="Error loading page: {}".format(url_string), + url=url_string, error="Authentication required", icon='') + self.set_html(error_page) def _connect_signals(self): view = self._widget diff --git a/tests/end2end/features/prompts.feature b/tests/end2end/features/prompts.feature index 00cb93dd9..25ce208c6 100644 --- a/tests/end2end/features/prompts.feature +++ b/tests/end2end/features/prompts.feature @@ -353,6 +353,22 @@ Feature: Prompts "user": "user4" } + # https://github.com/The-Compiler/qutebrowser/issues/2156 + @qtwebkit_skip + Scenario: Cancellling webpage authentification with QtWebEngine + When I open basic-auth/user5/password5 without waiting + And I wait for a prompt + And I run :leave-mode + And I wait for "Changing title for idx * to 'Error loading page: *'" in the log + Then the page should contain the plaintext "Authentication required" + + @qtwebengine_skip + Scenario: Cancellling webpage authentification with QtWebKit + When I open basic-auth/user6/password6 without waiting + And I wait for a prompt + And I run :leave-mode + Then basic-auth/user6/password6 should be loaded + # :prompt-accept with value argument Scenario: Javascript alert with value From b31d978a3a0d25f747913ca6de5bc9272c9ece0c Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 6 Dec 2016 15:21:06 +0100 Subject: [PATCH 052/561] Clarify 'go' --- doc/quickstart.asciidoc | 3 ++- misc/cheatsheet.svg | 10 +++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/doc/quickstart.asciidoc b/doc/quickstart.asciidoc index dc1426931..6188b6a54 100644 --- a/doc/quickstart.asciidoc +++ b/doc/quickstart.asciidoc @@ -9,7 +9,8 @@ Basic keybindings to get you started ------------------------------------ * Use the arrow keys or `hjkl` to move around a webpage (vim-like syntax is used in quite a few places) -* To go to a new webpage, press `o`, then type a url, then press Enter (Use `O` to open the url in a new tab). If what you've typed isn't a url, then a search engine will be used instead (DuckDuckGo, by default) +* To go to a new webpage, press `o`, then type a url, then press Enter (Use `O` to open the url in a new tab, `go` to edit the current URL) +* If what you've typed isn't a url, then a search engine will be used instead (DuckDuckGo, by default) * To switch between tabs, use `J` (next tab) and `K` (previous tab), or press ``, where `num` is the position of the tab to switch to * To close the current tab, press `d` (and press `u` to undo closing a tab) * Use `H` and `L` to go back and forth in the history diff --git a/misc/cheatsheet.svg b/misc/cheatsheet.svg index 29d0a86d7..e3f560b87 100644 --- a/misc/cheatsheet.svg +++ b/misc/cheatsheet.svg @@ -33,17 +33,17 @@ inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:zoom="1.7582312" - inkscape:cx="875.18895" + inkscape:cx="382.46963" inkscape:cy="136.8726" inkscape:document-units="px" inkscape:current-layer="layer1" width="1024px" height="640px" showgrid="false" - inkscape:window-width="1362" - inkscape:window-height="740" + inkscape:window-width="1400" + inkscape:window-height="1050" inkscape:window-x="0" - inkscape:window-y="24" + inkscape:window-y="0" showguides="true" inkscape:guide-bbox="true" inkscape:window-maximized="0" @@ -2834,7 +2834,7 @@ style="font-style:normal;font-weight:bold;-inkscape-font-specification:'Sans Bold';fill:#ff0000" id="flowSpan5705-5-8-3">(6) opening:go - open based on cur. URLgo - edit & open current URLgO - like Date: Thu, 25 Aug 2016 02:13:26 +0300 Subject: [PATCH 053/561] Add network logger --- qutebrowser/utils/log.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qutebrowser/utils/log.py b/qutebrowser/utils/log.py index 05163577c..07ec78741 100644 --- a/qutebrowser/utils/log.py +++ b/qutebrowser/utils/log.py @@ -94,7 +94,7 @@ LOGGER_NAMES = [ 'commands', 'signals', 'downloads', 'js', 'qt', 'rfc6266', 'ipc', 'shlexer', 'save', 'message', 'config', 'sessions', - 'webelem', 'prompt' + 'webelem', 'prompt', 'network' ] @@ -140,6 +140,7 @@ config = logging.getLogger('config') sessions = logging.getLogger('sessions') webelem = logging.getLogger('webelem') prompt = logging.getLogger('prompt') +network = logging.getLogger('network') ram_handler = None From caf1950868c20265d52beaf368fca86b82178146 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 6 Dec 2016 20:28:18 +0100 Subject: [PATCH 054/561] Set the default zoom initially with QtWebEngine This solves a part of #1993, however when the user navigates to a different page, the zoom is reset. --- qutebrowser/browser/browsertab.py | 2 ++ qutebrowser/browser/webkit/webkittab.py | 1 - tests/end2end/features/zoom.feature | 6 ++++++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/qutebrowser/browser/browsertab.py b/qutebrowser/browser/browsertab.py index 484e2a39e..22aa48013 100644 --- a/qutebrowser/browser/browsertab.py +++ b/qutebrowser/browser/browsertab.py @@ -570,7 +570,9 @@ class AbstractTab(QWidget): self.search._widget = widget self.printing._widget = widget self.elements._widget = widget + self._install_event_filter() + self.zoom.set_default() def _install_event_filter(self): raise NotImplementedError diff --git a/qutebrowser/browser/webkit/webkittab.py b/qutebrowser/browser/webkit/webkittab.py index 407ff2591..1a9a91b49 100644 --- a/qutebrowser/browser/webkit/webkittab.py +++ b/qutebrowser/browser/webkit/webkittab.py @@ -605,7 +605,6 @@ class WebKitTab(browsertab.AbstractTab): self.elements = WebKitElements(self) self._set_widget(widget) self._connect_signals() - self.zoom.set_default() self.backend = usertypes.Backend.QtWebKit def _install_event_filter(self): diff --git a/tests/end2end/features/zoom.feature b/tests/end2end/features/zoom.feature index 8d8e9ed0a..0c6790fa8 100644 --- a/tests/end2end/features/zoom.feature +++ b/tests/end2end/features/zoom.feature @@ -85,3 +85,9 @@ Feature: Zooming in and out And I run :zoom-in Then the message "Zoom level: 120%" should be shown And the zoom should be 120% + + Scenario: Setting a default zoom + When I set ui -> default-zoom to 200% + And I open data/hello.txt in a new tab + And I run :tab-only + Then the zoom should be 200% From d709756eb115e55bd754c2c12e4ba1b41efe28c4 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 6 Dec 2016 22:50:08 +0100 Subject: [PATCH 055/561] Add AbstractZoom._set_factor_internal --- qutebrowser/browser/browsertab.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/qutebrowser/browser/browsertab.py b/qutebrowser/browser/browsertab.py index 22aa48013..1480efbf0 100644 --- a/qutebrowser/browser/browsertab.py +++ b/qutebrowser/browser/browsertab.py @@ -215,6 +215,9 @@ class AbstractZoom(QObject): self.set_factor(float(level) / 100, fuzzyval=False) return level + def _set_factor_internal(self, factor): + raise NotImplementedError + def set_factor(self, factor, *, fuzzyval=True): """Zoom to a given zoom factor. From e39d6996c6bdd40f6b01ab134ebda251c89759cc Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 6 Dec 2016 22:50:25 +0100 Subject: [PATCH 056/561] Fix test_tab --- tests/unit/browser/test_tab.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/unit/browser/test_tab.py b/tests/unit/browser/test_tab.py index ef35dfafa..7dabbab75 100644 --- a/tests/unit/browser/test_tab.py +++ b/tests/unit/browser/test_tab.py @@ -85,6 +85,12 @@ def tab(request, default_config, qtbot, tab_registry, cookiejar_and_cache): objreg.delete('mode-manager', scope='window', window=0) +class Zoom(browsertab.AbstractZoom): + + def _set_factor_internal(self, _factor): + pass + + class Tab(browsertab.AbstractTab): # pylint: disable=abstract-method @@ -97,7 +103,7 @@ class Tab(browsertab.AbstractTab): self.caret = browsertab.AbstractCaret(win_id=self.win_id, mode_manager=mode_manager, tab=self, parent=self) - self.zoom = browsertab.AbstractZoom(win_id=self.win_id) + self.zoom = Zoom(win_id=self.win_id) self.search = browsertab.AbstractSearch(parent=self) self.printing = browsertab.AbstractPrinting() self.elements = browsertab.AbstractElements(self) From 1536a3324e1a88fe0e8cffe3c912cef3d2a104ec Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 6 Dec 2016 23:09:23 +0100 Subject: [PATCH 057/561] Fix lint --- tests/unit/browser/test_tab.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/unit/browser/test_tab.py b/tests/unit/browser/test_tab.py index 7dabbab75..10e08938d 100644 --- a/tests/unit/browser/test_tab.py +++ b/tests/unit/browser/test_tab.py @@ -90,6 +90,9 @@ class Zoom(browsertab.AbstractZoom): def _set_factor_internal(self, _factor): pass + def factor(self): + assert False + class Tab(browsertab.AbstractTab): From 87890dab20ee2f16ada98c9cfd9b1506fd45e458 Mon Sep 17 00:00:00 2001 From: Nikolay Amiantov Date: Thu, 25 Aug 2016 02:14:22 +0300 Subject: [PATCH 058/561] Add PAC files support --- .../browser/webkit/network/networkmanager.py | 8 + qutebrowser/browser/webkit/network/pac.py | 305 ++++++++++++++++++ qutebrowser/browser/webkit/network/proxy.py | 20 +- qutebrowser/config/configtypes.py | 41 ++- qutebrowser/javascript/pac_utils.js | 257 +++++++++++++++ scripts/dev/ci/travis_install.sh | 2 +- 6 files changed, 619 insertions(+), 14 deletions(-) create mode 100644 qutebrowser/browser/webkit/network/pac.py create mode 100644 qutebrowser/javascript/pac_utils.js diff --git a/qutebrowser/browser/webkit/network/networkmanager.py b/qutebrowser/browser/webkit/network/networkmanager.py index 477a9958a..5e26893f5 100644 --- a/qutebrowser/browser/webkit/network/networkmanager.py +++ b/qutebrowser/browser/webkit/network/networkmanager.py @@ -396,6 +396,14 @@ class NetworkManager(QNetworkAccessManager): Return: A QNetworkReply. """ + proxy_factory = objreg.get('proxy-factory', None) + if proxy_factory is not None: + proxy_error = proxy_factory.get_error() + if proxy_error is not None: + return networkreply.ErrorNetworkReply( + req, proxy_error, QNetworkReply.UnknownProxyError, + self) + scheme = req.url().scheme() if scheme in self._scheme_handlers: result = self._scheme_handlers[scheme].createRequest( diff --git a/qutebrowser/browser/webkit/network/pac.py b/qutebrowser/browser/webkit/network/pac.py new file mode 100644 index 000000000..dfa4d42d6 --- /dev/null +++ b/qutebrowser/browser/webkit/network/pac.py @@ -0,0 +1,305 @@ +# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: + +# Copyright 2016 Florian Bruhin (The Compiler) +# +# This file is part of qutebrowser. +# +# qutebrowser is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# qutebrowser is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with qutebrowser. If not, see . + +"""Evaluation of PAC scripts.""" + +import sys +import functools + +from PyQt5.QtCore import (QObject, pyqtSignal, pyqtSlot) +from PyQt5.QtNetwork import (QNetworkProxy, QNetworkRequest, QHostInfo, + QNetworkReply, QNetworkAccessManager, + QHostAddress) +from PyQt5.QtQml import QJSEngine, QJSValue + +from qutebrowser.utils import log, utils, qtutils + + +class ParseProxyError(Exception): + + """Error while parsing PAC result string.""" + + pass + + +class EvalProxyError(Exception): + + """Error while evaluating PAC script.""" + + pass + + +def _js_slot(*args): + """Wrap a methods as a JavaScript function. + + Register a PACContext method as a JavaScript function, and catch + exceptions returning them as JavaScript Error objects. + + Args: + args: Types of method arguments. + + Return: Wrapped method. + """ + def _decorator(method): + @functools.wraps(method) + def new_method(self, *args, **kwargs): + try: + return method(self, *args, **kwargs) + except: + e = str(sys.exc_info()[0]) + log.network.exception("PAC evaluation error") + # pylint: disable=protected-access + return self._error_con.callAsConstructor([e]) + # pylint: enable=protected-access + return pyqtSlot(*args, result=QJSValue)(new_method) + return _decorator + + +class _PACContext(QObject): + + """Implementation of PAC API functions that require native calls. + + See https://developer.mozilla.org/en-US/docs/Mozilla/Projects/Necko/Proxy_Auto-Configuration_(PAC)_file + """ + + JS_DEFINITIONS = """ + function dnsResolve(host) { + return PAC.dnsResolve(host); + } + + function myIpAddress() { + return PAC.myIpAddress(); + } + """ + + def __init__(self, engine): + """Create a new PAC API implementation instance. + + Args: + engine: QJSEngine which is used for running PAC. + """ + super().__init__(parent=engine) + self._engine = engine + self._error_con = engine.globalObject().property("Error") + + @_js_slot(str) + def dnsResolve(self, host): + """Resolve a DNS hostname. + + Resolves the given DNS hostname into an IP address, and returns it + in the dot-separated format as a string. + + Args: + host: hostname to resolve. + """ + ips = QHostInfo.fromName(host) + if ips.error() != QHostInfo.NoError or not ips.addresses(): + err_f = "Failed to resolve host during PAC evaluation: {}" + log.network.info(err_f.format(host)) + return QJSValue(QJSValue.NullValue) + else: + return ips.addresses()[0].toString() + + @_js_slot() + def myIpAddress(self): + """Get host IP address. + + Return the server IP address of the current machine, as a string in + the dot-separated integer format. + """ + return QHostAddress(QHostAddress.LocalHost).toString() + + +class PACResolver(object): + + """Evaluate PAC script files and resolve proxies.""" + + @staticmethod + def _parse_proxy_host(host_str): + host, _colon, port_str = host_str.partition(':') + try: + port = int(port_str) + except ValueError: + raise ParseProxyError("Invalid port number") + return (host, port) + + @staticmethod + def _parse_proxy_entry(proxy_str): + """Parse one proxy string entry, as described in PAC specification.""" + config = [c.strip() for c in proxy_str.split(' ') if c] + if not config: + raise ParseProxyError("Empty proxy entry") + elif config[0] == "DIRECT": + if len(config) != 1: + raise ParseProxyError("Invalid number of parameters for " + + "DIRECT") + return QNetworkProxy(QNetworkProxy.NoProxy) + elif config[0] == "PROXY": + if len(config) != 2: + raise ParseProxyError("Invalid number of parameters for PROXY") + host, port = PACResolver._parse_proxy_host(config[1]) + return QNetworkProxy(QNetworkProxy.HttpProxy, host, port) + elif config[0] == "SOCKS": + if len(config) != 2: + raise ParseProxyError("Invalid number of parameters for SOCKS") + host, port = PACResolver._parse_proxy_host(config[1]) + return QNetworkProxy(QNetworkProxy.Socks5Proxy, host, port) + else: + err = "Unknown proxy type: {}" + raise ParseProxyError(err.format(config[0])) + + @staticmethod + def _parse_proxy_string(proxy_str): + proxies = proxy_str.split(';') + return [PACResolver._parse_proxy_entry(x) for x in proxies] + + def _evaluate(self, js_code, js_file): + ret = self._engine.evaluate(js_code, js_file) + if ret.isError(): + err = "JavaScript error while evaluating PAC file: {}" + raise EvalProxyError(err.format(ret.toString())) + + def __init__(self, pac_str): + """Create a PAC resolver. + + Args: + pac_str: JavaScript code containing PAC resolver. + """ + self._engine = QJSEngine() + + self._ctx = _PACContext(self._engine) + self._engine.globalObject().setProperty( + "PAC", self._engine.newQObject(self._ctx)) + self._evaluate(_PACContext.JS_DEFINITIONS, "pac_js_definitions") + self._evaluate(utils.read_file("javascript/pac_utils.js"), "pac_utils") + proxy_config = self._engine.newObject() + proxy_config.setProperty("bindings", self._engine.newObject()) + self._engine.globalObject().setProperty("ProxyConfig", proxy_config) + + self._evaluate(pac_str, "pac") + global_js_object = self._engine.globalObject() + self._resolver = global_js_object.property("FindProxyForURL") + if not self._resolver.isCallable(): + err = "Cannot resolve FindProxyForURL function, got '{}' instead" + raise EvalProxyError(err.format(self._resolver.toString())) + + def resolve(self, query): + """Resolve a proxy via PAC. + + Args: + query: QNetworkProxyQuery. + + Return: + A list of QNetworkProxy objects in order of preference. + """ + result = self._resolver.call([query.url().toString(), + query.peerHostName()]) + result_str = result.toString() + if not result.isString(): + err = "Got strange value from FindProxyForURL: '{}'" + raise EvalProxyError(err.format(result_str)) + return self._parse_proxy_string(result_str) + + +class PACFetcher(QObject): + + """Asynchronous fetcher of PAC files.""" + + finished = pyqtSignal() + + def __init__(self, url, parent=None): + """Resolve a PAC proxy from URL. + + Args: + url: QUrl of a PAC proxy. + """ + super().__init__(parent) + + pac_prefix = "pac+" + + assert url.scheme().startswith(pac_prefix) + url.setScheme(url.scheme()[len(pac_prefix):]) + + self._manager = QNetworkAccessManager() + self._manager.setProxy(QNetworkProxy(QNetworkProxy.NoProxy)) + self._reply = self._manager.get(QNetworkRequest(url)) + self._reply.finished.connect(self._finish) + self._pac = None + self._error_message = None + + @pyqtSlot() + def _finish(self): + if self._reply.error() != QNetworkReply.NoError: + error = "Can't fetch PAC file from URL, error code {}: {}" + self._error_message = error.format( + self._reply.error(), self._reply.errorString()) + log.network.error(self._error_message) + else: + try: + pacscript = bytes(self._reply.readAll()).decode("utf-8") + except UnicodeError as e: + error = "Invalid encoding of a PAC file: {}" + self._error_message = error.format(e) + log.network.exception(self._error_message) + try: + self._pac = PACResolver(pacscript) + log.network.debug("Successfully evaluated PAC file.") + except EvalProxyError as e: + error = "Error in PAC evaluation: {}" + self._error_message = error.format(e) + log.network.exception(self._error_message) + self._manager = None + self._reply = None + self.finished.emit() + + def _wait(self): + """Wait until a reply from the remote server is received.""" + if self._manager is not None: + loop = qtutils.EventLoop() + self.finished.connect(loop.quit) + loop.exec_() + + def fetch_error(self): + """Check if PAC script is successfully fetched. + + Return None iff PAC script is downloaded and evaluated successfully, + error string otherwise. + """ + self._wait() + return self._error_message + + def resolve(self, query): + """Resolve a query via PAC. + + Args: QNetworkProxyQuery. + + Return a list of QNetworkProxy objects in order of preference. + """ + self._wait() + try: + return self._pac.resolve(query) + except (EvalProxyError, ParseProxyError) as e: + log.network.exception("Error in PAC resolution: {}.".format(e)) + # .invalid is guaranteed to be inaccessible in RFC 6761. + # Port 9 is for DISCARD protocol -- DISCARD servers act like + # /dev/null. + # Later NetworkManager.createRequest will detect this and display + # an error message. + error_host = "pac-resolve-error.qutebrowser.invalid" + return QNetworkProxy(QNetworkProxy.HttpProxy, error_host, 9) diff --git a/qutebrowser/browser/webkit/network/proxy.py b/qutebrowser/browser/webkit/network/proxy.py index 2469bd0c7..db52482d2 100644 --- a/qutebrowser/browser/webkit/network/proxy.py +++ b/qutebrowser/browser/webkit/network/proxy.py @@ -23,17 +23,33 @@ from PyQt5.QtNetwork import QNetworkProxy, QNetworkProxyFactory from qutebrowser.config import config, configtypes +from qutebrowser.utils import objreg +from qutebrowser.browser.webkit.network import pac def init(): """Set the application wide proxy factory.""" - QNetworkProxyFactory.setApplicationProxyFactory(ProxyFactory()) + proxy_factory = ProxyFactory() + objreg.register('proxy-factory', proxy_factory) + QNetworkProxyFactory.setApplicationProxyFactory(proxy_factory) class ProxyFactory(QNetworkProxyFactory): """Factory for proxies to be used by qutebrowser.""" + def get_error(self): + """Check if proxy can't be resolved. + + Return: + None if proxy is correct, otherwise an error message. + """ + proxy = config.get('network', 'proxy') + if isinstance(proxy, pac.PACFetcher): + return proxy.fetch_error() + else: + return None + def queryProxy(self, query): """Get the QNetworkProxies for a query. @@ -46,6 +62,8 @@ class ProxyFactory(QNetworkProxyFactory): proxy = config.get('network', 'proxy') if proxy is configtypes.SYSTEM_PROXY: proxies = QNetworkProxyFactory.systemProxyForQuery(query) + elif isinstance(proxy, pac.PACFetcher): + proxies = proxy.resolve(query) else: proxies = [proxy] for p in proxies: diff --git a/qutebrowser/config/configtypes.py b/qutebrowser/config/configtypes.py index 457781e77..7c5f9721c 100644 --- a/qutebrowser/config/configtypes.py +++ b/qutebrowser/config/configtypes.py @@ -28,6 +28,7 @@ import itertools import collections import warnings import datetime +import functools from PyQt5.QtCore import QUrl, Qt from PyQt5.QtGui import QColor, QFont @@ -37,6 +38,7 @@ from PyQt5.QtWidgets import QTabWidget, QTabBar from qutebrowser.commands import cmdutils from qutebrowser.config import configexc from qutebrowser.utils import standarddir, utils +from qutebrowser.browser.webkit.network import pac SYSTEM_PROXY = object() # Return value for Proxy type @@ -1014,14 +1016,36 @@ class ShellCommand(BaseType): return shlex.split(value) +def proxy_from_url(typ, url): + """Create a QNetworkProxy from QUrl and a proxy type. + + Args: + typ: QNetworkProxy::ProxyType. + url: URL of a proxy (possibly with credentials). + + Return: + New QNetworkProxy. + """ + proxy = QNetworkProxy(typ, url.host()) + if url.port() != -1: + proxy.setPort(url.port()) + if url.userName(): + proxy.setUser(url.userName()) + if url.password(): + proxy.setPassword(url.password()) + return proxy + + class Proxy(BaseType): """A proxy URL or special value.""" PROXY_TYPES = { - 'http': QNetworkProxy.HttpProxy, - 'socks': QNetworkProxy.Socks5Proxy, - 'socks5': QNetworkProxy.Socks5Proxy, + 'http': functools.partial(proxy_from_url, QNetworkProxy.HttpProxy), + 'pac+http': pac.PACFetcher, + 'pac+https': pac.PACFetcher, + 'socks': functools.partial(proxy_from_url, QNetworkProxy.Socks5Proxy), + 'socks5': functools.partial(proxy_from_url, QNetworkProxy.Socks5Proxy), } def __init__(self, none_ok=False): @@ -1053,6 +1077,7 @@ class Proxy(BaseType): out.append(('socks://', 'SOCKS proxy URL')) out.append(('socks://localhost:9050/', 'Tor via SOCKS')) out.append(('http://localhost:8080/', 'Local HTTP proxy')) + out.append(('pac+https://example.com/proxy.pac', 'Proxy autoconfiguration file URL')) return out def transform(self, value): @@ -1063,15 +1088,7 @@ class Proxy(BaseType): elif value == 'none': return QNetworkProxy(QNetworkProxy.NoProxy) url = QUrl(value) - typ = self.PROXY_TYPES[url.scheme()] - proxy = QNetworkProxy(typ, url.host()) - if url.port() != -1: - proxy.setPort(url.port()) - if url.userName(): - proxy.setUser(url.userName()) - if url.password(): - proxy.setPassword(url.password()) - return proxy + return self.PROXY_TYPES[url.scheme()](url) class SearchEngineName(BaseType): diff --git a/qutebrowser/javascript/pac_utils.js b/qutebrowser/javascript/pac_utils.js new file mode 100644 index 000000000..a6102df9f --- /dev/null +++ b/qutebrowser/javascript/pac_utils.js @@ -0,0 +1,257 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is mozilla.org code. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Akhil Arora + * Tomi Leppikangas + * Darin Fisher + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* + Script for Proxy Auto Config in the new world order. + - Gagan Saksena 04/24/00 +*/ + +function dnsDomainIs(host, domain) { + return (host.length >= domain.length && + host.substring(host.length - domain.length) == domain); +} + +function dnsDomainLevels(host) { + return host.split('.').length-1; +} + +function convert_addr(ipchars) { + var bytes = ipchars.split('.'); + var result = ((bytes[0] & 0xff) << 24) | + ((bytes[1] & 0xff) << 16) | + ((bytes[2] & 0xff) << 8) | + (bytes[3] & 0xff); + return result; +} + +function isInNet(ipaddr, pattern, maskstr) { + var test = /^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$/ + .exec(ipaddr); + if (test == null) { + ipaddr = dnsResolve(ipaddr); + if (ipaddr == null) + return false; + } else if (test[1] > 255 || test[2] > 255 || + test[3] > 255 || test[4] > 255) { + return false; // not an IP address + } + var host = convert_addr(ipaddr); + var pat = convert_addr(pattern); + var mask = convert_addr(maskstr); + return ((host & mask) == (pat & mask)); +} + +function isPlainHostName(host) { + return (host.search('\\\\.') == -1); +} + +function isResolvable(host) { + var ip = dnsResolve(host); + return (ip != null); +} + +function localHostOrDomainIs(host, hostdom) { + return (host == hostdom) || + (hostdom.lastIndexOf(host + '.', 0) == 0); +} + +function shExpMatch(url, pattern) { + pattern = pattern.replace(/\\./g, '\\\\.'); + pattern = pattern.replace(/\\*/g, '.*'); + pattern = pattern.replace(/\\?/g, '.'); + var newRe = new RegExp('^'+pattern+'$'); + return newRe.test(url); +} + +var wdays = {SUN: 0, MON: 1, TUE: 2, WED: 3, THU: 4, FRI: 5, SAT: 6}; + +var months = {JAN: 0, FEB: 1, MAR: 2, APR: 3, MAY: 4, JUN: 5, JUL: 6, + AUG: 7, SEP: 8, OCT: 9, NOV: 10, DEC: 11}; + +function weekdayRange() { + function getDay(weekday) { + if (weekday in wdays) { + return wdays[weekday]; + } + return -1; + } + var date = new Date(); + var argc = arguments.length; + var wday; + if (argc < 1) + return false; + if (arguments[argc - 1] == 'GMT') { + argc--; + wday = date.getUTCDay(); + } else { + wday = date.getDay(); + } + var wd1 = getDay(arguments[0]); + var wd2 = (argc == 2) ? getDay(arguments[1]) : wd1; + return (wd1 == -1 || wd2 == -1) ? false + : (wd1 <= wday && wday <= wd2); +} + +function dateRange() { + function getMonth(name) { + if (name in months) { + return months[name]; + } + return -1; + } + var date = new Date(); + var argc = arguments.length; + if (argc < 1) { + return false; + } + var isGMT = (arguments[argc - 1] == 'GMT'); + + if (isGMT) { + argc--; + } + // function will work even without explict handling of this case + if (argc == 1) { + var tmp = parseInt(arguments[0]); + if (isNaN(tmp)) { + return ((isGMT ? date.getUTCMonth() : date.getMonth()) == + getMonth(arguments[0])); + } else if (tmp < 32) { + return ((isGMT ? date.getUTCDate() : date.getDate()) == tmp); + } else { + return ((isGMT ? date.getUTCFullYear() : date.getFullYear()) == + tmp); + } + } + var year = date.getFullYear(); + var date1, date2; + date1 = new Date(year, 0, 1, 0, 0, 0); + date2 = new Date(year, 11, 31, 23, 59, 59); + var adjustMonth = false; + for (var i = 0; i < (argc >> 1); i++) { + var tmp = parseInt(arguments[i]); + if (isNaN(tmp)) { + var mon = getMonth(arguments[i]); + date1.setMonth(mon); + } else if (tmp < 32) { + adjustMonth = (argc <= 2); + date1.setDate(tmp); + } else { + date1.setFullYear(tmp); + } + } + for (var i = (argc >> 1); i < argc; i++) { + var tmp = parseInt(arguments[i]); + if (isNaN(tmp)) { + var mon = getMonth(arguments[i]); + date2.setMonth(mon); + } else if (tmp < 32) { + date2.setDate(tmp); + } else { + date2.setFullYear(tmp); + } + } + if (adjustMonth) { + date1.setMonth(date.getMonth()); + date2.setMonth(date.getMonth()); + } + if (isGMT) { + var tmp = date; + tmp.setFullYear(date.getUTCFullYear()); + tmp.setMonth(date.getUTCMonth()); + tmp.setDate(date.getUTCDate()); + tmp.setHours(date.getUTCHours()); + tmp.setMinutes(date.getUTCMinutes()); + tmp.setSeconds(date.getUTCSeconds()); + date = tmp; + } + return ((date1 <= date) && (date <= date2)); +} + +function timeRange() { + var argc = arguments.length; + var date = new Date(); + var isGMT= false; + + if (argc < 1) { + return false; + } + if (arguments[argc - 1] == 'GMT') { + isGMT = true; + argc--; + } + + var hour = isGMT ? date.getUTCHours() : date.getHours(); + var date1, date2; + date1 = new Date(); + date2 = new Date(); + + if (argc == 1) { + return (hour == arguments[0]); + } else if (argc == 2) { + return ((arguments[0] <= hour) && (hour <= arguments[1])); + } else { + switch (argc) { + case 6: + date1.setSeconds(arguments[2]); + date2.setSeconds(arguments[5]); + case 4: + var middle = argc >> 1; + date1.setHours(arguments[0]); + date1.setMinutes(arguments[1]); + date2.setHours(arguments[middle]); + date2.setMinutes(arguments[middle + 1]); + if (middle == 2) { + date2.setSeconds(59); + } + break; + default: + throw 'timeRange: bad number of arguments' + } + } + + if (isGMT) { + date.setFullYear(date.getUTCFullYear()); + date.setMonth(date.getUTCMonth()); + date.setDate(date.getUTCDate()); + date.setHours(date.getUTCHours()); + date.setMinutes(date.getUTCMinutes()); + date.setSeconds(date.getUTCSeconds()); + } + return ((date1 <= date) && (date <= date2)); +} diff --git a/scripts/dev/ci/travis_install.sh b/scripts/dev/ci/travis_install.sh index f5d75eed5..5317e2135 100644 --- a/scripts/dev/ci/travis_install.sh +++ b/scripts/dev/ci/travis_install.sh @@ -102,7 +102,7 @@ elif [[ $TRAVIS_OS_NAME == osx ]]; then exit 0 fi -pyqt_pkgs="python3-pyqt5 python3-pyqt5.qtwebkit" +pyqt_pkgs="python3-pyqt5 python3-pyqt5.qtquick python3-pyqt5.qtwebkit" pip_install pip pip_install -r misc/requirements/requirements-tox.txt From 27d64d368059bc09d9e5ea52e95e9c635418b704 Mon Sep 17 00:00:00 2001 From: Nikolay Amiantov Date: Thu, 25 Aug 2016 02:18:00 +0300 Subject: [PATCH 059/561] Add tests for PAC resolver and fetcher --- tests/unit/browser/webkit/network/test_pac.py | 173 ++++++++++++++++++ tests/unit/config/test_configtypes.py | 2 + 2 files changed, 175 insertions(+) create mode 100644 tests/unit/browser/webkit/network/test_pac.py diff --git a/tests/unit/browser/webkit/network/test_pac.py b/tests/unit/browser/webkit/network/test_pac.py new file mode 100644 index 000000000..13daf7642 --- /dev/null +++ b/tests/unit/browser/webkit/network/test_pac.py @@ -0,0 +1,173 @@ +# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: + +# Copyright 2016 Florian Bruhin (The Compiler) +# +# This file is part of qutebrowser. +# +# qutebrowser is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# qutebrowser is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with qutebrowser. If not, see . + +import http.server +import threading +import logging +import sys +import pytest + +from PyQt5.QtCore import QUrl, QT_VERSION_STR +from PyQt5.QtNetwork import (QNetworkProxy, QNetworkProxyQuery, QHostInfo, + QHostAddress) + +from qutebrowser.browser.webkit.network import pac + + +pytestmark = pytest.mark.usefixtures('qapp') + + +def _pac_common_test(test_str): + fun_str_f = """ + function FindProxyForURL(domain, host) {{ + {} + return "DIRECT; PROXY 127.0.0.1:8080; SOCKS 192.168.1.1:4444"; + }} + """ + + fun_str = fun_str_f.format(test_str) + res = pac.PACResolver(fun_str) + proxies = res.resolve(QNetworkProxyQuery(QUrl("https://example.com/test"))) + assert len(proxies) == 3 + assert proxies[0].type() == QNetworkProxy.NoProxy + assert proxies[1].type() == QNetworkProxy.HttpProxy + assert proxies[1].hostName() == "127.0.0.1" + assert proxies[1].port() == 8080 + assert proxies[2].type() == QNetworkProxy.Socks5Proxy + assert proxies[2].hostName() == "192.168.1.1" + assert proxies[2].port() == 4444 + + +def _pac_equality_test(call, expected): + test_str_f = """ + var res = ({0}); + var expected = ({1}); + if(res !== expected) {{ + throw new Error("failed test {0}: got '" + res + "', expected '" + expected + "'"); + }} + """ + _pac_common_test(test_str_f.format(call, expected)) + + +def _pac_except_test(caplog, call): + test_str_f = """ + var thrown = false; + try {{ + var res = ({0}); + }} catch(e) {{ + thrown = true; + }} + if(!thrown) {{ + throw new Error("failed test {0}: got '" + res + "', expected exception"); + }} + """ + with caplog.at_level(logging.ERROR): + _pac_common_test(test_str_f.format(call)) + + +def _pac_noexcept_test(call): + test_str_f = """ + var res = ({0}); + """ + _pac_common_test(test_str_f.format(call)) + + +# pylint: disable=line-too-long, invalid-name + + +@pytest.mark.parametrize("domain, expected", [ + ("known.domain", "'1.2.3.4'"), + ("bogus.domain.foobar", "null") +]) +def test_dnsResolve(monkeypatch, domain, expected): + def mock_fromName(host): + info = QHostInfo() + if host == "known.domain": + info.setAddresses([QHostAddress("1.2.3.4")]) + return info + monkeypatch.setattr(QHostInfo, 'fromName', mock_fromName) + _pac_equality_test("dnsResolve('{}')".format(domain), expected) + + +def test_myIpAddress(): + _pac_equality_test("isResolvable(myIpAddress())", "true") + + +def test_proxyBindings(): + _pac_equality_test("JSON.stringify(ProxyConfig.bindings)", "'{}'") + + +def test_invalid_port(): + test_str = """ + function FindProxyForURL(domain, host) { + return "PROXY 127.0.0.1:FOO"; + } + """ + + res = pac.PACResolver(test_str) + with pytest.raises(pac.ParseProxyError): + res.resolve(QNetworkProxyQuery(QUrl("https://example.com/test"))) + + +# See https://github.com/The-Compiler/qutebrowser/pull/1891#issuecomment-259222615 + +try: + from PyQt5 import QtWebEngineWidgets +except ImportError: + QtWebEngineWidgets = None + + +@pytest.mark.skipif(QT_VERSION_STR == "5.7.0" and + QtWebEngineWidgets is not None and + sys.platform == "linux", + reason="Segfaults when run with QtWebEngine tests on Linux") +def test_fetch(): + test_str = """ + function FindProxyForURL(domain, host) { + return "DIRECT; PROXY 127.0.0.1:8080; SOCKS 192.168.1.1:4444"; + } + """ + + class PACHandler(http.server.BaseHTTPRequestHandler): + def do_GET(self): + self.send_response(200) + + self.send_header('Content-type', 'application/x-ns-proxy-autoconfig') + self.end_headers() + + self.wfile.write(test_str.encode("ascii")) + + ready_event = threading.Event() + + def serve(): + httpd = http.server.HTTPServer(("127.0.0.1", 8081), PACHandler) + ready_event.set() + httpd.handle_request() + httpd.server_close() + + serve_thread = threading.Thread(target=serve, daemon=True) + serve_thread.start() + try: + ready_event.wait() + res = pac.PACFetcher(QUrl("pac+http://127.0.0.1:8081")) + assert res.fetch_error() is None + finally: + serve_thread.join() + proxies = res.resolve(QNetworkProxyQuery(QUrl("https://example.com/test"))) + assert len(proxies) == 3 diff --git a/tests/unit/config/test_configtypes.py b/tests/unit/config/test_configtypes.py index 77cfdfdee..37e763999 100644 --- a/tests/unit/config/test_configtypes.py +++ b/tests/unit/config/test_configtypes.py @@ -1492,6 +1492,8 @@ class TestProxy: 'http://user:pass@example.com:2323/', 'socks://user:pass@example.com:2323/', 'socks5://user:pass@example.com:2323/', + 'pac+http://example.com/proxy.pac', + 'pac+https://example.com/proxy.pac', ]) def test_validate_valid(self, klass, val): klass(none_ok=True).validate(val) From a7d96e0b858c4ca1c60e5dd86a6acabb46b85ebc Mon Sep 17 00:00:00 2001 From: Nikolay Amiantov Date: Tue, 6 Dec 2016 19:04:14 +0300 Subject: [PATCH 060/561] Ignore ESLint warnings for Mozilla's PAC utils --- qutebrowser/javascript/.eslintignore | 2 ++ tox.ini | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 qutebrowser/javascript/.eslintignore diff --git a/qutebrowser/javascript/.eslintignore b/qutebrowser/javascript/.eslintignore new file mode 100644 index 000000000..ca4d3c667 --- /dev/null +++ b/qutebrowser/javascript/.eslintignore @@ -0,0 +1,2 @@ +# Upstream Mozilla's code +pac_utils.js diff --git a/tox.ini b/tox.ini index 22e584667..a25effb05 100644 --- a/tox.ini +++ b/tox.ini @@ -183,4 +183,5 @@ commands = [testenv:eslint] deps = whitelist_externals = eslint -commands = eslint --color qutebrowser/javascript +changedir = {toxinidir}/qutebrowser/javascript +commands = eslint --color . From 57adcea58740663a3bb01a155f19b72a4b0efe51 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 7 Dec 2016 07:01:06 +0100 Subject: [PATCH 061/561] Fix QTBUG-54419 workaround with tabs-are-windows set Fixes #2162 --- qutebrowser/mainwindow/tabbedbrowser.py | 11 ++++++++--- tests/end2end/features/tabs.feature | 17 +++++++++++++++++ 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py index eb3b97e2c..6d46abbf8 100644 --- a/qutebrowser/mainwindow/tabbedbrowser.py +++ b/qutebrowser/mainwindow/tabbedbrowser.py @@ -346,7 +346,8 @@ class TabbedBrowser(tabwidget.TabWidget): @pyqtSlot('QUrl') @pyqtSlot('QUrl', bool) - def tabopen(self, url=None, background=None, explicit=False, idx=None): + def tabopen(self, url=None, background=None, explicit=False, idx=None, *, + ignore_tabs_are_windows=False): """Open a new tab with a given URL. Inner logic for open-tab and open-tab-bg. @@ -363,6 +364,8 @@ class TabbedBrowser(tabwidget.TabWidget): the current. - Explicitly opened tabs are at the very right. idx: The index where the new tab should be opened. + ignore_tabs_are_windows: If given, never open a new window, even + with tabs-are-windows set. Return: The opened WebView instance. @@ -373,7 +376,8 @@ class TabbedBrowser(tabwidget.TabWidget): "explicit {}, idx {}".format( url, background, explicit, idx)) - if config.get('tabs', 'tabs-are-windows') and self.count() > 0: + if (config.get('tabs', 'tabs-are-windows') and self.count() > 0 and + not ignore_tabs_are_windows): from qutebrowser.mainwindow import mainwindow window = mainwindow.MainWindow() window.show() @@ -528,7 +532,8 @@ class TabbedBrowser(tabwidget.TabWidget): background = self.currentIndex() != idx self.setUpdatesEnabled(False) try: - self.tabopen(url, background=background, idx=idx) + self.tabopen(url, background=background, idx=idx, + ignore_tabs_are_windows=True) self.close_tab(tab, add_undo=False) finally: self.setUpdatesEnabled(True) diff --git a/tests/end2end/features/tabs.feature b/tests/end2end/features/tabs.feature index 8066a6518..9d32fc126 100644 --- a/tests/end2end/features/tabs.feature +++ b/tests/end2end/features/tabs.feature @@ -973,6 +973,8 @@ Feature: Tab management And I run :buffer "1/2/3" Then the error "No matching tab for: 1/2/3" should be shown + # Other + Scenario: Using :tab-next after closing last tab (#1448) When I set tabs -> last-close to close And I run :tab-only @@ -986,3 +988,18 @@ Feature: Tab management And I run :tab-close ;; tab-prev Then qutebrowser should quit And no crash should happen + + Scenario: Opening link with tabs-are-windows set (#2162) + When I set tabs -> tabs-are-windows to true + And I open data/hints/html/simple.html + And I hint with args "all tab-fg" and follow a + And I wait until data/hello.txt is loaded + Then the session should look like: + windows: + - tabs: + - history: + - url: about:blank + - url: http://localhost:*/data/hints/html/simple.html + - tabs: + - history: + - url: http://localhost:*/data/hello.txt From e5ddb281f4e00f8831ebda0ae84cb47af1f8b027 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 7 Dec 2016 12:33:44 +0100 Subject: [PATCH 062/561] Add a ui -> prompt-filebrowser option See #2104 --- qutebrowser/config/configdata.py | 4 ++++ qutebrowser/mainwindow/prompt.py | 13 ++++++++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index da0fea5e3..76bbc260d 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -392,6 +392,10 @@ def data(readonly=False): SettingValue(typ.Int(minval=0), '8'), "The rounding radius for the edges of prompts."), + ('prompt-filebrowser', + SettingValue(typ.Bool(), 'true'), + "Show a filebrowser in upload/download prompts."), + readonly=readonly )), diff --git a/qutebrowser/mainwindow/prompt.py b/qutebrowser/mainwindow/prompt.py index 6e2ab68d6..5af2f6b7c 100644 --- a/qutebrowser/mainwindow/prompt.py +++ b/qutebrowser/mainwindow/prompt.py @@ -30,7 +30,7 @@ from PyQt5.QtWidgets import (QWidget, QGridLayout, QVBoxLayout, QLineEdit, QLabel, QFileSystemModel, QTreeView, QSizePolicy) from qutebrowser.browser import downloads -from qutebrowser.config import style +from qutebrowser.config import style, config from qutebrowser.utils import usertypes, log, utils, qtutils, objreg, message from qutebrowser.keyinput import modeman from qutebrowser.commands import cmdutils, cmdexc @@ -564,7 +564,9 @@ class FilenamePrompt(_BasePrompt): self.setFocusProxy(self._lineedit) self._init_key_label() - self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) + + if config.get('ui', 'prompt-filebrowser'): + self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) @pyqtSlot(str) def _set_fileview_root(self, path, *, tabbed=False): @@ -624,7 +626,12 @@ class FilenamePrompt(_BasePrompt): self._file_model = QFileSystemModel(self) self._file_view.setModel(self._file_model) self._file_view.clicked.connect(self._insert_path) - self._vbox.addWidget(self._file_view) + + if config.get('ui', 'prompt-filebrowser'): + self._vbox.addWidget(self._file_view) + else: + self._file_view.hide() + # Only show name self._file_view.setHeaderHidden(True) for col in range(1, 4): From 8116aef77a7bdf13dcdd7b390ee5a322b2895061 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 7 Dec 2016 12:55:58 +0100 Subject: [PATCH 063/561] Fix prompt unittests --- tests/unit/mainwindow/test_prompt.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/unit/mainwindow/test_prompt.py b/tests/unit/mainwindow/test_prompt.py index 1288ccd3f..dcc5461a5 100644 --- a/tests/unit/mainwindow/test_prompt.py +++ b/tests/unit/mainwindow/test_prompt.py @@ -34,7 +34,7 @@ def setup(qapp, key_config_stub): class TestFileCompletion: @pytest.fixture - def get_prompt(self, qtbot): + def get_prompt(self, qtbot, config_stub): """Get a function to display a prompt with a path.""" def _get_prompt_func(path): question = usertypes.Question() @@ -48,6 +48,7 @@ class TestFileCompletion: assert prompt._lineedit.text() == path return prompt + config_stub.data = {'ui': {'prompt-filebrowser': 'true'}} return _get_prompt_func @pytest.mark.parametrize('steps, where, subfolder', [ From 3e1b8f0ee30168be31acd195d8e757ad98f310e0 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 7 Dec 2016 14:02:25 +0100 Subject: [PATCH 064/561] Update docs --- doc/help/settings.asciidoc | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc index 0ee5f5769..af3fd459b 100644 --- a/doc/help/settings.asciidoc +++ b/doc/help/settings.asciidoc @@ -56,6 +56,7 @@ |<>|Hide the window decoration when using wayland (requires restart) |<>|Keychains that shouldn't be shown in the keyhint dialog |<>|The rounding radius for the edges of prompts. +|<>|Show a filebrowser in upload/download prompts. |============== .Quick reference for section ``network'' @@ -729,6 +730,17 @@ The rounding radius for the edges of prompts. Default: +pass:[8]+ +[[ui-prompt-filebrowser]] +=== prompt-filebrowser +Show a filebrowser in upload/download prompts. + +Valid values: + + * +true+ + * +false+ + +Default: +pass:[true]+ + == network Settings related to the network. From 70b0a86729b82906659fcfb5cd4e4c515f1dfb6f Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 8 Dec 2016 06:46:11 +0100 Subject: [PATCH 065/561] WebEngine: Save/restore zoom when opening a new page This isn't very pleasurable, but I can't find a solution to set the zoom correctly while the page is still loading. See https://bugreports.qt.io/browse/QTBUG-51851 Fixes #1993 --- qutebrowser/browser/webengine/webenginetab.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index e954edd76..a7c7dcc59 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -472,6 +472,7 @@ class WebEngineTab(browsertab.AbstractTab): self._init_js() self._child_event_filter = None self.needs_qtbug54419_workaround = False + self._saved_zoom = None def _init_js(self): js_code = '\n'.join([ @@ -503,7 +504,15 @@ class WebEngineTab(browsertab.AbstractTab): parent=self) self._widget.installEventFilter(self._child_event_filter) + @pyqtSlot() + def _restore_zoom(self): + if self._saved_zoom is None: + return + self.zoom.set_factor(self._saved_zoom) + self._saved_zoom = None + def openurl(self, url): + self._saved_zoom = self.zoom.factor() self._openurl_prepare(url) self._widget.load(url) @@ -620,16 +629,19 @@ class WebEngineTab(browsertab.AbstractTab): def _connect_signals(self): view = self._widget page = view.page() + page.windowCloseRequested.connect(self.window_close_requested) page.linkHovered.connect(self.link_hovered) page.loadProgress.connect(self._on_load_progress) page.loadStarted.connect(self._on_load_started) page.loadFinished.connect(self._on_history_trigger) - view.titleChanged.connect(self.title_changed) - view.urlChanged.connect(self._on_url_changed) + page.loadFinished.connect(self._restore_zoom) page.loadFinished.connect(self._on_load_finished) page.certificate_error.connect(self._on_ssl_errors) page.authenticationRequired.connect(self._on_authentication_required) + + view.titleChanged.connect(self.title_changed) + view.urlChanged.connect(self._on_url_changed) try: view.iconChanged.connect(self.icon_changed) except AttributeError: From c7d4ea52472bf072a273f906502b046519c3ca50 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 8 Dec 2016 07:05:18 +0100 Subject: [PATCH 066/561] Ignore connection failures for debug.log_signals --- qutebrowser/utils/debug.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/qutebrowser/utils/debug.py b/qutebrowser/utils/debug.py index 194540f34..956c40689 100644 --- a/qutebrowser/utils/debug.py +++ b/qutebrowser/utils/debug.py @@ -70,7 +70,10 @@ def log_signals(obj): name = bytes(meta_method.name()).decode('ascii') if name != 'destroyed': signal = getattr(obj, name) - signal.connect(functools.partial(log_slot, obj, signal)) + try: + signal.connect(functools.partial(log_slot, obj, signal)) + except TypeError: + pass if inspect.isclass(obj): old_init = obj.__init__ From 62bcd24c9a9e3310d2eb46d0876f22ef30a24451 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 8 Dec 2016 09:45:46 +0100 Subject: [PATCH 067/561] Enable HighDPI scaling This should scale up things automatically, without needing to set e.g. default-zoom (at least with QtWebEngine). See #1993, #1585 --- qutebrowser/app.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/qutebrowser/app.py b/qutebrowser/app.py index de8a131ad..62cbf48c3 100644 --- a/qutebrowser/app.py +++ b/qutebrowser/app.py @@ -34,7 +34,7 @@ import tokenize from PyQt5.QtWidgets import QApplication, QWidget from PyQt5.QtGui import QDesktopServices, QPixmap, QIcon, QWindow from PyQt5.QtCore import (pyqtSlot, qInstallMessageHandler, QTimer, QUrl, - QObject, QEvent, pyqtSignal) + QObject, QEvent, Qt, pyqtSignal) try: import hunter except ImportError: @@ -70,6 +70,14 @@ def run(args): quitter = Quitter(args) objreg.register('quitter', quitter) + # Enable HighDPI + assert QApplication.instance() is None + try: + QApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True) + except AttributeError: + # Qt < 5.6 + pass + global qApp qApp = Application(args) qApp.setOrganizationName("qutebrowser") From 976cd5f881153156f456bef108e21f758140f336 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 8 Dec 2016 10:31:52 +0100 Subject: [PATCH 068/561] Fix lint --- qutebrowser/utils/debug.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/qutebrowser/utils/debug.py b/qutebrowser/utils/debug.py index 956c40689..e342446b4 100644 --- a/qutebrowser/utils/debug.py +++ b/qutebrowser/utils/debug.py @@ -71,8 +71,9 @@ def log_signals(obj): if name != 'destroyed': signal = getattr(obj, name) try: - signal.connect(functools.partial(log_slot, obj, signal)) - except TypeError: + signal.connect(functools.partial( + log_slot, obj, signal)) + except TypeError: # pragma: no cover pass if inspect.isclass(obj): From 5b70df15fa9c2f01c51ef2e42e8e07ff3fa0f835 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 8 Dec 2016 21:23:56 +0100 Subject: [PATCH 069/561] Revert "Enable HighDPI scaling" This reverts commit 62bcd24c9a9e3310d2eb46d0876f22ef30a24451. Looks like this causes issues for various people... --- qutebrowser/app.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/qutebrowser/app.py b/qutebrowser/app.py index 62cbf48c3..de8a131ad 100644 --- a/qutebrowser/app.py +++ b/qutebrowser/app.py @@ -34,7 +34,7 @@ import tokenize from PyQt5.QtWidgets import QApplication, QWidget from PyQt5.QtGui import QDesktopServices, QPixmap, QIcon, QWindow from PyQt5.QtCore import (pyqtSlot, qInstallMessageHandler, QTimer, QUrl, - QObject, QEvent, Qt, pyqtSignal) + QObject, QEvent, pyqtSignal) try: import hunter except ImportError: @@ -70,14 +70,6 @@ def run(args): quitter = Quitter(args) objreg.register('quitter', quitter) - # Enable HighDPI - assert QApplication.instance() is None - try: - QApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True) - except AttributeError: - # Qt < 5.6 - pass - global qApp qApp = Application(args) qApp.setOrganizationName("qutebrowser") From 03eea7f62a751ca3db01ad9a36493e921d894c48 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 9 Dec 2016 07:00:25 +0100 Subject: [PATCH 070/561] Remove download filename suffixes with QtWebEngine --- .../browser/webengine/webenginedownloads.py | 18 +++++++++- .../webengine/test_webenginedownloads.py | 34 +++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 tests/unit/browser/webengine/test_webenginedownloads.py diff --git a/qutebrowser/browser/webengine/webenginedownloads.py b/qutebrowser/browser/webengine/webenginedownloads.py index 5286a6298..8ce0cdc2e 100644 --- a/qutebrowser/browser/webengine/webenginedownloads.py +++ b/qutebrowser/browser/webengine/webenginedownloads.py @@ -19,6 +19,7 @@ """QtWebEngine specific code for downloads.""" +import re import os.path import functools @@ -122,6 +123,21 @@ class DownloadItem(downloads.AbstractDownloadItem): self._qt_item.accept() +def _get_suggested_filename(path): + """Convert a path we got from chromium to a suggested filename. + + Chromium thinks we want to download stuff to ~/Download, so even if we + don't, we get downloads with a suffix like (1) for files existing there. + + We simply strip the suffix off via regex. + + See https://bugreports.qt.io/browse/QTBUG-56978 + """ + filename = os.path.basename(path) + filename = re.sub(r'\([0-9]+\)$', '', filename) + return filename + + class DownloadManager(downloads.AbstractDownloadManager): """Manager for currently running downloads.""" @@ -134,7 +150,7 @@ class DownloadManager(downloads.AbstractDownloadManager): @pyqtSlot(QWebEngineDownloadItem) def handle_download(self, qt_item): """Start a download coming from a QWebEngineProfile.""" - suggested_filename = os.path.basename(qt_item.path()) + suggested_filename = _get_suggested_filename(qt_item.path()) download = DownloadItem(qt_item) self._init_item(download, auto_remove=False, diff --git a/tests/unit/browser/webengine/test_webenginedownloads.py b/tests/unit/browser/webengine/test_webenginedownloads.py new file mode 100644 index 000000000..3cead0e97 --- /dev/null +++ b/tests/unit/browser/webengine/test_webenginedownloads.py @@ -0,0 +1,34 @@ +# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: + +# Copyright 2016 Florian Bruhin (The Compiler) +# +# This file is part of qutebrowser. +# +# qutebrowser is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# qutebrowser is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with qutebrowser. If not, see . + +import os.path + +import pytest + +from qutebrowser.browser.webengine import webenginedownloads + + +@pytest.mark.parametrize('path, expected', [ + (os.path.join('subfolder', 'foo'), 'foo'), + ('foo(1)', 'foo'), + ('foo(a)', 'foo(a)'), + ('foo1', 'foo1'), +]) +def test_get_suggested_filename(path, expected): + assert webenginedownloads._get_suggested_filename(path) == expected From d0372f1730e4d1d775ba4fdf818f0e70735b98c9 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 9 Dec 2016 07:08:51 +0100 Subject: [PATCH 071/561] Decode percent sequences in QtWebEngine downloads Fixes #2122. --- qutebrowser/browser/webengine/webenginedownloads.py | 2 ++ .../end2end/data/downloads/download with spaces.bin | Bin 0 -> 1 bytes tests/end2end/features/downloads.feature | 5 +++++ .../browser/webengine/test_webenginedownloads.py | 1 + 4 files changed, 8 insertions(+) create mode 100644 tests/end2end/data/downloads/download with spaces.bin diff --git a/qutebrowser/browser/webengine/webenginedownloads.py b/qutebrowser/browser/webengine/webenginedownloads.py index 8ce0cdc2e..e78e939e1 100644 --- a/qutebrowser/browser/webengine/webenginedownloads.py +++ b/qutebrowser/browser/webengine/webenginedownloads.py @@ -21,6 +21,7 @@ import re import os.path +import urllib import functools from PyQt5.QtCore import pyqtSlot, Qt @@ -135,6 +136,7 @@ def _get_suggested_filename(path): """ filename = os.path.basename(path) filename = re.sub(r'\([0-9]+\)$', '', filename) + filename = urllib.parse.unquote(filename) return filename diff --git a/tests/end2end/data/downloads/download with spaces.bin b/tests/end2end/data/downloads/download with spaces.bin new file mode 100644 index 0000000000000000000000000000000000000000..f76dd238ade08917e6712764a16a22005a50573d GIT binary patch literal 1 IcmZPo000310RR91 literal 0 HcmV?d00001 diff --git a/tests/end2end/features/downloads.feature b/tests/end2end/features/downloads.feature index 14757c87a..b6a38da29 100644 --- a/tests/end2end/features/downloads.feature +++ b/tests/end2end/features/downloads.feature @@ -113,6 +113,11 @@ Feature: Downloading things from a website. And I wait for "Download drip finished" in the log Then the downloaded file drip should contain 128 bytes + Scenario: Downloading a file with spaces + When I open data/downloads/download with spaces.bin without waiting + And I wait until the download is finished + Then the downloaded file download with spaces.bin should exist + ## :download-retry Scenario: Retrying a failed download diff --git a/tests/unit/browser/webengine/test_webenginedownloads.py b/tests/unit/browser/webengine/test_webenginedownloads.py index 3cead0e97..fcd3c2adb 100644 --- a/tests/unit/browser/webengine/test_webenginedownloads.py +++ b/tests/unit/browser/webengine/test_webenginedownloads.py @@ -29,6 +29,7 @@ from qutebrowser.browser.webengine import webenginedownloads ('foo(1)', 'foo'), ('foo(a)', 'foo(a)'), ('foo1', 'foo1'), + ('foo%20bar', 'foo bar'), ]) def test_get_suggested_filename(path, expected): assert webenginedownloads._get_suggested_filename(path) == expected From 5746337733143f38bcfc8681eedda29a0e34ecd6 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 9 Dec 2016 07:29:51 +0100 Subject: [PATCH 072/561] Fix downloads without QtWebEngine --- tests/unit/browser/webengine/test_webenginedownloads.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/unit/browser/webengine/test_webenginedownloads.py b/tests/unit/browser/webengine/test_webenginedownloads.py index fcd3c2adb..afd6e85a2 100644 --- a/tests/unit/browser/webengine/test_webenginedownloads.py +++ b/tests/unit/browser/webengine/test_webenginedownloads.py @@ -21,6 +21,8 @@ import os.path import pytest +pytest.importorskip('PyQt5.QtWebEngineWidgets') + from qutebrowser.browser.webengine import webenginedownloads From c63e2ee3b0c2400610ccd97714d0ab0135dbdf0c Mon Sep 17 00:00:00 2001 From: Fritz Reichwald Date: Sat, 10 Dec 2016 13:38:30 +0100 Subject: [PATCH 073/561] Add Keywords entry to comply with freedesktop guidelines --- qutebrowser.desktop | 1 + 1 file changed, 1 insertion(+) diff --git a/qutebrowser.desktop b/qutebrowser.desktop index 0dbfa2db7..e505774a8 100644 --- a/qutebrowser.desktop +++ b/qutebrowser.desktop @@ -8,3 +8,4 @@ Exec=qutebrowser %u Terminal=false StartupNotify=false MimeType=text/html;text/xml;application/xhtml+xml;application/xml;application/rdf+xml;image/gif;image/jpeg;image/png;x-scheme-handler/http;x-scheme-handler/https; +Keywords=Browser From f0831dacaeb08dfe2e54a4fd051fcfe809b7d670 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 13 Dec 2016 21:49:10 +0000 Subject: [PATCH 074/561] Bind back/forward keys by default Those are the XF86Back/Forward media keys on a keyboard, which are apparently handled by default with QtWebKit, but not QtWebEngine. Fixes #2166. --- qutebrowser/config/configdata.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index 76bbc260d..3a1b71e01 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -1562,10 +1562,10 @@ KEY_DATA = collections.OrderedDict([ ('tab-clone', ['gC']), ('reload', ['r', '']), ('reload -f', ['R', '']), - ('back', ['H']), + ('back', ['H', '']), ('back -t', ['th']), ('back -w', ['wh']), - ('forward', ['L']), + ('forward', ['L', '']), ('forward -t', ['tl']), ('forward -w', ['wl']), ('fullscreen', ['']), From caa3df1595379252401e5ab2284bcae371338ca6 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 14 Dec 2016 20:32:00 +0100 Subject: [PATCH 075/561] requirement updates --- misc/requirements/requirements-codecov.txt | 2 +- misc/requirements/requirements-flake8.txt | 2 +- misc/requirements/requirements-pylint-master.txt | 4 ++-- misc/requirements/requirements-pylint.txt | 4 ++-- misc/requirements/requirements-pyroma.txt | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/misc/requirements/requirements-codecov.txt b/misc/requirements/requirements-codecov.txt index deb399503..659764d86 100644 --- a/misc/requirements/requirements-codecov.txt +++ b/misc/requirements/requirements-codecov.txt @@ -2,4 +2,4 @@ codecov==2.0.5 coverage==4.2 -requests==2.12.3 +requests==2.12.4 diff --git a/misc/requirements/requirements-flake8.txt b/misc/requirements/requirements-flake8.txt index 51e45a808..1b48ba135 100644 --- a/misc/requirements/requirements-flake8.txt +++ b/misc/requirements/requirements-flake8.txt @@ -12,7 +12,7 @@ flake8-putty==0.4.0 flake8-string-format==0.2.3 flake8-tidy-imports==1.0.3 flake8-tuple==0.2.12 -mccabe==0.5.2 +mccabe==0.5.3 packaging==16.8 pep8-naming==0.4.1 pycodestyle==2.2.0 diff --git a/misc/requirements/requirements-pylint-master.txt b/misc/requirements/requirements-pylint-master.txt index 63ea698a6..db616759b 100644 --- a/misc/requirements/requirements-pylint-master.txt +++ b/misc/requirements/requirements-pylint-master.txt @@ -4,9 +4,9 @@ editdistance==0.3.1 isort==4.2.5 lazy-object-proxy==1.2.2 -mccabe==0.5.2 +mccabe==0.5.3 -e git+https://github.com/PyCQA/pylint.git#egg=pylint ./scripts/dev/pylint_checkers -requests==2.12.3 +requests==2.12.4 six==1.10.0 wrapt==1.10.8 diff --git a/misc/requirements/requirements-pylint.txt b/misc/requirements/requirements-pylint.txt index b18c26b02..317c1efd8 100644 --- a/misc/requirements/requirements-pylint.txt +++ b/misc/requirements/requirements-pylint.txt @@ -4,10 +4,10 @@ astroid==1.4.8 github3.py==0.9.6 isort==4.2.5 lazy-object-proxy==1.2.2 -mccabe==0.5.2 +mccabe==0.5.3 pylint==1.6.4 ./scripts/dev/pylint_checkers -requests==2.12.3 +requests==2.12.4 six==1.10.0 uritemplate==3.0.0 uritemplate.py==3.0.2 diff --git a/misc/requirements/requirements-pyroma.txt b/misc/requirements/requirements-pyroma.txt index 889bf362f..9febd961b 100644 --- a/misc/requirements/requirements-pyroma.txt +++ b/misc/requirements/requirements-pyroma.txt @@ -1,4 +1,4 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -docutils==0.12 +docutils==0.13.1 pyroma==2.2 From 6a1eafd9b1cf74ed6cad463fa43740f523ef7328 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 15 Dec 2016 06:38:25 +0100 Subject: [PATCH 076/561] Update authors --- README.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.asciidoc b/README.asciidoc index 272828b37..b69bb85de 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -197,6 +197,7 @@ Contributors, sorted by the number of commits in descending order: * Peter Rice * Ismail S * Halfwit +* Fritz Reichwald * David Vogt * Claire Cavanaugh * rikn00 @@ -207,7 +208,6 @@ Contributors, sorted by the number of commits in descending order: * Michael Ilsaas * Martin Zimmermann * Jussi Timperi -* Fritz Reichwald * Brian Jackson * thuck * sbinix From f6c73f3ad641b9c45409c15041233b07a7c740f9 Mon Sep 17 00:00:00 2001 From: Samuel Walladge Date: Fri, 16 Dec 2016 21:54:04 +1030 Subject: [PATCH 077/561] implement optional download-open handler setting - adds new `general` -> `default-open-dispatcher` setting (string) - if set, will be used instead of QDesktopServices.openUrl (xdg-open backend) - fixes the 'download handlers' part of issue #841 - note that this is only relevent to the `:download-open` command and other methods of opening downloaded files from qutebrowser --- doc/help/settings.asciidoc | 9 +++++++++ qutebrowser/browser/downloads.py | 15 ++++++++++++--- qutebrowser/config/configdata.py | 7 +++++++ 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc index af3fd459b..5dffe1733 100644 --- a/doc/help/settings.asciidoc +++ b/doc/help/settings.asciidoc @@ -11,6 +11,7 @@ |<>|Whether to find text on a page case-insensitively. |<>|The default page(s) to open at the start, separated by commas. |<>|The URL parameters to strip with :yank url, separated by commas. +|<>|The default program used to open downloads. Set to an empty string to use the default internal handler. |<>|The page to open if :open -t/-b/-w is used without URL. Use `about:blank` for a blank page. |<>|Whether to start a search when something else than a URL is entered. |<>|Whether to save the config automatically on quit. @@ -331,6 +332,14 @@ The URL parameters to strip with :yank url, separated by commas. Default: +pass:[ref,utm_source,utm_medium,utm_campaign,utm_term,utm_content]+ +[[general-default-open-dispatcher]] +=== default-open-dispatcher +The default program used to open downloads. Set to an empty string to use the default internal handler. + +Any {} in the string will be expanded to the filename, else the filename will be appended. + +Default: empty + [[general-default-page]] === default-page The page to open if :open -t/-b/-w is used without URL. Use `about:blank` for a blank page. diff --git a/qutebrowser/browser/downloads.py b/qutebrowser/browser/downloads.py index 12e766ecb..b8b3de5cd 100644 --- a/qutebrowser/browser/downloads.py +++ b/qutebrowser/browser/downloads.py @@ -512,8 +512,8 @@ class AbstractDownloadItem(QObject): Args: cmdline: The command to use as string. A `{}` is expanded to the filename. None means to use the system's default - application. If no `{}` is found, the filename is appended - to the cmdline. + application or `default-open-dispatcher` if set. If no + `{}` is found, the filename is appended to the cmdline. """ assert self.successful filename = self._get_open_filename() @@ -521,13 +521,22 @@ class AbstractDownloadItem(QObject): log.downloads.error("No filename to open the download!") return - if cmdline is None: + # the default program to open downloads with - will be empty string + # if we want to use the default + override = config.get('general', 'default-open-dispatcher') + + # precedence order: supplied cmdline > default-open-dispatcher > openUrl + + if cmdline is None and not override: log.downloads.debug("Opening {} with the system application" .format(filename)) url = QUrl.fromLocalFile(filename) QDesktopServices.openUrl(url) return + if cmdline is None and override: + cmdline = override + cmd, *args = shlex.split(cmdline) args = [arg.replace('{}', filename) for arg in args] if '{}' not in cmdline: diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index 3a1b71e01..3b71c6044 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -147,6 +147,13 @@ def data(readonly=False): "The URL parameters to strip with :yank url, separated by " "commas."), + ('default-open-dispatcher', + SettingValue(typ.String(none_ok=True), ''), + "The default program used to open downloads. Set to an empty " + "string to use the default internal handler.\n\n" + "Any {} in the string will be expanded to the filename, else " + "the filename will be appended."), + ('default-page', SettingValue(typ.FuzzyUrl(), '${startpage}'), "The page to open if :open -t/-b/-w is used without URL. Use " From aafcd1f1d7be5dc5122dab207e9de84011a53bf2 Mon Sep 17 00:00:00 2001 From: Samuel Walladge Date: Sat, 17 Dec 2016 09:22:22 +1030 Subject: [PATCH 078/561] fix failing pylint test (line too long) --- qutebrowser/browser/downloads.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutebrowser/browser/downloads.py b/qutebrowser/browser/downloads.py index b8b3de5cd..6326e6279 100644 --- a/qutebrowser/browser/downloads.py +++ b/qutebrowser/browser/downloads.py @@ -525,7 +525,7 @@ class AbstractDownloadItem(QObject): # if we want to use the default override = config.get('general', 'default-open-dispatcher') - # precedence order: supplied cmdline > default-open-dispatcher > openUrl + # precedence order: cmdline > default-open-dispatcher > openUrl if cmdline is None and not override: log.downloads.debug("Opening {} with the system application" From 28ccd03acd5d0e34eec457519ffc74c45a541fc3 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 19 Dec 2016 08:11:05 +0100 Subject: [PATCH 079/561] tox/test requirements: Update py to 1.4.32 --- misc/requirements/requirements-tests.txt | 2 +- misc/requirements/requirements-tox.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index b086bcb6a..e50180d91 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -16,7 +16,7 @@ Mako==1.0.6 # MarkupSafe==0.23 parse==1.6.6 parse-type==0.3.4 -py==1.4.31 +py==1.4.32 pytest==3.0.4 # rq.filter: != 3.0.5 pytest-bdd==2.18.1 pytest-catchlog==1.2.2 diff --git a/misc/requirements/requirements-tox.txt b/misc/requirements/requirements-tox.txt index 4768ddc09..caac25cd3 100644 --- a/misc/requirements/requirements-tox.txt +++ b/misc/requirements/requirements-tox.txt @@ -1,6 +1,6 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py pluggy==0.4.0 -py==1.4.31 +py==1.4.32 tox==2.5.0 virtualenv==15.1.0 From 3f61c7871ca48083be8a13fa7e993edc08b3ca73 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 19 Dec 2016 08:12:21 +0100 Subject: [PATCH 080/561] pylint requirements: Update astroid to 1.4.9 --- misc/requirements/requirements-pylint.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-pylint.txt b/misc/requirements/requirements-pylint.txt index 317c1efd8..2db77f1c5 100644 --- a/misc/requirements/requirements-pylint.txt +++ b/misc/requirements/requirements-pylint.txt @@ -1,6 +1,6 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -astroid==1.4.8 +astroid==1.4.9 github3.py==0.9.6 isort==4.2.5 lazy-object-proxy==1.2.2 From 135f99c467ed5b0510b47bbf3cb873ea0e17121c Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 19 Dec 2016 08:13:37 +0100 Subject: [PATCH 081/561] test requirements: Update CherryPy to 8.1.3 --- misc/requirements/requirements-tests.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index e50180d91..0743df1ae 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -1,7 +1,7 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py beautifulsoup4==4.5.1 -CherryPy==8.1.2 +CherryPy==8.1.3 click==6.6 coverage==4.2 decorator==4.0.10 From f0ccc7be140f13069d55058528485617c48a8132 Mon Sep 17 00:00:00 2001 From: Nikolay Amiantov Date: Mon, 19 Dec 2016 12:42:32 +0300 Subject: [PATCH 082/561] [DONOTMERGE] Re-run Codecov --- qutebrowser.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qutebrowser.py b/qutebrowser.py index a16bd9ac7..0a3a07e22 100755 --- a/qutebrowser.py +++ b/qutebrowser.py @@ -27,3 +27,4 @@ import sys if __name__ == '__main__': sys.exit(qutebrowser.qutebrowser.main()) + From 3d8dedc48bee1bf54c5ba11c82ca97e51a61ec3f Mon Sep 17 00:00:00 2001 From: Samuel Walladge Date: Tue, 20 Dec 2016 12:09:30 +1030 Subject: [PATCH 083/561] add tests for default-open-dispatcher --- tests/end2end/features/downloads.feature | 14 ++++++++++++++ tests/end2end/features/test_downloads_bdd.py | 6 +++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/tests/end2end/features/downloads.feature b/tests/end2end/features/downloads.feature index b6a38da29..3bdba2c41 100644 --- a/tests/end2end/features/downloads.feature +++ b/tests/end2end/features/downloads.feature @@ -337,6 +337,20 @@ Feature: Downloading things from a website. And I open the download with a placeholder Then "Opening *download.bin* with [*python*]" should be logged + Scenario: Opening a download with default-open-dispatcher set + When I set general -> default-open-dispatcher to python {} -c "import sys; print(sys.argv[1])" + And I open data/downloads/download.bin without waiting + And I wait until the download is finished + And I open the download with no args + Then "Opening *download.bin* with [*python*]" should be logged + + Scenario: Opening a download with default-open-dispatcher set to cat + When I set general -> default-open-dispatcher to cat + And I open data/downloads/download.bin without waiting + And I wait until the download is finished + And I open the download with no args + Then "Opening *download.bin* with [*cat*]" should be logged + Scenario: Opening a download which does not exist When I run :download-open with count 42 Then the error "There's no download 42!" should be shown diff --git a/tests/end2end/features/test_downloads_bdd.py b/tests/end2end/features/test_downloads_bdd.py index 65ccf1f3e..2538a033e 100644 --- a/tests/end2end/features/test_downloads_bdd.py +++ b/tests/end2end/features/test_downloads_bdd.py @@ -44,7 +44,6 @@ def temporary_download_dir(quteproc, tmpdir): unwritable.ensure(dir=True) unwritable.chmod(0) - @bdd.given("I clean old downloads") def clean_old_downloads(quteproc): quteproc.send_cmd(':download-cancel --all') @@ -108,6 +107,11 @@ def download_open(quteproc): quteproc.send_cmd(':download-open {}'.format(cmd)) +@bdd.when("I open the download with no args") +def download_open_plain(quteproc): + quteproc.send_cmd(':download-open') + + @bdd.when("I open the download with a placeholder") def download_open_placeholder(quteproc): cmd = '{} -c "import sys; print(sys.argv[1])"'.format( From a27d7cddb34d5ddeded523eefd22ead5e880ea82 Mon Sep 17 00:00:00 2001 From: Samuel Walladge Date: Tue, 20 Dec 2016 20:13:22 +1030 Subject: [PATCH 084/561] fix tests and add another --- tests/end2end/features/downloads.feature | 13 ++++++++++--- tests/end2end/features/test_downloads_bdd.py | 13 ++++++++----- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/tests/end2end/features/downloads.feature b/tests/end2end/features/downloads.feature index 3bdba2c41..d141f8af5 100644 --- a/tests/end2end/features/downloads.feature +++ b/tests/end2end/features/downloads.feature @@ -338,19 +338,26 @@ Feature: Downloading things from a website. Then "Opening *download.bin* with [*python*]" should be logged Scenario: Opening a download with default-open-dispatcher set - When I set general -> default-open-dispatcher to python {} -c "import sys; print(sys.argv[1])" + When I set a test python default-open-dispatcher And I open data/downloads/download.bin without waiting And I wait until the download is finished - And I open the download with no args + And I run :download-open Then "Opening *download.bin* with [*python*]" should be logged Scenario: Opening a download with default-open-dispatcher set to cat When I set general -> default-open-dispatcher to cat And I open data/downloads/download.bin without waiting And I wait until the download is finished - And I open the download with no args + And I run :download-open Then "Opening *download.bin* with [*cat*]" should be logged + Scenario: Opening a download with default-open-dispatcher set and override + When I set general -> default-open-dispatcher to cat + And I open data/downloads/download.bin without waiting + And I wait until the download is finished + And I open the download + Then "Opening *download.bin* with [*python*]" should be logged + Scenario: Opening a download which does not exist When I run :download-open with count 42 Then the error "There's no download 42!" should be shown diff --git a/tests/end2end/features/test_downloads_bdd.py b/tests/end2end/features/test_downloads_bdd.py index 2538a033e..753cfa75c 100644 --- a/tests/end2end/features/test_downloads_bdd.py +++ b/tests/end2end/features/test_downloads_bdd.py @@ -44,6 +44,7 @@ def temporary_download_dir(quteproc, tmpdir): unwritable.ensure(dir=True) unwritable.chmod(0) + @bdd.given("I clean old downloads") def clean_old_downloads(quteproc): quteproc.send_cmd(':download-cancel --all') @@ -100,6 +101,13 @@ def download_prompt(tmpdir, quteproc, path): quteproc.send_cmd(':leave-mode') +@bdd.when("I set a test python default-open-dispatcher") +def default_open_dispatcher_python(quteproc, tmpdir): + cmd = '{} -c "import sys; print(sys.argv[1])"'.format( + shlex.quote(sys.executable)) + quteproc.set_setting('general', 'default-open-dispatcher', cmd) + + @bdd.when("I open the download") def download_open(quteproc): cmd = '{} -c "import sys; print(sys.argv[1])"'.format( @@ -107,11 +115,6 @@ def download_open(quteproc): quteproc.send_cmd(':download-open {}'.format(cmd)) -@bdd.when("I open the download with no args") -def download_open_plain(quteproc): - quteproc.send_cmd(':download-open') - - @bdd.when("I open the download with a placeholder") def download_open_placeholder(quteproc): cmd = '{} -c "import sys; print(sys.argv[1])"'.format( From 0afa74a9de6fc07fbc347a5a320a9070f92d5388 Mon Sep 17 00:00:00 2001 From: Samuel Walladge Date: Tue, 20 Dec 2016 20:48:56 +1030 Subject: [PATCH 085/561] remove cat test --- tests/end2end/features/downloads.feature | 7 ------- 1 file changed, 7 deletions(-) diff --git a/tests/end2end/features/downloads.feature b/tests/end2end/features/downloads.feature index d141f8af5..5b4c4f9a4 100644 --- a/tests/end2end/features/downloads.feature +++ b/tests/end2end/features/downloads.feature @@ -344,13 +344,6 @@ Feature: Downloading things from a website. And I run :download-open Then "Opening *download.bin* with [*python*]" should be logged - Scenario: Opening a download with default-open-dispatcher set to cat - When I set general -> default-open-dispatcher to cat - And I open data/downloads/download.bin without waiting - And I wait until the download is finished - And I run :download-open - Then "Opening *download.bin* with [*cat*]" should be logged - Scenario: Opening a download with default-open-dispatcher set and override When I set general -> default-open-dispatcher to cat And I open data/downloads/download.bin without waiting From f92c82ead14152e7ac5c91a3b8febf6ceb430290 Mon Sep 17 00:00:00 2001 From: Daniel Schadt Date: Tue, 20 Dec 2016 14:52:39 +0100 Subject: [PATCH 086/561] downloads: don't remember dir for temp downloads Fixes #2173 --- qutebrowser/browser/downloads.py | 8 ++++++-- qutebrowser/browser/webengine/webenginedownloads.py | 3 ++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/qutebrowser/browser/downloads.py b/qutebrowser/browser/downloads.py index 12e766ecb..74fb12edb 100644 --- a/qutebrowser/browser/downloads.py +++ b/qutebrowser/browser/downloads.py @@ -564,13 +564,16 @@ class AbstractDownloadItem(QObject): """Set a temporary file when opening the download.""" raise NotImplementedError - def _set_filename(self, filename, *, force_overwrite=False): + def _set_filename(self, filename, *, force_overwrite=False, + remember_directory=True): """Set the filename to save the download to. Args: filename: The full filename to save the download to. None: special value to stop the download. force_overwrite: Force overwriting existing files. + remember_directory: If True, remember the directory for future + downloads. """ global last_used_directory filename = os.path.expanduser(filename) @@ -600,7 +603,8 @@ class AbstractDownloadItem(QObject): os.path.expanduser('~')) self.basename = os.path.basename(self._filename) - last_used_directory = os.path.dirname(self._filename) + if remember_directory: + last_used_directory = os.path.dirname(self._filename) log.downloads.debug("Setting filename to {}".format(filename)) if force_overwrite: diff --git a/qutebrowser/browser/webengine/webenginedownloads.py b/qutebrowser/browser/webengine/webenginedownloads.py index e78e939e1..44b326d36 100644 --- a/qutebrowser/browser/webengine/webenginedownloads.py +++ b/qutebrowser/browser/webengine/webenginedownloads.py @@ -96,7 +96,8 @@ class DownloadItem(downloads.AbstractDownloadItem): raise downloads.UnsupportedOperationError def _set_tempfile(self, fileobj): - self._set_filename(fileobj.name, force_overwrite=True) + self._set_filename(fileobj.name, force_overwrite=True, + remember_directory=False) def _ensure_can_set_filename(self, filename): state = self._qt_item.state() From a24bcd424330c68de2cd5fde97352d8f18046613 Mon Sep 17 00:00:00 2001 From: Daniel Schadt Date: Tue, 20 Dec 2016 15:57:00 +0100 Subject: [PATCH 087/561] add a test for remembering the temp download dir --- tests/end2end/features/downloads.feature | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/end2end/features/downloads.feature b/tests/end2end/features/downloads.feature index b6a38da29..85917864e 100644 --- a/tests/end2end/features/downloads.feature +++ b/tests/end2end/features/downloads.feature @@ -424,6 +424,20 @@ Feature: Downloading things from a website. And I open data/downloads/download2.bin without waiting Then the download prompt should be shown with "(tmpdir)/download2.bin" + # https://github.com/The-Compiler/qutebrowser/issues/2173 + + Scenario: Remembering the temporary download directory (issue 2173) + When I set storage -> prompt-download-directory to true + And I set storage -> remember-download-directory to true + And I open data/downloads/download.bin without waiting + And I wait for the download prompt for "*" + And I run :prompt-accept (tmpdir) + And I open data/downloads/download.bin without waiting + And I wait for the download prompt for "*" + And I directly open the download + And I open data/downloads/download.bin without waiting + Then the download prompt should be shown with "(tmpdir)/download.bin" + # Overwriting files Scenario: Not overwriting an existing file From c7970eb2eed6079015d62476211b1efb706a3d87 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 20 Dec 2016 21:32:46 +0100 Subject: [PATCH 088/561] Xfail tests which fail on Docker/Travis See #2183 --- pytest.ini | 1 + scripts/dev/ci/travis_run.sh | 2 +- tests/conftest.py | 6 ++++++ tests/end2end/features/editor.feature | 1 + tests/end2end/features/yankpaste.feature | 5 ++++- tests/end2end/features/zoom.feature | 2 ++ tox.ini | 2 +- 7 files changed, 16 insertions(+), 3 deletions(-) diff --git a/pytest.ini b/pytest.ini index 2285053a9..24f5a4528 100644 --- a/pytest.ini +++ b/pytest.ini @@ -20,6 +20,7 @@ markers = qtwebengine_flaky: Tests which are flaky (and currently skipped) with QtWebEngine qtwebengine_osx_xfail: Tests which fail on OS X with QtWebEngine js_prompt: Tests needing to display a javascript prompt + issue2183: https://github.com/The-Compiler/qutebrowser/issues/2183 this: Used to mark tests during development qt_log_level_fail = WARNING qt_log_ignore = diff --git a/scripts/dev/ci/travis_run.sh b/scripts/dev/ci/travis_run.sh index 86c2b11a8..a4c4c6cdb 100644 --- a/scripts/dev/ci/travis_run.sh +++ b/scripts/dev/ci/travis_run.sh @@ -1,7 +1,7 @@ #!/bin/bash if [[ $DOCKER ]]; then - docker run --privileged -v $PWD:/outside -e QUTE_BDD_WEBENGINE=$QUTE_BDD_WEBENGINE qutebrowser/travis:$DOCKER + docker run --privileged -v $PWD:/outside -e QUTE_BDD_WEBENGINE=$QUTE_BDD_WEBENGINE -e DOCKER=$DOCKER qutebrowser/travis:$DOCKER else args=() [[ $TESTENV == docs ]] && args=('--no-authors') diff --git a/tests/conftest.py b/tests/conftest.py index 7fa94ffc3..26ad52d37 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -129,6 +129,12 @@ def pytest_collection_modifyitems(config, items): item.add_marker(pytest.mark.skipif( PYQT_VERSION <= js_prompt_pyqt_version, reason='JS prompts are not supported with this PyQt version')) + if item.get_marker('issue2183'): + item.add_marker(pytest.mark.xfail( + bool(os.environ.get('QUTE_BDD_WEBENGINE', None) and + os.environ.get('DOCKER', None)), + reason='https://github.com/The-Compiler/qutebrowser/issues/' + '2183')) if deselected: deselected_items.append(item) diff --git a/tests/end2end/features/editor.feature b/tests/end2end/features/editor.feature index 861c71710..84ce47412 100644 --- a/tests/end2end/features/editor.feature +++ b/tests/end2end/features/editor.feature @@ -94,6 +94,7 @@ Feature: Opening external editors And I run :click-element id qute-button Then the javascript message "text: foobar" should be logged + @issue2183 Scenario: Spawning an editor with existing text When I set up a fake editor replacing "foo" by "bar" And I open data/editor.html diff --git a/tests/end2end/features/yankpaste.feature b/tests/end2end/features/yankpaste.feature index 9a4bf53a8..1c65c63b4 100644 --- a/tests/end2end/features/yankpaste.feature +++ b/tests/end2end/features/yankpaste.feature @@ -247,6 +247,7 @@ Feature: Yanking and pasting. #### :insert-text + @issue2183 Scenario: Inserting text into an empty text field When I set general -> log-javascript-console to info And I open data/paste_primary.html @@ -256,6 +257,7 @@ Feature: Yanking and pasting. # Compare Then the javascript message "textarea contents: Hello world" should be logged + @issue2183 Scenario: Inserting text into an empty text field with javascript disabled When I set general -> log-javascript-console to info And I set content -> allow-javascript to false @@ -270,6 +272,7 @@ Feature: Yanking and pasting. # Compare Then the javascript message "textarea contents: Hello world" should be logged + @issue2183 Scenario: Inserting text into a text field at specific position When I set general -> log-javascript-console to info And I open data/paste_primary.html @@ -284,7 +287,7 @@ Feature: Yanking and pasting. # Compare Then the javascript message "textarea contents: onHello worlde two three four" should be logged - @qtwebengine_osx_xfail + @qtwebengine_osx_xfail @issue2183 Scenario: Inserting text into a text field with undo When I set general -> log-javascript-console to info And I open data/paste_primary.html diff --git a/tests/end2end/features/zoom.feature b/tests/end2end/features/zoom.feature index 0c6790fa8..d94bd3a27 100644 --- a/tests/end2end/features/zoom.feature +++ b/tests/end2end/features/zoom.feature @@ -86,6 +86,8 @@ Feature: Zooming in and out Then the message "Zoom level: 120%" should be shown And the zoom should be 120% + # https://github.com/The-Compiler/qutebrowser/issues/2183 + @qtwebengine_flaky Scenario: Setting a default zoom When I set ui -> default-zoom to 200% And I open data/hello.txt in a new tab diff --git a/tox.ini b/tox.ini index 22e584667..6c51695dd 100644 --- a/tox.ini +++ b/tox.ini @@ -13,7 +13,7 @@ skipsdist = true setenv = QT_QPA_PLATFORM_PLUGIN_PATH={envdir}/Lib/site-packages/PyQt5/plugins/platforms PYTEST_QT_API=pyqt5 -passenv = PYTHON DISPLAY XAUTHORITY HOME USERNAME USER CI TRAVIS XDG_* QUTE_* +passenv = PYTHON DISPLAY XAUTHORITY HOME USERNAME USER CI TRAVIS XDG_* QUTE_* DOCKER deps = -r{toxinidir}/requirements.txt -r{toxinidir}/misc/requirements/requirements-tests.txt From 154748d56e08f15d19b37782c0439d493068ed60 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 20 Dec 2016 21:40:46 +0100 Subject: [PATCH 089/561] test requirements: Update hypothesis to 3.6.1 --- misc/requirements/requirements-tests.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index 0743df1ae..0a5231cca 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -9,7 +9,7 @@ EasyProcess==0.2.3 Flask==0.11.1 glob2==0.5 httpbin==0.5.0 -hypothesis==3.6.0 +hypothesis==3.6.1 itsdangerous==0.24 # Jinja2==2.8 Mako==1.0.6 From 8dc54bed935c33c47e82df0e40babe5cdb743d8b Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 22 Dec 2016 07:01:32 +0100 Subject: [PATCH 090/561] Update docs --- CHANGELOG.asciidoc | 2 ++ README.asciidoc | 1 + 2 files changed, 3 insertions(+) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 51301965d..054eb3bd1 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -52,6 +52,8 @@ Added - New `:record-macro` (`q`) and `:run-macro` (`@`) commands for keyboard macros. - New `ui -> hide-scrollbar` setting to hide the scrollbar independently of the `user-stylesheet` setting. +- New `general -> default-open-dispatcher` setting to configure what to open + downloads with (instead of e.g. `xdg-open` on Linux). Changed ~~~~~~~ diff --git a/README.asciidoc b/README.asciidoc index b69bb85de..de96063ab 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -194,6 +194,7 @@ Contributors, sorted by the number of commits in descending order: * skinnay * Zach-Button * Tomasz Kramkowski +* Samuel Walladge * Peter Rice * Ismail S * Halfwit From 40c397ebaffd66a53d6dcf69923900228de85388 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 22 Dec 2016 08:11:09 +0100 Subject: [PATCH 091/561] test requirements: Update Flask to 0.12 --- misc/requirements/requirements-tests.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index 0a5231cca..786bb7251 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -6,7 +6,7 @@ click==6.6 coverage==4.2 decorator==4.0.10 EasyProcess==0.2.3 -Flask==0.11.1 +Flask==0.12 glob2==0.5 httpbin==0.5.0 hypothesis==3.6.1 From c762fa6279e3516b51692cca6e33f0bccdcb0359 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 22 Dec 2016 08:55:43 +0100 Subject: [PATCH 092/561] Revert "[DONOTMERGE] Re-run Codecov" This reverts commit f0ccc7be140f13069d55058528485617c48a8132. --- qutebrowser.py | 1 - 1 file changed, 1 deletion(-) diff --git a/qutebrowser.py b/qutebrowser.py index 0a3a07e22..a16bd9ac7 100755 --- a/qutebrowser.py +++ b/qutebrowser.py @@ -27,4 +27,3 @@ import sys if __name__ == '__main__': sys.exit(qutebrowser.qutebrowser.main()) - From 470ef781eb029e81bd8fd38d672e838a04f5d1e6 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 22 Dec 2016 08:55:56 +0100 Subject: [PATCH 093/561] Update docs --- CHANGELOG.asciidoc | 3 +++ README.asciidoc | 1 + 2 files changed, 4 insertions(+) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 054eb3bd1..3b68428e8 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -20,6 +20,8 @@ v0.9.0 (unreleased) Added ~~~~~ +- *New dependency:* qutebrowser now depends on the Qt Quick module, which is + packaged separately in some distributions. - New `:rl-backward-kill-word` command which does what `:rl-unix-word-rubout` did before v0.8.0. - New `:rl-unix-filename-rubout` command which is similar to readline's @@ -54,6 +56,7 @@ Added `user-stylesheet` setting. - New `general -> default-open-dispatcher` setting to configure what to open downloads with (instead of e.g. `xdg-open` on Linux). +- Support for PAC (proxy autoconfig) with QtWebKit Changed ~~~~~~~ diff --git a/README.asciidoc b/README.asciidoc index de96063ab..ef255fb47 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -196,6 +196,7 @@ Contributors, sorted by the number of commits in descending order: * Tomasz Kramkowski * Samuel Walladge * Peter Rice +* Nikolay Amiantov * Ismail S * Halfwit * Fritz Reichwald From a3d0ea7e01b854d25ee4efd475a9c0085bd723ed Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 22 Dec 2016 09:04:23 +0100 Subject: [PATCH 094/561] Adjust skipped test --- tests/unit/browser/webkit/network/test_pac.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/browser/webkit/network/test_pac.py b/tests/unit/browser/webkit/network/test_pac.py index 13daf7642..b99675b6a 100644 --- a/tests/unit/browser/webkit/network/test_pac.py +++ b/tests/unit/browser/webkit/network/test_pac.py @@ -133,7 +133,7 @@ except ImportError: QtWebEngineWidgets = None -@pytest.mark.skipif(QT_VERSION_STR == "5.7.0" and +@pytest.mark.skipif(QT_VERSION_STR.startswith('5.7') and QtWebEngineWidgets is not None and sys.platform == "linux", reason="Segfaults when run with QtWebEngine tests on Linux") From cd8d179813a300d999e102d3cf7ffee96ba94bbe Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 22 Dec 2016 09:17:00 +0100 Subject: [PATCH 095/561] Also xfail #2183 tests without Docker/Travis --- tests/conftest.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 26ad52d37..d016f2e58 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -35,6 +35,7 @@ from helpers import logfail from helpers.logfail import fail_on_logging from helpers.messagemock import message_mock from helpers.fixtures import * # pylint: disable=wildcard-import +from qutebrowser.utils import qtutils # Set hypothesis settings @@ -131,8 +132,7 @@ def pytest_collection_modifyitems(config, items): reason='JS prompts are not supported with this PyQt version')) if item.get_marker('issue2183'): item.add_marker(pytest.mark.xfail( - bool(os.environ.get('QUTE_BDD_WEBENGINE', None) and - os.environ.get('DOCKER', None)), + config.webengine and qtutils.version_check('5.7.1'), reason='https://github.com/The-Compiler/qutebrowser/issues/' '2183')) From b220b5438f1f49a26ff742c8d313f062e7e68b1d Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 22 Dec 2016 13:51:27 +0100 Subject: [PATCH 096/561] Add urlutils.proxy_for_url --- qutebrowser/config/configtypes.py | 55 +++++++-------------------- qutebrowser/utils/urlutils.py | 47 ++++++++++++++++++++++- tests/unit/config/test_configtypes.py | 32 +++------------- tests/unit/utils/test_urlutils.py | 42 ++++++++++++++++++++ 4 files changed, 106 insertions(+), 70 deletions(-) diff --git a/qutebrowser/config/configtypes.py b/qutebrowser/config/configtypes.py index 7c5f9721c..b0399fcac 100644 --- a/qutebrowser/config/configtypes.py +++ b/qutebrowser/config/configtypes.py @@ -28,17 +28,14 @@ import itertools import collections import warnings import datetime -import functools from PyQt5.QtCore import QUrl, Qt from PyQt5.QtGui import QColor, QFont -from PyQt5.QtNetwork import QNetworkProxy from PyQt5.QtWidgets import QTabWidget, QTabBar from qutebrowser.commands import cmdutils from qutebrowser.config import configexc from qutebrowser.utils import standarddir, utils -from qutebrowser.browser.webkit.network import pac SYSTEM_PROXY = object() # Return value for Proxy type @@ -1016,38 +1013,10 @@ class ShellCommand(BaseType): return shlex.split(value) -def proxy_from_url(typ, url): - """Create a QNetworkProxy from QUrl and a proxy type. - - Args: - typ: QNetworkProxy::ProxyType. - url: URL of a proxy (possibly with credentials). - - Return: - New QNetworkProxy. - """ - proxy = QNetworkProxy(typ, url.host()) - if url.port() != -1: - proxy.setPort(url.port()) - if url.userName(): - proxy.setUser(url.userName()) - if url.password(): - proxy.setPassword(url.password()) - return proxy - - class Proxy(BaseType): """A proxy URL or special value.""" - PROXY_TYPES = { - 'http': functools.partial(proxy_from_url, QNetworkProxy.HttpProxy), - 'pac+http': pac.PACFetcher, - 'pac+https': pac.PACFetcher, - 'socks': functools.partial(proxy_from_url, QNetworkProxy.Socks5Proxy), - 'socks5': functools.partial(proxy_from_url, QNetworkProxy.Socks5Proxy), - } - def __init__(self, none_ok=False): super().__init__(none_ok) self.valid_values = ValidValues( @@ -1055,19 +1024,18 @@ class Proxy(BaseType): ('none', "Don't use any proxy")) def validate(self, value): + from qutebrowser.utils import urlutils self._basic_validation(value) if not value: return elif value in self.valid_values: return url = QUrl(value) - if not url.isValid(): - raise configexc.ValidationError( - value, "invalid url, {}".format(url.errorString())) - elif url.scheme() not in self.PROXY_TYPES: - raise configexc.ValidationError(value, "must be a proxy URL " - "(http://... or socks://...) or " - "system/none!") + + try: + self.transform(value) + except (urlutils.InvalidUrlError, urlutils.InvalidProxyTypeError) as e: + raise configexc.ValidationError(value, e) def complete(self): out = [] @@ -1081,14 +1049,17 @@ class Proxy(BaseType): return out def transform(self, value): + from qutebrowser.utils import urlutils if not value: return None elif value == 'system': return SYSTEM_PROXY - elif value == 'none': - return QNetworkProxy(QNetworkProxy.NoProxy) - url = QUrl(value) - return self.PROXY_TYPES[url.scheme()](url) + + if value == 'none': + url = QUrl('direct://') + else: + url = QUrl(value) + return urlutils.proxy_from_url(url) class SearchEngineName(BaseType): diff --git a/qutebrowser/utils/urlutils.py b/qutebrowser/utils/urlutils.py index 6a24ceb82..ae5733bc6 100644 --- a/qutebrowser/utils/urlutils.py +++ b/qutebrowser/utils/urlutils.py @@ -27,11 +27,12 @@ import posixpath import urllib.parse from PyQt5.QtCore import QUrl -from PyQt5.QtNetwork import QHostInfo, QHostAddress +from PyQt5.QtNetwork import QHostInfo, QHostAddress, QNetworkProxy from qutebrowser.config import config, configexc from qutebrowser.utils import log, qtutils, message, utils from qutebrowser.commands import cmdexc +from qutebrowser.browser.webkit.network import pac # FIXME: we probably could raise some exceptions on invalid URLs @@ -589,3 +590,47 @@ def data_url(mimetype, data): url = QUrl('data:{};base64,{}'.format(mimetype, b64)) qtutils.ensure_valid(url) return url + + +class InvalidProxyTypeError(Exception): + + """Error raised when proxy_from_url gets an unknown proxy type.""" + + def __init__(self, typ): + super().__init__("Invalid proxy type {}!".format(typ)) + + +def proxy_from_url(url): + """Create a QNetworkProxy from QUrl and a proxy type. + + Args: + url: URL of a proxy (possibly with credentials). + + Return: + New QNetworkProxy. + """ + if not url.isValid(): + raise InvalidUrlError(url) + + scheme = url.scheme() + if scheme in ['pac+http', 'pac+https']: + return pac.PACFetcher + + types = { + 'http': QNetworkProxy.HttpProxy, + 'socks': QNetworkProxy.Socks5Proxy, + 'socks5': QNetworkProxy.Socks5Proxy, + 'direct': QNetworkProxy.NoProxy, + } + if scheme not in types: + raise InvalidProxyTypeError(scheme) + + proxy = QNetworkProxy(types[scheme], url.host()) + + if url.port() != -1: + proxy.setPort(url.port()) + if url.userName(): + proxy.setUser(url.userName()) + if url.password(): + proxy.setPassword(url.password()) + return proxy diff --git a/tests/unit/config/test_configtypes.py b/tests/unit/config/test_configtypes.py index 37e763999..f2e18c459 100644 --- a/tests/unit/config/test_configtypes.py +++ b/tests/unit/config/test_configtypes.py @@ -59,16 +59,6 @@ class Font(QFont): return f -class NetworkProxy(QNetworkProxy): - - """A QNetworkProxy with a nicer repr().""" - - def __repr__(self): - return utils.get_repr(self, type=self.type(), hostName=self.hostName(), - port=self.port(), user=self.user(), - password=self.password()) - - class RegexEq: """A class to compare regex objects.""" @@ -1490,10 +1480,7 @@ class TestProxy: 'system', 'none', 'http://user:pass@example.com:2323/', - 'socks://user:pass@example.com:2323/', - 'socks5://user:pass@example.com:2323/', 'pac+http://example.com/proxy.pac', - 'pac+https://example.com/proxy.pac', ]) def test_validate_valid(self, klass, val): klass(none_ok=True).validate(val) @@ -1519,27 +1506,18 @@ class TestProxy: @pytest.mark.parametrize('val, expected', [ ('', None), ('system', configtypes.SYSTEM_PROXY), - ('none', NetworkProxy(QNetworkProxy.NoProxy)), + ('none', QNetworkProxy(QNetworkProxy.NoProxy)), ('socks://example.com/', - NetworkProxy(QNetworkProxy.Socks5Proxy, 'example.com')), - ('socks5://example.com', - NetworkProxy(QNetworkProxy.Socks5Proxy, 'example.com')), - ('socks5://example.com:2342', - NetworkProxy(QNetworkProxy.Socks5Proxy, 'example.com', 2342)), - ('socks5://foo@example.com', - NetworkProxy(QNetworkProxy.Socks5Proxy, 'example.com', 0, 'foo')), - ('socks5://foo:bar@example.com', - NetworkProxy(QNetworkProxy.Socks5Proxy, 'example.com', 0, 'foo', - 'bar')), + QNetworkProxy(QNetworkProxy.Socks5Proxy, 'example.com')), ('socks5://foo:bar@example.com:2323', - NetworkProxy(QNetworkProxy.Socks5Proxy, 'example.com', 2323, 'foo', - 'bar')), + QNetworkProxy(QNetworkProxy.Socks5Proxy, 'example.com', 2323, + 'foo', 'bar')), ]) def test_transform(self, klass, val, expected): """Test transform with an empty value.""" actual = klass().transform(val) if isinstance(actual, QNetworkProxy): - actual = NetworkProxy(actual) + actual = QNetworkProxy(actual) assert actual == expected diff --git a/tests/unit/utils/test_urlutils.py b/tests/unit/utils/test_urlutils.py index 984971b3c..06196f5d2 100644 --- a/tests/unit/utils/test_urlutils.py +++ b/tests/unit/utils/test_urlutils.py @@ -24,9 +24,11 @@ import collections import logging from PyQt5.QtCore import QUrl +from PyQt5.QtNetwork import QNetworkProxy import pytest from qutebrowser.commands import cmdexc +from qutebrowser.browser.webkit.network import pac from qutebrowser.utils import utils, urlutils, qtutils, usertypes @@ -736,3 +738,43 @@ def test_file_url(): def test_data_url(): url = urlutils.data_url('text/plain', b'foo') assert url == QUrl('data:text/plain;base64,Zm9v') + + + +class TestProxyFromUrl: + + @pytest.mark.parametrize('url, expected', [ + ('socks://example.com/', + QNetworkProxy(QNetworkProxy.Socks5Proxy, 'example.com')), + ('socks5://example.com', + QNetworkProxy(QNetworkProxy.Socks5Proxy, 'example.com')), + ('socks5://example.com:2342', + QNetworkProxy(QNetworkProxy.Socks5Proxy, 'example.com', 2342)), + ('socks5://foo@example.com', + QNetworkProxy(QNetworkProxy.Socks5Proxy, 'example.com', 0, 'foo')), + ('socks5://foo:bar@example.com', + QNetworkProxy(QNetworkProxy.Socks5Proxy, 'example.com', 0, 'foo', + 'bar')), + ('socks5://foo:bar@example.com:2323', + QNetworkProxy(QNetworkProxy.Socks5Proxy, 'example.com', 2323, + 'foo', 'bar')), + ('direct://', QNetworkProxy(QNetworkProxy.NoProxy)), + ]) + def test_proxy_from_url_valid(self, url, expected): + assert urlutils.proxy_from_url(QUrl(url)) == expected + + @pytest.mark.parametrize('scheme', ['pac+http', 'pac+https']) + def test_proxy_from_url_pac(self, scheme): + fetcher = urlutils.proxy_from_url(QUrl('{}://foo'.format(scheme))) + assert fetcher is pac.PACFetcher + + @pytest.mark.parametrize('url, exception', [ + ('blah', urlutils.InvalidProxyTypeError), + (':', urlutils.InvalidUrlError), # invalid URL + # Invalid/unsupported scheme + ('ftp://example.com/', urlutils.InvalidProxyTypeError), + ('socks4://example.com/', urlutils.InvalidProxyTypeError), + ]) + def test_invalid(self, url, exception): + with pytest.raises(exception): + urlutils.proxy_from_url(QUrl(url)) From 6c1b7dcca12e1f8860b90f3df4b5783afad7adab Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 22 Dec 2016 13:54:11 +0100 Subject: [PATCH 097/561] Move proxy/pac out of QtWebKit folder --- qutebrowser/browser/{webkit => }/network/pac.py | 0 qutebrowser/browser/{webkit => }/network/proxy.py | 2 +- qutebrowser/browser/webkit/webkittab.py | 3 ++- qutebrowser/utils/urlutils.py | 2 +- tests/unit/browser/webkit/network/test_pac.py | 2 +- tests/unit/utils/test_urlutils.py | 2 +- 6 files changed, 6 insertions(+), 5 deletions(-) rename qutebrowser/browser/{webkit => }/network/pac.py (100%) rename qutebrowser/browser/{webkit => }/network/proxy.py (98%) diff --git a/qutebrowser/browser/webkit/network/pac.py b/qutebrowser/browser/network/pac.py similarity index 100% rename from qutebrowser/browser/webkit/network/pac.py rename to qutebrowser/browser/network/pac.py diff --git a/qutebrowser/browser/webkit/network/proxy.py b/qutebrowser/browser/network/proxy.py similarity index 98% rename from qutebrowser/browser/webkit/network/proxy.py rename to qutebrowser/browser/network/proxy.py index db52482d2..719c33178 100644 --- a/qutebrowser/browser/webkit/network/proxy.py +++ b/qutebrowser/browser/network/proxy.py @@ -24,7 +24,7 @@ from PyQt5.QtNetwork import QNetworkProxy, QNetworkProxyFactory from qutebrowser.config import config, configtypes from qutebrowser.utils import objreg -from qutebrowser.browser.webkit.network import pac +from qutebrowser.browser.network import pac def init(): diff --git a/qutebrowser/browser/webkit/webkittab.py b/qutebrowser/browser/webkit/webkittab.py index 1a9a91b49..12c643f46 100644 --- a/qutebrowser/browser/webkit/webkittab.py +++ b/qutebrowser/browser/webkit/webkittab.py @@ -32,8 +32,9 @@ from PyQt5.QtWebKit import QWebSettings from PyQt5.QtPrintSupport import QPrinter from qutebrowser.browser import browsertab +from qutebrowser.browser.network import proxy from qutebrowser.browser.webkit import webview, tabhistory, webkitelem -from qutebrowser.browser.webkit.network import proxy, webkitqutescheme +from qutebrowser.browser.webkit.network import webkitqutescheme from qutebrowser.utils import qtutils, objreg, usertypes, utils, log diff --git a/qutebrowser/utils/urlutils.py b/qutebrowser/utils/urlutils.py index ae5733bc6..e70cc41fa 100644 --- a/qutebrowser/utils/urlutils.py +++ b/qutebrowser/utils/urlutils.py @@ -32,7 +32,7 @@ from PyQt5.QtNetwork import QHostInfo, QHostAddress, QNetworkProxy from qutebrowser.config import config, configexc from qutebrowser.utils import log, qtutils, message, utils from qutebrowser.commands import cmdexc -from qutebrowser.browser.webkit.network import pac +from qutebrowser.browser.network import pac # FIXME: we probably could raise some exceptions on invalid URLs diff --git a/tests/unit/browser/webkit/network/test_pac.py b/tests/unit/browser/webkit/network/test_pac.py index b99675b6a..ebb081aff 100644 --- a/tests/unit/browser/webkit/network/test_pac.py +++ b/tests/unit/browser/webkit/network/test_pac.py @@ -27,7 +27,7 @@ from PyQt5.QtCore import QUrl, QT_VERSION_STR from PyQt5.QtNetwork import (QNetworkProxy, QNetworkProxyQuery, QHostInfo, QHostAddress) -from qutebrowser.browser.webkit.network import pac +from qutebrowser.browser.network import pac pytestmark = pytest.mark.usefixtures('qapp') diff --git a/tests/unit/utils/test_urlutils.py b/tests/unit/utils/test_urlutils.py index 06196f5d2..10ba54b3a 100644 --- a/tests/unit/utils/test_urlutils.py +++ b/tests/unit/utils/test_urlutils.py @@ -28,7 +28,7 @@ from PyQt5.QtNetwork import QNetworkProxy import pytest from qutebrowser.commands import cmdexc -from qutebrowser.browser.webkit.network import pac +from qutebrowser.browser.network import pac from qutebrowser.utils import utils, urlutils, qtutils, usertypes From 4b7818589d00ded482ee084dfd9722beb56d73a9 Mon Sep 17 00:00:00 2001 From: Daniel Schadt Date: Thu, 22 Dec 2016 14:22:37 +0100 Subject: [PATCH 098/561] make test independent of previous tests --- tests/end2end/features/downloads.feature | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/end2end/features/downloads.feature b/tests/end2end/features/downloads.feature index 85917864e..2c04cb5bf 100644 --- a/tests/end2end/features/downloads.feature +++ b/tests/end2end/features/downloads.feature @@ -428,6 +428,7 @@ Feature: Downloading things from a website. Scenario: Remembering the temporary download directory (issue 2173) When I set storage -> prompt-download-directory to true + And I set completion -> download-path-suggestion to both And I set storage -> remember-download-directory to true And I open data/downloads/download.bin without waiting And I wait for the download prompt for "*" From 0df2761b03e671850e7319155a391c6623915fa4 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 22 Dec 2016 14:26:15 +0100 Subject: [PATCH 099/561] Update changelog --- CHANGELOG.asciidoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 3b68428e8..59a69d6a1 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -20,8 +20,8 @@ v0.9.0 (unreleased) Added ~~~~~ -- *New dependency:* qutebrowser now depends on the Qt Quick module, which is - packaged separately in some distributions. +- *New dependency:* qutebrowser now depends on the Qt QML module, which is + packaged separately in some distributions (as Qt Declarative/QML/Quick). - New `:rl-backward-kill-word` command which does what `:rl-unix-word-rubout` did before v0.8.0. - New `:rl-unix-filename-rubout` command which is similar to readline's From 89fe725a85f80cdef57c06b77ac339750631c0dc Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 22 Dec 2016 14:26:35 +0100 Subject: [PATCH 100/561] Add __init__.py --- qutebrowser/browser/network/__init__.py | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 qutebrowser/browser/network/__init__.py diff --git a/qutebrowser/browser/network/__init__.py b/qutebrowser/browser/network/__init__.py new file mode 100644 index 000000000..c3d713ac2 --- /dev/null +++ b/qutebrowser/browser/network/__init__.py @@ -0,0 +1,3 @@ +# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: + +"""Modules related to network operations.""" From 71404a87b98a963ba0590ef4fcc7eae699e7b66c Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 22 Dec 2016 15:14:35 +0100 Subject: [PATCH 101/561] Fix lint --- qutebrowser/config/configtypes.py | 1 - tests/unit/utils/test_urlutils.py | 1 - 2 files changed, 2 deletions(-) diff --git a/qutebrowser/config/configtypes.py b/qutebrowser/config/configtypes.py index b0399fcac..c21e5ced7 100644 --- a/qutebrowser/config/configtypes.py +++ b/qutebrowser/config/configtypes.py @@ -1030,7 +1030,6 @@ class Proxy(BaseType): return elif value in self.valid_values: return - url = QUrl(value) try: self.transform(value) diff --git a/tests/unit/utils/test_urlutils.py b/tests/unit/utils/test_urlutils.py index 10ba54b3a..49bc85f07 100644 --- a/tests/unit/utils/test_urlutils.py +++ b/tests/unit/utils/test_urlutils.py @@ -740,7 +740,6 @@ def test_data_url(): assert url == QUrl('data:text/plain;base64,Zm9v') - class TestProxyFromUrl: @pytest.mark.parametrize('url, expected', [ From 8169d1865ae4bc9f36729b79d943bbd20875eb25 Mon Sep 17 00:00:00 2001 From: rsteube Date: Sun, 25 Dec 2016 17:50:24 +0100 Subject: [PATCH 102/561] Added ripbang userscript --- misc/userscripts/ripbang | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100755 misc/userscripts/ripbang diff --git a/misc/userscripts/ripbang b/misc/userscripts/ripbang new file mode 100755 index 000000000..3a083cad3 --- /dev/null +++ b/misc/userscripts/ripbang @@ -0,0 +1,31 @@ +#!/usr/bin/env python2 +# +# Adds DuckDuckGo bang as searchengine. +# +# Usage: +# :spawn --userscript ripbang [bang]... +# +# Example: +# :spawn --userscript ripbang amazon maps +# +import os, re, requests, sys, urllib + +try: + for argument in sys.argv[1:]: + bang = '!' + argument + r = requests.get('https://duckduckgo.com/', params={'q': bang + ' SEARCHTEXT'}) + + searchengine = urllib.unquote(re.search("url=[^']+", r.text).group( + 0)) + searchengine = searchengine.replace('url=', '') + searchengine = searchengine.replace('/l/?kh=-1&uddg=', '') + searchengine = searchengine.replace('SEARCHTEXT', '{}') + + if os.getenv('QUTE_FIFO'): + with open(os.environ['QUTE_FIFO'], 'w') as fifo: + fifo.write('set searchengines %s %s' % (bang, searchengine)) + else: + print '%s %s' % (bang, searchengine) +except: + # fail silent + exit(1) From 45d2046b9669a752c7d0949cba1a9a63b462ada2 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 26 Dec 2016 16:50:51 +0100 Subject: [PATCH 103/561] test requirements: Update Werkzeug to 0.11.12 --- misc/requirements/requirements-tests.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index 786bb7251..3340b4966 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -33,4 +33,4 @@ pytest-xvfb==1.0.0 PyVirtualDisplay==0.2.1 six==1.10.0 vulture==0.11 -Werkzeug==0.11.11 +Werkzeug==0.11.12 From a57c8f078fee8c5915eb20ecec1076e6b68bd487 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 26 Dec 2016 16:53:03 +0100 Subject: [PATCH 104/561] test requirements: Update CherryPy to 8.5.0 --- misc/requirements/requirements-tests.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index 3340b4966..64110824a 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -1,7 +1,7 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py beautifulsoup4==4.5.1 -CherryPy==8.1.3 +CherryPy==8.5.0 click==6.6 coverage==4.2 decorator==4.0.10 From 004b0dc911e3aa88cbbfd2bc2065bb5873f72a07 Mon Sep 17 00:00:00 2001 From: Fritz Reichwald Date: Tue, 27 Dec 2016 17:54:06 +0100 Subject: [PATCH 105/561] Add fallback for missing error.html --- qutebrowser/utils/jinja.py | 47 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/qutebrowser/utils/jinja.py b/qutebrowser/utils/jinja.py index e60032ff4..f2dbbfe5f 100644 --- a/qutebrowser/utils/jinja.py +++ b/qutebrowser/utils/jinja.py @@ -31,6 +31,49 @@ from qutebrowser.utils import utils, urlutils, log from PyQt5.QtCore import QUrl +html_fallback = ''' + + + + {{ title }} + {% if icon %} + + {% endif %} + + + +
+ + + + + +
+ + +

The error.html template could not be found!
Please check your qutebrowser installation

''' +html_fallback2 = '''

+

Unable to load page

+ Error while opening {{ url }}:
+

{{ error }}



+ +
+ +
+ +
+
+ + +''' class Loader(jinja2.BaseLoader): @@ -47,8 +90,8 @@ class Loader(jinja2.BaseLoader): path = os.path.join(self._subdir, template) try: source = utils.read_file(path) - except OSError: - raise jinja2.TemplateNotFound(template) + except OSError as e: + source = html_fallback + str(e) + html_fallback2; # Currently we don't implement auto-reloading, so we always return True # for up-to-date. return source, path, lambda: True From 07143dd433bbe05588f1d3fa18b451672d083bac Mon Sep 17 00:00:00 2001 From: Fritz Reichwald Date: Tue, 27 Dec 2016 20:31:57 +0100 Subject: [PATCH 106/561] Add html escape proper quotes and log call --- qutebrowser/utils/jinja.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/qutebrowser/utils/jinja.py b/qutebrowser/utils/jinja.py index f2dbbfe5f..16ccb2959 100644 --- a/qutebrowser/utils/jinja.py +++ b/qutebrowser/utils/jinja.py @@ -23,6 +23,7 @@ import os import os.path import traceback import mimetypes +import html import jinja2 import jinja2.exceptions @@ -31,7 +32,7 @@ from qutebrowser.utils import utils, urlutils, log from PyQt5.QtCore import QUrl -html_fallback = ''' +html_fallback = """ @@ -57,8 +58,8 @@ html_fallback = ''' -

The error.html template could not be found!
Please check your qutebrowser installation

''' -html_fallback2 = '''

+

The error.html template could not be found!
Please check your qutebrowser installation

+%ERROR%

Unable to load page

Error while opening {{ url }}:

{{ error }}



@@ -73,7 +74,7 @@ html_fallback2 = '''

-''' +""" class Loader(jinja2.BaseLoader): @@ -91,7 +92,8 @@ class Loader(jinja2.BaseLoader): try: source = utils.read_file(path) except OSError as e: - source = html_fallback + str(e) + html_fallback2; + source = html_fallback.replace("%ERROR%", html.escape(str(e))) + log.misc.error("The error.html template could not be found" + path) # Currently we don't implement auto-reloading, so we always return True # for up-to-date. return source, path, lambda: True From 59f583aed331345f6a99e83d41217fdbd677cf69 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 27 Dec 2016 21:06:49 +0100 Subject: [PATCH 107/561] Add python3-pyqt5.qtquick to INSTALL --- INSTALL.asciidoc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/INSTALL.asciidoc b/INSTALL.asciidoc index f5ab70daa..f5007ccfd 100644 --- a/INSTALL.asciidoc +++ b/INSTALL.asciidoc @@ -21,7 +21,7 @@ Using the packages Install the dependencies via apt-get: ---- -# apt-get install python3-lxml python-tox python3-pyqt5 python3-pyqt5.qtwebkit python3-sip python3-jinja2 python3-pygments python3-yaml +# apt-get install python3-lxml python-tox python3-pyqt5 python3-pyqt5.qtwebkit python3-pyqt5.qtquick python3-sip python3-jinja2 python3-pygments python3-yaml ---- Get the qutebrowser package from the @@ -56,7 +56,7 @@ Then install the packages like this: ---- # apt-get update -# apt-get install -t experimental python3-pyqt5 python3-pyqt5.qtwebkit python3-sip python3-dev +# apt-get install -t experimental python3-pyqt5 python3-pyqt5.qtwebkit python3-pyqt5.qtquick python3-sip python3-dev # apt-get install python-tox ---- @@ -74,7 +74,7 @@ For distributions other than Debian or if you prefer to not use the experimental repo: ---- -# apt-get install python3-pyqt5 python3-pyqt5.qtwebkit python-tox python3-sip python3-dev +# apt-get install python3-pyqt5 python3-pyqt5.qtwebkit python3-pyqt5.qtquick python-tox python3-sip python3-dev ---- To generate the documentation for the `:help` command, when using the git From ba702db94ecc5ffbc90b9e96a01103bf94eaa1aa Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 27 Dec 2016 21:12:36 +0100 Subject: [PATCH 108/561] test requirements: Update CherryPy to 8.6.0 --- misc/requirements/requirements-tests.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index 64110824a..0d600cfb7 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -1,7 +1,7 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py beautifulsoup4==4.5.1 -CherryPy==8.5.0 +CherryPy==8.6.0 click==6.6 coverage==4.2 decorator==4.0.10 From 9726cd7e220d1b770bccb3e9842a424ab8cb425f Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 27 Dec 2016 21:12:49 +0100 Subject: [PATCH 109/561] test requirements: Update Werkzeug to 0.11.13 --- misc/requirements/requirements-tests.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index 0d600cfb7..45c7c1362 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -33,4 +33,4 @@ pytest-xvfb==1.0.0 PyVirtualDisplay==0.2.1 six==1.10.0 vulture==0.11 -Werkzeug==0.11.12 +Werkzeug==0.11.13 From eb03f79978ef03af57b5de35130eb3625e6ffed8 Mon Sep 17 00:00:00 2001 From: Fritz Reichwald Date: Tue, 27 Dec 2016 22:51:30 +0100 Subject: [PATCH 110/561] Add error handling for UnicodeDecodeError --- qutebrowser/commands/userscripts.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/qutebrowser/commands/userscripts.py b/qutebrowser/commands/userscripts.py index 3c1d4d89e..4cd96ba02 100644 --- a/qutebrowser/commands/userscripts.py +++ b/qutebrowser/commands/userscripts.py @@ -65,9 +65,12 @@ class _QtFIFOReader(QObject): """(Try to) read a line from the FIFO.""" log.procs.debug("QSocketNotifier triggered!") self._notifier.setEnabled(False) - for line in self._fifo: - self.got_line.emit(line.rstrip('\r\n')) - self._notifier.setEnabled(True) + try: + for line in self._fifo: + self.got_line.emit(line.rstrip('\r\n')) + self._notifier.setEnabled(True) + except UnicodeDecodeError: + log.misc.error("Invalid unicode in userscript output") def cleanup(self): """Clean up so the FIFO can be closed.""" From ab784a82fd2dfcbd34be41a153718ef3f10a3000 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 28 Dec 2016 00:19:17 +0100 Subject: [PATCH 111/561] test/codecov requirements: Update coverage to 4.3 --- misc/requirements/requirements-codecov.txt | 2 +- misc/requirements/requirements-tests.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/misc/requirements/requirements-codecov.txt b/misc/requirements/requirements-codecov.txt index 659764d86..43bf7b623 100644 --- a/misc/requirements/requirements-codecov.txt +++ b/misc/requirements/requirements-codecov.txt @@ -1,5 +1,5 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py codecov==2.0.5 -coverage==4.2 +coverage==4.3 requests==2.12.4 diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index 45c7c1362..9a279fc3a 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -3,7 +3,7 @@ beautifulsoup4==4.5.1 CherryPy==8.6.0 click==6.6 -coverage==4.2 +coverage==4.3 decorator==4.0.10 EasyProcess==0.2.3 Flask==0.12 From f38dda5f16005347120838adf5b5d9b736697414 Mon Sep 17 00:00:00 2001 From: Fritz Reichwald Date: Wed, 28 Dec 2016 02:23:35 +0100 Subject: [PATCH 112/561] Fix html_fallback and logged error message --- qutebrowser/utils/jinja.py | 72 +++++++++++++++++--------------------- 1 file changed, 32 insertions(+), 40 deletions(-) diff --git a/qutebrowser/utils/jinja.py b/qutebrowser/utils/jinja.py index 16ccb2959..b3f491ad9 100644 --- a/qutebrowser/utils/jinja.py +++ b/qutebrowser/utils/jinja.py @@ -34,45 +34,37 @@ from PyQt5.QtCore import QUrl html_fallback = """ - - - {{ title }} - {% if icon %} - - {% endif %} - - - -
- - - - - -
- - -

The error.html template could not be found!
Please check your qutebrowser installation

-%ERROR%

-

Unable to load page

- Error while opening {{ url }}:
-

{{ error }}



- -
- -
- -
-
- + + + {{ title }} + {% if icon %} + + {% endif %} + + + +
+ + + + + +
+ + +

The error.html template could not be found!
Please check your qutebrowser installation

+ %ERROR%

+
+
+ """ @@ -93,7 +85,7 @@ class Loader(jinja2.BaseLoader): source = utils.read_file(path) except OSError as e: source = html_fallback.replace("%ERROR%", html.escape(str(e))) - log.misc.error("The error.html template could not be found" + path) + log.misc.error("The error.html template could not be found at " + path) # Currently we don't implement auto-reloading, so we always return True # for up-to-date. return source, path, lambda: True From 473df00ae5251dbd7397ccf3655d844191d01831 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 28 Dec 2016 17:39:01 +0100 Subject: [PATCH 113/561] Update ace.js to 1.2.6 --- tests/end2end/data/hints/ace/ace.js | 39 +++++++++++++++++++---------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/tests/end2end/data/hints/ace/ace.js b/tests/end2end/data/hints/ace/ace.js index eaa57239b..4ee0b7b8d 100644 --- a/tests/end2end/data/hints/ace/ace.js +++ b/tests/end2end/data/hints/ace/ace.js @@ -2283,6 +2283,9 @@ var TextInput = function(parentNode, host) { if (e.type == "compositionend" && c.range) { host.selection.setRange(c.range); } + if (useragent.isChrome && useragent.isChrome >= 53) { + onInput(); + } }; @@ -6047,7 +6050,7 @@ var Mode = function() { }; (function() { - this.$behaviour = new CstyleBehaviour(); + this.$defaultBehaviour = new CstyleBehaviour(); this.tokenRe = new RegExp("^[" + unicode.packages.L @@ -10533,7 +10536,7 @@ var Search = function() { needle = lang.escapeRegExp(needle); if (options.wholeWord) - needle = "\\b" + needle + "\\b"; + needle = addWordBoundary(needle, options); var modifier = options.caseSensitive ? "gm" : "gmi"; @@ -10622,6 +10625,15 @@ var Search = function() { }).call(Search.prototype); +function addWordBoundary(needle, options) { + function wordBoundary(c) { + if (/\w/.test(c) || options.regExp) return "\\b"; + return ""; + } + return wordBoundary(needle[0]) + needle + + wordBoundary(needle[needle.length - 1]); +} + exports.Search = Search; }); @@ -10976,7 +10988,7 @@ exports.commands = [{ readOnly: true }, { name: "goToNextError", - bindKey: bindKey("Alt-E", "Ctrl-E"), + bindKey: bindKey("Alt-E", "F4"), exec: function(editor) { config.loadModule("ace/ext/error_marker", function(module) { module.showErrorMarker(editor, 1); @@ -10986,7 +10998,7 @@ exports.commands = [{ readOnly: true }, { name: "goToPreviousError", - bindKey: bindKey("Alt-Shift-E", "Ctrl-Shift-E"), + bindKey: bindKey("Alt-Shift-E", "Shift-F4"), exec: function(editor) { config.loadModule("ace/ext/error_marker", function(module) { module.showErrorMarker(editor, -1); @@ -11111,7 +11123,7 @@ exports.commands = [{ readOnly: true }, { name: "selecttostart", - bindKey: bindKey("Ctrl-Shift-Home", "Command-Shift-Up"), + bindKey: bindKey("Ctrl-Shift-Home", "Command-Shift-Home|Command-Shift-Up"), exec: function(editor) { editor.getSelection().selectFileStart(); }, multiSelectAction: "forEach", readOnly: true, @@ -11127,7 +11139,7 @@ exports.commands = [{ aceCommandGroup: "fileJump" }, { name: "selectup", - bindKey: bindKey("Shift-Up", "Shift-Up"), + bindKey: bindKey("Shift-Up", "Shift-Up|Ctrl-Shift-P"), exec: function(editor) { editor.getSelection().selectUp(); }, multiSelectAction: "forEach", scrollIntoView: "cursor", @@ -11141,7 +11153,7 @@ exports.commands = [{ readOnly: true }, { name: "selecttoend", - bindKey: bindKey("Ctrl-Shift-End", "Command-Shift-Down"), + bindKey: bindKey("Ctrl-Shift-End", "Command-Shift-End|Command-Shift-Down"), exec: function(editor) { editor.getSelection().selectFileEnd(); }, multiSelectAction: "forEach", readOnly: true, @@ -11157,7 +11169,7 @@ exports.commands = [{ aceCommandGroup: "fileJump" }, { name: "selectdown", - bindKey: bindKey("Shift-Down", "Shift-Down"), + bindKey: bindKey("Shift-Down", "Shift-Down|Ctrl-Shift-N"), exec: function(editor) { editor.getSelection().selectDown(); }, multiSelectAction: "forEach", scrollIntoView: "cursor", @@ -11185,7 +11197,7 @@ exports.commands = [{ readOnly: true }, { name: "selecttolinestart", - bindKey: bindKey("Alt-Shift-Left", "Command-Shift-Left"), + bindKey: bindKey("Alt-Shift-Left", "Command-Shift-Left|Ctrl-Shift-A"), exec: function(editor) { editor.getSelection().selectLineStart(); }, multiSelectAction: "forEach", scrollIntoView: "cursor", @@ -11199,7 +11211,7 @@ exports.commands = [{ readOnly: true }, { name: "selectleft", - bindKey: bindKey("Shift-Left", "Shift-Left"), + bindKey: bindKey("Shift-Left", "Shift-Left|Ctrl-Shift-B"), exec: function(editor) { editor.getSelection().selectLeft(); }, multiSelectAction: "forEach", scrollIntoView: "cursor", @@ -11227,7 +11239,7 @@ exports.commands = [{ readOnly: true }, { name: "selecttolineend", - bindKey: bindKey("Alt-Shift-Right", "Command-Shift-Right"), + bindKey: bindKey("Alt-Shift-Right", "Command-Shift-Right|Shift-End|Ctrl-Shift-E"), exec: function(editor) { editor.getSelection().selectLineEnd(); }, multiSelectAction: "forEach", scrollIntoView: "cursor", @@ -12091,7 +12103,8 @@ var Editor = function(renderer, session) { var row = iterator.getCurrentTokenRow(); var column = iterator.getCurrentTokenColumn(); var range = new Range(row, column, row, column+token.value.length); - if (session.$tagHighlight && range.compareRange(session.$backMarkers[session.$tagHighlight].range)!==0) { + var sbm = session.$backMarkers[session.$tagHighlight]; + if (session.$tagHighlight && sbm != undefined && range.compareRange(sbm.range) !== 0) { session.removeMarker(session.$tagHighlight); session.$tagHighlight = null; } @@ -19039,7 +19052,7 @@ exports.createEditSession = function(text, mode) { } exports.EditSession = EditSession; exports.UndoManager = UndoManager; -exports.version = "1.2.5"; +exports.version = "1.2.6"; }); (function() { window.require(["ace/ace"], function(a) { From 1ef69704e2f6398e5728dbcdcd6aca79e3f2989f Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 28 Dec 2016 17:43:46 +0100 Subject: [PATCH 114/561] test/codecov requirements: Update coverage to 4.3.1 --- misc/requirements/requirements-codecov.txt | 2 +- misc/requirements/requirements-tests.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/misc/requirements/requirements-codecov.txt b/misc/requirements/requirements-codecov.txt index 43bf7b623..2b0690335 100644 --- a/misc/requirements/requirements-codecov.txt +++ b/misc/requirements/requirements-codecov.txt @@ -1,5 +1,5 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py codecov==2.0.5 -coverage==4.3 +coverage==4.3.1 requests==2.12.4 diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index 9a279fc3a..d5e89a7a4 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -3,7 +3,7 @@ beautifulsoup4==4.5.1 CherryPy==8.6.0 click==6.6 -coverage==4.3 +coverage==4.3.1 decorator==4.0.10 EasyProcess==0.2.3 Flask==0.12 From 0bd3100de81ed122c9afad98bf51e631f5615b5c Mon Sep 17 00:00:00 2001 From: Fritz Reichwald Date: Wed, 28 Dec 2016 22:11:34 +0100 Subject: [PATCH 115/561] Fix test_not_found --- qutebrowser/utils/jinja.py | 6 ++++-- tests/unit/utils/test_jinja.py | 12 ++++++++---- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/qutebrowser/utils/jinja.py b/qutebrowser/utils/jinja.py index b3f491ad9..737b4aee1 100644 --- a/qutebrowser/utils/jinja.py +++ b/qutebrowser/utils/jinja.py @@ -58,7 +58,7 @@ html_fallback = """ -

The error.html template could not be found!
Please check your qutebrowser installation

+

The %FILE% template could not be found!
Please check your qutebrowser installation

%ERROR%

@@ -85,7 +85,9 @@ class Loader(jinja2.BaseLoader): source = utils.read_file(path) except OSError as e: source = html_fallback.replace("%ERROR%", html.escape(str(e))) - log.misc.error("The error.html template could not be found at " + path) + source = source.replace("%FILE%", html.escape(template)) + log.misc.error("The {} template could not be found at {}".format( + template, path)) # Currently we don't implement auto-reloading, so we always return True # for up-to-date. return source, path, lambda: True diff --git a/tests/unit/utils/test_jinja.py b/tests/unit/utils/test_jinja.py index e951e3994..155de6d78 100644 --- a/tests/unit/utils/test_jinja.py +++ b/tests/unit/utils/test_jinja.py @@ -105,11 +105,15 @@ def test_data_url(): assert data == 'data:text/plain;base64,Zm9v' # 'foo' -def test_not_found(): +def test_not_found(caplog): """Test with a template which does not exist.""" - with pytest.raises(jinja2.TemplateNotFound) as excinfo: - jinja.render('does_not_exist.html') - assert str(excinfo.value) == 'does_not_exist.html' + with caplog.at_level(logging.ERROR): + data = jinja.render('does_not_exist.html') + assert "The does_not_exist.html template could not be found!" in data + + assert len(caplog.records) == 1 + assert caplog.records[0].msg.startswith("The does_not_exist.html template" + " could not be found at") def test_utf8(): From 421fc7eca34155239f2ecc6586df68f320295f26 Mon Sep 17 00:00:00 2001 From: Fritz Reichwald Date: Wed, 28 Dec 2016 22:23:05 +0100 Subject: [PATCH 116/561] Remove icon from html_fallback --- qutebrowser/utils/jinja.py | 36 +++++++++--------------------------- 1 file changed, 9 insertions(+), 27 deletions(-) diff --git a/qutebrowser/utils/jinja.py b/qutebrowser/utils/jinja.py index 737b4aee1..e9ca30c5d 100644 --- a/qutebrowser/utils/jinja.py +++ b/qutebrowser/utils/jinja.py @@ -32,38 +32,20 @@ from qutebrowser.utils import utils, urlutils, log from PyQt5.QtCore import QUrl -html_fallback = """ +html_fallback = """ + - {{ title }} - {% if icon %} - - {% endif %} - + Error while loading template -
- - - - - -
- - -

The %FILE% template could not be found!
Please check your qutebrowser installation

- %ERROR%

-
-
+

+ The %FILE% template could not be found!
+ Please check your qutebrowser installation +

+ %ERROR% +

""" From 8c3d461482218a8a08623b5d103d1027dc156100 Mon Sep 17 00:00:00 2001 From: Fritz Reichwald Date: Wed, 28 Dec 2016 22:35:03 +0100 Subject: [PATCH 117/561] Fix test for logging --- qutebrowser/utils/jinja.py | 4 ++-- tests/unit/utils/test_jinja.py | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/qutebrowser/utils/jinja.py b/qutebrowser/utils/jinja.py index e9ca30c5d..35cfebd19 100644 --- a/qutebrowser/utils/jinja.py +++ b/qutebrowser/utils/jinja.py @@ -68,8 +68,8 @@ class Loader(jinja2.BaseLoader): except OSError as e: source = html_fallback.replace("%ERROR%", html.escape(str(e))) source = source.replace("%FILE%", html.escape(template)) - log.misc.error("The {} template could not be found at {}".format( - template, path)) + log.misc.exception("The {} template could not be loaded from {}" + .format(template, path)) # Currently we don't implement auto-reloading, so we always return True # for up-to-date. return source, path, lambda: True diff --git a/tests/unit/utils/test_jinja.py b/tests/unit/utils/test_jinja.py index 155de6d78..d2f0bb7a6 100644 --- a/tests/unit/utils/test_jinja.py +++ b/tests/unit/utils/test_jinja.py @@ -111,9 +111,8 @@ def test_not_found(caplog): data = jinja.render('does_not_exist.html') assert "The does_not_exist.html template could not be found!" in data - assert len(caplog.records) == 1 assert caplog.records[0].msg.startswith("The does_not_exist.html template" - " could not be found at") + " could not be loaded from") def test_utf8(): From ae736da5f7c2de2c59989f677e5f00f4032c0442 Mon Sep 17 00:00:00 2001 From: Fritz Reichwald Date: Wed, 28 Dec 2016 22:51:20 +0100 Subject: [PATCH 118/561] Fix lint --- qutebrowser/utils/jinja.py | 5 +++-- tests/unit/utils/test_jinja.py | 1 - 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/qutebrowser/utils/jinja.py b/qutebrowser/utils/jinja.py index 35cfebd19..2ad2be448 100644 --- a/qutebrowser/utils/jinja.py +++ b/qutebrowser/utils/jinja.py @@ -41,8 +41,8 @@ html_fallback = """

- The %FILE% template could not be found!
- Please check your qutebrowser installation + The %FILE% template could not be found!
+ Please check your qutebrowser installation

%ERROR%

@@ -50,6 +50,7 @@ html_fallback = """ """ + class Loader(jinja2.BaseLoader): """Jinja loader which uses utils.read_file to load templates. diff --git a/tests/unit/utils/test_jinja.py b/tests/unit/utils/test_jinja.py index d2f0bb7a6..fc4ce3074 100644 --- a/tests/unit/utils/test_jinja.py +++ b/tests/unit/utils/test_jinja.py @@ -24,7 +24,6 @@ import os.path import pytest import logging -import jinja2 from PyQt5.QtCore import QUrl from qutebrowser.utils import utils, jinja From 9727d6c74f763079d5818f2d065486a2862030a8 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 28 Dec 2016 23:03:30 +0100 Subject: [PATCH 119/561] Improve error output --- qutebrowser/commands/userscripts.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/qutebrowser/commands/userscripts.py b/qutebrowser/commands/userscripts.py index 4cd96ba02..fc356f44a 100644 --- a/qutebrowser/commands/userscripts.py +++ b/qutebrowser/commands/userscripts.py @@ -69,8 +69,9 @@ class _QtFIFOReader(QObject): for line in self._fifo: self.got_line.emit(line.rstrip('\r\n')) self._notifier.setEnabled(True) - except UnicodeDecodeError: - log.misc.error("Invalid unicode in userscript output") + except UnicodeDecodeError as e: + log.misc.error("Invalid unicode in userscript output: {}" + .format(e)) def cleanup(self): """Clean up so the FIFO can be closed.""" From 864b9870a5dd0bcfe1b97c74e70259e10d294c05 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 28 Dec 2016 23:03:37 +0100 Subject: [PATCH 120/561] Also catch UnicodeDecodeError on Windows --- qutebrowser/commands/userscripts.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/qutebrowser/commands/userscripts.py b/qutebrowser/commands/userscripts.py index fc356f44a..960e97318 100644 --- a/qutebrowser/commands/userscripts.py +++ b/qutebrowser/commands/userscripts.py @@ -293,6 +293,9 @@ class _WindowsUserscriptRunner(_BaseUserscriptRunner): self.got_cmd.emit(line.rstrip()) except OSError: log.procs.exception("Failed to read command file!") + except UnicodeDecodeError as e: + log.misc.error("Invalid unicode in userscript output: {}" + .format(e)) super()._cleanup() self.finished.emit() From d9c8bd7fca349c665c05c0eb46089aa221f1943e Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 28 Dec 2016 23:03:46 +0100 Subject: [PATCH 121/561] Add a test for userscript unicode errors --- tests/unit/commands/test_userscripts.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/unit/commands/test_userscripts.py b/tests/unit/commands/test_userscripts.py index 778270551..5212b0d32 100644 --- a/tests/unit/commands/test_userscripts.py +++ b/tests/unit/commands/test_userscripts.py @@ -227,6 +227,24 @@ def test_temporary_files_failed_cleanup(caplog, qtbot, py_proc, runner): assert caplog.records[0].message.startswith(expected) +def test_unicode_error(caplog, qtbot, py_proc, runner): + cmd, args = py_proc(r""" + import os + with open(os.environ['QUTE_FIFO'], 'wb') as f: + f.write(b'\x80') + """) + with caplog.at_level(logging.ERROR): + with qtbot.waitSignal(runner.finished, timeout=10000): + runner.prepare_run(cmd, *args) + runner.store_text('') + runner.store_html('') + + assert len(caplog.records) == 1 + expected = ("Invalid unicode in userscript output: 'utf-8' codec can't " + "decode byte 0x80 in position 0: invalid start byte") + assert caplog.records[0].message == expected + + def test_unsupported(monkeypatch, tabbed_browser_stubs): monkeypatch.setattr(userscripts.os, 'name', 'toaster') with pytest.raises(userscripts.UnsupportedError) as excinfo: From 25eabb4662ae7787d30ae304133306412b93622a Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 28 Dec 2016 23:04:14 +0100 Subject: [PATCH 122/561] Update authors --- README.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.asciidoc b/README.asciidoc index ef255fb47..60f64133c 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -191,6 +191,7 @@ Contributors, sorted by the number of commits in descending order: * Michael Hoang * Liam BEGUIN * Julie Engel +* Fritz Reichwald * skinnay * Zach-Button * Tomasz Kramkowski @@ -199,7 +200,6 @@ Contributors, sorted by the number of commits in descending order: * Nikolay Amiantov * Ismail S * Halfwit -* Fritz Reichwald * David Vogt * Claire Cavanaugh * rikn00 From cbb046b5a365b135f2cdd5c3866453f55d8033da Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 28 Dec 2016 23:31:31 +0100 Subject: [PATCH 123/561] Update authors --- README.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.asciidoc b/README.asciidoc index 60f64133c..604a1a447 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -172,6 +172,7 @@ Contributors, sorted by the number of commits in descending order: * Austin Anderson * Jimmy * Niklas Haas +* Fritz Reichwald * Maciej Wołczyk * Spreadyy * Alexey "Averrin" Nabrodov @@ -191,7 +192,6 @@ Contributors, sorted by the number of commits in descending order: * Michael Hoang * Liam BEGUIN * Julie Engel -* Fritz Reichwald * skinnay * Zach-Button * Tomasz Kramkowski From 057e5c9807b38e4aad14026ba9e29e26d04bf650 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 28 Dec 2016 23:33:52 +0100 Subject: [PATCH 124/561] Update authors --- README.asciidoc | 3 +-- scripts/dev/src2asciidoc.py | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.asciidoc b/README.asciidoc index 604a1a447..0560250b7 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -171,8 +171,8 @@ Contributors, sorted by the number of commits in descending order: * Thorsten Wißmann * Austin Anderson * Jimmy -* Niklas Haas * Fritz Reichwald +* Niklas Haas * Maciej Wołczyk * Spreadyy * Alexey "Averrin" Nabrodov @@ -235,7 +235,6 @@ Contributors, sorted by the number of commits in descending order: * Marcelo Santos * Joel Bradshaw * Jean-Louis Fuchs -* Fritz V155 Reichwald * Franz Fellner * Eric Drechsel * zwarag diff --git a/scripts/dev/src2asciidoc.py b/scripts/dev/src2asciidoc.py index 2628ba282..c7b620488 100755 --- a/scripts/dev/src2asciidoc.py +++ b/scripts/dev/src2asciidoc.py @@ -433,6 +433,7 @@ def _get_authors(): 'Corentin Jule': 'Corentin Julé', 'Claire C.C': 'Claire Cavanaugh', 'Rahid': 'Maciej Wołczyk', + 'Fritz V155 Reichwald': 'Fritz Reichwald', } commits = subprocess.check_output(['git', 'log', '--format=%aN']) authors = [corrections.get(author, author) From 9b5f7db03a02e44ef90795ce333ecf2529d30ab7 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 28 Dec 2016 23:36:24 +0100 Subject: [PATCH 125/561] Update changelog --- CHANGELOG.asciidoc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 59a69d6a1..029edd115 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -195,6 +195,8 @@ Fixed - Fixed hang when using multiple spaces in a row with the URL completion - qutebrowser now still starts with an incorrectly configured `$XDG_RUNTIME_DIR`. +- Fixed crash when a userscript writes invalid unicode data to the FIFO +- Fixed crash when a included HTML was not found v0.8.3 ------ From 1c4a28e5a016aac98a510fbd47ea901ed00d88b6 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 28 Dec 2016 23:47:09 +0100 Subject: [PATCH 126/561] Add old config for 0.9.0 --- .../old_configs/qutebrowser-v0.9.0.conf | 263 ++++++++++++++++++ tests/unit/config/test_config.py | 2 +- 2 files changed, 264 insertions(+), 1 deletion(-) create mode 100644 tests/unit/config/old_configs/qutebrowser-v0.9.0.conf diff --git a/tests/unit/config/old_configs/qutebrowser-v0.9.0.conf b/tests/unit/config/old_configs/qutebrowser-v0.9.0.conf new file mode 100644 index 000000000..8949fd417 --- /dev/null +++ b/tests/unit/config/old_configs/qutebrowser-v0.9.0.conf @@ -0,0 +1,263 @@ + + +[general] +ignore-case = smart +startpage = https://start.duckduckgo.com +yank-ignored-url-parameters = ref,utm_source,utm_medium,utm_campaign,utm_term,utm_content +default-open-dispatcher = +default-page = ${startpage} +auto-search = naive +auto-save-config = true +auto-save-interval = 15000 +editor = gvim -f "{}" +editor-encoding = utf-8 +private-browsing = false +developer-extras = false +print-element-backgrounds = true +xss-auditing = false +site-specific-quirks = true +default-encoding = +new-instance-open-target = tab +new-instance-open-target.window = last-focused +log-javascript-console = debug +save-session = false +session-default-name = +url-incdec-segments = path,query + +[ui] +zoom-levels = 25%,33%,50%,67%,75%,90%,100%,110%,125%,150%,175%,200%,250%,300%,400%,500% +default-zoom = 100% +downloads-position = top +status-position = bottom +message-timeout = 2000 +message-unfocused = false +confirm-quit = never +zoom-text-only = false +frame-flattening = false +user-stylesheet = +hide-scrollbar = true +css-media-type = +smooth-scrolling = false +remove-finished-downloads = -1 +hide-statusbar = false +statusbar-padding = 1,1,0,0 +window-title-format = {perc}{title}{title_sep}qutebrowser +modal-js-dialog = false +hide-wayland-decoration = false +keyhint-blacklist = +prompt-radius = 8 +prompt-filebrowser = true + +[network] +do-not-track = true +accept-language = en-US,en +referer-header = same-domain +user-agent = +proxy = system +proxy-dns-requests = true +ssl-strict = ask +dns-prefetch = true +custom-headers = +netrc-file = + +[completion] +show = always +download-path-suggestion = path +timestamp-format = %Y-%m-%d +height = 50% +cmd-history-max-items = 100 +web-history-max-items = 1000 +quick-complete = true +shrink = false +scrollbar-width = 12 +scrollbar-padding = 2 + +[input] +timeout = 500 +partial-timeout = 5000 +insert-mode-on-plugins = false +auto-leave-insert-mode = true +auto-insert-mode = false +forward-unbound-keys = auto +spatial-navigation = false +links-included-in-focus-chain = true +rocker-gestures = false +mouse-zoom-divider = 512 + +[tabs] +background-tabs = false +select-on-remove = next +new-tab-position = next +new-tab-position-explicit = last +last-close = ignore +show = always +show-switching-delay = 800 +wrap = true +movable = true +close-mouse-button = middle +position = top +show-favicons = true +width = 20% +indicator-width = 3 +tabs-are-windows = false +title-format = {index}: {title} +title-alignment = left +mousewheel-tab-switching = true +padding = 0,0,5,5 +indicator-padding = 2,2,0,4 + +[storage] +download-directory = +prompt-download-directory = true +remember-download-directory = true +maximum-pages-in-cache = +object-cache-capacities = +offline-storage-default-quota = +offline-web-application-cache-quota = +offline-storage-database = true +offline-web-application-storage = true +local-storage = true +cache-size = 52428800 + +[content] +allow-images = true +allow-javascript = true +allow-plugins = false +webgl = false +css-regions = true +hyperlink-auditing = false +geolocation = ask +notifications = ask +media-capture = ask +javascript-can-open-windows-automatically = false +javascript-can-close-windows = false +javascript-can-access-clipboard = false +ignore-javascript-prompt = false +ignore-javascript-alert = false +local-content-can-access-remote-urls = false +local-content-can-access-file-urls = true +cookies-accept = no-3rdparty +cookies-store = true +host-block-lists = https://www.malwaredomainlist.com/hostslist/hosts.txt,http://someonewhocares.org/hosts/hosts,http://winhelp2002.mvps.org/hosts.zip,http://malwaredomains.lehigh.edu/files/justdomains.zip,https://pgl.yoyo.org/adservers/serverlist.php?hostformat=hosts&mimetype=plaintext +host-blocking-enabled = true +host-blocking-whitelist = piwik.org +enable-pdfjs = false + +[hints] +border = 1px solid #E3BE23 +mode = letter +chars = asdfghjkl +min-chars = 1 +scatter = true +uppercase = false +dictionary = /usr/share/dict/words +auto-follow = unique-match +auto-follow-timeout = 0 +next-regexes = \bnext\b,\bmore\b,\bnewer\b,\b[>→≫]\b,\b(>>|»)\b,\bcontinue\b +prev-regexes = \bprev(ious)?\b,\bback\b,\bolder\b,\b[<←≪]\b,\b(<<|«)\b +find-implementation = python +hide-unmatched-rapid-hints = true + +[searchengines] +DEFAULT = https://duckduckgo.com/?q={} + +[aliases] + +[colors] +completion.fg = white +completion.bg = #333333 +completion.alternate-bg = #444444 +completion.category.fg = white +completion.category.bg = qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #888888, stop:1 #505050) +completion.category.border.top = black +completion.category.border.bottom = ${completion.category.border.top} +completion.item.selected.fg = black +completion.item.selected.bg = #e8c000 +completion.item.selected.border.top = #bbbb00 +completion.item.selected.border.bottom = ${completion.item.selected.border.top} +completion.match.fg = #ff4444 +completion.scrollbar.fg = ${completion.fg} +completion.scrollbar.bg = ${completion.bg} +statusbar.fg = white +statusbar.bg = black +statusbar.fg.insert = ${statusbar.fg} +statusbar.bg.insert = darkgreen +statusbar.fg.command = ${statusbar.fg} +statusbar.bg.command = ${statusbar.bg} +statusbar.fg.caret = ${statusbar.fg} +statusbar.bg.caret = purple +statusbar.fg.caret-selection = ${statusbar.fg} +statusbar.bg.caret-selection = #a12dff +statusbar.progress.bg = white +statusbar.url.fg = ${statusbar.fg} +statusbar.url.fg.success = white +statusbar.url.fg.success.https = lime +statusbar.url.fg.error = orange +statusbar.url.fg.warn = yellow +statusbar.url.fg.hover = aqua +tabs.fg.odd = white +tabs.bg.odd = grey +tabs.fg.even = white +tabs.bg.even = darkgrey +tabs.fg.selected.odd = white +tabs.bg.selected.odd = black +tabs.fg.selected.even = ${tabs.fg.selected.odd} +tabs.bg.selected.even = ${tabs.bg.selected.odd} +tabs.bg.bar = #555555 +tabs.indicator.start = #0000aa +tabs.indicator.stop = #00aa00 +tabs.indicator.error = #ff0000 +tabs.indicator.system = rgb +hints.fg = black +hints.bg = qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 rgba(255, 247, 133, 0.8), stop:1 rgba(255, 197, 66, 0.8)) +hints.fg.match = green +downloads.bg.bar = black +downloads.fg.start = white +downloads.bg.start = #0000aa +downloads.fg.stop = ${downloads.fg.start} +downloads.bg.stop = #00aa00 +downloads.fg.system = rgb +downloads.bg.system = rgb +downloads.fg.error = white +downloads.bg.error = red +webpage.bg = white +keyhint.fg = #FFFFFF +keyhint.fg.suffix = #FFFF00 +keyhint.bg = rgba(0, 0, 0, 80%) +messages.fg.error = white +messages.bg.error = red +messages.border.error = #bb0000 +messages.fg.warning = white +messages.bg.warning = darkorange +messages.border.warning = #d47300 +messages.fg.info = white +messages.bg.info = black +messages.border.info = #333333 +prompts.fg = white +prompts.bg = darkblue +prompts.selected.bg = #308cc6 + +[fonts] +_monospace = Terminus, Monospace, "DejaVu Sans Mono", Monaco, "Bitstream Vera Sans Mono", "Andale Mono", "Courier New", Courier, "Liberation Mono", monospace, Fixed, Consolas, Terminal +completion = 8pt ${_monospace} +completion.category = bold ${completion} +tabbar = 8pt ${_monospace} +statusbar = 8pt ${_monospace} +downloads = 8pt ${_monospace} +hints = bold 13px ${_monospace} +debug-console = 8pt ${_monospace} +web-family-standard = +web-family-fixed = +web-family-serif = +web-family-sans-serif = +web-family-cursive = +web-family-fantasy = +web-size-minimum = +web-size-minimum-logical = +web-size-default = +web-size-default-fixed = +keyhint = 8pt ${_monospace} +messages.error = 8pt ${_monospace} +messages.warning = 8pt ${_monospace} +messages.info = 8pt ${_monospace} +prompts = 8pt sans-serif diff --git a/tests/unit/config/test_config.py b/tests/unit/config/test_config.py index 3b6f21f7d..b22057a7c 100644 --- a/tests/unit/config/test_config.py +++ b/tests/unit/config/test_config.py @@ -403,7 +403,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.4' + assert qutebrowser.__version__ == '0.9.0' @pytest.mark.parametrize('filename', os.listdir(os.path.join(os.path.dirname(__file__), 'old_configs')), From 9837c9f70b2164785d93f19959d6f42af16e511c Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 28 Dec 2016 23:47:59 +0100 Subject: [PATCH 127/561] Release v0.9.0 --- CHANGELOG.asciidoc | 4 ++-- qutebrowser/__init__.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 029edd115..6ca5e0e67 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -14,8 +14,8 @@ 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.9.0 (unreleased) -------------------- +v0.9.0 +------ Added ~~~~~ diff --git a/qutebrowser/__init__.py b/qutebrowser/__init__.py index 8be034d21..3fe6648e0 100644 --- a/qutebrowser/__init__.py +++ b/qutebrowser/__init__.py @@ -28,7 +28,7 @@ __copyright__ = "Copyright 2014-2016 Florian Bruhin (The Compiler)" __license__ = "GPL" __maintainer__ = __author__ __email__ = "mail@qutebrowser.org" -__version_info__ = (0, 8, 4) +__version_info__ = (0, 9, 0) __version__ = '.'.join(str(e) for e in __version_info__) __description__ = "A keyboard-driven, vim-like browser based on PyQt5 and QtWebKit." From f25c5dedd6be9a6ffd863dfc4590cc3e50696e07 Mon Sep 17 00:00:00 2001 From: Fritz Reichwald Date: Thu, 29 Dec 2016 00:21:42 +0100 Subject: [PATCH 128/561] First try for horizontal scrolling with mouse --- qutebrowser/browser/mouse.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/qutebrowser/browser/mouse.py b/qutebrowser/browser/mouse.py index db5055d51..9f9ed34fe 100644 --- a/qutebrowser/browser/mouse.py +++ b/qutebrowser/browser/mouse.py @@ -127,6 +127,12 @@ class MouseEventFilter(QObject): message.info("Zoom level: {}%".format(perc)) self._tab.zoom.set_factor(factor) + if e.modifiers() & Qt.ShiftModifier: + if e.anglelta().y() < 0: + self._tab.scroll.left() + else: + self._tab.scroll.right() + return False def _handle_context_menu(self, _e): From 643cf3f5788946010bdc8997985a9e5465d66c87 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 29 Dec 2016 00:38:52 +0100 Subject: [PATCH 129/561] Fix various small mouse scrolling issues --- qutebrowser/browser/mouse.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/qutebrowser/browser/mouse.py b/qutebrowser/browser/mouse.py index 9f9ed34fe..a1b56b96a 100644 --- a/qutebrowser/browser/mouse.py +++ b/qutebrowser/browser/mouse.py @@ -126,12 +126,12 @@ class MouseEventFilter(QObject): perc = int(100 * factor) message.info("Zoom level: {}%".format(perc)) self._tab.zoom.set_factor(factor) - - if e.modifiers() & Qt.ShiftModifier: - if e.anglelta().y() < 0: - self._tab.scroll.left() + elif e.modifiers() & Qt.ShiftModifier: + if e.angleDelta().y() > 0: + self._tab.scroller.left() else: - self._tab.scroll.right() + self._tab.scroller.right() + return True return False From 45ed0b3a16cb8a0df1be781cc4d64c7506139b61 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 29 Dec 2016 00:39:18 +0100 Subject: [PATCH 130/561] Update authors --- README.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.asciidoc b/README.asciidoc index 0560250b7..5fdb2c736 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -170,8 +170,8 @@ Contributors, sorted by the number of commits in descending order: * Nathan Isom * Thorsten Wißmann * Austin Anderson -* Jimmy * Fritz Reichwald +* Jimmy * Niklas Haas * Maciej Wołczyk * Spreadyy From 824ddb72a97cabf2152e459d8195afff35fe59f9 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 29 Dec 2016 00:40:20 +0100 Subject: [PATCH 131/561] Update changelog --- CHANGELOG.asciidoc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 6ca5e0e67..bbfbc204b 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -14,6 +14,15 @@ 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.0 +------- + +Changed +~~~~~~~ + +- Scrolling with the scrollwheel while holding shift now scrolls sideways + + v0.9.0 ------ From ea2eefccb1321211896aa0595ae5cff2ced4cee9 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 29 Dec 2016 16:36:43 +0100 Subject: [PATCH 132/561] requirements: Update Jinja2 to 2.8.1 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 3d65de342..6481a5b5a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ colorama==0.3.7 cssutils==1.0.1 -Jinja2==2.8 +Jinja2==2.8.1 MarkupSafe==0.23 Pygments==2.1.3 pyPEG2==2.15.2 From f4081c8b87138b6233522ec5ce1518aed02cad84 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 29 Dec 2016 20:12:53 +0100 Subject: [PATCH 133/561] Fix error position when the statusbar is invisible --- qutebrowser/mainwindow/mainwindow.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/qutebrowser/mainwindow/mainwindow.py b/qutebrowser/mainwindow/mainwindow.py index f7d326a09..069c5c6b8 100644 --- a/qutebrowser/mainwindow/mainwindow.py +++ b/qutebrowser/mainwindow/mainwindow.py @@ -238,13 +238,25 @@ class MainWindow(QWidget): height_padding = 20 status_position = config.get('ui', 'status-position') if status_position == 'bottom': - top = self.height() - self.status.height() - size_hint.height() + if self.status.isVisible(): + status_height = self.status.height() + bottom = self.status.geometry().top() + else: + status_height = 0 + bottom = self.height() + top = self.height() - status_height - size_hint.height() top = qtutils.check_overflow(top, 'int', fatal=False) topleft = QPoint(left, max(height_padding, top)) - bottomright = QPoint(left + width, self.status.geometry().top()) + bottomright = QPoint(left + width, bottom) elif status_position == 'top': - topleft = QPoint(left, self.status.geometry().bottom()) - bottom = self.status.height() + size_hint.height() + if self.status.isVisible(): + status_height = self.status.height() + top = self.status.geometry().bottom() + else: + status_height = 0 + top = 0 + topleft = QPoint(left, top) + bottom = status_height + size_hint.height() bottom = qtutils.check_overflow(bottom, 'int', fatal=False) bottomright = QPoint(left + width, min(self.height() - height_padding, bottom)) From 48d4c9311ac47382002beea302cb0cf322f46139 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 2 Jan 2017 20:16:51 +0100 Subject: [PATCH 134/561] Various dependency updates --- misc/requirements/requirements-flake8.txt | 2 +- misc/requirements/requirements-tests.txt | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/misc/requirements/requirements-flake8.txt b/misc/requirements/requirements-flake8.txt index 1b48ba135..3b723d2c0 100644 --- a/misc/requirements/requirements-flake8.txt +++ b/misc/requirements/requirements-flake8.txt @@ -17,6 +17,6 @@ packaging==16.8 pep8-naming==0.4.1 pycodestyle==2.2.0 pydocstyle==1.1.1 -pyflakes==1.3.0 +pyflakes==1.4.0 pyparsing==2.1.10 six==1.10.0 diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index d5e89a7a4..b3c537634 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -1,7 +1,7 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -beautifulsoup4==4.5.1 -CherryPy==8.6.0 +beautifulsoup4==4.5.3 +CherryPy==8.7.0 click==6.6 coverage==4.3.1 decorator==4.0.10 @@ -11,7 +11,7 @@ glob2==0.5 httpbin==0.5.0 hypothesis==3.6.1 itsdangerous==0.24 -# Jinja2==2.8 +# Jinja2==2.8.1 Mako==1.0.6 # MarkupSafe==0.23 parse==1.6.6 @@ -33,4 +33,4 @@ pytest-xvfb==1.0.0 PyVirtualDisplay==0.2.1 six==1.10.0 vulture==0.11 -Werkzeug==0.11.13 +Werkzeug==0.11.15 From a716861bfa52b9429c0027b568a27c204aab2516 Mon Sep 17 00:00:00 2001 From: rsteube Date: Mon, 2 Jan 2017 21:53:04 +0100 Subject: [PATCH 135/561] removed try/except from ripbang userscript --- misc/userscripts/ripbang | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/misc/userscripts/ripbang b/misc/userscripts/ripbang index 3a083cad3..4b418443d 100755 --- a/misc/userscripts/ripbang +++ b/misc/userscripts/ripbang @@ -4,28 +4,24 @@ # # Usage: # :spawn --userscript ripbang [bang]... -# +# # Example: # :spawn --userscript ripbang amazon maps # import os, re, requests, sys, urllib -try: - for argument in sys.argv[1:]: - bang = '!' + argument - r = requests.get('https://duckduckgo.com/', params={'q': bang + ' SEARCHTEXT'}) +for argument in sys.argv[1:]: + bang = '!' + argument + r = requests.get('https://duckduckgo.com/', + params={'q': bang + ' SEARCHTEXT'}) - searchengine = urllib.unquote(re.search("url=[^']+", r.text).group( - 0)) - searchengine = searchengine.replace('url=', '') - searchengine = searchengine.replace('/l/?kh=-1&uddg=', '') - searchengine = searchengine.replace('SEARCHTEXT', '{}') + searchengine = urllib.unquote(re.search("url=[^']+", r.text).group(0)) + searchengine = searchengine.replace('url=', '') + searchengine = searchengine.replace('/l/?kh=-1&uddg=', '') + searchengine = searchengine.replace('SEARCHTEXT', '{}') - if os.getenv('QUTE_FIFO'): - with open(os.environ['QUTE_FIFO'], 'w') as fifo: - fifo.write('set searchengines %s %s' % (bang, searchengine)) - else: - print '%s %s' % (bang, searchengine) -except: - # fail silent - exit(1) + if os.getenv('QUTE_FIFO'): + with open(os.environ['QUTE_FIFO'], 'w') as fifo: + fifo.write('set searchengines %s %s' % (bang, searchengine)) + else: + print '%s %s' % (bang, searchengine) From 19966a9b9f00f4f0bc4e975910a90f308471f4c6 Mon Sep 17 00:00:00 2001 From: Nikolay Amiantov Date: Mon, 2 Jan 2017 18:49:14 +0300 Subject: [PATCH 136/561] proxy_from_url: fix PACFetcher construction --- qutebrowser/utils/urlutils.py | 2 +- tests/unit/utils/test_urlutils.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/qutebrowser/utils/urlutils.py b/qutebrowser/utils/urlutils.py index e70cc41fa..1199c73f4 100644 --- a/qutebrowser/utils/urlutils.py +++ b/qutebrowser/utils/urlutils.py @@ -614,7 +614,7 @@ def proxy_from_url(url): scheme = url.scheme() if scheme in ['pac+http', 'pac+https']: - return pac.PACFetcher + return pac.PACFetcher(url) types = { 'http': QNetworkProxy.HttpProxy, diff --git a/tests/unit/utils/test_urlutils.py b/tests/unit/utils/test_urlutils.py index 49bc85f07..0db43a0e6 100644 --- a/tests/unit/utils/test_urlutils.py +++ b/tests/unit/utils/test_urlutils.py @@ -765,7 +765,7 @@ class TestProxyFromUrl: @pytest.mark.parametrize('scheme', ['pac+http', 'pac+https']) def test_proxy_from_url_pac(self, scheme): fetcher = urlutils.proxy_from_url(QUrl('{}://foo'.format(scheme))) - assert fetcher is pac.PACFetcher + assert isinstance(fetcher, pac.PACFetcher) @pytest.mark.parametrize('url, exception', [ ('blah', urlutils.InvalidProxyTypeError), From aec002fa29ec4f54e986848f0f4876060653330b Mon Sep 17 00:00:00 2001 From: Nikolay Amiantov Date: Mon, 2 Jan 2017 18:49:19 +0300 Subject: [PATCH 137/561] Add more tests for PAC --- tests/unit/browser/webkit/network/test_pac.py | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/tests/unit/browser/webkit/network/test_pac.py b/tests/unit/browser/webkit/network/test_pac.py index ebb081aff..af03c9859 100644 --- a/tests/unit/browser/webkit/network/test_pac.py +++ b/tests/unit/browser/webkit/network/test_pac.py @@ -125,6 +125,47 @@ def test_invalid_port(): res.resolve(QNetworkProxyQuery(QUrl("https://example.com/test"))) +def test_no_function(): + with pytest.raises(pac.EvalProxyError): + pac.PACResolver("") + + +def test_fail_eval(): + with pytest.raises(pac.EvalProxyError): + pac.PACResolver("{") + + +@pytest.mark.parametrize("value", [ + "", + "DIRECT FOO", + "PROXY", + "SOCKS", + "FOOBAR", +]) +def test_fail_parse(value): + test_str_f = """ + function FindProxyForURL(domain, host) {{ + return "{}"; + }} + """ + + res = pac.PACResolver(test_str_f.format(value)) + with pytest.raises(pac.ParseProxyError): + res.resolve(QNetworkProxyQuery(QUrl("https://example.com/test"))) + + +def test_fail_return(): + test_str = """ + function FindProxyForURL(domain, host) { + return null; + } + """ + + res = pac.PACResolver(test_str) + with pytest.raises(pac.EvalProxyError): + res.resolve(QNetworkProxyQuery(QUrl("https://example.com/test"))) + + # See https://github.com/The-Compiler/qutebrowser/pull/1891#issuecomment-259222615 try: From 8c5ad7d46d18489f2caaf180f49afde74631a7f3 Mon Sep 17 00:00:00 2001 From: Daniel Schadt Date: Wed, 4 Jan 2017 15:18:56 +0100 Subject: [PATCH 138/561] use download prompt for mhtml downloads Fixes #2204 We didn't previously use PromptMode.download for MHTML download prompts to avoid dealing with thinks like "Open download", but the new download prompt is just way better than the old, which justifies the extra work. This means that MHTML downloads can now also be opened directly. --- qutebrowser/browser/commands.py | 44 ++++++++--------- qutebrowser/browser/downloads.py | 15 +++++- qutebrowser/browser/webkit/mhtml.py | 77 +++++++++++++++++++++++------ 3 files changed, 96 insertions(+), 40 deletions(-) diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index e8f15720d..dbbe0a8c0 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -1302,26 +1302,21 @@ class CommandDispatcher: # FIXME:qtwebengine do this with the QtWebEngine download manager? download_manager = objreg.get('qtnetwork-download-manager', scope='window', window=self._win_id) + target = None + if dest is not None: + target = downloads.FileDownloadTarget(dest) + if url: if mhtml_: raise cmdexc.CommandError("Can only download the current page" " as mhtml.") url = urlutils.qurl_from_user_input(url) urlutils.raise_cmdexc_if_invalid(url) - if dest is None: - target = None - else: - target = downloads.FileDownloadTarget(dest) download_manager.get(url, target=target) elif mhtml_: self._download_mhtml(dest) else: qnam = self._current_widget().networkaccessmanager() - - if dest is None: - target = None - else: - target = downloads.FileDownloadTarget(dest) download_manager.get(self._current_url(), qnam=qnam, target=target) def _download_mhtml(self, dest=None): @@ -1334,22 +1329,23 @@ class CommandDispatcher: if tab.backend == usertypes.Backend.QtWebEngine: raise cmdexc.CommandError("Download --mhtml is not implemented " "with QtWebEngine yet") - - if dest is None: - suggested_fn = self._current_title() + ".mht" - suggested_fn = utils.sanitize_filename(suggested_fn) - - filename = downloads.immediate_download_path() - if filename is not None: - mhtml.start_download_checked(filename, tab=tab) - else: - question = downloads.get_filename_question( - suggested_filename=suggested_fn, url=tab.url(), parent=tab) - question.answered.connect(functools.partial( - mhtml.start_download_checked, tab=tab)) - message.global_bridge.ask(question, blocking=False) - else: + if dest is not None: mhtml.start_download_checked(dest, tab=tab) + return + + suggested_fn = self._current_title() + ".mht" + suggested_fn = utils.sanitize_filename(suggested_fn) + + filename = downloads.immediate_download_path() + if filename is not None: + target = downloads.FileDownloadTarget(filename) + mhtml.start_download_checked(target, tab=tab) + else: + question = downloads.get_filename_question( + suggested_filename=suggested_fn, url=tab.url(), parent=tab) + question.answered.connect(functools.partial( + mhtml.start_download_checked, tab=tab)) + message.global_bridge.ask(question, blocking=False) @cmdutils.register(instance='command-dispatcher', scope='window') def view_source(self): diff --git a/qutebrowser/browser/downloads.py b/qutebrowser/browser/downloads.py index 548313b93..a899fc789 100644 --- a/qutebrowser/browser/downloads.py +++ b/qutebrowser/browser/downloads.py @@ -158,7 +158,7 @@ def get_filename_question(*, suggested_filename, url, parent=None): q.title = "Save file to:" q.text = "Please enter a location for {}".format( html.escape(url.toDisplayString())) - q.mode = usertypes.PromptMode.text + q.mode = usertypes.PromptMode.download q.completed.connect(q.deleteLater) q.default = _path_suggestion(suggested_filename) return q @@ -197,6 +197,9 @@ class FileDownloadTarget(_DownloadTarget): def suggested_filename(self): return os.path.basename(self.filename) + def __str__(self): + return self.filename + class FileObjDownloadTarget(_DownloadTarget): @@ -216,6 +219,12 @@ class FileObjDownloadTarget(_DownloadTarget): except AttributeError: raise NoFilenameError + def __str__(self): + try: + return 'file object at {}'.format(self.fileobj.name) + except AttributeError: + return 'anonymous file object' + class OpenFileDownloadTarget(_DownloadTarget): @@ -234,6 +243,9 @@ class OpenFileDownloadTarget(_DownloadTarget): def suggested_filename(self): raise NoFilenameError + def __str__(self): + return 'temporary file' + class DownloadItemStats(QObject): @@ -780,7 +792,6 @@ class AbstractDownloadManager(QObject): def _init_filename_question(self, question, download): """Set up an existing filename question with a download.""" - question.mode = usertypes.PromptMode.download question.answered.connect(download.set_target) question.cancelled.connect(download.cancel) download.cancelled.connect(question.abort) diff --git a/qutebrowser/browser/webkit/mhtml.py b/qutebrowser/browser/webkit/mhtml.py index fbfeafef4..438db7b28 100644 --- a/qutebrowser/browser/webkit/mhtml.py +++ b/qutebrowser/browser/webkit/mhtml.py @@ -35,10 +35,12 @@ import email.message import quopri from PyQt5.QtCore import QUrl +from PyQt5.QtGui import QDesktopServices from qutebrowser.browser import downloads from qutebrowser.browser.webkit import webkitelem from qutebrowser.utils import log, objreg, message, usertypes, utils, urlutils +from qutebrowser.config import config _File = collections.namedtuple('_File', ['content', 'content_type', 'content_location', @@ -242,7 +244,7 @@ class _Downloader: Attributes: tab: The AbstractTab which contains the website that will be saved. - dest: Destination filename. + target: DownloadTarget where the file should be downloaded to. writer: The MHTMLWriter object which is used to save the page. loaded_urls: A set of QUrls of finished asset downloads. pending_downloads: A set of unfinished (url, DownloadItem) tuples. @@ -252,9 +254,9 @@ class _Downloader: _win_id: The window this downloader belongs to. """ - def __init__(self, tab, dest): + def __init__(self, tab, target): self.tab = tab - self.dest = dest + self.target = target self.writer = None self.loaded_urls = {tab.url()} self.pending_downloads = set() @@ -462,14 +464,57 @@ class _Downloader: return self._finished_file = True log.downloads.debug("All assets downloaded, ready to finish off!") + + if isinstance(self.target, downloads.FileDownloadTarget): + fobj = open(self.target.filename, 'wb') + elif isinstance(self.target, downloads.FileObjDownloadTarget): + fobj = self.target.fileobj + elif isinstance(self.target, downloads.OpenFileDownloadTarget): + try: + fobj = downloads.temp_download_manager.get_tmpfile( + self.tab.title() + '.mht') + except OSError as exc: + msg = "Download error: {}".format(exc) + message.error(msg) + return + else: + raise ValueError("Invalid DownloadTarget given") + try: - with open(self.dest, 'wb') as file_output: - self.writer.write_to(file_output) + with fobj: + self.writer.write_to(fobj) except OSError as error: message.error("Could not save file: {}".format(error)) return log.downloads.debug("File successfully written.") - message.info("Page saved as {}".format(self.dest)) + message.info("Page saved as {}".format(self.target)) + + if isinstance(self.target, downloads.OpenFileDownloadTarget): + filename = fobj.name + # the default program to open downloads with - will be empty string + # if we want to use the default + override = config.get('general', 'default-open-dispatcher') + cmdline = self.target.cmdline + + # precedence order: cmdline > default-open-dispatcher > openUrl + if cmdline is None and not override: + log.downloads.debug("Opening {} with the system application" + .format(filename)) + url = QUrl.fromLocalFile(filename) + QDesktopServices.openUrl(url) + return + + if cmdline is None and override: + cmdline = override + + cmd, *args = shlex.split(cmdline) + args = [arg.replace('{}', filename) for arg in args] + if '{}' not in cmdline: + args.append(filename) + log.downloads.debug("Opening {} with {}" + .format(filename, [cmd] + args)) + proc = guiprocess.GUIProcess(what='download') + proc.start_detached(cmd, args) def _collect_zombies(self): """Collect done downloads and add their data to the MHTML file. @@ -501,26 +546,29 @@ class _NoCloseBytesIO(io.BytesIO): super().close() -def _start_download(dest, tab): +def _start_download(target, tab): """Start downloading the current page and all assets to an MHTML file. This will overwrite dest if it already exists. Args: - dest: The filename where the resulting file should be saved. + target: The DownloadTarget where the resulting file should be saved. tab: Specify the tab whose page should be loaded. """ - loader = _Downloader(tab, dest) + loader = _Downloader(tab, target) loader.run() -def start_download_checked(dest, tab): +def start_download_checked(target, tab): """First check if dest is already a file, then start the download. Args: - dest: The filename where the resulting file should be saved. + target: The DownloadTarget where the resulting file should be saved. tab: Specify the tab whose page should be loaded. """ + if not isinstance(target, downloads.FileDownloadTarget): + _start_download(target, tab) + return # The default name is 'page title.mht' title = tab.title() default_name = utils.sanitize_filename(title + '.mht') @@ -528,7 +576,7 @@ def start_download_checked(dest, tab): # Remove characters which cannot be expressed in the file system encoding encoding = sys.getfilesystemencoding() default_name = utils.force_encoding(default_name, encoding) - dest = utils.force_encoding(dest, encoding) + dest = utils.force_encoding(target.filename, encoding) dest = os.path.expanduser(dest) @@ -549,8 +597,9 @@ def start_download_checked(dest, tab): message.error("Directory {} does not exist.".format(folder)) return + target = downloads.FileDownloadTarget(path) if not os.path.isfile(path): - _start_download(path, tab=tab) + _start_download(target, tab=tab) return q = usertypes.Question() @@ -560,5 +609,5 @@ def start_download_checked(dest, tab): html.escape(path)) q.completed.connect(q.deleteLater) q.answered_yes.connect(functools.partial( - _start_download, path, tab=tab)) + _start_download, target, tab=tab)) message.global_bridge.ask(q, blocking=False) From 4fdd3cd7616b2c07ea0dc7014a7ea537f4cf583e Mon Sep 17 00:00:00 2001 From: Daniel Schadt Date: Wed, 4 Jan 2017 15:31:47 +0100 Subject: [PATCH 139/561] deduplicate download opening code --- qutebrowser/browser/downloads.py | 29 +----------------- qutebrowser/browser/webkit/mhtml.py | 28 +---------------- qutebrowser/utils/utils.py | 47 +++++++++++++++++++++++++++-- 3 files changed, 47 insertions(+), 57 deletions(-) diff --git a/qutebrowser/browser/downloads.py b/qutebrowser/browser/downloads.py index a899fc789..3791dfc60 100644 --- a/qutebrowser/browser/downloads.py +++ b/qutebrowser/browser/downloads.py @@ -20,7 +20,6 @@ """Shared QtWebKit/QtWebEngine code for downloads.""" import sys -import shlex import html import os.path import collections @@ -30,13 +29,11 @@ import tempfile import sip from PyQt5.QtCore import (pyqtSlot, pyqtSignal, Qt, QObject, QUrl, QModelIndex, QTimer, QAbstractListModel) -from PyQt5.QtGui import QDesktopServices from qutebrowser.commands import cmdexc, cmdutils from qutebrowser.config import config from qutebrowser.utils import (usertypes, standarddir, utils, message, log, qtutils) -from qutebrowser.misc import guiprocess ModelRole = usertypes.enum('ModelRole', ['item'], start=Qt.UserRole, @@ -532,31 +529,7 @@ class AbstractDownloadItem(QObject): if filename is None: # pragma: no cover log.downloads.error("No filename to open the download!") return - - # the default program to open downloads with - will be empty string - # if we want to use the default - override = config.get('general', 'default-open-dispatcher') - - # precedence order: cmdline > default-open-dispatcher > openUrl - - if cmdline is None and not override: - log.downloads.debug("Opening {} with the system application" - .format(filename)) - url = QUrl.fromLocalFile(filename) - QDesktopServices.openUrl(url) - return - - if cmdline is None and override: - cmdline = override - - cmd, *args = shlex.split(cmdline) - args = [arg.replace('{}', filename) for arg in args] - if '{}' not in cmdline: - args.append(filename) - log.downloads.debug("Opening {} with {}" - .format(filename, [cmd] + args)) - proc = guiprocess.GUIProcess(what='download') - proc.start_detached(cmd, args) + utils.open_file(filename, cmdline) def _ensure_can_set_filename(self, filename): """Make sure we can still set a filename.""" diff --git a/qutebrowser/browser/webkit/mhtml.py b/qutebrowser/browser/webkit/mhtml.py index 438db7b28..23b962b4f 100644 --- a/qutebrowser/browser/webkit/mhtml.py +++ b/qutebrowser/browser/webkit/mhtml.py @@ -35,12 +35,10 @@ import email.message import quopri from PyQt5.QtCore import QUrl -from PyQt5.QtGui import QDesktopServices from qutebrowser.browser import downloads from qutebrowser.browser.webkit import webkitelem from qutebrowser.utils import log, objreg, message, usertypes, utils, urlutils -from qutebrowser.config import config _File = collections.namedtuple('_File', ['content', 'content_type', 'content_location', @@ -490,31 +488,7 @@ class _Downloader: message.info("Page saved as {}".format(self.target)) if isinstance(self.target, downloads.OpenFileDownloadTarget): - filename = fobj.name - # the default program to open downloads with - will be empty string - # if we want to use the default - override = config.get('general', 'default-open-dispatcher') - cmdline = self.target.cmdline - - # precedence order: cmdline > default-open-dispatcher > openUrl - if cmdline is None and not override: - log.downloads.debug("Opening {} with the system application" - .format(filename)) - url = QUrl.fromLocalFile(filename) - QDesktopServices.openUrl(url) - return - - if cmdline is None and override: - cmdline = override - - cmd, *args = shlex.split(cmdline) - args = [arg.replace('{}', filename) for arg in args] - if '{}' not in cmdline: - args.append(filename) - log.downloads.debug("Opening {} with {}" - .format(filename, [cmd] + args)) - proc = guiprocess.GUIProcess(what='download') - proc.start_detached(cmd, args) + utils.open_file(fobj.name, self.target.cmdline) def _collect_zombies(self): """Collect done downloads and add their data to the MHTML file. diff --git a/qutebrowser/utils/utils.py b/qutebrowser/utils/utils.py index 2e5142511..afed444d4 100644 --- a/qutebrowser/utils/utils.py +++ b/qutebrowser/utils/utils.py @@ -29,14 +29,17 @@ import functools import contextlib import itertools import socket +import shlex -from PyQt5.QtCore import Qt -from PyQt5.QtGui import QKeySequence, QColor, QClipboard +from PyQt5.QtCore import Qt, QUrl +from PyQt5.QtGui import QKeySequence, QColor, QClipboard, QDesktopServices from PyQt5.QtWidgets import QApplication import pkg_resources import qutebrowser from qutebrowser.utils import qtutils, log +from qutebrowser.misc import guiprocess +from qutebrowser.config import config fake_clipboard = None @@ -825,3 +828,43 @@ def random_port(): port = sock.getsockname()[1] sock.close() return port + + +def open_file(filename, cmdline=None): + """Open the given file. + + If cmdline is not given, general->default-open-dispatcher is used. + If default-open-dispatcher is unset, the system's default application is + used. + + Args: + filename: The filename to open. + cmdline: The command to use as string. A `{}` is expanded to the + filename. None means to use the system's default application + or `default-open-dispatcher` if set. If no `{}` is found, the + filename is appended to the cmdline. + """ + # the default program to open downloads with - will be empty string + # if we want to use the default + override = config.get('general', 'default-open-dispatcher') + + # precedence order: cmdline > default-open-dispatcher > openUrl + + if cmdline is None and not override: + log.misc.debug("Opening {} with the system application" + .format(filename)) + url = QUrl.fromLocalFile(filename) + QDesktopServices.openUrl(url) + return + + if cmdline is None and override: + cmdline = override + + cmd, *args = shlex.split(cmdline) + args = [arg.replace('{}', filename) for arg in args] + if '{}' not in cmdline: + args.append(filename) + log.misc.debug("Opening {} with {}" + .format(filename, [cmd] + args)) + proc = guiprocess.GUIProcess(what='open-file') + proc.start_detached(cmd, args) From 6497bb5ace557a95a0f2a0063683c6811b31a77e Mon Sep 17 00:00:00 2001 From: Daniel Schadt Date: Wed, 4 Jan 2017 16:04:06 +0100 Subject: [PATCH 140/561] break cicular imports in utils --- qutebrowser/utils/utils.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/qutebrowser/utils/utils.py b/qutebrowser/utils/utils.py index afed444d4..681ae1dc8 100644 --- a/qutebrowser/utils/utils.py +++ b/qutebrowser/utils/utils.py @@ -38,8 +38,6 @@ import pkg_resources import qutebrowser from qutebrowser.utils import qtutils, log -from qutebrowser.misc import guiprocess -from qutebrowser.config import config fake_clipboard = None @@ -844,6 +842,11 @@ def open_file(filename, cmdline=None): or `default-open-dispatcher` if set. If no `{}` is found, the filename is appended to the cmdline. """ + # Import late to avoid circular imports: + # utils -> config -> configdata -> configtypes -> cmdutils -> command -> + # utils + from qutebrowser.misc import guiprocess + from qutebrowser.config import config # the default program to open downloads with - will be empty string # if we want to use the default override = config.get('general', 'default-open-dispatcher') From 69001111dadc142cc7d1357bb1af7b2df6dd6606 Mon Sep 17 00:00:00 2001 From: Daniel Schadt Date: Wed, 4 Jan 2017 16:32:25 +0100 Subject: [PATCH 141/561] actually use DownloadTarget for :download -m /path --- qutebrowser/browser/commands.py | 10 +++++----- qutebrowser/browser/webkit/mhtml.py | 3 ++- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index dbbe0a8c0..3eb73185a 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -1314,23 +1314,23 @@ class CommandDispatcher: urlutils.raise_cmdexc_if_invalid(url) download_manager.get(url, target=target) elif mhtml_: - self._download_mhtml(dest) + self._download_mhtml(target) else: qnam = self._current_widget().networkaccessmanager() download_manager.get(self._current_url(), qnam=qnam, target=target) - def _download_mhtml(self, dest=None): + def _download_mhtml(self, target=None): """Download the current page as an MHTML file, including all assets. Args: - dest: The file path to write the download to. + target: The download target for the file. """ tab = self._current_widget() if tab.backend == usertypes.Backend.QtWebEngine: raise cmdexc.CommandError("Download --mhtml is not implemented " "with QtWebEngine yet") - if dest is not None: - mhtml.start_download_checked(dest, tab=tab) + if target is not None: + mhtml.start_download_checked(target, tab=tab) return suggested_fn = self._current_title() + ".mht" diff --git a/qutebrowser/browser/webkit/mhtml.py b/qutebrowser/browser/webkit/mhtml.py index 23b962b4f..97da148a0 100644 --- a/qutebrowser/browser/webkit/mhtml.py +++ b/qutebrowser/browser/webkit/mhtml.py @@ -476,7 +476,8 @@ class _Downloader: message.error(msg) return else: - raise ValueError("Invalid DownloadTarget given") + raise ValueError("Invalid DownloadTarget given: {!r}" + .format(self.target)) try: with fobj: From bd5274af5a35c6f51b40d19fc045503e66b312f5 Mon Sep 17 00:00:00 2001 From: Daniel Schadt Date: Wed, 4 Jan 2017 17:56:56 +0100 Subject: [PATCH 142/561] fix tests --- qutebrowser/browser/downloads.py | 2 +- qutebrowser/utils/utils.py | 2 +- tests/end2end/features/downloads.feature | 4 ++-- tests/end2end/test_invocations.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/qutebrowser/browser/downloads.py b/qutebrowser/browser/downloads.py index 3791dfc60..378fe9a19 100644 --- a/qutebrowser/browser/downloads.py +++ b/qutebrowser/browser/downloads.py @@ -27,7 +27,7 @@ import functools import tempfile import sip -from PyQt5.QtCore import (pyqtSlot, pyqtSignal, Qt, QObject, QUrl, QModelIndex, +from PyQt5.QtCore import (pyqtSlot, pyqtSignal, Qt, QObject, QModelIndex, QTimer, QAbstractListModel) from qutebrowser.commands import cmdexc, cmdutils diff --git a/qutebrowser/utils/utils.py b/qutebrowser/utils/utils.py index 681ae1dc8..fbed4ede3 100644 --- a/qutebrowser/utils/utils.py +++ b/qutebrowser/utils/utils.py @@ -855,7 +855,7 @@ def open_file(filename, cmdline=None): if cmdline is None and not override: log.misc.debug("Opening {} with the system application" - .format(filename)) + .format(filename)) url = QUrl.fromLocalFile(filename) QDesktopServices.openUrl(url) return diff --git a/tests/end2end/features/downloads.feature b/tests/end2end/features/downloads.feature index 1b0ff1170..6aebab952 100644 --- a/tests/end2end/features/downloads.feature +++ b/tests/end2end/features/downloads.feature @@ -208,11 +208,11 @@ Feature: Downloading things from a website. When I set storage -> prompt-download-directory to true And I open html And I run :download --mhtml - And I wait for "Asking question text='Please enter a location for http://localhost:*/html' title='Save file to:'>, *" in the log + And I wait for "Asking question text='Please enter a location for http://localhost:*/html' title='Save file to:'>, *" in the log And I run :prompt-accept And I wait for "File successfully written." in the log And I run :download --mhtml - And I wait for "Asking question text='Please enter a location for http://localhost:*/html' title='Save file to:'>, *" in the log + And I wait for "Asking question text='Please enter a location for http://localhost:*/html' title='Save file to:'>, *" in the log And I run :prompt-accept And I wait for "Asking question text='* already exists. Overwrite?' title='Overwrite existing file?'>, *" in the log And I run :prompt-accept yes diff --git a/tests/end2end/test_invocations.py b/tests/end2end/test_invocations.py index d85e2a467..6d1bd6a27 100644 --- a/tests/end2end/test_invocations.py +++ b/tests/end2end/test_invocations.py @@ -96,7 +96,7 @@ def test_ascii_locale(request, httpbin, tmpdir, quteproc_new): .format(sys.executable)) quteproc_new.wait_for(category='downloads', message='Download ä-issue908.bin finished') - quteproc_new.wait_for(category='downloads', + quteproc_new.wait_for(category='misc', message='Opening * with [*python*]') assert len(tmpdir.listdir()) == 1 From fa539acd79604ad7fe0fd51273ec01308372e6f7 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 5 Jan 2017 13:35:12 +0100 Subject: [PATCH 143/561] test/vulture reqs: Update vulture to 0.12 --- misc/requirements/requirements-tests.txt | 2 +- misc/requirements/requirements-vulture.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index b3c537634..54826d0e8 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -32,5 +32,5 @@ pytest-warnings==0.2.0 pytest-xvfb==1.0.0 PyVirtualDisplay==0.2.1 six==1.10.0 -vulture==0.11 +vulture==0.12 Werkzeug==0.11.15 diff --git a/misc/requirements/requirements-vulture.txt b/misc/requirements/requirements-vulture.txt index 88a7b8eed..32c9c3718 100644 --- a/misc/requirements/requirements-vulture.txt +++ b/misc/requirements/requirements-vulture.txt @@ -1,3 +1,3 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -vulture==0.11 +vulture==0.12 From 536c76848e9b5d9c58b957884a53e678a9d39970 Mon Sep 17 00:00:00 2001 From: Daniel Schadt Date: Thu, 5 Jan 2017 19:02:28 +0100 Subject: [PATCH 144/561] add a test for opening mhtml downloads --- tests/end2end/features/downloads.feature | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/end2end/features/downloads.feature b/tests/end2end/features/downloads.feature index 6aebab952..d933d32fd 100644 --- a/tests/end2end/features/downloads.feature +++ b/tests/end2end/features/downloads.feature @@ -219,6 +219,15 @@ Feature: Downloading things from a website. And I wait for "File successfully written." in the log Then no crash should happen + @qtwebengine_todo: :download --mhtml is not implemented yet + Scenario: Opening a mhtml download directly + When I set storage -> prompt-download-directory to true + And I open html + And I run :download --mhtml + And I wait for the download prompt for "*" + And I directly open the download + Then "Opening *.mht* with [*python*]" should be logged + ## :download-cancel Scenario: Cancelling a download From 2986f7b615ed8d036b04d02a366b7a2fd36690cc Mon Sep 17 00:00:00 2001 From: Daniel Schadt Date: Fri, 6 Jan 2017 13:32:46 +0100 Subject: [PATCH 145/561] add tests for utils.open_file --- tests/unit/utils/test_utils.py | 43 ++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/tests/unit/utils/test_utils.py b/tests/unit/utils/test_utils.py index 73252ddc4..c64d00e4d 100644 --- a/tests/unit/utils/test_utils.py +++ b/tests/unit/utils/test_utils.py @@ -27,6 +27,7 @@ import logging import functools import collections import socket +import re from PyQt5.QtCore import Qt from PyQt5.QtGui import QColor, QClipboard @@ -984,3 +985,45 @@ def test_random_port(): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.bind(('localhost', port)) sock.close() + + +class TestOpenFile: + + def test_cmdline_without_argument(self, caplog, config_stub): + config_stub.data = {'general': {'default-open-dispatcher': ''}} + cmdline = '{} -c pass'.format(sys.executable) + utils.open_file('/foo/bar', cmdline) + result = caplog.records[0].message + assert re.match( + r'Opening /foo/bar with \[.*python.*/foo/bar.*\]', result) + + def test_cmdline_with_argument(self, caplog, config_stub): + config_stub.data = {'general': {'default-open-dispatcher': ''}} + cmdline = '{} -c pass {{}} raboof'.format(sys.executable) + utils.open_file('/foo/bar', cmdline) + result = caplog.records[0].message + assert re.match( + r"Opening /foo/bar with \[.*python.*/foo/bar.*'raboof'\]", result) + + def test_setting_override(self, caplog, config_stub): + cmdline = '{} -c pass'.format(sys.executable) + config_stub.data = {'general': {'default-open-dispatcher': cmdline}} + utils.open_file('/foo/bar') + result = caplog.records[0].message + assert re.match( + r"Opening /foo/bar with \[.*python.*/foo/bar.*\]", result) + + def test_system_default_application(self, caplog, config_stub, + monkeypatch): + self.open_url_called = False + config_stub.data = {'general': {'default-open-dispatcher': ''}} + monkeypatch.setattr('PyQt5.QtGui.QDesktopServices.openUrl', + self.mock_open_url) + utils.open_file('/foo/bar') + result = caplog.records[0].message + assert re.match( + r"Opening /foo/bar with the system application", result) + assert self.open_url_called + + def mock_open_url(self, url): + self.open_url_called = True From bb135a00e6a714b91c47a76656e0dec982be3f0a Mon Sep 17 00:00:00 2001 From: Daniel Schadt Date: Fri, 6 Jan 2017 13:53:05 +0100 Subject: [PATCH 146/561] fix lint --- tests/unit/utils/test_utils.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/unit/utils/test_utils.py b/tests/unit/utils/test_utils.py index c64d00e4d..a765cd977 100644 --- a/tests/unit/utils/test_utils.py +++ b/tests/unit/utils/test_utils.py @@ -989,6 +989,9 @@ def test_random_port(): class TestOpenFile: + def __init__(self): + self.open_url_called = False + def test_cmdline_without_argument(self, caplog, config_stub): config_stub.data = {'general': {'default-open-dispatcher': ''}} cmdline = '{} -c pass'.format(sys.executable) @@ -1015,7 +1018,6 @@ class TestOpenFile: def test_system_default_application(self, caplog, config_stub, monkeypatch): - self.open_url_called = False config_stub.data = {'general': {'default-open-dispatcher': ''}} monkeypatch.setattr('PyQt5.QtGui.QDesktopServices.openUrl', self.mock_open_url) From dd7b16c8c385ea7745024582100f85293167213c Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 6 Jan 2017 23:54:15 +0100 Subject: [PATCH 147/561] test requirements: Update click to 6.7 --- misc/requirements/requirements-tests.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index 54826d0e8..e84161a20 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -2,7 +2,7 @@ beautifulsoup4==4.5.3 CherryPy==8.7.0 -click==6.6 +click==6.7 coverage==4.3.1 decorator==4.0.10 EasyProcess==0.2.3 From 3a1ecad1f32b679c7086527140597505be601816 Mon Sep 17 00:00:00 2001 From: Kevin Wang Date: Sat, 7 Jan 2017 00:45:06 -0500 Subject: [PATCH 148/561] Clear keychain on in addition to . --- qutebrowser/config/configdata.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index 3b71c6044..212999ff6 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -1543,7 +1543,7 @@ KEY_DATA = collections.OrderedDict([ ])), ('normal', collections.OrderedDict([ - ('clear-keychain ;; search', ['']), + ('clear-keychain ;; search', ['', '']), ('set-cmd-text -s :open', ['o']), ('set-cmd-text :open {url:pretty}', ['go']), ('set-cmd-text -s :open -t', ['O']), From 16c8c1b1899b97da446343162969f38fae6c6525 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 7 Jan 2017 12:21:22 +0100 Subject: [PATCH 149/561] Document how webelem.js works --- qutebrowser/javascript/webelem.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/qutebrowser/javascript/webelem.js b/qutebrowser/javascript/webelem.js index 84d38b348..99c8edf80 100644 --- a/qutebrowser/javascript/webelem.js +++ b/qutebrowser/javascript/webelem.js @@ -17,6 +17,23 @@ * along with qutebrowser. If not, see . */ +/** + * The connection for web elements between Python and Javascript works like + * this: + * + * - Python calls into Javascript and invokes a function to find elements (like + * find_all, focus_element, element_at_pos or element_by_id). + * - Javascript gets the requested element, and calls serialize_elem on it. + * - serialize_elem saves the javascript element object in "elements", gets some + * attributes from the element, and assigns an ID (index into 'elements') to + * it. + * - Python gets this information and constructs a Python wrapper object with + * the information it got right away, and the ID. + * - When Python wants to modify an element, it calls javascript again with the + * element ID. + * - Javascript gets the element from the elements array, and modifies it. + */ + "use strict"; window._qutebrowser.webelem = (function() { From 90b17610ea6b36b2f00aed74b7b4fb06c985f54e Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 7 Jan 2017 21:29:37 +0100 Subject: [PATCH 150/561] Update authors --- README.asciidoc | 1 + 1 file changed, 1 insertion(+) diff --git a/README.asciidoc b/README.asciidoc index 5fdb2c736..bdc4065c0 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -261,6 +261,7 @@ Contributors, sorted by the number of commits in descending order: * Matthias Lisin * Marcel Schilling * Lazlow Carmichael +* Kevin Wang * Ján Kobezda * Johannes Martinsson * Jean-Christophe Petkovich From 61e598552ee415360c98dd1b1ace4ed188c1fefe Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 8 Jan 2017 15:36:51 +0100 Subject: [PATCH 151/561] eslint: Turn off prefer-destructuring --- qutebrowser/javascript/.eslintrc.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/qutebrowser/javascript/.eslintrc.yaml b/qutebrowser/javascript/.eslintrc.yaml index 756cc362e..ef7ed00b5 100644 --- a/qutebrowser/javascript/.eslintrc.yaml +++ b/qutebrowser/javascript/.eslintrc.yaml @@ -37,3 +37,4 @@ rules: no-warning-comments: "off" max-len: ["error", {"ignoreUrls": true}] capitalized-comments: "off" + prefer-destructuring: "off" From 2127fd2432089d6cab36c9495e8ad7b50f734cbe Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 8 Jan 2017 15:39:21 +0100 Subject: [PATCH 152/561] requirements: Update jinja2 to 2.9.2 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 6481a5b5a..7a9242789 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ colorama==0.3.7 cssutils==1.0.1 -Jinja2==2.8.1 +Jinja2==2.9.2 MarkupSafe==0.23 Pygments==2.1.3 pyPEG2==2.15.2 From 2d364f43cf8283d3802f9ab00b09208018e0aa8c Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 8 Jan 2017 22:15:39 +0100 Subject: [PATCH 153/561] Ignore Python 3.6 warnings in dependencies --- qutebrowser/utils/log.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/qutebrowser/utils/log.py b/qutebrowser/utils/log.py index 07ec78741..91593e569 100644 --- a/qutebrowser/utils/log.py +++ b/qutebrowser/utils/log.py @@ -200,6 +200,22 @@ def _init_py_warnings(): """Initialize Python warning handling.""" warnings.simplefilter('default') warnings.filterwarnings('ignore', module='pdb', category=ResourceWarning) + # https://github.com/pallets/jinja/pull/628 + warnings.filterwarnings('ignore', module=r'jinja2\.filters', + category=DeprecationWarning, + message='Flags not at the start of the expression') + # https://bitbucket.org/birkenfeld/pygments-main/issues/1314/ + warnings.filterwarnings('ignore', module=r'pygments\.util', + category=DeprecationWarning, + message='Flags not at the start of the expression') + # https://github.com/pallets/jinja/issues/646 + warnings.filterwarnings('ignore', module='jinja2\..*', + category=DeprecationWarning, + message='invalid escape sequence') + # https://bitbucket.org/fdik/pypeg/commits/dd15ca462b532019c0a3be1d39b8ee2f3fa32f4e + warnings.filterwarnings('ignore', module='pypeg2', + category=DeprecationWarning, + message='invalid escape sequence') @contextlib.contextmanager From 7711e07b0c459826a84ac919d94a7da0ec6bed28 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 10 Jan 2017 10:29:01 +0100 Subject: [PATCH 154/561] flake8 requirements: Update pyflakes to 1.5.0 --- misc/requirements/requirements-flake8.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-flake8.txt b/misc/requirements/requirements-flake8.txt index 3b723d2c0..0b4fd03c9 100644 --- a/misc/requirements/requirements-flake8.txt +++ b/misc/requirements/requirements-flake8.txt @@ -17,6 +17,6 @@ packaging==16.8 pep8-naming==0.4.1 pycodestyle==2.2.0 pydocstyle==1.1.1 -pyflakes==1.4.0 +pyflakes==1.5.0 pyparsing==2.1.10 six==1.10.0 From 2b48ee14c48addb2d02ddf9129664a6809e049ee Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 10 Jan 2017 10:30:26 +0100 Subject: [PATCH 155/561] test requirements: Update CherryPy to 8.8.0 --- misc/requirements/requirements-tests.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index e84161a20..5c49464f5 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -1,7 +1,7 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py beautifulsoup4==4.5.3 -CherryPy==8.7.0 +CherryPy==8.8.0 click==6.7 coverage==4.3.1 decorator==4.0.10 From 5ede2c64179e6d0047961476993e9a80fe4a1b46 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 10 Jan 2017 10:32:30 +0100 Subject: [PATCH 156/561] requirements: Update jinja to 2.9.4 --- misc/requirements/requirements-tests.txt | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index 5c49464f5..0114cfadf 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -11,7 +11,7 @@ glob2==0.5 httpbin==0.5.0 hypothesis==3.6.1 itsdangerous==0.24 -# Jinja2==2.8.1 +# Jinja2==2.9.4 Mako==1.0.6 # MarkupSafe==0.23 parse==1.6.6 diff --git a/requirements.txt b/requirements.txt index 7a9242789..bd2277623 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ colorama==0.3.7 cssutils==1.0.1 -Jinja2==2.9.2 +Jinja2==2.9.4 MarkupSafe==0.23 Pygments==2.1.3 pyPEG2==2.15.2 From ea56ded7fceedf708318d6983bfabe1e66d979cd Mon Sep 17 00:00:00 2001 From: Daniel Schadt Date: Tue, 10 Jan 2017 14:14:03 +0100 Subject: [PATCH 157/561] fix TestOpenFile pytest doesn't like test classes which define __init__, and pylint doesn't like attributes defined outside __init__. We can disable pylint's check, but we can't force pytest to accept our test class... --- tests/unit/utils/test_utils.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/unit/utils/test_utils.py b/tests/unit/utils/test_utils.py index a765cd977..ef677f120 100644 --- a/tests/unit/utils/test_utils.py +++ b/tests/unit/utils/test_utils.py @@ -989,9 +989,6 @@ def test_random_port(): class TestOpenFile: - def __init__(self): - self.open_url_called = False - def test_cmdline_without_argument(self, caplog, config_stub): config_stub.data = {'general': {'default-open-dispatcher': ''}} cmdline = '{} -c pass'.format(sys.executable) @@ -1018,6 +1015,8 @@ class TestOpenFile: def test_system_default_application(self, caplog, config_stub, monkeypatch): + # pylint: disable=attribute-defined-outside-init + self.open_url_called = False config_stub.data = {'general': {'default-open-dispatcher': ''}} monkeypatch.setattr('PyQt5.QtGui.QDesktopServices.openUrl', self.mock_open_url) @@ -1028,4 +1027,5 @@ class TestOpenFile: assert self.open_url_called def mock_open_url(self, url): + # pylint: disable=attribute-defined-outside-init self.open_url_called = True From db0ac07c15ede3e6db04aa96adafd17c74862d2e Mon Sep 17 00:00:00 2001 From: Akselmo Date: Wed, 11 Jan 2017 20:12:42 +0200 Subject: [PATCH 158/561] Explanation about quickmarks and bookmarks to FAQ I was too confused about what was the difference between quickmarks and bookmarks #2226 but I think I figured it out. Here's my proposal for the FAQ. Now please bear with me if I'm doing something wrong, I'm new to Github and still learning things. --- FAQ.asciidoc | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/FAQ.asciidoc b/FAQ.asciidoc index 293f76216..5459a63cf 100644 --- a/FAQ.asciidoc +++ b/FAQ.asciidoc @@ -112,6 +112,18 @@ How do I use qutebrowser with mutt?:: text/html; mv %s %s.html && qutebrowser %s.html >/dev/null 2>/dev/null; needsterminal; ---- +What is the difference between bookmarks and quickmarks?:: + Bookmarks will always use the title of the website as their name, but with quickmarks + you can set your own title. ++ +For example, if you bookmark multiple food recipe websites and use `:open`, +you have to type the title or address of the website. ++ +When using quickmark, you can give them all names, like +`foodrecipes1`, `foodrecipes2` and so on. When you type +`:open foodrecipes`, you will see a list of all the food recipe sites, +without having to remember the exact website title or address. + == Troubleshooting Configuration not saved after modifying config.:: From 07460832b6b5d78a5f8d50da555ca57da947ff14 Mon Sep 17 00:00:00 2001 From: Daniel Schadt Date: Thu, 12 Jan 2017 15:38:38 +0100 Subject: [PATCH 159/561] fix open-file tests on windows Windows filenames have backslashes, so we need to escape them, otherwise shlex.split will delete them. Also, we can't prodive our own executable on frozen tests. --- tests/unit/utils/test_utils.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/tests/unit/utils/test_utils.py b/tests/unit/utils/test_utils.py index ef677f120..ef6d1e72c 100644 --- a/tests/unit/utils/test_utils.py +++ b/tests/unit/utils/test_utils.py @@ -28,6 +28,7 @@ import functools import collections import socket import re +import shlex from PyQt5.QtCore import Qt from PyQt5.QtGui import QColor, QClipboard @@ -989,24 +990,30 @@ def test_random_port(): class TestOpenFile: + @pytest.mark.not_frozen def test_cmdline_without_argument(self, caplog, config_stub): config_stub.data = {'general': {'default-open-dispatcher': ''}} - cmdline = '{} -c pass'.format(sys.executable) + executable = shlex.quote(sys.executable) + cmdline = '{} -c pass'.format(executable) utils.open_file('/foo/bar', cmdline) result = caplog.records[0].message assert re.match( r'Opening /foo/bar with \[.*python.*/foo/bar.*\]', result) + @pytest.mark.not_frozen def test_cmdline_with_argument(self, caplog, config_stub): config_stub.data = {'general': {'default-open-dispatcher': ''}} - cmdline = '{} -c pass {{}} raboof'.format(sys.executable) + executable = shlex.quote(sys.executable) + cmdline = '{} -c pass {{}} raboof'.format(executable) utils.open_file('/foo/bar', cmdline) result = caplog.records[0].message assert re.match( r"Opening /foo/bar with \[.*python.*/foo/bar.*'raboof'\]", result) + @pytest.mark.not_frozen def test_setting_override(self, caplog, config_stub): - cmdline = '{} -c pass'.format(sys.executable) + executable = shlex.quote(sys.executable) + cmdline = '{} -c pass'.format(executable) config_stub.data = {'general': {'default-open-dispatcher': cmdline}} utils.open_file('/foo/bar') result = caplog.records[0].message From 2f3e671578a5b9e582a523e4516b9efb430f54b1 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 13 Jan 2017 17:59:03 +0100 Subject: [PATCH 160/561] Use a tmpdir subdir for download BDD tests --- tests/end2end/features/downloads.feature | 22 ++++++++++---------- tests/end2end/features/test_downloads_bdd.py | 22 ++++++++++++-------- 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/tests/end2end/features/downloads.feature b/tests/end2end/features/downloads.feature index 1b0ff1170..0cab862b5 100644 --- a/tests/end2end/features/downloads.feature +++ b/tests/end2end/features/downloads.feature @@ -184,7 +184,7 @@ Feature: Downloading things from a website. Then the error "Can only download the current page as mhtml." should be shown Scenario: :download with a directory which doesn't exist - When I run :download --dest (tmpdir)/somedir/filename http://localhost:(port)/ + When I run :download --dest (tmpdir)/downloads/somedir/filename http://localhost:(port)/ Then the error "Download error: No such file or directory" should be shown ## mhtml downloads @@ -402,7 +402,7 @@ Feature: Downloading things from a website. When I set storage -> prompt-download-directory to true And I set completion -> download-path-suggestion to path And I open data/downloads/download.bin without waiting - Then the download prompt should be shown with "(tmpdir)/" + Then the download prompt should be shown with "(tmpdir)/downloads/" Scenario: completion -> download-path-suggestion = filename When I set storage -> prompt-download-directory to true @@ -414,7 +414,7 @@ Feature: Downloading things from a website. When I set storage -> prompt-download-directory to true And I set completion -> download-path-suggestion to both And I open data/downloads/download.bin without waiting - Then the download prompt should be shown with "(tmpdir)/download.bin" + Then the download prompt should be shown with "(tmpdir)/downloads/download.bin" ## storage -> remember-download-directory @@ -424,19 +424,19 @@ Feature: Downloading things from a website. And I set storage -> remember-download-directory to true And I open data/downloads/download.bin without waiting And I wait for the download prompt for "*/download.bin" - And I run :prompt-accept (tmpdir)(dirsep)subdir + And I run :prompt-accept (tmpdir)(dirsep)downloads(dirsep)subdir And I open data/downloads/download2.bin without waiting - Then the download prompt should be shown with "(tmpdir)/subdir/download2.bin" + Then the download prompt should be shown with "(tmpdir)/downloads/subdir/download2.bin" Scenario: Not remembering the last download directory When I set storage -> prompt-download-directory to true And I set completion -> download-path-suggestion to both And I set storage -> remember-download-directory to false And I open data/downloads/download.bin without waiting - And I wait for the download prompt for "(tmpdir)/download.bin" - And I run :prompt-accept (tmpdir)(dirsep)subdir + And I wait for the download prompt for "(tmpdir)/downloads/download.bin" + And I run :prompt-accept (tmpdir)(dirsep)downloads(dirsep)subdir And I open data/downloads/download2.bin without waiting - Then the download prompt should be shown with "(tmpdir)/download2.bin" + Then the download prompt should be shown with "(tmpdir)/downloads/download2.bin" # https://github.com/The-Compiler/qutebrowser/issues/2173 @@ -446,12 +446,12 @@ Feature: Downloading things from a website. And I set storage -> remember-download-directory to true And I open data/downloads/download.bin without waiting And I wait for the download prompt for "*" - And I run :prompt-accept (tmpdir) + And I run :prompt-accept (tmpdir)/downloads And I open data/downloads/download.bin without waiting And I wait for the download prompt for "*" And I directly open the download And I open data/downloads/download.bin without waiting - Then the download prompt should be shown with "(tmpdir)/download.bin" + Then the download prompt should be shown with "(tmpdir)/downloads/download.bin" # Overwriting files @@ -520,7 +520,7 @@ Feature: Downloading things from a website. @posix Scenario: Downloading to unwritable destination When I set storage -> prompt-download-directory to false - And I run :download http://localhost:(port)/data/downloads/download.bin --dest (tmpdir)/unwritable + And I run :download http://localhost:(port)/data/downloads/download.bin --dest (tmpdir)/downloads/unwritable Then the error "Download error: Permission denied" should be shown Scenario: Downloading 20MB file diff --git a/tests/end2end/features/test_downloads_bdd.py b/tests/end2end/features/test_downloads_bdd.py index 753cfa75c..dbfb4341d 100644 --- a/tests/end2end/features/test_downloads_bdd.py +++ b/tests/end2end/features/test_downloads_bdd.py @@ -32,15 +32,17 @@ PROMPT_MSG = ("Asking question Date: Fri, 13 Jan 2017 18:04:04 +0100 Subject: [PATCH 161/561] Prevent using %2F as slash in a Content-Disposition header --- qutebrowser/browser/webengine/webenginedownloads.py | 3 +++ tests/end2end/features/downloads.feature | 8 ++++++++ tests/unit/browser/webengine/test_webenginedownloads.py | 1 + 3 files changed, 12 insertions(+) diff --git a/qutebrowser/browser/webengine/webenginedownloads.py b/qutebrowser/browser/webengine/webenginedownloads.py index 44b326d36..2baf27541 100644 --- a/qutebrowser/browser/webengine/webenginedownloads.py +++ b/qutebrowser/browser/webengine/webenginedownloads.py @@ -137,7 +137,10 @@ def _get_suggested_filename(path): """ filename = os.path.basename(path) filename = re.sub(r'\([0-9]+\)$', '', filename) + # https://bugreports.qt.io/browse/QTBUG-58155 filename = urllib.parse.unquote(filename) + # Doing basename a *second* time because there could be a %2F in there... + filename = os.path.basename(filename) return filename diff --git a/tests/end2end/features/downloads.feature b/tests/end2end/features/downloads.feature index 0cab862b5..44775e39a 100644 --- a/tests/end2end/features/downloads.feature +++ b/tests/end2end/features/downloads.feature @@ -118,6 +118,14 @@ Feature: Downloading things from a website. And I wait until the download is finished Then the downloaded file download with spaces.bin should exist + @qtwebkit_skip + Scenario: Downloading a file with evil content-disposition header + # Content-Disposition: download; filename=..%2Ffoo + When I open response-headers?Content-Disposition=download;%20filename%3D..%252Ffoo without waiting + And I wait until the download is finished + Then the downloaded file ../foo should not exist + And the downloaded file foo should exist + ## :download-retry Scenario: Retrying a failed download diff --git a/tests/unit/browser/webengine/test_webenginedownloads.py b/tests/unit/browser/webengine/test_webenginedownloads.py index afd6e85a2..ee5b8e22a 100644 --- a/tests/unit/browser/webengine/test_webenginedownloads.py +++ b/tests/unit/browser/webengine/test_webenginedownloads.py @@ -32,6 +32,7 @@ from qutebrowser.browser.webengine import webenginedownloads ('foo(a)', 'foo(a)'), ('foo1', 'foo1'), ('foo%20bar', 'foo bar'), + ('foo%2Fbar', 'bar'), ]) def test_get_suggested_filename(path, expected): assert webenginedownloads._get_suggested_filename(path) == expected From e887825aaa0f6419db5ede3c99a532005699c1c3 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 13 Jan 2017 18:07:56 +0100 Subject: [PATCH 162/561] Update changelog --- CHANGELOG.asciidoc | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index bbfbc204b..0e74a0f4c 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -14,8 +14,8 @@ 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.0 -------- +v0.10.0 (unreleased) +-------------------- Changed ~~~~~~~ @@ -23,6 +23,15 @@ Changed - Scrolling with the scrollwheel while holding shift now scrolls sideways +v0.9.1 +------ + +Fixed +~~~~~ + +- Prevent websites from downloading files to a location outside of the download + folder with QtWebEngine. + v0.9.0 ------ From 73f371a956754c14d35bb2e28a598db5bfb02f91 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 13 Jan 2017 18:09:42 +0100 Subject: [PATCH 163/561] Release v0.9.1 --- qutebrowser/__init__.py | 2 +- tests/unit/config/test_config.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/qutebrowser/__init__.py b/qutebrowser/__init__.py index 3fe6648e0..514254066 100644 --- a/qutebrowser/__init__.py +++ b/qutebrowser/__init__.py @@ -28,7 +28,7 @@ __copyright__ = "Copyright 2014-2016 Florian Bruhin (The Compiler)" __license__ = "GPL" __maintainer__ = __author__ __email__ = "mail@qutebrowser.org" -__version_info__ = (0, 9, 0) +__version_info__ = (0, 9, 1) __version__ = '.'.join(str(e) for e in __version_info__) __description__ = "A keyboard-driven, vim-like browser based on PyQt5 and QtWebKit." diff --git a/tests/unit/config/test_config.py b/tests/unit/config/test_config.py index b22057a7c..47beb3228 100644 --- a/tests/unit/config/test_config.py +++ b/tests/unit/config/test_config.py @@ -403,7 +403,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.9.0' + assert qutebrowser.__version__ == '0.9.1' @pytest.mark.parametrize('filename', os.listdir(os.path.join(os.path.dirname(__file__), 'old_configs')), From 22fb3c3042a02e6ea3a7d0a1624031b07611fb80 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 13 Jan 2017 18:25:26 +0100 Subject: [PATCH 164/561] tox: Switch to Python 3.6 --- tox.ini | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index b52550056..0ff46d232 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,7 @@ # and then run "tox" from this directory. [tox] -envlist = py34,py35-cov,misc,vulture,flake8,pylint,pyroma,check-manifest +envlist = py34,py35,py36-cov,misc,vulture,flake8,pylint,pyroma,check-manifest distshare = {toxworkdir} skipsdist = true @@ -21,6 +21,16 @@ commands = {envpython} scripts/link_pyqt.py --tox {envdir} {envpython} scripts/dev/run_pytest.py {posargs:tests} +[testenv:py36-cov] +basepython = python3.6 +setenv = {[testenv]setenv} +passenv = {[testenv]passenv} +deps = {[testenv]deps} +commands = + {envpython} scripts/link_pyqt.py --tox {envdir} + {envpython} scripts/dev/run_pytest.py --cov --cov-report xml --cov-report=html --cov-report= {posargs:tests} + {envpython} scripts/dev/check_coverage.py {posargs} + [testenv:py35-cov] basepython = python3.5 setenv = {[testenv]setenv} From 04dddc2475a0dea8b579237c0ff77b382dd9a118 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 13 Jan 2017 18:25:34 +0100 Subject: [PATCH 165/561] Fix backslash escape --- qutebrowser/utils/log.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutebrowser/utils/log.py b/qutebrowser/utils/log.py index 91593e569..d96eca9a3 100644 --- a/qutebrowser/utils/log.py +++ b/qutebrowser/utils/log.py @@ -209,7 +209,7 @@ def _init_py_warnings(): category=DeprecationWarning, message='Flags not at the start of the expression') # https://github.com/pallets/jinja/issues/646 - warnings.filterwarnings('ignore', module='jinja2\..*', + warnings.filterwarnings('ignore', module=r'jinja2\..*', category=DeprecationWarning, message='invalid escape sequence') # https://bitbucket.org/fdik/pypeg/commits/dd15ca462b532019c0a3be1d39b8ee2f3fa32f4e From 80372eb3f29444bc470baf1b445e4404f04b1130 Mon Sep 17 00:00:00 2001 From: Cosmin Popescu Date: Fri, 13 Jan 2017 18:57:55 +0100 Subject: [PATCH 166/561] stop using the commands handler --- qutebrowser/app.py | 2 +- qutebrowser/browser/browsertab.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/qutebrowser/app.py b/qutebrowser/app.py index da0d3cd55..ca0df1605 100644 --- a/qutebrowser/app.py +++ b/qutebrowser/app.py @@ -726,7 +726,7 @@ class Quitter: # Now we can hopefully quit without segfaults log.destroy.debug("Deferring QApplication::exit...") objreg.get('signal-handler').deactivate() - objreg.get('session-manager').session_delete('_autosave', force=True) + objreg.get('session-manager').delete('_autosave') # We use a singleshot timer to exit here to minimize the likelihood of # segfaults. QTimer.singleShot(0, functools.partial(qApp.exit, status)) diff --git a/qutebrowser/browser/browsertab.py b/qutebrowser/browser/browsertab.py index dc88efefc..58d210e01 100644 --- a/qutebrowser/browser/browsertab.py +++ b/qutebrowser/browser/browsertab.py @@ -686,7 +686,7 @@ class AbstractTab(QWidget): @pyqtSlot(bool) def _on_load_finished(self, ok): sess_manager = objreg.get('session-manager') - sess_manager.session_save('_autosave', quiet=True, force=True) + sess_manager.save('_autosave') if ok and not self._has_ssl_errors: if self.url().scheme() == 'https': self._set_load_status(usertypes.LoadStatus.success_https) From d9389ff0a79600880dfa89eabd3f87af91c4706d Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 17 Jan 2017 08:31:23 +0100 Subject: [PATCH 167/561] Various requirements updates --- misc/requirements/requirements-codecov.txt | 2 +- misc/requirements/requirements-flake8.txt | 2 +- misc/requirements/requirements-tests.txt | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/misc/requirements/requirements-codecov.txt b/misc/requirements/requirements-codecov.txt index 2b0690335..d20e3ae72 100644 --- a/misc/requirements/requirements-codecov.txt +++ b/misc/requirements/requirements-codecov.txt @@ -1,5 +1,5 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py codecov==2.0.5 -coverage==4.3.1 +coverage==4.3.2 requests==2.12.4 diff --git a/misc/requirements/requirements-flake8.txt b/misc/requirements/requirements-flake8.txt index 0b4fd03c9..43db4de33 100644 --- a/misc/requirements/requirements-flake8.txt +++ b/misc/requirements/requirements-flake8.txt @@ -10,7 +10,7 @@ flake8-mock==0.3 flake8-pep3101==1.0 flake8-putty==0.4.0 flake8-string-format==0.2.3 -flake8-tidy-imports==1.0.3 +flake8-tidy-imports==1.0.5 flake8-tuple==0.2.12 mccabe==0.5.3 packaging==16.8 diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index 0114cfadf..6111b34af 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -1,10 +1,10 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py beautifulsoup4==4.5.3 -CherryPy==8.8.0 +CherryPy==8.9.1 click==6.7 -coverage==4.3.1 -decorator==4.0.10 +coverage==4.3.2 +decorator==4.0.11 EasyProcess==0.2.3 Flask==0.12 glob2==0.5 From b47f90d24faf3678cce9474a02ba4dc33429187f Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 18 Jan 2017 08:40:57 +0100 Subject: [PATCH 168/561] Really fix Python 3.6 deprecation warnings Before, the module regexes didn't actually work properly, but we thought the warnings were gone as they only were shown once because of __pycache__. Now we instead don't filter by module, but simply hide those messages globally during the earlyinit dependency import (which is the first import). --- qutebrowser/misc/earlyinit.py | 13 ++++++++++++- qutebrowser/utils/log.py | 16 ---------------- 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/qutebrowser/misc/earlyinit.py b/qutebrowser/misc/earlyinit.py index 08c933a6e..def252a91 100644 --- a/qutebrowser/misc/earlyinit.py +++ b/qutebrowser/misc/earlyinit.py @@ -299,9 +299,20 @@ def check_libraries(args): else: modules['PyQt5.QtWebKit'] = _missing_str("PyQt5.QtWebKit") + from qutebrowser.utils import log + for name, text in modules.items(): try: - importlib.import_module(name) + # https://github.com/pallets/jinja/pull/628 + # https://bitbucket.org/birkenfeld/pygments-main/issues/1314/ + # https://github.com/pallets/jinja/issues/646 + # https://bitbucket.org/fdik/pypeg/commits/dd15ca462b532019c0a3be1d39b8ee2f3fa32f4e + messages = ['invalid escape sequence', + 'Flags not at the start of the expression'] + with log.ignore_py_warnings( + category=DeprecationWarning, + message=r'({})'.format('|'.join(messages))): + importlib.import_module(name) except ImportError as e: _die(text, e) diff --git a/qutebrowser/utils/log.py b/qutebrowser/utils/log.py index d96eca9a3..07ec78741 100644 --- a/qutebrowser/utils/log.py +++ b/qutebrowser/utils/log.py @@ -200,22 +200,6 @@ def _init_py_warnings(): """Initialize Python warning handling.""" warnings.simplefilter('default') warnings.filterwarnings('ignore', module='pdb', category=ResourceWarning) - # https://github.com/pallets/jinja/pull/628 - warnings.filterwarnings('ignore', module=r'jinja2\.filters', - category=DeprecationWarning, - message='Flags not at the start of the expression') - # https://bitbucket.org/birkenfeld/pygments-main/issues/1314/ - warnings.filterwarnings('ignore', module=r'pygments\.util', - category=DeprecationWarning, - message='Flags not at the start of the expression') - # https://github.com/pallets/jinja/issues/646 - warnings.filterwarnings('ignore', module=r'jinja2\..*', - category=DeprecationWarning, - message='invalid escape sequence') - # https://bitbucket.org/fdik/pypeg/commits/dd15ca462b532019c0a3be1d39b8ee2f3fa32f4e - warnings.filterwarnings('ignore', module='pypeg2', - category=DeprecationWarning, - message='invalid escape sequence') @contextlib.contextmanager From b74bae2200830f912caff0f230ce23a44a2ce760 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 18 Jan 2017 08:51:03 +0100 Subject: [PATCH 169/561] Hopefully fix download test on Windows --- tests/end2end/features/downloads.feature | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/end2end/features/downloads.feature b/tests/end2end/features/downloads.feature index 44775e39a..8552ca3e0 100644 --- a/tests/end2end/features/downloads.feature +++ b/tests/end2end/features/downloads.feature @@ -454,7 +454,7 @@ Feature: Downloading things from a website. And I set storage -> remember-download-directory to true And I open data/downloads/download.bin without waiting And I wait for the download prompt for "*" - And I run :prompt-accept (tmpdir)/downloads + And I run :prompt-accept (tmpdir)(dirsep)downloads And I open data/downloads/download.bin without waiting And I wait for the download prompt for "*" And I directly open the download From 8922fd68d02332855b78ad086136c48181f9072d Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 18 Jan 2017 08:51:57 +0100 Subject: [PATCH 170/561] requirements: Update coverage to 4.3.4 --- misc/requirements/requirements-codecov.txt | 2 +- misc/requirements/requirements-tests.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/misc/requirements/requirements-codecov.txt b/misc/requirements/requirements-codecov.txt index d20e3ae72..d14383651 100644 --- a/misc/requirements/requirements-codecov.txt +++ b/misc/requirements/requirements-codecov.txt @@ -1,5 +1,5 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py codecov==2.0.5 -coverage==4.3.2 +coverage==4.3.4 requests==2.12.4 diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index 6111b34af..b75ac8206 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -3,7 +3,7 @@ beautifulsoup4==4.5.3 CherryPy==8.9.1 click==6.7 -coverage==4.3.2 +coverage==4.3.4 decorator==4.0.11 EasyProcess==0.2.3 Flask==0.12 From 79ef728af11e7fc44821ec8e24d0dca54e98e93f Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 18 Jan 2017 15:07:44 +0100 Subject: [PATCH 171/561] requirements: Update requests to 2.12.5 --- misc/requirements/requirements-codecov.txt | 2 +- misc/requirements/requirements-pylint-master.txt | 2 +- misc/requirements/requirements-pylint.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/misc/requirements/requirements-codecov.txt b/misc/requirements/requirements-codecov.txt index d14383651..3ac13aa04 100644 --- a/misc/requirements/requirements-codecov.txt +++ b/misc/requirements/requirements-codecov.txt @@ -2,4 +2,4 @@ codecov==2.0.5 coverage==4.3.4 -requests==2.12.4 +requests==2.12.5 diff --git a/misc/requirements/requirements-pylint-master.txt b/misc/requirements/requirements-pylint-master.txt index db616759b..c87d1e1be 100644 --- a/misc/requirements/requirements-pylint-master.txt +++ b/misc/requirements/requirements-pylint-master.txt @@ -7,6 +7,6 @@ lazy-object-proxy==1.2.2 mccabe==0.5.3 -e git+https://github.com/PyCQA/pylint.git#egg=pylint ./scripts/dev/pylint_checkers -requests==2.12.4 +requests==2.12.5 six==1.10.0 wrapt==1.10.8 diff --git a/misc/requirements/requirements-pylint.txt b/misc/requirements/requirements-pylint.txt index 2db77f1c5..648fc9c2c 100644 --- a/misc/requirements/requirements-pylint.txt +++ b/misc/requirements/requirements-pylint.txt @@ -7,7 +7,7 @@ lazy-object-proxy==1.2.2 mccabe==0.5.3 pylint==1.6.4 ./scripts/dev/pylint_checkers -requests==2.12.4 +requests==2.12.5 six==1.10.0 uritemplate==3.0.0 uritemplate.py==3.0.2 From 1ab2b3af83412cefdef71ecb2c30401c5355d801 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 20 Jan 2017 08:07:55 +0100 Subject: [PATCH 172/561] Switch from CherryPy to cheroot The WSGIServer got split off from CherryPy in a separate project --- misc/requirements/requirements-tests-git.txt | 2 +- misc/requirements/requirements-tests.txt | 2 +- misc/requirements/requirements-tests.txt-raw | 2 +- scripts/dev/freeze_tests.py | 3 +-- tests/end2end/fixtures/webserver_sub.py | 4 ++-- 5 files changed, 6 insertions(+), 7 deletions(-) diff --git a/misc/requirements/requirements-tests-git.txt b/misc/requirements/requirements-tests-git.txt index fa89aa4de..68f93bc7a 100644 --- a/misc/requirements/requirements-tests-git.txt +++ b/misc/requirements/requirements-tests-git.txt @@ -1,5 +1,5 @@ bzr+lp:beautifulsoup -git+https://github.com/cherrypy/cherrypy.git +git+https://github.com/cherrypy/cheroot.git hg+https://bitbucket.org/ned/coveragepy git+https://github.com/micheles/decorator.git git+https://github.com/pallets/flask.git diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index b75ac8206..d0d6cf4b3 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -1,7 +1,7 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py beautifulsoup4==4.5.3 -CherryPy==8.9.1 +cheroot==5.0.1 click==6.7 coverage==4.3.4 decorator==4.0.11 diff --git a/misc/requirements/requirements-tests.txt-raw b/misc/requirements/requirements-tests.txt-raw index 523d8bfe4..2f81ed911 100644 --- a/misc/requirements/requirements-tests.txt-raw +++ b/misc/requirements/requirements-tests.txt-raw @@ -1,5 +1,5 @@ beautifulsoup4 -CherryPy +cheroot coverage Flask httpbin diff --git a/scripts/dev/freeze_tests.py b/scripts/dev/freeze_tests.py index 9c57b10e1..082d4e999 100755 --- a/scripts/dev/freeze_tests.py +++ b/scripts/dev/freeze_tests.py @@ -55,8 +55,7 @@ def get_build_exe_options(): opts = freeze.get_build_exe_options(skip_html=True) opts['includes'] += pytest.freeze_includes() opts['includes'] += ['unittest.mock', 'PyQt5.QtTest', 'hypothesis', 'bs4', - 'httpbin', 'jinja2.ext', 'cherrypy.wsgiserver', - 'pstats'] + 'httpbin', 'jinja2.ext', 'cheroot', 'pstats'] httpbin_dir = os.path.dirname(httpbin.__file__) opts['include_files'] += [ diff --git a/tests/end2end/fixtures/webserver_sub.py b/tests/end2end/fixtures/webserver_sub.py index 0fa1d7139..73903a7ea 100644 --- a/tests/end2end/fixtures/webserver_sub.py +++ b/tests/end2end/fixtures/webserver_sub.py @@ -31,7 +31,7 @@ import threading from httpbin.core import app from httpbin.structures import CaseInsensitiveDict -import cherrypy.wsgiserver +import cheroot.wsgi import flask @@ -135,7 +135,7 @@ def log_request(response): return response -class WSGIServer(cherrypy.wsgiserver.CherryPyWSGIServer): +class WSGIServer(cheroot.wsgi.WSGIServer): """A custom WSGIServer that prints a line on stderr when it's ready. From 37dc99c26f54a86837c89c46738f586c9b61526a Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 20 Jan 2017 09:06:49 +0100 Subject: [PATCH 173/561] Revert "Switch from CherryPy to cheroot" This reverts commit 1ab2b3af83412cefdef71ecb2c30401c5355d801. See https://github.com/cherrypy/cheroot/pull/2 --- misc/requirements/requirements-tests-git.txt | 2 +- misc/requirements/requirements-tests.txt | 2 +- misc/requirements/requirements-tests.txt-raw | 2 +- scripts/dev/freeze_tests.py | 3 ++- tests/end2end/fixtures/webserver_sub.py | 4 ++-- 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/misc/requirements/requirements-tests-git.txt b/misc/requirements/requirements-tests-git.txt index 68f93bc7a..fa89aa4de 100644 --- a/misc/requirements/requirements-tests-git.txt +++ b/misc/requirements/requirements-tests-git.txt @@ -1,5 +1,5 @@ bzr+lp:beautifulsoup -git+https://github.com/cherrypy/cheroot.git +git+https://github.com/cherrypy/cherrypy.git hg+https://bitbucket.org/ned/coveragepy git+https://github.com/micheles/decorator.git git+https://github.com/pallets/flask.git diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index d0d6cf4b3..b75ac8206 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -1,7 +1,7 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py beautifulsoup4==4.5.3 -cheroot==5.0.1 +CherryPy==8.9.1 click==6.7 coverage==4.3.4 decorator==4.0.11 diff --git a/misc/requirements/requirements-tests.txt-raw b/misc/requirements/requirements-tests.txt-raw index 2f81ed911..523d8bfe4 100644 --- a/misc/requirements/requirements-tests.txt-raw +++ b/misc/requirements/requirements-tests.txt-raw @@ -1,5 +1,5 @@ beautifulsoup4 -cheroot +CherryPy coverage Flask httpbin diff --git a/scripts/dev/freeze_tests.py b/scripts/dev/freeze_tests.py index 082d4e999..9c57b10e1 100755 --- a/scripts/dev/freeze_tests.py +++ b/scripts/dev/freeze_tests.py @@ -55,7 +55,8 @@ def get_build_exe_options(): opts = freeze.get_build_exe_options(skip_html=True) opts['includes'] += pytest.freeze_includes() opts['includes'] += ['unittest.mock', 'PyQt5.QtTest', 'hypothesis', 'bs4', - 'httpbin', 'jinja2.ext', 'cheroot', 'pstats'] + 'httpbin', 'jinja2.ext', 'cherrypy.wsgiserver', + 'pstats'] httpbin_dir = os.path.dirname(httpbin.__file__) opts['include_files'] += [ diff --git a/tests/end2end/fixtures/webserver_sub.py b/tests/end2end/fixtures/webserver_sub.py index 73903a7ea..0fa1d7139 100644 --- a/tests/end2end/fixtures/webserver_sub.py +++ b/tests/end2end/fixtures/webserver_sub.py @@ -31,7 +31,7 @@ import threading from httpbin.core import app from httpbin.structures import CaseInsensitiveDict -import cheroot.wsgi +import cherrypy.wsgiserver import flask @@ -135,7 +135,7 @@ def log_request(response): return response -class WSGIServer(cheroot.wsgi.WSGIServer): +class WSGIServer(cherrypy.wsgiserver.CherryPyWSGIServer): """A custom WSGIServer that prints a line on stderr when it's ready. From 3584eabd6f8b61e82c4221fa7d9d51c5a6f0d8bd Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 20 Jan 2017 09:11:56 +0100 Subject: [PATCH 174/561] requirements: Filter CherryPy 9.0.0 --- misc/requirements/requirements-tests.txt | 2 +- misc/requirements/requirements-tests.txt-raw | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index b75ac8206..e9448b9fc 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -1,7 +1,7 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py beautifulsoup4==4.5.3 -CherryPy==8.9.1 +CherryPy==8.9.1 # rq.filter: < 9.0.0 click==6.7 coverage==4.3.4 decorator==4.0.11 diff --git a/misc/requirements/requirements-tests.txt-raw b/misc/requirements/requirements-tests.txt-raw index 523d8bfe4..74ebfe63e 100644 --- a/misc/requirements/requirements-tests.txt-raw +++ b/misc/requirements/requirements-tests.txt-raw @@ -1,5 +1,5 @@ beautifulsoup4 -CherryPy +CherryPy<9.0.0 coverage Flask httpbin @@ -20,4 +20,6 @@ pytest-xvfb vulture #@ ignore: Jinja2, MarkupSafe -#@ filter: pytest != 3.0.5 \ No newline at end of file +#@ filter: pytest != 3.0.5 +# https://github.com/cherrypy/cheroot/pull/2 +#@ filter: CherryPy < 9.0.0 \ No newline at end of file From 7e7cac2d4865c3ce634a7e79541ad99c655e041a Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Fri, 20 Jan 2017 17:26:14 +0100 Subject: [PATCH 175/561] Ignore non-register keys MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ignore all keys with an empty .text() return value, not just modifier keys. You can still use unusual things like ß for registers, but XF86WakeUp is out. Fixes #2125. --- qutebrowser/keyinput/modeparsers.py | 8 ++++---- tests/end2end/features/keyinput.feature | 16 ++++++++++++++++ 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/qutebrowser/keyinput/modeparsers.py b/qutebrowser/keyinput/modeparsers.py index 257937852..db540b58e 100644 --- a/qutebrowser/keyinput/modeparsers.py +++ b/qutebrowser/keyinput/modeparsers.py @@ -294,12 +294,12 @@ class RegisterKeyParser(keyparser.CommandKeyParser): if super().handle(e): return True - if utils.keyevent_to_string(e) is None: - # this is a modifier key, let it pass and keep going - return False - key = e.text() + if key == '' or utils.keyevent_to_string(e) is None: + # this is not a proper register key, let it pass and keep going + return False + tabbed_browser = objreg.get('tabbed-browser', scope='window', window=self._win_id) macro_recorder = objreg.get('macro-recorder') diff --git a/tests/end2end/features/keyinput.feature b/tests/end2end/features/keyinput.feature index 3b4248e83..6777056e8 100644 --- a/tests/end2end/features/keyinput.feature +++ b/tests/end2end/features/keyinput.feature @@ -260,3 +260,19 @@ Feature: Keyboard input When I run :record-macro And I press the key "" Then "Leaving mode KeyMode.record_macro (reason: leave current)" should be logged + + Scenario: Ignoring non-register keys + Given I open data/scroll/simple.html + And I run :tab-only + When I run :scroll down with count 2 + And I wait until the scroll position changed + And I run :record-macro + And I press the key "" + And I press the key "c" + And I run :scroll up + And I wait until the scroll position changed + And I run :record-macro + And I run :run-macro + And I press the key "c" + And I wait until the scroll position changed to 0/0 + Then the page should not be scrolled From bd3c2f92fc9f07748d502c21429b5376deb5fddf Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Fri, 20 Jan 2017 19:32:16 +0100 Subject: [PATCH 176/561] Blacklist some modes for :enter-mode This disallows using :enter-mode with modes that don't work properly when entered that way. Fixes #1137. --- qutebrowser/keyinput/modeman.py | 4 ++++ tests/end2end/features/misc.feature | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/qutebrowser/keyinput/modeman.py b/qutebrowser/keyinput/modeman.py index d86b4996f..eb4baf7f5 100644 --- a/qutebrowser/keyinput/modeman.py +++ b/qutebrowser/keyinput/modeman.py @@ -265,6 +265,10 @@ class ModeManager(QObject): m = usertypes.KeyMode[mode] except KeyError: raise cmdexc.CommandError("Mode {} does not exist!".format(mode)) + if m in [usertypes.KeyMode.hint, usertypes.KeyMode.command, + usertypes.KeyMode.yesno, usertypes.KeyMode.prompt]: + raise cmdexc.CommandError( + "Mode {} can't be entered manually!".format(mode)) self.enter(m, 'command') @pyqtSlot(usertypes.KeyMode, str, bool) diff --git a/tests/end2end/features/misc.feature b/tests/end2end/features/misc.feature index 143988c64..67767f0df 100644 --- a/tests/end2end/features/misc.feature +++ b/tests/end2end/features/misc.feature @@ -642,3 +642,9 @@ Feature: Various utility commands. And I run :command-accept And I set general -> private-browsing to false Then the message "blah" should be shown + + ## Modes blacklisted for :enter-mode + + Scenario: Trying to enter command mode with :enter-mode + When I run :enter-mode command + Then the error "Mode command can't be entered manually!" should be shown From 2700739a3ab46aa7fd82417ab465a3eeef964f2e Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Sat, 21 Jan 2017 14:43:50 +0100 Subject: [PATCH 177/561] Strip mailto: when yanking a hint Resolves #61. --- qutebrowser/browser/hints.py | 1 + tests/end2end/data/email_address.html | 11 +++++++++++ tests/end2end/features/hints.feature | 6 ++++++ 3 files changed, 18 insertions(+) create mode 100644 tests/end2end/data/email_address.html diff --git a/qutebrowser/browser/hints.py b/qutebrowser/browser/hints.py index b895c7db1..065da7114 100644 --- a/qutebrowser/browser/hints.py +++ b/qutebrowser/browser/hints.py @@ -236,6 +236,7 @@ class HintActions: utils.supports_selection()) urlstr = url.toString(QUrl.FullyEncoded | QUrl.RemovePassword) + urlstr = urlstr.lstrip('mailto:') utils.set_clipboard(urlstr, selection=sel) msg = "Yanked URL to {}: {}".format( diff --git a/tests/end2end/data/email_address.html b/tests/end2end/data/email_address.html new file mode 100644 index 000000000..3c86446ad --- /dev/null +++ b/tests/end2end/data/email_address.html @@ -0,0 +1,11 @@ + + + + + + Email address + + + Email address + + diff --git a/tests/end2end/features/hints.feature b/tests/end2end/features/hints.feature index 8d23d0199..18b36e6e8 100644 --- a/tests/end2end/features/hints.feature +++ b/tests/end2end/features/hints.feature @@ -110,6 +110,12 @@ Feature: Using hints And I hint with args "links yank-primary" and follow a Then the clipboard should contain "http://localhost:(port)/data/hello.txt" + Scenario: Yanking email address to clipboard + When I run :debug-set-fake-clipboard + And I open data/email_address.html + And I hint with args "links yank" and follow a + Then the clipboard should contain "nobody" + Scenario: Rapid hinting When I open data/hints/rapid.html in a new tab And I run :tab-only From 9845cbbd81c0d2cdef2a83196534fcf8a847afa5 Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Sat, 21 Jan 2017 23:19:15 +0100 Subject: [PATCH 178/561] Remove mailto: scheme properly --- qutebrowser/browser/hints.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/qutebrowser/browser/hints.py b/qutebrowser/browser/hints.py index 065da7114..ece6ed89e 100644 --- a/qutebrowser/browser/hints.py +++ b/qutebrowser/browser/hints.py @@ -235,8 +235,10 @@ class HintActions: sel = (context.target == Target.yank_primary and utils.supports_selection()) - urlstr = url.toString(QUrl.FullyEncoded | QUrl.RemovePassword) - urlstr = urlstr.lstrip('mailto:') + flags = QUrl.FullyEncoded | QUrl.RemovePassword + if url.scheme() == 'mailto': + flags |= QUrl.RemoveScheme + urlstr = url.toString(flags) utils.set_clipboard(urlstr, selection=sel) msg = "Yanked URL to {}: {}".format( From 1a04e554fc1b163e4e31275e30fe2ae3c86af1a6 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 22 Jan 2017 22:22:14 +0100 Subject: [PATCH 179/561] pylint requirements: Update pylint to 1.6.5 --- misc/requirements/requirements-pylint.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-pylint.txt b/misc/requirements/requirements-pylint.txt index 648fc9c2c..bb1f52ad2 100644 --- a/misc/requirements/requirements-pylint.txt +++ b/misc/requirements/requirements-pylint.txt @@ -5,7 +5,7 @@ github3.py==0.9.6 isort==4.2.5 lazy-object-proxy==1.2.2 mccabe==0.5.3 -pylint==1.6.4 +pylint==1.6.5 ./scripts/dev/pylint_checkers requests==2.12.5 six==1.10.0 From a3c605fe543d2f4c5df13caee31ba4f83e7980b0 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 22 Jan 2017 22:23:20 +0100 Subject: [PATCH 180/561] pytest requirements: Update pytest to 3.0.6 --- misc/requirements/requirements-tests.txt | 2 +- misc/requirements/requirements-tests.txt-raw | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index e9448b9fc..5ca2b0418 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -17,7 +17,7 @@ Mako==1.0.6 parse==1.6.6 parse-type==0.3.4 py==1.4.32 -pytest==3.0.4 # rq.filter: != 3.0.5 +pytest==3.0.6 pytest-bdd==2.18.1 pytest-catchlog==1.2.2 pytest-cov==2.4.0 diff --git a/misc/requirements/requirements-tests.txt-raw b/misc/requirements/requirements-tests.txt-raw index 74ebfe63e..a0b8c1e3a 100644 --- a/misc/requirements/requirements-tests.txt-raw +++ b/misc/requirements/requirements-tests.txt-raw @@ -4,7 +4,7 @@ coverage Flask httpbin hypothesis -pytest!=3.0.5 +pytest pytest-bdd pytest-catchlog pytest-cov @@ -20,6 +20,5 @@ pytest-xvfb vulture #@ ignore: Jinja2, MarkupSafe -#@ filter: pytest != 3.0.5 # https://github.com/cherrypy/cheroot/pull/2 #@ filter: CherryPy < 9.0.0 \ No newline at end of file From 25ddbdb57cb27b7480a3c4be1464344e4dffbcad Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 22 Jan 2017 23:28:29 +0100 Subject: [PATCH 181/561] requirements: Update Pygments to 2.2.0 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index bd2277623..7b9f9f1d0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,6 +4,6 @@ colorama==0.3.7 cssutils==1.0.1 Jinja2==2.9.4 MarkupSafe==0.23 -Pygments==2.1.3 +Pygments==2.2.0 pyPEG2==2.15.2 PyYAML==3.12 From d9808aefeda85fa22a90f81612561e0a254b8cdd Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 23 Jan 2017 07:36:17 +0100 Subject: [PATCH 182/561] Switch from CherryPy to cheroot, take 2 This reverts commit 3584eabd6f8b61e82c4221fa7d9d51c5a6f0d8bd. This reverts commit 37dc99c26f54a86837c89c46738f586c9b61526a. --- misc/requirements/requirements-tests-git.txt | 2 +- misc/requirements/requirements-tests.txt | 2 +- misc/requirements/requirements-tests.txt-raw | 4 +--- scripts/dev/freeze_tests.py | 3 +-- tests/end2end/fixtures/webserver_sub.py | 4 ++-- 5 files changed, 6 insertions(+), 9 deletions(-) diff --git a/misc/requirements/requirements-tests-git.txt b/misc/requirements/requirements-tests-git.txt index fa89aa4de..68f93bc7a 100644 --- a/misc/requirements/requirements-tests-git.txt +++ b/misc/requirements/requirements-tests-git.txt @@ -1,5 +1,5 @@ bzr+lp:beautifulsoup -git+https://github.com/cherrypy/cherrypy.git +git+https://github.com/cherrypy/cheroot.git hg+https://bitbucket.org/ned/coveragepy git+https://github.com/micheles/decorator.git git+https://github.com/pallets/flask.git diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index 5ca2b0418..9a9b60d83 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -1,7 +1,7 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py beautifulsoup4==4.5.3 -CherryPy==8.9.1 # rq.filter: < 9.0.0 +cheroot==5.1.0 click==6.7 coverage==4.3.4 decorator==4.0.11 diff --git a/misc/requirements/requirements-tests.txt-raw b/misc/requirements/requirements-tests.txt-raw index a0b8c1e3a..619095ad8 100644 --- a/misc/requirements/requirements-tests.txt-raw +++ b/misc/requirements/requirements-tests.txt-raw @@ -1,5 +1,5 @@ beautifulsoup4 -CherryPy<9.0.0 +cheroot coverage Flask httpbin @@ -20,5 +20,3 @@ pytest-xvfb vulture #@ ignore: Jinja2, MarkupSafe -# https://github.com/cherrypy/cheroot/pull/2 -#@ filter: CherryPy < 9.0.0 \ No newline at end of file diff --git a/scripts/dev/freeze_tests.py b/scripts/dev/freeze_tests.py index 9c57b10e1..082d4e999 100755 --- a/scripts/dev/freeze_tests.py +++ b/scripts/dev/freeze_tests.py @@ -55,8 +55,7 @@ def get_build_exe_options(): opts = freeze.get_build_exe_options(skip_html=True) opts['includes'] += pytest.freeze_includes() opts['includes'] += ['unittest.mock', 'PyQt5.QtTest', 'hypothesis', 'bs4', - 'httpbin', 'jinja2.ext', 'cherrypy.wsgiserver', - 'pstats'] + 'httpbin', 'jinja2.ext', 'cheroot', 'pstats'] httpbin_dir = os.path.dirname(httpbin.__file__) opts['include_files'] += [ diff --git a/tests/end2end/fixtures/webserver_sub.py b/tests/end2end/fixtures/webserver_sub.py index 0fa1d7139..df84d26d6 100644 --- a/tests/end2end/fixtures/webserver_sub.py +++ b/tests/end2end/fixtures/webserver_sub.py @@ -31,7 +31,7 @@ import threading from httpbin.core import app from httpbin.structures import CaseInsensitiveDict -import cherrypy.wsgiserver +import cheroot.wsgi import flask @@ -135,7 +135,7 @@ def log_request(response): return response -class WSGIServer(cherrypy.wsgiserver.CherryPyWSGIServer): +class WSGIServer(cheroot.wsgi.Server): """A custom WSGIServer that prints a line on stderr when it's ready. From 43ae66dba47404c25d4ac671b4f5468a69c2643c Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 23 Jan 2017 09:22:50 +0100 Subject: [PATCH 183/561] freeze_tests: Add queue module for cheroot --- scripts/dev/freeze_tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/dev/freeze_tests.py b/scripts/dev/freeze_tests.py index 082d4e999..9f9e2bbd2 100755 --- a/scripts/dev/freeze_tests.py +++ b/scripts/dev/freeze_tests.py @@ -55,7 +55,7 @@ def get_build_exe_options(): opts = freeze.get_build_exe_options(skip_html=True) opts['includes'] += pytest.freeze_includes() opts['includes'] += ['unittest.mock', 'PyQt5.QtTest', 'hypothesis', 'bs4', - 'httpbin', 'jinja2.ext', 'cheroot', 'pstats'] + 'httpbin', 'jinja2.ext', 'cheroot', 'pstats', 'queue'] httpbin_dir = os.path.dirname(httpbin.__file__) opts['include_files'] += [ From 2a773a2c55adf7b67c6faaafe8a99ad260487363 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 24 Jan 2017 23:55:12 +0100 Subject: [PATCH 184/561] Enable WebGL by default --- doc/help/settings.asciidoc | 2 +- qutebrowser/config/configdata.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc index 5dffe1733..62700ac6f 100644 --- a/doc/help/settings.asciidoc +++ b/doc/help/settings.asciidoc @@ -1434,7 +1434,7 @@ Valid values: * +true+ * +false+ -Default: +pass:[false]+ +Default: +pass:[true]+ [[content-css-regions]] === css-regions diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index 212999ff6..91588468d 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -815,7 +815,7 @@ def data(readonly=False): "are not affected by this setting."), ('webgl', - SettingValue(typ.Bool(), 'false'), + SettingValue(typ.Bool(), 'true'), "Enables or disables WebGL. For QtWebEngine, Qt/PyQt >= 5.7 is " "required for this setting."), From 2934ffd4d9341e688dc70a83f0e191e4c70385b8 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 25 Jan 2017 08:04:58 +0100 Subject: [PATCH 185/561] Disable WebGL for tests See #2250 --- tests/end2end/fixtures/quteprocess.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/end2end/fixtures/quteprocess.py b/tests/end2end/fixtures/quteprocess.py index 8b2134769..ac99ec375 100644 --- a/tests/end2end/fixtures/quteprocess.py +++ b/tests/end2end/fixtures/quteprocess.py @@ -381,7 +381,9 @@ class QuteProc(testprocess.Process): settings = [ ('ui', 'message-timeout', '0'), ('general', 'auto-save-interval', '0'), - ('general', 'new-instance-open-target.window', 'last-opened') + ('general', 'new-instance-open-target.window', 'last-opened'), + # https://github.com/The-Compiler/qutebrowser/issues/2250 + ('content', 'webgl', 'false'), ] if not self.request.config.webengine: settings.append(('network', 'ssl-strict', 'false')) From 7304db916cfc4ab91b361ab72404a6679306b277 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 25 Jan 2017 08:05:40 +0100 Subject: [PATCH 186/561] freeze.py: Remove pkg_resources._vendor packages See https://github.com/pypa/setuptools/pull/933 --- scripts/dev/freeze.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/scripts/dev/freeze.py b/scripts/dev/freeze.py index 67bda081a..f254f4d90 100755 --- a/scripts/dev/freeze.py +++ b/scripts/dev/freeze.py @@ -98,10 +98,7 @@ def get_build_exe_options(skip_html=False): 'include_msvcr': True, 'includes': [], 'excludes': ['tkinter'], - 'packages': ['pygments', 'pkg_resources._vendor.packaging', - 'pkg_resources._vendor.pyparsing', - 'pkg_resources._vendor.six', - 'pkg_resources._vendor.appdirs'], + 'packages': ['pygments'], } From 0863c3277cfbb5207c6d31e834b302839bfb063b Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 25 Jan 2017 08:45:18 +0100 Subject: [PATCH 187/561] Revert "Disable WebGL for tests" This reverts commit 2934ffd4d9341e688dc70a83f0e191e4c70385b8. --- tests/end2end/fixtures/quteprocess.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/end2end/fixtures/quteprocess.py b/tests/end2end/fixtures/quteprocess.py index ac99ec375..8b2134769 100644 --- a/tests/end2end/fixtures/quteprocess.py +++ b/tests/end2end/fixtures/quteprocess.py @@ -381,9 +381,7 @@ class QuteProc(testprocess.Process): settings = [ ('ui', 'message-timeout', '0'), ('general', 'auto-save-interval', '0'), - ('general', 'new-instance-open-target.window', 'last-opened'), - # https://github.com/The-Compiler/qutebrowser/issues/2250 - ('content', 'webgl', 'false'), + ('general', 'new-instance-open-target.window', 'last-opened') ] if not self.request.config.webengine: settings.append(('network', 'ssl-strict', 'false')) From 95aeb86328501ceb6476aba8764daf42e4e8b89b Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 27 Jan 2017 09:19:10 +0100 Subject: [PATCH 188/561] Update pdfjs version parsing Fixes #2254 --- qutebrowser/utils/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutebrowser/utils/version.py b/qutebrowser/utils/version.py index 3f6b9c8d0..0a3d35cfb 100644 --- a/qutebrowser/utils/version.py +++ b/qutebrowser/utils/version.py @@ -210,7 +210,7 @@ def _pdfjs_version(): else: pdfjs_file = pdfjs_file.decode('utf-8') version_re = re.compile( - r"^(PDFJS\.version|var pdfjsVersion) = '([^']+)';$", re.MULTILINE) + r"^ *(PDFJS\.version|var pdfjsVersion) = '([^']+)';$", re.MULTILINE) match = version_re.search(pdfjs_file) if not match: From 90f472bf590b6fdb3e7f4158ae420002ccbdee17 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 29 Jan 2017 22:56:53 +0100 Subject: [PATCH 189/561] Temporary fix for Qt 5.8 segfaults See #2261 --- qutebrowser/mainwindow/tabbedbrowser.py | 1 + qutebrowser/misc/miscwidgets.py | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py index 6d46abbf8..0cdb5eff9 100644 --- a/qutebrowser/mainwindow/tabbedbrowser.py +++ b/qutebrowser/mainwindow/tabbedbrowser.py @@ -279,6 +279,7 @@ class TabbedBrowser(tabwidget.TabWidget): urlutils.invalid_url_error(tab.url(), "saving tab") tab.shutdown() self.removeTab(idx) + tab.layout().unwrap() tab.deleteLater() def undo(self): diff --git a/qutebrowser/misc/miscwidgets.py b/qutebrowser/misc/miscwidgets.py index ba21e541a..c08a00946 100644 --- a/qutebrowser/misc/miscwidgets.py +++ b/qutebrowser/misc/miscwidgets.py @@ -260,3 +260,7 @@ class WrapperLayout(QLayout): self._widget = widget container.setFocusProxy(widget) widget.setParent(container) + + def unwrap(self): + self._widget.setParent(None) + self._widget.deleteLater() From bb46c01c5058d2f5986d38638e5136511deab633 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 1 Feb 2017 09:30:53 +0100 Subject: [PATCH 190/561] Clear search text before closing QtWebEngine tab See https://bugreports.qt.io/browse/QTBUG-58563 and #2261 --- qutebrowser/browser/webengine/webenginetab.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index a7c7dcc59..4c8b0602d 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -553,6 +553,9 @@ class WebEngineTab(browsertab.AbstractTab): def shutdown(self): self.shutting_down.emit() + # WORKAROUND for + # https://bugreports.qt.io/browse/QTBUG-58563 + self.search.clear() self._widget.shutdown() def reload(self, *, force=False): From 72bdf69fe34469ed3b9d6fd6cc35b09d52bf8e2d Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 1 Feb 2017 09:51:50 +0100 Subject: [PATCH 191/561] Fix long line --- qutebrowser/utils/version.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qutebrowser/utils/version.py b/qutebrowser/utils/version.py index 0a3d35cfb..c32cbb588 100644 --- a/qutebrowser/utils/version.py +++ b/qutebrowser/utils/version.py @@ -210,7 +210,8 @@ def _pdfjs_version(): else: pdfjs_file = pdfjs_file.decode('utf-8') version_re = re.compile( - r"^ *(PDFJS\.version|var pdfjsVersion) = '([^']+)';$", re.MULTILINE) + r"^ *(PDFJS\.version|var pdfjsVersion) = '([^']+)';$", + re.MULTILINE) match = version_re.search(pdfjs_file) if not match: From 8ad28719abd8f358afefc8b5240762ae2ee3be65 Mon Sep 17 00:00:00 2001 From: pkill9 Date: Mon, 30 Jan 2017 11:48:10 +0000 Subject: [PATCH 192/561] Add "QUTE_COMMANDLINE_TEXT" environment variable for userscripts. - Added "QUTE_COMMANDLINE_TEXT" environment variable for userscripts. - Updated documentation to include "QUTE_COMMANDLINE_TEXT" environment variable for userscripts. --- doc/userscripts.asciidoc | 1 + qutebrowser/commands/userscripts.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/doc/userscripts.asciidoc b/doc/userscripts.asciidoc index 433c71ff0..8904d0ef3 100644 --- a/doc/userscripts.asciidoc +++ b/doc/userscripts.asciidoc @@ -38,6 +38,7 @@ The following environment variables will be set when a userscript is launched: - `QUTE_CONFIG_DIR`: Path of the directory containing qutebrowser's configuration. - `QUTE_DATA_DIR`: Path of the directory containing qutebrowser's data. - `QUTE_DOWNLOAD_DIR`: Path of the downloads directory. +- `QUTE_COMMANDLINE_TEXT`: Text currently in qutebrowser's command line. (Depends on which window the userscript is launched in) In `command` mode: diff --git a/qutebrowser/commands/userscripts.py b/qutebrowser/commands/userscripts.py index 960e97318..4533e86b1 100644 --- a/qutebrowser/commands/userscripts.py +++ b/qutebrowser/commands/userscripts.py @@ -419,6 +419,8 @@ def run_async(tab, cmd, *args, win_id, env, verbose=False): env['QUTE_CONFIG_DIR'] = standarddir.config() env['QUTE_DATA_DIR'] = standarddir.data() env['QUTE_DOWNLOAD_DIR'] = downloads.download_dir() + env['QUTE_COMMANDLINE_TEXT'] = objreg.get('status-command', scope='window', + window=win_id).text() cmd_path = os.path.expanduser(cmd) From 428e4959352d73b257321bf1deec2c5216d930af Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 1 Feb 2017 11:09:08 +0100 Subject: [PATCH 193/561] Unconditionally disable test_tab This now also causes segfaults with Qt 5.8, so something is definitely wrong here... See #1638 and #2261 --- tests/unit/browser/test_tab.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/unit/browser/test_tab.py b/tests/unit/browser/test_tab.py index 10e08938d..d5324e597 100644 --- a/tests/unit/browser/test_tab.py +++ b/tests/unit/browser/test_tab.py @@ -115,8 +115,7 @@ class Tab(browsertab.AbstractTab): pass -@pytest.mark.skipif(PYQT_VERSION < 0x050600, - reason='Causes segfaults, see #1638') +@pytest.mark.xfail(run=False, reason='Causes segfaults, see #1638') def test_tab(qtbot, view, config_stub, tab_registry, mode_manager): tab_w = Tab(win_id=0, mode_manager=mode_manager) qtbot.add_widget(tab_w) From e5176e18bd2d1be73888ee90a17b4e6145c75291 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 1 Feb 2017 12:33:47 +0100 Subject: [PATCH 194/561] tests: Fix QtWebEngine focus checking --- tests/end2end/fixtures/quteprocess.py | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/tests/end2end/fixtures/quteprocess.py b/tests/end2end/fixtures/quteprocess.py index 8b2134769..831e210ce 100644 --- a/tests/end2end/fixtures/quteprocess.py +++ b/tests/end2end/fixtures/quteprocess.py @@ -196,13 +196,17 @@ class QuteProc(testprocess.Process): start_okay_message_load = ( "load status for : LoadStatus.success") - start_okay_message_focus = ( + start_okay_messages_focus = [ + # QtWebKit "Focus object changed: " - "") - # With QtWebEngine the QOpenGLWidget has the actual focus - start_okay_message_focus_qtwe = ( - "Focus object changed: " - ) + "", + # QtWebEngine + "Focus object changed: " + "", + # QtWebEngine with Qt >= 5.8 + "Focus object changed: " + "", + ] if (log_line.category == 'ipc' and log_line.message.startswith("Listening as ")): @@ -213,13 +217,8 @@ class QuteProc(testprocess.Process): if not self._load_ready: log_line.waited_for = True self._is_ready('load') - elif (log_line.category == 'misc' and - testutils.pattern_match(pattern=start_okay_message_focus, - value=log_line.message)): - self._is_ready('focus') - elif (log_line.category == 'misc' and - testutils.pattern_match(pattern=start_okay_message_focus_qtwe, - value=log_line.message)): + elif log_line.category == 'misc' and any(testutils.pattern_match( + pattern=pattern, value=log_line.message) for pattern in start_okay_messages_focus): self._is_ready('focus') elif (log_line.category == 'init' and log_line.module == 'standarddir' and From ba2f4fb1b9c5251c8955092a46eeecc6e59c2783 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 1 Feb 2017 13:08:11 +0100 Subject: [PATCH 195/561] Use event_target to filter out mouse events Fixes #2262 --- qutebrowser/browser/browsertab.py | 12 +++--------- qutebrowser/browser/mouse.py | 11 +++-------- qutebrowser/browser/webengine/webenginetab.py | 4 +--- qutebrowser/browser/webkit/webkittab.py | 4 +--- 4 files changed, 8 insertions(+), 23 deletions(-) diff --git a/qutebrowser/browser/browsertab.py b/qutebrowser/browser/browsertab.py index 1480efbf0..e868f3261 100644 --- a/qutebrowser/browser/browsertab.py +++ b/qutebrowser/browser/browsertab.py @@ -486,10 +486,6 @@ class AbstractTab(QWidget): We use this to unify QWebView and QWebEngineView. - Class attributes: - WIDGET_CLASS: The class of the main widget recieving events. - Needs to be overridden by subclasses. - Attributes: history: The AbstractHistory for the current tab. registry: The ObjectRegistry associated with this tab. @@ -523,8 +519,6 @@ class AbstractTab(QWidget): contents_size_changed = pyqtSignal(QSizeF) add_history_item = pyqtSignal(QUrl, QUrl, str) # url, requested url, title - WIDGET_CLASS = None - def __init__(self, win_id, mode_manager, parent=None): self.win_id = win_id self.tab_id = next(tab_id_gen) @@ -553,7 +547,7 @@ class AbstractTab(QWidget): self._mode_manager = mode_manager self._load_status = usertypes.LoadStatus.none self._mouse_event_filter = mouse.MouseEventFilter( - self, widget_class=self.WIDGET_CLASS, parent=self) + self, parent=self) self.backend = None # FIXME:qtwebengine Should this be public api via self.hints? @@ -588,7 +582,7 @@ class AbstractTab(QWidget): self._load_status = val self.load_status_changed.emit(val.name) - def _event_target(self): + def event_target(self): """Return the widget events should be sent to.""" raise NotImplementedError @@ -603,7 +597,7 @@ class AbstractTab(QWidget): if getattr(evt, 'posted', False): raise AssertionError("Can't re-use an event which was already " "posted!") - recipient = self._event_target() + recipient = self.event_target() evt.posted = True QApplication.postEvent(recipient, evt) diff --git a/qutebrowser/browser/mouse.py b/qutebrowser/browser/mouse.py index a1b56b96a..1346a7693 100644 --- a/qutebrowser/browser/mouse.py +++ b/qutebrowser/browser/mouse.py @@ -64,8 +64,6 @@ class MouseEventFilter(QObject): """Handle mouse events on a tab. Attributes: - _widget_class: The class of the main widget getting the events. - All other events are ignored. _tab: The browsertab object this filter is installed on. _handlers: A dict of handler functions for the handled events. _ignore_wheel_event: Whether to ignore the next wheelEvent. @@ -73,9 +71,8 @@ class MouseEventFilter(QObject): done when the mouse is released. """ - def __init__(self, tab, *, widget_class, parent=None): + def __init__(self, tab, *, parent=None): super().__init__(parent) - self._widget_class = widget_class self._tab = tab self._handlers = { QEvent.MouseButtonPress: self._handle_mouse_press, @@ -207,9 +204,7 @@ class MouseEventFilter(QObject): evtype = event.type() if evtype not in self._handlers: return False - if not isinstance(obj, self._widget_class): - log.mouse.debug("Ignoring {} to {} which is not an instance of " - "{}".format(event.__class__.__name__, obj, - self._widget_class)) + if obj is not self._tab.event_target(): + log.mouse.debug("Ignoring {} to {}".format(event.__class__.__name__, obj)) return False return self._handlers[evtype](event) diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index 4c8b0602d..a555a2d15 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -452,8 +452,6 @@ class WebEngineTab(browsertab.AbstractTab): """A QtWebEngine tab in the browser.""" - WIDGET_CLASS = QOpenGLWidget - def __init__(self, win_id, mode_manager, parent=None): super().__init__(win_id=win_id, mode_manager=mode_manager, parent=parent) @@ -654,5 +652,5 @@ class WebEngineTab(browsertab.AbstractTab): except AttributeError: log.stub('contentsSizeChanged, on Qt < 5.7') - def _event_target(self): + def event_target(self): return self._widget.focusProxy() diff --git a/qutebrowser/browser/webkit/webkittab.py b/qutebrowser/browser/webkit/webkittab.py index 12c643f46..82a25d634 100644 --- a/qutebrowser/browser/webkit/webkittab.py +++ b/qutebrowser/browser/webkit/webkittab.py @@ -590,8 +590,6 @@ class WebKitTab(browsertab.AbstractTab): """A QtWebKit tab in the browser.""" - WIDGET_CLASS = webview.WebView - def __init__(self, win_id, mode_manager, parent=None): super().__init__(win_id=win_id, mode_manager=mode_manager, parent=parent) @@ -717,5 +715,5 @@ class WebKitTab(browsertab.AbstractTab): frame.contentsSizeChanged.connect(self._on_contents_size_changed) frame.initialLayoutCompleted.connect(self._on_history_trigger) - def _event_target(self): + def event_target(self): return self._widget From 545539f28d86c162f0e7f803480ad7fe24b6870f Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 3 Feb 2017 22:20:15 +0100 Subject: [PATCH 196/561] Do more sophisticated clicking for hints with QtWebEngine We now use click() or focus() in JS if possible, or manually follow links in a href attribute. While this probably introduces some new corner cases, it fixes a handful of older ones: - window.open() in JS can now be handled correctly as we don't need hacks in createWindow anymore. - Focusing input fields with images now works - fixes #1613, #1879 - Hinting now works better on QtWebEngine with Qt 5.8 - fixes #2273 Also see #70. --- doc/help/commands.asciidoc | 3 +- qutebrowser/browser/commands.py | 6 +- qutebrowser/browser/mouse.py | 5 +- qutebrowser/browser/webelem.py | 77 ++++++++++++++++--- .../browser/webengine/webengineelem.py | 19 ++++- qutebrowser/browser/webengine/webview.py | 36 ++++----- qutebrowser/browser/webkit/webkitelem.py | 14 ++++ qutebrowser/javascript/webelem.js | 10 +++ tests/end2end/data/scroll/simple.html | 1 + tests/end2end/features/editor.feature | 8 +- tests/end2end/features/hints.feature | 15 +--- tests/end2end/features/misc.feature | 10 +-- tests/end2end/features/yankpaste.feature | 8 +- tests/end2end/test_insert_mode.py | 10 +-- 14 files changed, 148 insertions(+), 74 deletions(-) diff --git a/doc/help/commands.asciidoc b/doc/help/commands.asciidoc index 283acdf9f..eb7a25dee 100644 --- a/doc/help/commands.asciidoc +++ b/doc/help/commands.asciidoc @@ -1036,7 +1036,7 @@ Clear the currently entered key chain. [[click-element]] === click-element -Syntax: +:click-element [*--target* 'target'] 'filter' 'value'+ +Syntax: +:click-element [*--target* 'target'] [*--force-event*] 'filter' 'value'+ Click the element matching the given filter. @@ -1049,6 +1049,7 @@ The given filter needs to result in exactly one element, otherwise, an error is ==== optional arguments * +*-t*+, +*--target*+: How to open the clicked element (normal/tab/tab-bg/window). +* +*-f*+, +*--force-event*+: Force generating a fake click event. [[command-accept]] === command-accept diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index e8f15720d..63080af4f 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -1545,7 +1545,8 @@ class CommandDispatcher: @cmdutils.argument('filter_', choices=['id']) def click_element(self, filter_: str, value, *, target: usertypes.ClickTarget= - usertypes.ClickTarget.normal): + usertypes.ClickTarget.normal, + force_event=False): """Click the element matching the given filter. The given filter needs to result in exactly one element, otherwise, an @@ -1556,6 +1557,7 @@ class CommandDispatcher: id: Get an element based on its ID. value: The value to filter for. target: How to open the clicked element (normal/tab/tab-bg/window). + force_event: Force generating a fake click event. """ tab = self._current_widget() @@ -1565,7 +1567,7 @@ class CommandDispatcher: message.error("No element found with id {}!".format(value)) return try: - elem.click(target) + elem.click(target, force_event=force_event) except webelem.Error as e: message.error(str(e)) return diff --git a/qutebrowser/browser/mouse.py b/qutebrowser/browser/mouse.py index 1346a7693..23ddce6ee 100644 --- a/qutebrowser/browser/mouse.py +++ b/qutebrowser/browser/mouse.py @@ -93,7 +93,10 @@ class MouseEventFilter(QObject): return True self._ignore_wheel_event = True - self._tab.elements.find_at_pos(e.pos(), self._mousepress_insertmode_cb) + + if e.button() != Qt.NoButton: + self._tab.elements.find_at_pos(e.pos(), + self._mousepress_insertmode_cb) return False diff --git a/qutebrowser/browser/webelem.py b/qutebrowser/browser/webelem.py index 0ddd4d265..560674e98 100644 --- a/qutebrowser/browser/webelem.py +++ b/qutebrowser/browser/webelem.py @@ -33,7 +33,8 @@ from PyQt5.QtCore import QUrl, Qt, QEvent, QTimer from PyQt5.QtGui import QMouseEvent from qutebrowser.config import config -from qutebrowser.utils import log, usertypes, utils, qtutils +from qutebrowser.keyinput import modeman +from qutebrowser.utils import log, usertypes, utils, qtutils, objreg Group = usertypes.enum('Group', ['all', 'links', 'images', 'url', 'prevnext', @@ -322,14 +323,8 @@ class AbstractWebElement(collections.abc.MutableMapping): raise Error("Element position is out of view!") return pos - def click(self, click_target): - """Simulate a click on the element.""" - # FIXME:qtwebengine do we need this? - # self._widget.setFocus() - - # For QtWebKit - self._tab.data.override_target = click_target - + def _click_fake_event(self, click_target): + """Send a fake click event to the element.""" pos = self._mouse_pos() log.webelem.debug("Sending fake click to {!r} at position {} with " @@ -364,6 +359,70 @@ class AbstractWebElement(collections.abc.MutableMapping): self._tab.caret.move_to_end_of_document() QTimer.singleShot(0, after_click) + def _click_editable(self): + """Fake a click on an editable input field.""" + raise NotImplementedError + + def _click_js(self, click_target): + """Fake a click by using the JS .click() method.""" + raise NotImplementedError + + def _click_href(self, click_target): + """Fake a click on an element with a href by opening the link.""" + baseurl = self._tab.url() + url = self.resolve_url(baseurl) + if url is None: + self._click_fake_event(click_target) + return + + if click_target in [usertypes.ClickTarget.tab, + usertypes.ClickTarget.tab_bg]: + background = click_target == usertypes.ClickTarget.tab_bg + tabbed_browser = objreg.get('tabbed-browser', scope='window', + window=self._tab.win_id) + tabbed_browser.tabopen(url, background=background) + elif click_target == usertypes.ClickTarget.window: + from qutebrowser.mainwindow import mainwindow + window = mainwindow.MainWindow() + window.show() + window.tabbed_browser.tabopen(url) + else: + raise ValueError("Unknown ClickTarget {}".format(click_target)) + + def click(self, click_target, *, force_event=False): + """Simulate a click on the element. + + Args: + click_target: An usertypes.ClickTarget member, what kind of click + to simulate. + force_event: Force generating a fake mouse event. + """ + if force_event: + self._click_fake_event(click_target) + return + + href_tags = ['a', 'area', 'link'] + if click_target == usertypes.ClickTarget.normal: + if self.tag_name() in href_tags: + log.webelem.debug("Clicking via JS click()") + self._click_js(click_target) + elif self.is_editable(strict=True): + log.webelem.debug("Clicking via JS focus()") + self._click_editable() + modeman.enter(self._tab.win_id, usertypes.KeyMode.insert, + 'clicking input') + else: + self._click_fake_event(click_target) + elif click_target in [usertypes.ClickTarget.tab, + usertypes.ClickTarget.tab_bg, + usertypes.ClickTarget.window]: + if self.tag_name() in href_tags: + self._click_href(click_target) + else: + self._click_fake_event(click_target) + else: + raise ValueError("Unknown ClickTarget {}".format(click_target)) + def hover(self): """Simulate a mouse hover over the element.""" pos = self._mouse_pos() diff --git a/qutebrowser/browser/webengine/webengineelem.py b/qutebrowser/browser/webengine/webengineelem.py index bf8d06474..fe42d355d 100644 --- a/qutebrowser/browser/webengine/webengineelem.py +++ b/qutebrowser/browser/webengine/webengineelem.py @@ -22,9 +22,10 @@ """QtWebEngine specific part of the web element API.""" -from PyQt5.QtCore import QRect +from PyQt5.QtCore import QRect, Qt, QPoint +from PyQt5.QtGui import QMouseEvent -from qutebrowser.utils import log, javascript +from qutebrowser.utils import log, javascript, usertypes from qutebrowser.browser import webelem @@ -149,3 +150,17 @@ class WebEngineElement(webelem.AbstractWebElement): js_code = javascript.assemble('webelem', 'remove_blank_target', self._id) self._tab.run_js_async(js_code) + + def _click_editable(self): + # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-58515 + ev = QMouseEvent(QMouseEvent.MouseButtonPress, QPoint(0, 0), + QPoint(0, 0), QPoint(0, 0), Qt.NoButton, Qt.NoButton, + Qt.NoModifier, Qt.MouseEventSynthesizedBySystem) + self._tab.send_event(ev) + # 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) + + def _click_js(self, _click_target): + js_code = javascript.assemble('webelem', 'click', self._id) + self._tab.run_js_async(js_code) diff --git a/qutebrowser/browser/webengine/webview.py b/qutebrowser/browser/webengine/webview.py index f3e9d6a94..585c184d9 100644 --- a/qutebrowser/browser/webengine/webview.py +++ b/qutebrowser/browser/webengine/webview.py @@ -73,10 +73,8 @@ class WebEngineView(QWebEngineView): The new QWebEngineView object. """ debug_type = debug.qenum_key(QWebEnginePage, wintype) - background_tabs = config.get('tabs', 'background-tabs') - log.webview.debug("createWindow with type {}, background_tabs " - "{}".format(debug_type, background_tabs)) + log.webview.debug("createWindow with type {}".format(debug_type)) try: background_tab_wintype = QWebEnginePage.WebBrowserBackgroundTab @@ -85,30 +83,22 @@ class WebEngineView(QWebEngineView): # this with a newer Qt... background_tab_wintype = 0x0003 - if wintype == QWebEnginePage.WebBrowserWindow: + click_target = { # Shift-Alt-Click - target = usertypes.ClickTarget.window - elif wintype == QWebEnginePage.WebDialog: + QWebEnginePage.WebBrowserWindow: usertypes.ClickTarget.window, + # ? + QWebEnginePage.WebDialog: usertypes.ClickTarget.tab, + # Middle-click / Ctrl-Click with Shift + QWebEnginePage.WebBrowserTab: usertypes.ClickTarget.tab_bg, + # Middle-click / Ctrl-Click + background_tab_wintype: usertypes.ClickTarget.tab, + } + + if wintype == QWebEnginePage.WebDialog: log.webview.warning("{} requested, but we don't support " "that!".format(debug_type)) - target = usertypes.ClickTarget.tab - elif wintype == QWebEnginePage.WebBrowserTab: - # Middle-click / Ctrl-Click with Shift - # FIXME:qtwebengine this also affects target=_blank links... - if background_tabs: - target = usertypes.ClickTarget.tab - else: - target = usertypes.ClickTarget.tab_bg - elif wintype == background_tab_wintype: - # Middle-click / Ctrl-Click - if background_tabs: - target = usertypes.ClickTarget.tab_bg - else: - target = usertypes.ClickTarget.tab - else: - raise ValueError("Invalid wintype {}".format(debug_type)) - tab = shared.get_tab(self._win_id, target) + tab = shared.get_tab(self._win_id, click_target[wintype]) # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-54419 vercheck = qtutils.version_check diff --git a/qutebrowser/browser/webkit/webkitelem.py b/qutebrowser/browser/webkit/webkitelem.py index 6de1842bc..aab3d237c 100644 --- a/qutebrowser/browser/webkit/webkitelem.py +++ b/qutebrowser/browser/webkit/webkitelem.py @@ -297,6 +297,20 @@ class WebKitElement(webelem.AbstractWebElement): break elem = elem._parent() # pylint: disable=protected-access + def _click_editable(self): + self._elem.evaluateJavaScript('this.focus();') + + def _click_js(self, click_target): + if self.get('target') == '_blank': + # QtWebKit does nothing in this case for some reason... + self._click_fake_event(click_target) + else: + self._elem.evaluateJavaScript('this.click();') + + def _click_fake_event(self, click_target): + self._tab.data.override_target = click_target + super()._click_fake_event(click_target) + def get_child_frames(startframe): """Get all children recursively of a given QWebFrame. diff --git a/qutebrowser/javascript/webelem.js b/qutebrowser/javascript/webelem.js index 99c8edf80..6e7209914 100644 --- a/qutebrowser/javascript/webelem.js +++ b/qutebrowser/javascript/webelem.js @@ -191,5 +191,15 @@ window._qutebrowser.webelem = (function() { } }; + funcs.click = function(id) { + var elem = elements[id]; + elem.click(); + }; + + funcs.focus = function(id) { + var elem = elements[id]; + elem.focus(); + }; + return funcs; })(); diff --git a/tests/end2end/data/scroll/simple.html b/tests/end2end/data/scroll/simple.html index 2b1412ea4..7da9df101 100644 --- a/tests/end2end/data/scroll/simple.html +++ b/tests/end2end/data/scroll/simple.html @@ -6,6 +6,7 @@ Just a link +
 0
 1
diff --git a/tests/end2end/features/editor.feature b/tests/end2end/features/editor.feature
index 84ce47412..c7a5b9726 100644
--- a/tests/end2end/features/editor.feature
+++ b/tests/end2end/features/editor.feature
@@ -62,7 +62,7 @@ Feature: Opening external editors
         When I set up a fake editor returning "foobar"
         And I open data/editor.html
         And I run :click-element id qute-textarea
-        And I wait for "Clicked editable element!" in the log
+        And I wait for "Entering mode KeyMode.insert (reason: clicking input)" in the log
         And I run :open-editor
         And I wait for "Read back: foobar" in the log
         And I run :click-element id qute-button
@@ -72,7 +72,7 @@ Feature: Opening external editors
         When I set up a fake editor returning "foobar"
         And I open data/editor.html
         And I run :click-element id qute-textarea
-        And I wait for "Clicked editable element!" in the log
+        And I wait for "Entering mode KeyMode.insert (reason: clicking input)" in the log
         And I run :leave-mode
         And I wait for "Leaving mode KeyMode.insert (reason: leave current)" in the log
         And I run :open-editor
@@ -84,7 +84,7 @@ Feature: Opening external editors
         When I set up a fake editor returning "foobar"
         And I open data/editor.html
         And I run :click-element id qute-textarea
-        And I wait for "Clicked editable element!" in the log
+        And I wait for "Entering mode KeyMode.insert (reason: clicking input)" in the log
         And I run :leave-mode
         And I wait for "Leaving mode KeyMode.insert (reason: leave current)" in the log
         And I run :enter-mode caret
@@ -99,7 +99,7 @@ Feature: Opening external editors
         When I set up a fake editor replacing "foo" by "bar"
         And I open data/editor.html
         And I run :click-element id qute-textarea
-        And I wait for "Clicked editable element!" in the log
+        And I wait for "Entering mode KeyMode.insert (reason: clicking input)" in the log
         And I run :insert-text foo
         And I wait for "Inserting text into element *" in the log
         And I run :open-editor
diff --git a/tests/end2end/features/hints.feature b/tests/end2end/features/hints.feature
index 8d23d0199..24effc777 100644
--- a/tests/end2end/features/hints.feature
+++ b/tests/end2end/features/hints.feature
@@ -9,17 +9,6 @@ Feature: Using hints
         And I hint with args "links normal" and follow xyz
         Then the error "No hint xyz!" should be shown
 
-    # https://travis-ci.org/The-Compiler/qutebrowser/jobs/159412291
-    @qtwebengine_flaky
-    Scenario: Following a link after scrolling down
-        When I open data/scroll/simple.html
-        And I run :hint links normal
-        And I wait for "hints: *" in the log
-        And I run :scroll-page 0 1
-        And I wait until the scroll position changed
-        And I run :follow-hint a
-        Then the error "Element position is out of view!" should be shown
-
     ### Opening in current or new tab
 
     Scenario: Following a hint and force to open in current tab.
@@ -159,7 +148,7 @@ Feature: Using hints
     Scenario: Hinting inputs without type
         When I open data/hints/input.html
         And I hint with args "inputs" and follow a
-        And I wait for "Entering mode KeyMode.insert (reason: click)" in the log
+        And I wait for "Entering mode KeyMode.insert (reason: clicking input)" in the log
         And I run :leave-mode
         # The actual check is already done above
         Then no crash should happen
@@ -167,7 +156,7 @@ Feature: Using hints
     Scenario: Hinting with ACE editor
         When I open data/hints/ace/ace.html
         And I hint with args "inputs" and follow a
-        And I wait for "Entering mode KeyMode.insert (reason: click)" in the log
+        And I wait for "Entering mode KeyMode.insert (reason: clicking input)" in the log
         And I run :leave-mode
         # The actual check is already done above
         Then no crash should happen
diff --git a/tests/end2end/features/misc.feature b/tests/end2end/features/misc.feature
index 143988c64..4cd0b5602 100644
--- a/tests/end2end/features/misc.feature
+++ b/tests/end2end/features/misc.feature
@@ -574,7 +574,7 @@ Feature: Various utility commands.
     Scenario: Clicking an element by ID
         When I open data/click_element.html
         And I run :click-element id qute-input
-        Then "Clicked editable element!" should be logged
+        Then "Entering mode KeyMode.insert (reason: clicking input)" should be logged
 
     Scenario: Clicking an element with tab target
         When I open data/click_element.html
@@ -585,14 +585,6 @@ Feature: Various utility commands.
             - data/click_element.html
             - data/hello.txt (active)
 
-    @qtwebengine_flaky
-    Scenario: Clicking an element which is out of view
-        When I open data/scroll/simple.html
-        And I run :scroll-page 0 1
-        And I wait until the scroll position changed
-        And I run :click-element id link
-        Then the error "Element position is out of view!" should be shown
-
     ## :command-history-{prev,next}
 
     Scenario: Calling previous command
diff --git a/tests/end2end/features/yankpaste.feature b/tests/end2end/features/yankpaste.feature
index 1c65c63b4..67e9e2e9c 100644
--- a/tests/end2end/features/yankpaste.feature
+++ b/tests/end2end/features/yankpaste.feature
@@ -252,7 +252,7 @@ Feature: Yanking and pasting.
         When I set general -> log-javascript-console to info
         And I open data/paste_primary.html
         And I run :click-element id qute-textarea
-        And I wait for "Clicked editable element!" in the log
+        And I wait for "Entering mode KeyMode.insert (reason: clicking input)" in the log
         And I run :insert-text Hello world
         # Compare
         Then the javascript message "textarea contents: Hello world" should be logged
@@ -263,7 +263,7 @@ Feature: Yanking and pasting.
         And I set content -> allow-javascript to false
         And I open data/paste_primary.html
         And I run :click-element id qute-textarea
-        And I wait for "Clicked editable element!" in the log
+        And I wait for "Entering mode KeyMode.insert (reason: clicking input)" in the log
         And I run :insert-text Hello world
         And I wait for "Inserting text into element *" in the log
         And I run :jseval console.log("textarea contents: " + document.getElementById('qute-textarea').value);
@@ -278,7 +278,7 @@ Feature: Yanking and pasting.
         And I open data/paste_primary.html
         And I set the text field to "one two three four"
         And I run :click-element id qute-textarea
-        And I wait for "Clicked editable element!" in the log
+        And I wait for "Entering mode KeyMode.insert (reason: clicking input)" in the log
         # Move to the beginning and two characters to the right
         And I press the keys ""
         And I press the key ""
@@ -292,7 +292,7 @@ Feature: Yanking and pasting.
         When I set general -> log-javascript-console to info
         And I open data/paste_primary.html
         And I run :click-element id qute-textarea
-        And I wait for "Clicked editable element!" in the log
+        And I wait for "Entering mode KeyMode.insert (reason: clicking input)" in the log
         # Paste and undo
         And I run :insert-text This text should be undone
         And I wait for the javascript message "textarea contents: This text should be undone"
diff --git a/tests/end2end/test_insert_mode.py b/tests/end2end/test_insert_mode.py
index 6939dae0e..bd6aeda5a 100644
--- a/tests/end2end/test_insert_mode.py
+++ b/tests/end2end/test_insert_mode.py
@@ -41,8 +41,8 @@ def test_insert_mode(file_name, elem_id, source, input_text, auto_insert,
     quteproc.open_path(url_path)
 
     quteproc.set_setting('input', 'auto-insert-mode', auto_insert)
-    quteproc.send_cmd(':click-element id {}'.format(elem_id))
-    quteproc.wait_for(message='Clicked editable element!')
+    quteproc.send_cmd(':click-element --force-event id {}'.format(elem_id))
+    quteproc.wait_for(message='Entering mode KeyMode.insert (reason: *)')
     quteproc.send_cmd(':debug-set-fake-clipboard')
 
     if source == 'keypress':
@@ -62,10 +62,8 @@ def test_insert_mode(file_name, elem_id, source, input_text, auto_insert,
     quteproc.wait_for_js('contents: {}'.format(input_text))
 
     quteproc.send_cmd(':leave-mode')
-    quteproc.send_cmd(':hint all')
-    quteproc.wait_for(message='hints: *')
-    quteproc.send_cmd(':follow-hint a')
-    quteproc.wait_for(message='Clicked editable element!')
+    quteproc.send_cmd(':click-element --force-event id {}'.format(elem_id))
+    quteproc.wait_for(message='Entering mode KeyMode.insert (reason: *)')
     quteproc.send_cmd(':enter-mode caret')
     quteproc.send_cmd(':toggle-selection')
     quteproc.send_cmd(':move-to-prev-word')

From 4cf7a3d1f4072275d5de4701d805c15d3da91d7d Mon Sep 17 00:00:00 2001
From: Florian Bruhin 
Date: Wed, 1 Feb 2017 13:31:00 +0100
Subject: [PATCH 197/561] Update changelog

[ci skip]
---
 CHANGELOG.asciidoc | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index 0e74a0f4c..0fb8bda11 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -21,7 +21,15 @@ Changed
 ~~~~~~~
 
 - Scrolling with the scrollwheel while holding shift now scrolls sideways
+- New way of clicking hints with which solves various small issues
 
+Fixed
+-----
+
+- Worked around a segfault with Qt 5.8 when closing a window
+- Worked around a segfault with Qt 5.8 when closing a tab with a search active
+- Fixed various mouse actions (like automatically entering insert mode) not working with Qt 5.8
+- Fixed hints sometimes not working with Qt 5.8
 
 v0.9.1
 ------

From 1f7e9e6b5915c83b79b61d689ce4065ac5566301 Mon Sep 17 00:00:00 2001
From: Florian Bruhin 
Date: Fri, 3 Feb 2017 23:32:20 +0100
Subject: [PATCH 198/561] Add a testcase for #1613

---
 tests/end2end/data/hints/input.html  | 2 ++
 tests/end2end/features/hints.feature | 9 +++++++++
 2 files changed, 11 insertions(+)

diff --git a/tests/end2end/data/hints/input.html b/tests/end2end/data/hints/input.html
index 9c93da15e..b7d86fea8 100644
--- a/tests/end2end/data/hints/input.html
+++ b/tests/end2end/data/hints/input.html
@@ -7,5 +7,7 @@
     
     
         
+ With padding: +
diff --git a/tests/end2end/features/hints.feature b/tests/end2end/features/hints.feature index 24effc777..9cd91f151 100644 --- a/tests/end2end/features/hints.feature +++ b/tests/end2end/features/hints.feature @@ -153,6 +153,15 @@ Feature: Using hints # The actual check is already done above Then no crash should happen + # https://github.com/The-Compiler/qutebrowser/issues/1613 + Scenario: Hinting inputs with padding + When I open data/hints/input.html + And I hint with args "inputs" and follow s + And I wait for "Entering mode KeyMode.insert (reason: clicking input)" in the log + And I run :leave-mode + # The actual check is already done above + Then no crash should happen + Scenario: Hinting with ACE editor When I open data/hints/ace/ace.html And I hint with args "inputs" and follow a From 20f83316e00332119951d72c1a48f1caa954e291 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 3 Feb 2017 23:41:33 +0100 Subject: [PATCH 199/561] Fix misc.feature with QtWebEngine We need to make sure we don't leave any stale prompts open. --- tests/end2end/features/misc.feature | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/end2end/features/misc.feature b/tests/end2end/features/misc.feature index 4cd0b5602..73956931a 100644 --- a/tests/end2end/features/misc.feature +++ b/tests/end2end/features/misc.feature @@ -304,7 +304,7 @@ Feature: Various utility commands. # pdfjs support - @qtwebengine_todo: pdfjs is not implemented yet + @qtwebengine_skip: pdfjs is not implemented yet Scenario: pdfjs is used for pdf files Given pdfjs is available When I set content -> enable-pdfjs to true @@ -318,7 +318,7 @@ Feature: Various utility commands. And I open data/misc/test.pdf Then "Download test.pdf finished" should be logged - @qtwebengine_todo: pdfjs is not implemented yet + @qtwebengine_skip: pdfjs is not implemented yet Scenario: Downloading a pdf via pdf.js button (issue 1214) Given pdfjs is available # WORKAROUND to prevent the "Painter ended with 2 saved states" warning @@ -396,6 +396,7 @@ Feature: Various utility commands. And I wait for "Entering mode KeyMode.prompt *" in the log And I press the key "" And I press the key "" + And I run :leave-mode Then no crash should happen ## Custom headers From 7d1316fe03c12213dcfa90dee7e70786ec6375f9 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 3 Feb 2017 23:46:44 +0100 Subject: [PATCH 200/561] Remove webelem.py from coverage check These tests need a bigger overhaul to work with QtWebEngine and use actual web elements. --- scripts/dev/check_coverage.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/dev/check_coverage.py b/scripts/dev/check_coverage.py index 895993bae..68b0b626b 100644 --- a/scripts/dev/check_coverage.py +++ b/scripts/dev/check_coverage.py @@ -63,8 +63,8 @@ PERFECT_FILES = [ 'qutebrowser/browser/webkit/rfc6266.py'), ('tests/unit/browser/webkit/test_webkitelem.py', 'qutebrowser/browser/webkit/webkitelem.py'), - ('tests/unit/browser/webkit/test_webkitelem.py', - 'qutebrowser/browser/webelem.py'), + # ('tests/unit/browser/webkit/test_webkitelem.py', + # 'qutebrowser/browser/webelem.py'), ('tests/unit/browser/webkit/network/test_schemehandler.py', 'qutebrowser/browser/webkit/network/schemehandler.py'), ('tests/unit/browser/webkit/network/test_filescheme.py', From 66719c5ecc88eb3433c1551d34f1b1ef281a24c4 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 3 Feb 2017 23:55:14 +0100 Subject: [PATCH 201/561] Fix lint --- qutebrowser/browser/mouse.py | 3 ++- qutebrowser/browser/webelem.py | 2 +- qutebrowser/browser/webengine/webengineelem.py | 7 ++++++- qutebrowser/browser/webengine/webenginetab.py | 2 +- tests/end2end/fixtures/quteprocess.py | 3 ++- 5 files changed, 12 insertions(+), 5 deletions(-) diff --git a/qutebrowser/browser/mouse.py b/qutebrowser/browser/mouse.py index 23ddce6ee..c284f3eab 100644 --- a/qutebrowser/browser/mouse.py +++ b/qutebrowser/browser/mouse.py @@ -208,6 +208,7 @@ class MouseEventFilter(QObject): if evtype not in self._handlers: return False if obj is not self._tab.event_target(): - log.mouse.debug("Ignoring {} to {}".format(event.__class__.__name__, obj)) + log.mouse.debug("Ignoring {} to {}".format(event.__class__.__name__, + obj)) return False return self._handlers[evtype](event) diff --git a/qutebrowser/browser/webelem.py b/qutebrowser/browser/webelem.py index 560674e98..dfcd69ef7 100644 --- a/qutebrowser/browser/webelem.py +++ b/qutebrowser/browser/webelem.py @@ -393,7 +393,7 @@ class AbstractWebElement(collections.abc.MutableMapping): """Simulate a click on the element. Args: - click_target: An usertypes.ClickTarget member, what kind of click + click_target: A usertypes.ClickTarget member, what kind of click to simulate. force_event: Force generating a fake mouse event. """ diff --git a/qutebrowser/browser/webengine/webengineelem.py b/qutebrowser/browser/webengine/webengineelem.py index fe42d355d..8e493bf9b 100644 --- a/qutebrowser/browser/webengine/webengineelem.py +++ b/qutebrowser/browser/webengine/webengineelem.py @@ -25,7 +25,7 @@ from PyQt5.QtCore import QRect, Qt, QPoint from PyQt5.QtGui import QMouseEvent -from qutebrowser.utils import log, javascript, usertypes +from qutebrowser.utils import log, javascript from qutebrowser.browser import webelem @@ -153,9 +153,14 @@ class WebEngineElement(webelem.AbstractWebElement): def _click_editable(self): # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-58515 + # pylint doesn't know about Qt.MouseEventSynthesizedBySystem + # because it was added in Qt 5.6, but we can be sure we use that with + # QtWebEngine. + # pylint: disable=no-member ev = QMouseEvent(QMouseEvent.MouseButtonPress, QPoint(0, 0), QPoint(0, 0), QPoint(0, 0), Qt.NoButton, Qt.NoButton, Qt.NoModifier, Qt.MouseEventSynthesizedBySystem) + # pylint: enable=no-member self._tab.send_event(ev) # This actually "clicks" the element by calling focus() on it in JS. js_code = javascript.assemble('webelem', 'focus', self._id) diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index a555a2d15..6c654a6ee 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -27,7 +27,7 @@ import functools from PyQt5.QtCore import pyqtSlot, Qt, QEvent, QPoint, QUrl, QTimer from PyQt5.QtGui import QKeyEvent, QIcon # pylint: disable=no-name-in-module,import-error,useless-suppression -from PyQt5.QtWidgets import QOpenGLWidget, QApplication +from PyQt5.QtWidgets import QApplication from PyQt5.QtWebEngineWidgets import (QWebEnginePage, QWebEngineScript, QWebEngineProfile) # pylint: enable=no-name-in-module,import-error,useless-suppression diff --git a/tests/end2end/fixtures/quteprocess.py b/tests/end2end/fixtures/quteprocess.py index 831e210ce..4fe3ff12b 100644 --- a/tests/end2end/fixtures/quteprocess.py +++ b/tests/end2end/fixtures/quteprocess.py @@ -218,7 +218,8 @@ class QuteProc(testprocess.Process): log_line.waited_for = True self._is_ready('load') elif log_line.category == 'misc' and any(testutils.pattern_match( - pattern=pattern, value=log_line.message) for pattern in start_okay_messages_focus): + pattern=pattern, value=log_line.message) for pattern in + start_okay_messages_focus): self._is_ready('focus') elif (log_line.category == 'init' and log_line.module == 'standarddir' and From 6cd4105ea1f6fe86f0b963a75f29d1cdc3f6d532 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 4 Feb 2017 00:11:48 +0100 Subject: [PATCH 202/561] Fix long line --- qutebrowser/browser/mouse.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qutebrowser/browser/mouse.py b/qutebrowser/browser/mouse.py index c284f3eab..19e37ad97 100644 --- a/qutebrowser/browser/mouse.py +++ b/qutebrowser/browser/mouse.py @@ -208,7 +208,7 @@ class MouseEventFilter(QObject): if evtype not in self._handlers: return False if obj is not self._tab.event_target(): - log.mouse.debug("Ignoring {} to {}".format(event.__class__.__name__, - obj)) + log.mouse.debug("Ignoring {} to {}".format( + event.__class__.__name__, obj)) return False return self._handlers[evtype](event) From 987cb236e89c1c6d822fbe57cd5eb5ff7decb62a Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 4 Feb 2017 11:52:59 +0100 Subject: [PATCH 203/561] Revert changes to WebEngineView.createWindow We still need those to open tabs in the background when middle-clicked. This was a regression introduced in 545539f28d86c162f0e7f803480ad7fe24b6870f. Fixes #2276. --- qutebrowser/browser/webengine/webview.py | 36 +++++++++++++++--------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/qutebrowser/browser/webengine/webview.py b/qutebrowser/browser/webengine/webview.py index 585c184d9..f3e9d6a94 100644 --- a/qutebrowser/browser/webengine/webview.py +++ b/qutebrowser/browser/webengine/webview.py @@ -73,8 +73,10 @@ class WebEngineView(QWebEngineView): The new QWebEngineView object. """ debug_type = debug.qenum_key(QWebEnginePage, wintype) + background_tabs = config.get('tabs', 'background-tabs') - log.webview.debug("createWindow with type {}".format(debug_type)) + log.webview.debug("createWindow with type {}, background_tabs " + "{}".format(debug_type, background_tabs)) try: background_tab_wintype = QWebEnginePage.WebBrowserBackgroundTab @@ -83,22 +85,30 @@ class WebEngineView(QWebEngineView): # this with a newer Qt... background_tab_wintype = 0x0003 - click_target = { + if wintype == QWebEnginePage.WebBrowserWindow: # Shift-Alt-Click - QWebEnginePage.WebBrowserWindow: usertypes.ClickTarget.window, - # ? - QWebEnginePage.WebDialog: usertypes.ClickTarget.tab, - # Middle-click / Ctrl-Click with Shift - QWebEnginePage.WebBrowserTab: usertypes.ClickTarget.tab_bg, - # Middle-click / Ctrl-Click - background_tab_wintype: usertypes.ClickTarget.tab, - } - - if wintype == QWebEnginePage.WebDialog: + target = usertypes.ClickTarget.window + elif wintype == QWebEnginePage.WebDialog: log.webview.warning("{} requested, but we don't support " "that!".format(debug_type)) + target = usertypes.ClickTarget.tab + elif wintype == QWebEnginePage.WebBrowserTab: + # Middle-click / Ctrl-Click with Shift + # FIXME:qtwebengine this also affects target=_blank links... + if background_tabs: + target = usertypes.ClickTarget.tab + else: + target = usertypes.ClickTarget.tab_bg + elif wintype == background_tab_wintype: + # Middle-click / Ctrl-Click + if background_tabs: + target = usertypes.ClickTarget.tab_bg + else: + target = usertypes.ClickTarget.tab + else: + raise ValueError("Invalid wintype {}".format(debug_type)) - tab = shared.get_tab(self._win_id, click_target[wintype]) + tab = shared.get_tab(self._win_id, target) # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-54419 vercheck = qtutils.version_check From 148b34a50bb0fe800b987ff8d5153de151245915 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 4 Feb 2017 13:28:38 +0100 Subject: [PATCH 204/561] Fix remaining Qt 5.8 QtWebEngine test failures --- doc/help/commands.asciidoc | 3 +++ qutebrowser/misc/utilcmds.py | 7 +++++-- tests/end2end/features/tabs.feature | 1 + tests/end2end/features/utilcmds.feature | 27 ++++++++++--------------- tests/end2end/fixtures/quteprocess.py | 8 ++++++++ 5 files changed, 28 insertions(+), 18 deletions(-) diff --git a/doc/help/commands.asciidoc b/doc/help/commands.asciidoc index eb7a25dee..3f656dc49 100644 --- a/doc/help/commands.asciidoc +++ b/doc/help/commands.asciidoc @@ -1138,6 +1138,9 @@ Show an info message in the statusbar. ==== positional arguments * +'text'+: The text to show. +==== count +How many times to show the message + [[message-warning]] === message-warning Syntax: +:message-warning 'text'+ diff --git a/qutebrowser/misc/utilcmds.py b/qutebrowser/misc/utilcmds.py index affcb6f1d..893e11342 100644 --- a/qutebrowser/misc/utilcmds.py +++ b/qutebrowser/misc/utilcmds.py @@ -115,13 +115,16 @@ def message_error(text): @cmdutils.register(hide=True) -def message_info(text): +@cmdutils.argument('count', count=True) +def message_info(text, count=1): """Show an info message in the statusbar. Args: text: The text to show. + count: How many times to show the message """ - message.info(text) + for _ in range(count): + message.info(text) @cmdutils.register(hide=True) diff --git a/tests/end2end/features/tabs.feature b/tests/end2end/features/tabs.feature index 9d32fc126..0ba6d62b7 100644 --- a/tests/end2end/features/tabs.feature +++ b/tests/end2end/features/tabs.feature @@ -917,6 +917,7 @@ Feature: Tab management And I run :buffer "99/1" Then the error "There's no window with id 99!" should be shown + @qtwebengine_flaky Scenario: :buffer with matching window index Given I have a fresh instance When I open data/title.html diff --git a/tests/end2end/features/utilcmds.feature b/tests/end2end/features/utilcmds.feature index 22acabc0b..426484600 100644 --- a/tests/end2end/features/utilcmds.feature +++ b/tests/end2end/features/utilcmds.feature @@ -99,29 +99,24 @@ Feature: Miscellaneous utility commands exposed to the user. ## :repeat-command Scenario: :repeat-command - When I run :scroll down + When I run :message-info test1 And I run :repeat-command - And I run :scroll up - Then the page should be scrolled vertically + Then the message "test1" should be shown + And the message "test1" should be shown Scenario: :repeat-command with count - When I run :scroll down with count 3 - And I wait until the scroll position changed - And I run :scroll up - And I wait until the scroll position changed + When I run :message-info test2 And I run :repeat-command with count 2 - And I wait until the scroll position changed to 0/0 - Then the page should not be scrolled + Then the message "test2" should be shown + And the message "test2" should be shown + And the message "test2" should be shown Scenario: :repeat-command with not-normal command inbetween - When I run :scroll down with count 3 - And I wait until the scroll position changed - And I run :scroll up - And I wait until the scroll position changed + When I run :message-info test3 And I run :prompt-accept - And I run :repeat-command with count 2 - And I wait until the scroll position changed to 0/0 - Then the page should not be scrolled + And I run :repeat-command + Then the message "test3" should be shown + And the message "test3" should be shown And the error "prompt-accept: This command is only allowed in prompt/yesno mode, not normal." should be shown Scenario: :repeat-command with mode-switching command diff --git a/tests/end2end/fixtures/quteprocess.py b/tests/end2end/fixtures/quteprocess.py index 4fe3ff12b..2f4908044 100644 --- a/tests/end2end/fixtures/quteprocess.py +++ b/tests/end2end/fixtures/quteprocess.py @@ -63,6 +63,14 @@ def is_ignored_lowlevel_message(message): return True elif 'CERT_PKIXVerifyCert for localhost failed' in message: return True + elif 'Invalid node channel message' in message: + # Started appearing in sessions.feature with Qt 5.8... + return True + elif ("_dl_allocate_tls_init: Assertion `listp->slotinfo[cnt].gen <= " + "GL(dl_tls_generation)' failed!" in message): + # Started appearing with Qt 5.8... + # http://patchwork.sourceware.org/patch/10255/ + return True return False From 22873f829c112d3956ead5112a19d84fcfd856ed Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 4 Feb 2017 17:43:27 +0100 Subject: [PATCH 205/561] Update docs --- CHANGELOG.asciidoc | 5 +++++ README.asciidoc | 1 + doc/userscripts.asciidoc | 2 +- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 0fb8bda11..ff303c637 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -17,6 +17,11 @@ This project adheres to http://semver.org/[Semantic Versioning]. v0.10.0 (unreleased) -------------------- +Added +----- + +- Userscripts now have a new `$QUTE_COMMANDLINE_TEXT` environment variable, containing the current commandline contents. + Changed ~~~~~~~ diff --git a/README.asciidoc b/README.asciidoc index bdc4065c0..bbd6277a5 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -241,6 +241,7 @@ Contributors, sorted by the number of commits in descending order: * xd1le * rsteube * rmortens +* pkill9 * oniondreams * issue * haxwithaxe diff --git a/doc/userscripts.asciidoc b/doc/userscripts.asciidoc index 8904d0ef3..b44a6b8ff 100644 --- a/doc/userscripts.asciidoc +++ b/doc/userscripts.asciidoc @@ -38,7 +38,7 @@ The following environment variables will be set when a userscript is launched: - `QUTE_CONFIG_DIR`: Path of the directory containing qutebrowser's configuration. - `QUTE_DATA_DIR`: Path of the directory containing qutebrowser's data. - `QUTE_DOWNLOAD_DIR`: Path of the downloads directory. -- `QUTE_COMMANDLINE_TEXT`: Text currently in qutebrowser's command line. (Depends on which window the userscript is launched in) +- `QUTE_COMMANDLINE_TEXT`: Text currently in qutebrowser's command line. In `command` mode: From 1144060ab9d57573c11d245006da47917f500311 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 4 Feb 2017 17:49:31 +0100 Subject: [PATCH 206/561] Update docs --- CHANGELOG.asciidoc | 1 + README.asciidoc | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index ff303c637..4d9cbfdb9 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -27,6 +27,7 @@ Changed - Scrolling with the scrollwheel while holding shift now scrolls sideways - New way of clicking hints with which solves various small issues +- When yanking a mailto: link via hints, the mailto: prefix is now stripped Fixed ----- diff --git a/README.asciidoc b/README.asciidoc index bbd6277a5..6c6214340 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -146,8 +146,8 @@ Contributors, sorted by the number of commits in descending order: * Florian Bruhin * Daniel Schadt * Ryan Roden-Corrent -* Jakub Klinkovský * Jan Verbeek +* Jakub Klinkovský * Antoni Boucher * Lamar Pavel * Marshall Lochbaum From 97feef03fdfe55b4399296b304aafbf90748ded6 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 4 Feb 2017 17:52:50 +0100 Subject: [PATCH 207/561] Update changelog --- CHANGELOG.asciidoc | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 4d9cbfdb9..193673f6b 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -36,6 +36,7 @@ Fixed - Worked around a segfault with Qt 5.8 when closing a tab with a search active - Fixed various mouse actions (like automatically entering insert mode) not working with Qt 5.8 - Fixed hints sometimes not working with Qt 5.8 +- `:enter-mode` now refuses to enter modes which can't be entered manually (which caused crashes). v0.9.1 ------ From 10ec240de14d8d2d8cfc87aa06fef9c0c57a6ce9 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 4 Feb 2017 18:01:22 +0100 Subject: [PATCH 208/561] Update changelog --- CHANGELOG.asciidoc | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 193673f6b..4c30d2dbb 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -37,6 +37,7 @@ Fixed - Fixed various mouse actions (like automatically entering insert mode) not working with Qt 5.8 - Fixed hints sometimes not working with Qt 5.8 - `:enter-mode` now refuses to enter modes which can't be entered manually (which caused crashes). +- `:record-macro` (`q`) now doesn't try to record macros for special keys without a text. v0.9.1 ------ From 2792503010580d329a40e50a4a559eca1e8280e0 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 4 Feb 2017 18:04:23 +0100 Subject: [PATCH 209/561] Update authors --- README.asciidoc | 1 + 1 file changed, 1 insertion(+) diff --git a/README.asciidoc b/README.asciidoc index 6c6214340..10f5a740b 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -277,6 +277,7 @@ Contributors, sorted by the number of commits in descending order: * Arseniy Seroka * Andy Balaam * Andreas Fischer +* Akselmo // QUTE_AUTHORS_END The following people have contributed graphics: From 660e3915a1016461f36e9381e1b6027f536aaec4 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 4 Feb 2017 18:08:53 +0100 Subject: [PATCH 210/561] Update docs --- CHANGELOG.asciidoc | 1 + README.asciidoc | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 4c30d2dbb..4a0812ba1 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -38,6 +38,7 @@ Fixed - Fixed hints sometimes not working with Qt 5.8 - `:enter-mode` now refuses to enter modes which can't be entered manually (which caused crashes). - `:record-macro` (`q`) now doesn't try to record macros for special keys without a text. +- Fixed PAC (proxy autoconfig) not working with QtWebKit v0.9.1 ------ diff --git a/README.asciidoc b/README.asciidoc index 10f5a740b..0fa8dc9b0 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -187,6 +187,7 @@ Contributors, sorted by the number of commits in descending order: * Oliver Caldwell * Julian Weigt * Sebastian Frysztak +* Nikolay Amiantov * Jonas Schürmann * error800 * Michael Hoang @@ -197,7 +198,6 @@ Contributors, sorted by the number of commits in descending order: * Tomasz Kramkowski * Samuel Walladge * Peter Rice -* Nikolay Amiantov * Ismail S * Halfwit * David Vogt From 0cc7f845e6c34a729a2c8776ea50f3eebc2d352a Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 4 Feb 2017 18:10:34 +0100 Subject: [PATCH 211/561] Simplify test --- tests/unit/browser/webkit/network/test_pac.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/tests/unit/browser/webkit/network/test_pac.py b/tests/unit/browser/webkit/network/test_pac.py index af03c9859..c5c12b1a1 100644 --- a/tests/unit/browser/webkit/network/test_pac.py +++ b/tests/unit/browser/webkit/network/test_pac.py @@ -125,14 +125,10 @@ def test_invalid_port(): res.resolve(QNetworkProxyQuery(QUrl("https://example.com/test"))) -def test_no_function(): +@pytest.mark.parametrize(string, ["", "{"]) +def test_wrong_pac_string(string): with pytest.raises(pac.EvalProxyError): - pac.PACResolver("") - - -def test_fail_eval(): - with pytest.raises(pac.EvalProxyError): - pac.PACResolver("{") + pac.PACResolver(string) @pytest.mark.parametrize("value", [ From 662859c133b0294a298df0128faf1ab0e31535d6 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 4 Feb 2017 18:10:57 +0100 Subject: [PATCH 212/561] Remove unnecessary inheritance on Python 3 --- qutebrowser/browser/network/pac.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutebrowser/browser/network/pac.py b/qutebrowser/browser/network/pac.py index dfa4d42d6..91f15dfaf 100644 --- a/qutebrowser/browser/network/pac.py +++ b/qutebrowser/browser/network/pac.py @@ -126,7 +126,7 @@ class _PACContext(QObject): return QHostAddress(QHostAddress.LocalHost).toString() -class PACResolver(object): +class PACResolver: """Evaluate PAC script files and resolve proxies.""" From 460389c30d31fe1f1a95e1938d706f2b4edc0853 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 4 Feb 2017 18:26:12 +0100 Subject: [PATCH 213/561] Simplify some utilcmds tests --- tests/end2end/features/utilcmds.feature | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/tests/end2end/features/utilcmds.feature b/tests/end2end/features/utilcmds.feature index 426484600..5804dd8cd 100644 --- a/tests/end2end/features/utilcmds.feature +++ b/tests/end2end/features/utilcmds.feature @@ -30,24 +30,28 @@ Feature: Miscellaneous utility commands exposed to the user. ## :repeat Scenario: :repeat simple - When I run :repeat 5 scroll-px 10 0 - And I wait until the scroll position changed to 50/0 - # Then already covered by above And + When I run :repeat 2 message-info repeat-test + Then the message "repeat-test" should be shown + And the message "repeat-test" should be shown Scenario: :repeat zero times - When I run :repeat 0 scroll-px 10 0 - And I wait 0.01s - Then the page should not be scrolled + When I run :repeat 0 message-error "repeat-test 2" + # If we have an error, the test will fail + Then no crash should happen ## :run-with-count Scenario: :run-with-count - When I run :run-with-count 2 scroll down - Then "command called: scroll ['down'] (count=2)" should be logged + When I run :run-with-count 2 message-info "run-with-count test" + Then the message "run-with-count test" should be shown + And the message "run-with-count test" should be shown Scenario: :run-with-count with count - When I run :run-with-count 2 scroll down with count 3 - Then "command called: scroll ['down'] (count=6)" should be logged + When I run :run-with-count 2 message-info "run-with-count test 2" with count 2 + Then the message "run-with-count test 2" should be shown + And the message "run-with-count test 2" should be shown + And the message "run-with-count test 2" should be shown + And the message "run-with-count test 2" should be shown ## :message-* From 1f170b8746a36ba232e107513e18438f6702f618 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 4 Feb 2017 18:35:14 +0100 Subject: [PATCH 214/561] Update changelog --- CHANGELOG.asciidoc | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 4a0812ba1..0362f7691 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -39,6 +39,7 @@ Fixed - `:enter-mode` now refuses to enter modes which can't be entered manually (which caused crashes). - `:record-macro` (`q`) now doesn't try to record macros for special keys without a text. - Fixed PAC (proxy autoconfig) not working with QtWebKit +- `:download --mhtml` now uses the new file dialog v0.9.1 ------ From 7b0f4e08129f130d164d0bcb4e8ca5d981d6ef37 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 4 Feb 2017 18:41:22 +0100 Subject: [PATCH 215/561] Use mock for open_file tests --- tests/unit/utils/test_utils.py | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/tests/unit/utils/test_utils.py b/tests/unit/utils/test_utils.py index ef6d1e72c..c790e7a31 100644 --- a/tests/unit/utils/test_utils.py +++ b/tests/unit/utils/test_utils.py @@ -30,7 +30,7 @@ import socket import re import shlex -from PyQt5.QtCore import Qt +from PyQt5.QtCore import Qt, QUrl from PyQt5.QtGui import QColor, QClipboard import pytest @@ -1020,19 +1020,12 @@ class TestOpenFile: assert re.match( r"Opening /foo/bar with \[.*python.*/foo/bar.*\]", result) - def test_system_default_application(self, caplog, config_stub, - monkeypatch): - # pylint: disable=attribute-defined-outside-init - self.open_url_called = False + def test_system_default_application(self, caplog, config_stub, mocker): config_stub.data = {'general': {'default-open-dispatcher': ''}} - monkeypatch.setattr('PyQt5.QtGui.QDesktopServices.openUrl', - self.mock_open_url) + m = mocker.patch('PyQt5.QtGui.QDesktopServices.openUrl', spec={}, + new_callable=mocker.Mock) utils.open_file('/foo/bar') result = caplog.records[0].message assert re.match( r"Opening /foo/bar with the system application", result) - assert self.open_url_called - - def mock_open_url(self, url): - # pylint: disable=attribute-defined-outside-init - self.open_url_called = True + m.assert_called_with(QUrl('file:///foo/bar')) From fb189f2539f81c3489fa3ac01a3ee4b7a7ebc5a1 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 4 Feb 2017 18:53:02 +0100 Subject: [PATCH 216/561] Update docs --- CHANGELOG.asciidoc | 1 + README.asciidoc | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 0362f7691..0dc4f9da0 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -21,6 +21,7 @@ Added ----- - Userscripts now have a new `$QUTE_COMMANDLINE_TEXT` environment variable, containing the current commandline contents. +- New `ripbang` userscript to create a searchengine from a duckduckgo bang Changed ~~~~~~~ diff --git a/README.asciidoc b/README.asciidoc index 0fa8dc9b0..50375ad1a 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -213,6 +213,7 @@ Contributors, sorted by the number of commits in descending order: * Brian Jackson * thuck * sbinix +* rsteube * neeasade * jnphilipp * Tobias Patzl @@ -239,7 +240,6 @@ Contributors, sorted by the number of commits in descending order: * Eric Drechsel * zwarag * xd1le -* rsteube * rmortens * pkill9 * oniondreams From b6e31d417285c8c51818e90ca1a13707abd5de57 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 4 Feb 2017 19:03:59 +0100 Subject: [PATCH 217/561] Fix parametrizing --- tests/unit/browser/webkit/network/test_pac.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/browser/webkit/network/test_pac.py b/tests/unit/browser/webkit/network/test_pac.py index c5c12b1a1..58b518886 100644 --- a/tests/unit/browser/webkit/network/test_pac.py +++ b/tests/unit/browser/webkit/network/test_pac.py @@ -125,7 +125,7 @@ def test_invalid_port(): res.resolve(QNetworkProxyQuery(QUrl("https://example.com/test"))) -@pytest.mark.parametrize(string, ["", "{"]) +@pytest.mark.parametrize('string', ["", "{"]) def test_wrong_pac_string(string): with pytest.raises(pac.EvalProxyError): pac.PACResolver(string) From fd29528e4f0c3ef445a85ea1d9e2c96bcc3e37ed Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 4 Feb 2017 21:39:22 +0100 Subject: [PATCH 218/561] Add proxy support for QtWebEngine with Qt >= 5.8 See #666 Fixes #2082 --- qutebrowser/app.py | 6 ++++++ qutebrowser/browser/webkit/webkittab.py | 6 ++++-- qutebrowser/config/configdata.py | 5 +++-- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/qutebrowser/app.py b/qutebrowser/app.py index de8a131ad..a2a062f51 100644 --- a/qutebrowser/app.py +++ b/qutebrowser/app.py @@ -47,6 +47,7 @@ from qutebrowser.commands import cmdutils, runners, cmdexc from qutebrowser.config import style, config, websettings, configexc from qutebrowser.browser import (urlmarks, adblock, history, browsertab, downloads) +from qutebrowser.browser.network import proxy from qutebrowser.browser.webkit import cookies, cache from qutebrowser.browser.webkit.network import networkmanager from qutebrowser.keyinput import macros @@ -386,6 +387,11 @@ def _init_modules(args, crash_handler): log.init.debug("Initializing network...") networkmanager.init() + if qtutils.version_check('5.8'): + # Otherwise we can only initialize it for QtWebKit because of crashes + log.init.debug("Initializing proxy...") + proxy.init() + log.init.debug("Initializing readline-bridge...") readline_bridge = readline.ReadlineBridge() objreg.register('readline-bridge', readline_bridge) diff --git a/qutebrowser/browser/webkit/webkittab.py b/qutebrowser/browser/webkit/webkittab.py index 82a25d634..9cb4ea68c 100644 --- a/qutebrowser/browser/webkit/webkittab.py +++ b/qutebrowser/browser/webkit/webkittab.py @@ -42,8 +42,10 @@ def init(): """Initialize QtWebKit-specific modules.""" qapp = QApplication.instance() - log.init.debug("Initializing proxy...") - proxy.init() + if not qtutils.version_check('5.8'): + # Otherwise we initialize it globally in app.py + log.init.debug("Initializing proxy...") + proxy.init() log.init.debug("Initializing js-bridge...") js_bridge = webkitqutescheme.JSBridge(qapp) diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index 91588468d..5a5770d38 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -35,7 +35,7 @@ from qutebrowser.config import configtypes as typ from qutebrowser.config import sections as sect from qutebrowser.config.value import SettingValue from qutebrowser.utils.qtutils import MAXVALS -from qutebrowser.utils import usertypes +from qutebrowser.utils import usertypes, qtutils FIRST_COMMENT = r""" @@ -433,7 +433,8 @@ def data(readonly=False): ('proxy', SettingValue(typ.Proxy(), 'system', - backends=[usertypes.Backend.QtWebKit]), + backends=(None if qtutils.version_check('5.8') + else [usertypes.Backend.QtWebKit])), "The proxy to use.\n\n" "In addition to the listed values, you can use a `socks://...` " "or `http://...` URL."), From d3f359e490512de89a1a0e42c32187bb3add1f9d Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 4 Feb 2017 21:43:32 +0100 Subject: [PATCH 219/561] Disallow caret mode with QtWebEngine --- qutebrowser/keyinput/modeman.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/qutebrowser/keyinput/modeman.py b/qutebrowser/keyinput/modeman.py index eb4baf7f5..54487ae5c 100644 --- a/qutebrowser/keyinput/modeman.py +++ b/qutebrowser/keyinput/modeman.py @@ -265,10 +265,17 @@ class ModeManager(QObject): m = usertypes.KeyMode[mode] except KeyError: raise cmdexc.CommandError("Mode {} does not exist!".format(mode)) + + backend = usertypes.arg2backend[objreg.get('args').backend] if m in [usertypes.KeyMode.hint, usertypes.KeyMode.command, usertypes.KeyMode.yesno, usertypes.KeyMode.prompt]: raise cmdexc.CommandError( "Mode {} can't be entered manually!".format(mode)) + elif (m == usertypes.KeyMode.caret and + backend == usertypes.Backend.QtWebEngine): + raise cmdexc.CommandError("Caret mode is not supported with " + "QtWebEngine yet.") + self.enter(m, 'command') @pyqtSlot(usertypes.KeyMode, str, bool) From f99bf661ccdb12d3d0ddc83931d8959d782b8945 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 4 Feb 2017 21:59:39 +0100 Subject: [PATCH 220/561] Fix lint --- qutebrowser/app.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qutebrowser/app.py b/qutebrowser/app.py index a2a062f51..5b7cf545c 100644 --- a/qutebrowser/app.py +++ b/qutebrowser/app.py @@ -376,6 +376,7 @@ def _init_modules(args, crash_handler): args: The argparse namespace. crash_handler: The CrashHandler instance. """ + # pylint: disable=too-many-statements log.init.debug("Initializing prompts...") prompt.init() From 6f0c8245e0513abce2d8c81577d0a528219708c3 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 4 Feb 2017 22:06:35 +0100 Subject: [PATCH 221/561] Fix test_insert_mode with QtWebEngine We can't pretend to enter caret mode now --- tests/end2end/test_insert_mode.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/tests/end2end/test_insert_mode.py b/tests/end2end/test_insert_mode.py index bd6aeda5a..727e7633d 100644 --- a/tests/end2end/test_insert_mode.py +++ b/tests/end2end/test_insert_mode.py @@ -64,9 +64,12 @@ def test_insert_mode(file_name, elem_id, source, input_text, auto_insert, quteproc.send_cmd(':leave-mode') quteproc.send_cmd(':click-element --force-event id {}'.format(elem_id)) quteproc.wait_for(message='Entering mode KeyMode.insert (reason: *)') - quteproc.send_cmd(':enter-mode caret') - quteproc.send_cmd(':toggle-selection') - quteproc.send_cmd(':move-to-prev-word') + + if not request.config.webengine: + quteproc.send_cmd(':enter-mode caret') + quteproc.send_cmd(':toggle-selection') + quteproc.send_cmd(':move-to-prev-word') + quteproc.send_cmd(':yank selection') expected_message = '{} chars yanked to clipboard'.format(len(input_text)) @@ -91,11 +94,3 @@ def test_auto_leave_insert_mode(quteproc): # Select the disabled input box to leave insert mode quteproc.send_cmd(':follow-hint s') quteproc.wait_for(message='Clicked non-editable element!') - quteproc.send_cmd(':enter-mode caret') - quteproc.send_cmd(':paste-primary') - - expected_message = ('paste-primary: This command is only allowed in ' - 'insert mode, not caret.') - quteproc.mark_expected(category='message', - loglevel=logging.ERROR, - message=expected_message) From e8bbc1adf8355d142d9f336b70504d27c88b0750 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 4 Feb 2017 22:12:13 +0100 Subject: [PATCH 222/561] QtWebEngine: Fix insert mode checks with zoom Fixes #2169 --- CHANGELOG.asciidoc | 1 + qutebrowser/browser/mouse.py | 3 ++- tests/end2end/test_insert_mode.py | 5 ++++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 0dc4f9da0..9132f9717 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -41,6 +41,7 @@ Fixed - `:record-macro` (`q`) now doesn't try to record macros for special keys without a text. - Fixed PAC (proxy autoconfig) not working with QtWebKit - `:download --mhtml` now uses the new file dialog +- Insert mode now gets entered correctly with a non-100% zoom v0.9.1 ------ diff --git a/qutebrowser/browser/mouse.py b/qutebrowser/browser/mouse.py index 19e37ad97..7052c9353 100644 --- a/qutebrowser/browser/mouse.py +++ b/qutebrowser/browser/mouse.py @@ -95,7 +95,8 @@ class MouseEventFilter(QObject): self._ignore_wheel_event = True if e.button() != Qt.NoButton: - self._tab.elements.find_at_pos(e.pos(), + zoom = self._tab.zoom.factor() + self._tab.elements.find_at_pos(e.pos() / zoom, self._mousepress_insertmode_cb) return False diff --git a/tests/end2end/test_insert_mode.py b/tests/end2end/test_insert_mode.py index 727e7633d..59c279feb 100644 --- a/tests/end2end/test_insert_mode.py +++ b/tests/end2end/test_insert_mode.py @@ -35,12 +35,15 @@ import pytest ('autofocus.html', 'qute-input-autofocus', 'keypress', 'cutebrowser', 'true'), ]) -def test_insert_mode(file_name, elem_id, source, input_text, auto_insert, +@pytest.mark.parametrize('zoom', [100, 125]) +def test_insert_mode(file_name, elem_id, source, input_text, auto_insert, zoom, quteproc, request): url_path = 'data/insert_mode_settings/html/{}'.format(file_name) quteproc.open_path(url_path) quteproc.set_setting('input', 'auto-insert-mode', auto_insert) + quteproc.send_cmd(':zoom {}'.format(zoom)) + quteproc.send_cmd(':click-element --force-event id {}'.format(elem_id)) quteproc.wait_for(message='Entering mode KeyMode.insert (reason: *)') quteproc.send_cmd(':debug-set-fake-clipboard') From 385969e05f2efcf79f679358a1945a88259c92b5 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 4 Feb 2017 22:16:22 +0100 Subject: [PATCH 223/561] Fix editor.feature with QtWebEngine --- tests/end2end/features/editor.feature | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/end2end/features/editor.feature b/tests/end2end/features/editor.feature index c7a5b9726..097ffd16f 100644 --- a/tests/end2end/features/editor.feature +++ b/tests/end2end/features/editor.feature @@ -80,6 +80,7 @@ Feature: Opening external editors And I run :click-element id qute-button Then the javascript message "text: foobar" should be logged + @qtwebengine_todo: Caret mode is not implemented yet Scenario: Spawning an editor in caret mode When I set up a fake editor returning "foobar" And I open data/editor.html From 378e4f1bdd951b25b8233dfd4863538d46eac94f Mon Sep 17 00:00:00 2001 From: Lucas Hoffmann Date: Sat, 4 Feb 2017 22:29:45 +0100 Subject: [PATCH 224/561] docs: Clarify behaviour of `view-source` --- doc/help/commands.asciidoc | 2 +- qutebrowser/browser/commands.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/help/commands.asciidoc b/doc/help/commands.asciidoc index 3f656dc49..856b95a2b 100644 --- a/doc/help/commands.asciidoc +++ b/doc/help/commands.asciidoc @@ -84,7 +84,7 @@ It is possible to run or bind multiple commands by separating them with `;;`. |<>|Switch to the previous tab, or switch [count] tabs back. |<>|Unbind a keychain. |<>|Re-open a closed tab (optionally skipping [count] closed tabs). -|<>|Show the source of the current page. +|<>|Show the source of the current page in a new tab. |<>|Close all windows except for the current one. |<>|Save open pages and quit. |<>|Yank something to the clipboard or primary selection. diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index adcd2e9e2..38c186447 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -1349,7 +1349,7 @@ class CommandDispatcher: @cmdutils.register(instance='command-dispatcher', scope='window') def view_source(self): - """Show the source of the current page.""" + """Show the source of the current page in a new tab.""" # pylint: disable=no-member # WORKAROUND for https://bitbucket.org/logilab/pylint/issue/491/ tab = self._current_widget() From d874f684635ca8e2088b3fd9fa9b1433f345222c Mon Sep 17 00:00:00 2001 From: Lucas Hoffmann Date: Sat, 4 Feb 2017 22:31:25 +0100 Subject: [PATCH 225/561] docs: Clarify hints.mode = number --- qutebrowser/config/configdata.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index 91588468d..0c0868dfe 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -936,7 +936,9 @@ def data(readonly=False): ('mode', SettingValue(typ.String( valid_values=typ.ValidValues( - ('number', "Use numeric hints."), + ('number', "Use numeric hints. (In this mode you can " + "also type letters form the hinted element to filter " + "and reduce the number of elements that are hinted.)"), ('letter', "Use the chars in the hints -> " "chars setting."), ('word', "Use hints words based on the html " From 92198f668b66a44230c48717baac51d973f28bae Mon Sep 17 00:00:00 2001 From: Lucas Hoffmann Date: Sat, 4 Feb 2017 22:32:34 +0100 Subject: [PATCH 226/561] Update autogenerated files --- README.asciidoc | 1 + doc/help/commands.asciidoc | 2 +- doc/help/settings.asciidoc | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/README.asciidoc b/README.asciidoc index 50375ad1a..d0c85d5f6 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -234,6 +234,7 @@ Contributors, sorted by the number of commits in descending order: * Regina Hug * Mathias Fussenegger * Marcelo Santos +* Lucas Hoffmann * Joel Bradshaw * Jean-Louis Fuchs * Franz Fellner diff --git a/doc/help/commands.asciidoc b/doc/help/commands.asciidoc index 856b95a2b..0e993b723 100644 --- a/doc/help/commands.asciidoc +++ b/doc/help/commands.asciidoc @@ -898,7 +898,7 @@ Re-open a closed tab (optionally skipping [count] closed tabs). [[view-source]] === view-source -Show the source of the current page. +Show the source of the current page in a new tab. [[window-only]] === window-only diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc index 62700ac6f..114a9c9d6 100644 --- a/doc/help/settings.asciidoc +++ b/doc/help/settings.asciidoc @@ -1666,7 +1666,7 @@ Mode to use for hints. Valid values: - * +number+: Use numeric hints. + * +number+: Use numeric hints. (In this mode you can also type letters form the hinted element to filter and reduce the number of elements that are hinted.) * +letter+: Use the chars in the hints -> chars setting. * +word+: Use hints words based on the html elements and the extra words. From 2c46f8ecdb759c7701fec107b088d496e180d10d Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 4 Feb 2017 23:30:12 +0100 Subject: [PATCH 227/561] Fix zoom handling with QtWebKit --- qutebrowser/browser/mouse.py | 3 +-- qutebrowser/browser/webengine/webenginetab.py | 1 + tests/end2end/test_insert_mode.py | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/qutebrowser/browser/mouse.py b/qutebrowser/browser/mouse.py index 7052c9353..19e37ad97 100644 --- a/qutebrowser/browser/mouse.py +++ b/qutebrowser/browser/mouse.py @@ -95,8 +95,7 @@ class MouseEventFilter(QObject): self._ignore_wheel_event = True if e.button() != Qt.NoButton: - zoom = self._tab.zoom.factor() - self._tab.elements.find_at_pos(e.pos() / zoom, + self._tab.elements.find_at_pos(e.pos(), self._mousepress_insertmode_cb) return False diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index 6c654a6ee..dbc04e905 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -442,6 +442,7 @@ class WebEngineElements(browsertab.AbstractElements): def find_at_pos(self, pos, callback): assert pos.x() >= 0 assert pos.y() >= 0 + pos /= self._tab.zoom.factor() js_code = javascript.assemble('webelem', 'element_at_pos', pos.x(), pos.y()) js_cb = functools.partial(self._js_cb_single, callback) diff --git a/tests/end2end/test_insert_mode.py b/tests/end2end/test_insert_mode.py index 59c279feb..d28ac20d2 100644 --- a/tests/end2end/test_insert_mode.py +++ b/tests/end2end/test_insert_mode.py @@ -35,7 +35,7 @@ import pytest ('autofocus.html', 'qute-input-autofocus', 'keypress', 'cutebrowser', 'true'), ]) -@pytest.mark.parametrize('zoom', [100, 125]) +@pytest.mark.parametrize('zoom', [100, 125, 250]) def test_insert_mode(file_name, elem_id, source, input_text, auto_insert, zoom, quteproc, request): url_path = 'data/insert_mode_settings/html/{}'.format(file_name) @@ -88,6 +88,7 @@ def test_auto_leave_insert_mode(quteproc): quteproc.open_path(url_path) quteproc.set_setting('input', 'auto-leave-insert-mode', 'true') + quteproc.send_cmd(':zoom 100') quteproc.press_keys('abcd') From 021b3645cc2ecac6547aaa3b26a4d4ae9fd8b86f Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 4 Feb 2017 23:42:55 +0100 Subject: [PATCH 228/561] Don't stack zoom level messages Fixes #1980 --- CHANGELOG.asciidoc | 1 + qutebrowser/browser/commands.py | 6 ++-- qutebrowser/browser/mouse.py | 2 +- qutebrowser/mainwindow/messageview.py | 13 ++++++--- qutebrowser/utils/message.py | 34 +++++++++++++++++------ tests/unit/mainwindow/test_messageview.py | 13 +++++++++ 6 files changed, 52 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 9132f9717..b8b8bdb6f 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -29,6 +29,7 @@ Changed - Scrolling with the scrollwheel while holding shift now scrolls sideways - New way of clicking hints with which solves various small issues - When yanking a mailto: link via hints, the mailto: prefix is now stripped +- Zoom level messages are now not stacked on top of each other anymore. Fixed ----- diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index adcd2e9e2..2a1988f3b 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -755,7 +755,7 @@ class CommandDispatcher: perc = tab.zoom.offset(count) except ValueError as e: raise cmdexc.CommandError(e) - message.info("Zoom level: {}%".format(perc)) + message.info("Zoom level: {}%".format(perc), replace=True) @cmdutils.register(instance='command-dispatcher', scope='window') @cmdutils.argument('count', count=True) @@ -770,7 +770,7 @@ class CommandDispatcher: perc = tab.zoom.offset(-count) except ValueError as e: raise cmdexc.CommandError(e) - message.info("Zoom level: {}%".format(perc)) + message.info("Zoom level: {}%".format(perc), replace=True) @cmdutils.register(instance='command-dispatcher', scope='window') @cmdutils.argument('count', count=True) @@ -794,7 +794,7 @@ class CommandDispatcher: tab.zoom.set_factor(float(level) / 100) except ValueError: raise cmdexc.CommandError("Can't zoom {}%!".format(level)) - message.info("Zoom level: {}%".format(level)) + message.info("Zoom level: {}%".format(level), replace=True) @cmdutils.register(instance='command-dispatcher', scope='window') def tab_only(self, prev=False, next_=False): diff --git a/qutebrowser/browser/mouse.py b/qutebrowser/browser/mouse.py index 19e37ad97..d608db466 100644 --- a/qutebrowser/browser/mouse.py +++ b/qutebrowser/browser/mouse.py @@ -124,7 +124,7 @@ class MouseEventFilter(QObject): if factor < 0: return False perc = int(100 * factor) - message.info("Zoom level: {}%".format(perc)) + message.info("Zoom level: {}%".format(perc), replace=True) self._tab.zoom.set_factor(factor) elif e.modifiers() & Qt.ShiftModifier: if e.angleDelta().y() > 0: diff --git a/qutebrowser/mainwindow/messageview.py b/qutebrowser/mainwindow/messageview.py index 3afbff102..86178716f 100644 --- a/qutebrowser/mainwindow/messageview.py +++ b/qutebrowser/mainwindow/messageview.py @@ -31,8 +31,9 @@ class Message(QLabel): """A single error/warning/info message.""" - def __init__(self, level, text, parent=None): + def __init__(self, level, text, replace, parent=None): super().__init__(text, parent) + self.replace = replace self.setAttribute(Qt.WA_StyledBackground, True) stylesheet = """ padding-top: 2px; @@ -111,13 +112,17 @@ class MessageView(QWidget): self.hide() self._clear_timer.stop() - @pyqtSlot(usertypes.MessageLevel, str) - def show_message(self, level, text): + @pyqtSlot(usertypes.MessageLevel, str, bool) + def show_message(self, level, text, replace=False): """Show the given message with the given MessageLevel.""" if text == self._last_text: return - widget = Message(level, text, parent=self) + if replace and self._messages and self._messages[-1].replace: + old = self._messages.pop() + old.hide() + + widget = Message(level, text, replace=replace, parent=self) self._vbox.addWidget(widget) widget.show() self._clear_timer.start() diff --git a/qutebrowser/utils/message.py b/qutebrowser/utils/message.py index 368bb8289..82fe74f04 100644 --- a/qutebrowser/utils/message.py +++ b/qutebrowser/utils/message.py @@ -46,12 +46,13 @@ def _log_stack(typ, stack): log.message.debug("Stack for {} message:\n{}".format(typ, stack_text)) -def error(message, *, stack=None): +def error(message, *, stack=None, replace=False): """Convenience function to display an error message in the statusbar. Args: message: The message to show stack: The stack trace to show. + replace: Replace existing messages with replace=True """ if stack is None: stack = traceback.format_stack() @@ -60,20 +61,33 @@ def error(message, *, stack=None): typ = 'error (from exception)' _log_stack(typ, stack) log.message.error(message) - global_bridge.show_message.emit(usertypes.MessageLevel.error, message) + global_bridge.show_message.emit(usertypes.MessageLevel.error, message, + replace) -def warning(message): - """Convenience function to display a warning message in the statusbar.""" +def warning(message, *, replace=False): + """Convenience function to display a warning message in the statusbar. + + Args: + message: The message to show + replace: Replace existing messages with replace=True + """ _log_stack('warning', traceback.format_stack()) log.message.warning(message) - global_bridge.show_message.emit(usertypes.MessageLevel.warning, message) + global_bridge.show_message.emit(usertypes.MessageLevel.warning, message, + replace) -def info(message): - """Convenience function to display an info message in the statusbar.""" +def info(message, *, replace=False): + """Convenience function to display an info message in the statusbar. + + Args: + message: The message to show + replace: Replace existing messages with replace=True + """ log.message.info(message) - global_bridge.show_message.emit(usertypes.MessageLevel.info, message) + global_bridge.show_message.emit(usertypes.MessageLevel.info, message, + replace) def _build_question(title, text=None, *, mode, default=None, abort_on=()): @@ -163,6 +177,8 @@ class GlobalMessageBridge(QObject): show_message: Show a message arg 0: A MessageLevel member arg 1: The text to show + arg 2: Whether to replace other messages with + replace=True. prompt_done: Emitted when a prompt was answered somewhere. ask_question: Ask a question to the user. arg 0: The Question object to ask. @@ -173,7 +189,7 @@ class GlobalMessageBridge(QObject): mode_left: Emitted when a keymode was left in any window. """ - show_message = pyqtSignal(usertypes.MessageLevel, str) + show_message = pyqtSignal(usertypes.MessageLevel, str, bool) prompt_done = pyqtSignal(usertypes.KeyMode) ask_question = pyqtSignal(usertypes.Question, bool) mode_left = pyqtSignal(usertypes.KeyMode) diff --git a/tests/unit/mainwindow/test_messageview.py b/tests/unit/mainwindow/test_messageview.py index 310ea7180..740c090b6 100644 --- a/tests/unit/mainwindow/test_messageview.py +++ b/tests/unit/mainwindow/test_messageview.py @@ -101,3 +101,16 @@ def test_changing_timer_with_messages_shown(qtbot, view, config_stub): view.show_message(usertypes.MessageLevel.info, 'test') with qtbot.waitSignal(view._clear_timer.timeout): config_stub.set('ui', 'message-timeout', 100) + + +@pytest.mark.parametrize('replace1, replace2, length', [ + (False, False, 2), # Two stacked messages + (True, True, 1), # Two replaceable messages + (False, True, 2), # Stacked and replaceable + (True, False, 2), # Replaceable and stacked +]) +def test_replaced_messages(view, replace1, replace2, length): + """Show two stack=False messages which should replace each other.""" + view.show_message(usertypes.MessageLevel.info, 'test', replace=replace1) + view.show_message(usertypes.MessageLevel.info, 'test 2', replace=replace2) + assert len(view._messages) == length From cc2b08544394a91f2392b663eee8493a80e5dd1f Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 4 Feb 2017 23:45:00 +0100 Subject: [PATCH 229/561] Uppercase word-hints with hints -> uppercase Fixes #2278 --- CHANGELOG.asciidoc | 1 + qutebrowser/browser/hints.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index b8b8bdb6f..479b010f5 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -43,6 +43,7 @@ Fixed - Fixed PAC (proxy autoconfig) not working with QtWebKit - `:download --mhtml` now uses the new file dialog - Insert mode now gets entered correctly with a non-100% zoom +- Word hints are now upper-cased correctly when hints -> uppercase is true v0.9.1 ------ diff --git a/qutebrowser/browser/hints.py b/qutebrowser/browser/hints.py index ece6ed89e..cb638d4c9 100644 --- a/qutebrowser/browser/hints.py +++ b/qutebrowser/browser/hints.py @@ -101,7 +101,7 @@ class HintLabel(QLabel): unmatched: The part of the text which was not typed yet. """ if (config.get('hints', 'uppercase') and - self._context.hint_mode == 'letter'): + self._context.hint_mode in ['letter', 'word']): matched = html.escape(matched.upper()) unmatched = html.escape(unmatched.upper()) else: From ba5ac6139a694722e4c008175b1a1c744ebd05f6 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 5 Feb 2017 00:09:18 +0100 Subject: [PATCH 230/561] Update docs --- README.asciidoc | 2 +- doc/help/settings.asciidoc | 4 ++-- qutebrowser/config/configdata.py | 4 +++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/README.asciidoc b/README.asciidoc index d0c85d5f6..73276634c 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -221,6 +221,7 @@ Contributors, sorted by the number of commits in descending order: * Samuel Loury * Peter Michely * Panashe M. Fundira +* Lucas Hoffmann * Link * Larry Hynes * Johannes Altmanninger @@ -234,7 +235,6 @@ Contributors, sorted by the number of commits in descending order: * Regina Hug * Mathias Fussenegger * Marcelo Santos -* Lucas Hoffmann * Joel Bradshaw * Jean-Louis Fuchs * Franz Fellner diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc index 114a9c9d6..122203d93 100644 --- a/doc/help/settings.asciidoc +++ b/doc/help/settings.asciidoc @@ -796,6 +796,8 @@ The proxy to use. In addition to the listed values, you can use a `socks://...` or `http://...` URL. +This setting only works with Qt 5.8 or newer when using the QtWebEngine backend. + Valid values: * +system+: Use the system wide proxy. @@ -803,8 +805,6 @@ Valid values: Default: +pass:[system]+ -This setting is only available with the QtWebKit backend. - [[network-proxy-dns-requests]] === proxy-dns-requests Whether to send DNS requests over the configured proxy. diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index 657e26857..f68e6cad5 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -437,7 +437,9 @@ def data(readonly=False): else [usertypes.Backend.QtWebKit])), "The proxy to use.\n\n" "In addition to the listed values, you can use a `socks://...` " - "or `http://...` URL."), + "or `http://...` URL.\n\n" + "This setting only works with Qt 5.8 or newer when using the " + "QtWebEngine backend."), ('proxy-dns-requests', SettingValue(typ.Bool(), 'true', From de50f30b9b7ec0adf5f2b8ba8cff5024568fb488 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 5 Feb 2017 00:13:11 +0100 Subject: [PATCH 231/561] Replace all GitHub links --- .travis.yml | 2 +- CHANGELOG.asciidoc | 14 +++++++------- CONTRIBUTING.asciidoc | 12 ++++++------ FAQ.asciidoc | 8 ++++---- INSTALL.asciidoc | 8 ++++---- README.asciidoc | 8 ++++---- doc/qutebrowser.1.asciidoc | 4 ++-- pytest.ini | 2 +- qutebrowser/app.py | 2 +- qutebrowser/browser/browsertab.py | 2 +- qutebrowser/browser/commands.py | 6 +++--- qutebrowser/browser/downloads.py | 4 ++-- qutebrowser/browser/downloadview.py | 4 ++-- qutebrowser/browser/history.py | 2 +- qutebrowser/browser/mouse.py | 2 +- qutebrowser/browser/shared.py | 2 +- qutebrowser/browser/webelem.py | 4 ++-- qutebrowser/browser/webengine/webengineelem.py | 2 +- qutebrowser/browser/webkit/mhtml.py | 4 ++-- .../browser/webkit/network/networkmanager.py | 2 +- .../browser/webkit/network/webkitqutescheme.py | 2 +- qutebrowser/browser/webkit/rfc6266.py | 4 ++-- qutebrowser/browser/webkit/webkitelem.py | 4 ++-- qutebrowser/browser/webkit/webkittab.py | 6 +++--- qutebrowser/browser/webkit/webview.py | 2 +- qutebrowser/completion/completer.py | 2 +- qutebrowser/completion/completiondelegate.py | 4 ++-- qutebrowser/completion/completionwidget.py | 2 +- qutebrowser/completion/models/configmodel.py | 6 +++--- qutebrowser/completion/models/miscmodels.py | 12 ++++++------ qutebrowser/config/config.py | 2 +- qutebrowser/keyinput/modeman.py | 2 +- qutebrowser/mainwindow/prompt.py | 4 ++-- qutebrowser/mainwindow/tabbedbrowser.py | 2 +- qutebrowser/mainwindow/tabwidget.py | 2 +- qutebrowser/misc/earlyinit.py | 2 +- qutebrowser/misc/editor.py | 2 +- qutebrowser/misc/ipc.py | 2 +- qutebrowser/misc/sessions.py | 6 +++--- qutebrowser/utils/debug.py | 2 +- qutebrowser/utils/log.py | 6 +++--- qutebrowser/utils/objreg.py | 2 +- qutebrowser/utils/urlutils.py | 4 ++-- qutebrowser/utils/utils.py | 2 +- scripts/dev/download_release.sh | 2 +- tests/conftest.py | 2 +- tests/end2end/data/downloads/issue1535.html | 2 +- .../data/downloads/mhtml/complex/complex.html | 2 +- .../data/downloads/mhtml/complex/complex.mht | 2 +- tests/end2end/features/downloads.feature | 10 +++++----- tests/end2end/features/hints.feature | 16 ++++++++-------- tests/end2end/features/javascript.feature | 2 +- tests/end2end/features/misc.feature | 10 +++++----- tests/end2end/features/prompts.feature | 8 ++++---- tests/end2end/features/scroll.feature | 2 +- tests/end2end/features/sessions.feature | 2 +- tests/end2end/features/spawn.feature | 2 +- tests/end2end/features/zoom.feature | 8 ++++---- tests/end2end/test_invocations.py | 8 ++++---- .../manual/hints/hide_unmatched_rapid_hints.html | 2 +- tests/manual/hints/issue824.html | 4 ++-- tests/manual/hints/issue925.html | 2 +- tests/manual/hints/other.html | 10 +++++----- tests/test_conftest.py | 2 +- tests/unit/browser/webkit/network/test_pac.py | 2 +- tests/unit/commands/test_cmdutils.py | 8 ++++---- tests/unit/commands/test_runners.py | 6 +++--- tests/unit/config/test_config.py | 4 ++-- tests/unit/keyinput/test_modeman.py | 2 +- tests/unit/mainwindow/statusbar/test_progress.py | 6 +++--- tests/unit/misc/test_ipc.py | 6 +++--- tests/unit/misc/test_readline.py | 2 +- tests/unit/misc/test_sessions.py | 2 +- tests/unit/utils/test_debug.py | 2 +- tests/unit/utils/test_error.py | 2 +- tests/unit/utils/test_jinja.py | 2 +- tests/unit/utils/test_log.py | 2 +- tests/unit/utils/test_qtutils.py | 2 +- tests/unit/utils/test_standarddir.py | 2 +- www/header.asciidoc | 4 ++-- 80 files changed, 165 insertions(+), 165 deletions(-) diff --git a/.travis.yml b/.travis.yml index 58e7f09d5..8fa525d35 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,7 +21,7 @@ matrix: - os: osx env: TESTENV=py35 OSX=elcapitan osx_image: xcode7.3 - # https://github.com/The-Compiler/qutebrowser/issues/2013 + # https://github.com/qutebrowser/qutebrowser/issues/2013 # - os: osx # env: TESTENV=py35 OSX=yosemite # osx_image: xcode6.4 diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 479b010f5..221c6b70e 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -845,7 +845,7 @@ Fixed - Fixed horrible completion performance when the `shrink` option was set. - Sessions now store zoom/scroll-position correctly. -https://github.com/The-Compiler/qutebrowser/releases/tag/v0.2.1[v0.2.1] +https://github.com/qutebrowser/qutebrowser/releases/tag/v0.2.1[v0.2.1] ----------------------------------------------------------------------- Fixed @@ -853,7 +853,7 @@ Fixed - Added missing manpage (doc/qutebrowser.1.asciidoc) to archive. -https://github.com/The-Compiler/qutebrowser/releases/tag/v0.2.0[v0.2.0] +https://github.com/qutebrowser/qutebrowser/releases/tag/v0.2.0[v0.2.0] ----------------------------------------------------------------------- Added @@ -996,7 +996,7 @@ Fixed - Add a timeout to pastebin HTTP replies. - Various other fixes for small/rare bugs. -https://github.com/The-Compiler/qutebrowser/releases/tag/v0.1.4[v0.1.4] +https://github.com/qutebrowser/qutebrowser/releases/tag/v0.1.4[v0.1.4] ----------------------------------------------------------------------- Changed @@ -1040,7 +1040,7 @@ Security * Stop the icon database from being created when private-browsing is set to true. * Disable insecure SSL ciphers. -https://github.com/The-Compiler/qutebrowser/releases/tag/v0.1.3[v0.1.3] +https://github.com/qutebrowser/qutebrowser/releases/tag/v0.1.3[v0.1.3] ----------------------------------------------------------------------- Changed @@ -1074,7 +1074,7 @@ Security * Fix for HTTP passwords accidentally being written to debug log. -https://github.com/The-Compiler/qutebrowser/releases/tag/v0.1.2[v0.1.2] +https://github.com/qutebrowser/qutebrowser/releases/tag/v0.1.2[v0.1.2] ----------------------------------------------------------------------- Changed @@ -1106,7 +1106,7 @@ Fixed * Fix user-stylesheet setting with an empty value. -https://github.com/The-Compiler/qutebrowser/releases/tag/v0.1.1[v0.1.1] +https://github.com/qutebrowser/qutebrowser/releases/tag/v0.1.1[v0.1.1] ----------------------------------------------------------------------- Added @@ -1164,7 +1164,7 @@ Fixed * Ensure the docs get included in `freeze.py`. * Fix crash with `:zoom`. -https://github.com/The-Compiler/qutebrowser/releases/tag/v0.1[v0.1] +https://github.com/qutebrowser/qutebrowser/releases/tag/v0.1[v0.1] ------------------------------------------------------------------- Initial release. diff --git a/CONTRIBUTING.asciidoc b/CONTRIBUTING.asciidoc index 05ec6df67..e12d9cb9a 100644 --- a/CONTRIBUTING.asciidoc +++ b/CONTRIBUTING.asciidoc @@ -34,12 +34,12 @@ this. It might be a good idea to ask on the mailing list or IRC channel to make sure nobody else started working on the same thing already. If you want to find something useful to do, check the -https://github.com/The-Compiler/qutebrowser/issues[issue tracker]. Some +https://github.com/qutebrowser/qutebrowser/issues[issue tracker]. Some pointers: -* https://github.com/The-Compiler/qutebrowser/labels/easy[Issues which should +* https://github.com/qutebrowser/qutebrowser/labels/easy[Issues which should be easy to solve] -* https://github.com/The-Compiler/qutebrowser/labels/not%20code[Issues which +* https://github.com/qutebrowser/qutebrowser/labels/not%20code[Issues which require little/no coding] There are also some things to do if you don't want to write code: @@ -55,7 +55,7 @@ qutebrowser uses http://git-scm.com/[git] for its development. You can clone the repo like this: ---- -git clone https://github.com/The-Compiler/qutebrowser.git +git clone https://github.com/qutebrowser/qutebrowser.git ---- If you don't know git, a http://git-scm.com/[git cheatsheet] might come in @@ -629,7 +629,7 @@ and make sure all bugs marked as resolved are actually fixed. * Grep for `WORKAROUND` in the code and test if fixed stuff works without the workaround. * Check relevant -https://github.com/The-Compiler/qutebrowser/issues?q=is%3Aopen+is%3Aissue+label%3Aqt[qutebrowser +https://github.com/qutebrowser/qutebrowser/issues?q=is%3Aopen+is%3Aissue+label%3Aqt[qutebrowser bugs] and check if they're fixed. New PyQt release @@ -659,7 +659,7 @@ qutebrowser release * `git push origin`; `git push origin v0.$x.$y` * If committing on minor branch, cherry-pick release commit to master. * Create release on github -* Mark the milestone at https://github.com/The-Compiler/qutebrowser/milestones +* Mark the milestone at https://github.com/qutebrowser/qutebrowser/milestones as closed. * Linux: Run `python3 scripts/dev/build_release.py --upload v0.$x.$y` diff --git a/FAQ.asciidoc b/FAQ.asciidoc index 5459a63cf..71d5f2834 100644 --- a/FAQ.asciidoc +++ b/FAQ.asciidoc @@ -105,7 +105,7 @@ It also works nicely with rapid hints: How do I use qutebrowser with mutt?:: Due to a Qt limitation, local files without `.html` extensions are "downloaded" instead of displayed, see - https://github.com/The-Compiler/qutebrowser/issues/566[#566]. You can work + https://github.com/qutebrowser/qutebrowser/issues/566[#566]. You can work around this by using this in your `mailcap`: + ---- @@ -141,7 +141,7 @@ Experiencing freezing on sites like duckduckgo and youtube.:: This issue could be caused by stale plugin files installed by `mozplugger` if mozplugger was subsequently removed. Try exiting qutebrowser and removing `~/.mozilla/plugins/mozplugger*.so`. - See https://github.com/The-Compiler/qutebrowser/issues/357[Issue #357] + See https://github.com/qutebrowser/qutebrowser/issues/357[Issue #357] for more details. Experiencing segfaults (crashes) on Debian systems.:: @@ -155,7 +155,7 @@ Segfaults on Facebook, Medium, Amazon, ...:: visiting these sites. This is caused by various bugs in Qt which have been fixed in Qt 5.4. However Debian and Ubuntu are slow to adopt or upgrade some packages. On Debian Jessie, it's recommended to use the experimental - repos as described in https://github.com/The-Compiler/qutebrowser/blob/master/INSTALL.asciidoc#on-debian--ubuntu[INSTALL]. + repos as described in https://github.com/qutebrowser/qutebrowser/blob/master/INSTALL.asciidoc#on-debian--ubuntu[INSTALL]. + Since Ubuntu Trusty (using Qt 5.2.1), https://bugreports.qt.io/browse/QTBUG-42417?jql=component%20%3D%20WebKit%20and%20resolution%20%3D%20Done%20and%20fixVersion%20in%20(5.3.0%2C%20%225.3.0%20Alpha%22%2C%20%225.3.0%20Beta1%22%2C%20%225.3.0%20RC1%22%2C%205.3.1%2C%205.3.2%2C%205.4.0%2C%20%225.4.0%20Alpha%22%2C%20%225.4.0%20Beta%22%2C%20%225.4.0%20RC%22)%20and%20priority%20in%20(%22P2%3A%20Important%22%2C%20%22P1%3A%20Critical%22%2C%20%22P0%3A%20Blocker%22)[over @@ -166,7 +166,7 @@ https://bugreports.qt.io/browse/QTBUG-42417?jql=component%20%3D%20WebKit%20and%2 My issue is not listed.:: If you experience any segfaults or crashes, you can report the issue in - https://github.com/The-Compiler/qutebrowser/issues[the issue tracker] or + https://github.com/qutebrowser/qutebrowser/issues[the issue tracker] or using the `:report` command. If you are reporting a segfault, make sure you read the link:doc/stacktrace.asciidoc[guide] on how to report them with all needed diff --git a/INSTALL.asciidoc b/INSTALL.asciidoc index f5007ccfd..f429d551b 100644 --- a/INSTALL.asciidoc +++ b/INSTALL.asciidoc @@ -25,7 +25,7 @@ Install the dependencies via apt-get: ---- Get the qutebrowser package from the -https://github.com/The-Compiler/qutebrowser/releases[release page] and download +https://github.com/qutebrowser/qutebrowser/releases[release page] and download the https://qutebrowser.org/python3-pypeg2_2.15.2-1_all.deb[PyPEG2 package]. Install the packages: @@ -214,7 +214,7 @@ Prebuilt binaries ~~~~~~~~~~~~~~~~~ Prebuilt standalone packages and MSI installers -https://github.com/The-Compiler/qutebrowser/releases[are built] for every +https://github.com/qutebrowser/qutebrowser/releases[are built] for every release. https://chocolatey.org/packages/qutebrowser[Chocolatey package] @@ -254,7 +254,7 @@ Prebuilt binary The easiest way to install qutebrowser on OS X is to use the prebuilt `.app` files from the -https://github.com/The-Compiler/qutebrowser/releases[release page]. +https://github.com/qutebrowser/qutebrowser/releases[release page]. This binary is also available through the https://caskroom.github.io/[Homebrew Cask] package manager: @@ -313,7 +313,7 @@ First of all, clone the repository using http://git-scm.org/[git] and switch into the repository folder: ---- -$ git clone https://github.com/The-Compiler/qutebrowser.git +$ git clone https://github.com/qutebrowser/qutebrowser.git $ cd qutebrowser ---- diff --git a/README.asciidoc b/README.asciidoc index 73276634c..69487cdcb 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -1,7 +1,7 @@ // If you are reading this in plaintext or on PyPi: // // A rendered version is available at: -// https://github.com/The-Compiler/qutebrowser/blob/master/README.asciidoc +// https://github.com/qutebrowser/qutebrowser/blob/master/README.asciidoc qutebrowser =========== @@ -9,14 +9,14 @@ qutebrowser // QUTE_WEB_HIDE image:icons/qutebrowser-64x64.png[qutebrowser logo] *A keyboard-driven, vim-like browser based on PyQt5 and QtWebKit.* -image:https://img.shields.io/pypi/l/qutebrowser.svg?style=flat["license badge",link="https://github.com/The-Compiler/qutebrowser/blob/master/COPYING"] +image:https://img.shields.io/pypi/l/qutebrowser.svg?style=flat["license badge",link="https://github.com/qutebrowser/qutebrowser/blob/master/COPYING"] image:https://img.shields.io/pypi/v/qutebrowser.svg?style=flat["version badge",link="https://pypi.python.org/pypi/qutebrowser/"] image:https://requires.io/github/The-Compiler/qutebrowser/requirements.svg?branch=master["requirements badge",link="https://requires.io/github/The-Compiler/qutebrowser/requirements/?branch=master"] image:https://travis-ci.org/The-Compiler/qutebrowser.svg?branch=master["Build Status", link="https://travis-ci.org/The-Compiler/qutebrowser"] image:https://ci.appveyor.com/api/projects/status/9gmnuip6i1oq7046?svg=true["AppVeyor build status", link="https://ci.appveyor.com/project/The-Compiler/qutebrowser"] image:https://codecov.io/github/The-Compiler/qutebrowser/coverage.svg?branch=master["coverage badge",link="https://codecov.io/github/The-Compiler/qutebrowser?branch=master"] -link:https://www.qutebrowser.org[website] | link:https://blog.qutebrowser.org[blog] | link:https://github.com/The-Compiler/qutebrowser/releases[releases] +link:https://www.qutebrowser.org[website] | link:https://blog.qutebrowser.org[blog] | link:https://github.com/qutebrowser/qutebrowser/releases[releases] // QUTE_WEB_HIDE_END qutebrowser is a keyboard-focused browser with a minimal GUI. It's based @@ -35,7 +35,7 @@ image:doc/img/hints.png["screenshot 4",width=300,link="doc/img/hints.png"] Downloads --------- -See the https://github.com/The-Compiler/qutebrowser/releases[github releases +See the https://github.com/qutebrowser/qutebrowser/releases[github releases page] for available downloads (currently a source archive, and standalone packages as well as MSI installers for Windows). diff --git a/doc/qutebrowser.1.asciidoc b/doc/qutebrowser.1.asciidoc index 93e632678..b427ed012 100644 --- a/doc/qutebrowser.1.asciidoc +++ b/doc/qutebrowser.1.asciidoc @@ -127,7 +127,7 @@ defaults. == BUGS Bugs are tracked in the Github issue tracker at -https://github.com/The-Compiler/qutebrowser/issues. +https://github.com/qutebrowser/qutebrowser/issues. If you found a bug, use the built-in ':report' command to create a bug report with all information needed. @@ -160,7 +160,7 @@ https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser-announce * IRC: irc://irc.freenode.org/#qutebrowser[`#qutebrowser`] on http://freenode.net/[Freenode] -* Github: https://github.com/The-Compiler/qutebrowser +* Github: https://github.com/qutebrowser/qutebrowser == AUTHOR *qutebrowser* was written by Florian Bruhin. All contributors can be found in diff --git a/pytest.ini b/pytest.ini index 24f5a4528..e8fd3dd95 100644 --- a/pytest.ini +++ b/pytest.ini @@ -20,7 +20,7 @@ markers = qtwebengine_flaky: Tests which are flaky (and currently skipped) with QtWebEngine qtwebengine_osx_xfail: Tests which fail on OS X with QtWebEngine js_prompt: Tests needing to display a javascript prompt - issue2183: https://github.com/The-Compiler/qutebrowser/issues/2183 + issue2183: https://github.com/qutebrowser/qutebrowser/issues/2183 this: Used to mark tests during development qt_log_level_fail = WARNING qt_log_ignore = diff --git a/qutebrowser/app.py b/qutebrowser/app.py index 5b7cf545c..3c4c605a6 100644 --- a/qutebrowser/app.py +++ b/qutebrowser/app.py @@ -534,7 +534,7 @@ class Quitter: if not os.path.isdir(cwd): # Probably running from a python egg. Let's fallback to # cwd=None and see if that works out. - # See https://github.com/The-Compiler/qutebrowser/issues/323 + # See https://github.com/qutebrowser/qutebrowser/issues/323 cwd = None # Add all open pages so they get reopened. diff --git a/qutebrowser/browser/browsertab.py b/qutebrowser/browser/browsertab.py index e868f3261..4144928d2 100644 --- a/qutebrowser/browser/browsertab.py +++ b/qutebrowser/browser/browsertab.py @@ -182,7 +182,7 @@ class AbstractZoom(QObject): # # FIXME:qtwebengine is this needed? # # For some reason, this signal doesn't get disconnected automatically # # when the WebView is destroyed on older PyQt versions. - # # See https://github.com/The-Compiler/qutebrowser/issues/390 + # # See https://github.com/qutebrowser/qutebrowser/issues/390 # self.destroyed.connect(functools.partial( # cfg.changed.disconnect, self.init_neighborlist)) diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 3bfe22bed..95dd0e4f2 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -652,7 +652,7 @@ class CommandDispatcher: """ tab = self._current_widget() if not tab.url().isValid(): - # See https://github.com/The-Compiler/qutebrowser/issues/701 + # See https://github.com/qutebrowser/qutebrowser/issues/701 return if bottom_navigate is not None and tab.scroller.at_bottom(): @@ -833,7 +833,7 @@ class CommandDispatcher: """ if self._count() == 0: # Running :tab-prev after last tab was closed - # See https://github.com/The-Compiler/qutebrowser/issues/1448 + # See https://github.com/qutebrowser/qutebrowser/issues/1448 return newidx = self._current_index() - count if newidx >= 0: @@ -853,7 +853,7 @@ class CommandDispatcher: """ if self._count() == 0: # Running :tab-next after last tab was closed - # See https://github.com/The-Compiler/qutebrowser/issues/1448 + # See https://github.com/qutebrowser/qutebrowser/issues/1448 return newidx = self._current_index() + count if newidx < self._count(): diff --git a/qutebrowser/browser/downloads.py b/qutebrowser/browser/downloads.py index 378fe9a19..8a1a7853a 100644 --- a/qutebrowser/browser/downloads.py +++ b/qutebrowser/browser/downloads.py @@ -125,7 +125,7 @@ def create_full_filename(basename, filename): The full absolute path, or None if filename creation was not possible. """ # Remove chars which can't be encoded in the filename encoding. - # See https://github.com/The-Compiler/qutebrowser/issues/427 + # See https://github.com/qutebrowser/qutebrowser/issues/427 encoding = sys.getfilesystemencoding() filename = utils.force_encoding(filename, encoding) basename = utils.force_encoding(basename, encoding) @@ -741,7 +741,7 @@ class AbstractDownloadManager(QObject): def _remove_item(self, download): """Remove a given download.""" if sip.isdeleted(self): - # https://github.com/The-Compiler/qutebrowser/issues/1242 + # https://github.com/qutebrowser/qutebrowser/issues/1242 return try: idx = self.downloads.index(download) diff --git a/qutebrowser/browser/downloadview.py b/qutebrowser/browser/downloadview.py index 783a6a94d..f2a380114 100644 --- a/qutebrowser/browser/downloadview.py +++ b/qutebrowser/browser/downloadview.py @@ -39,8 +39,8 @@ def update_geometry(obj): Here we check if obj ("self") was deleted and just ignore the event if so. - Original bug: https://github.com/The-Compiler/qutebrowser/issues/167 - Workaround bug: https://github.com/The-Compiler/qutebrowser/issues/171 + Original bug: https://github.com/qutebrowser/qutebrowser/issues/167 + Workaround bug: https://github.com/qutebrowser/qutebrowser/issues/171 """ def _update_geometry(): """Actually update the geometry if the object still exists.""" diff --git a/qutebrowser/browser/history.py b/qutebrowser/browser/history.py index 40c497823..130a25c67 100644 --- a/qutebrowser/browser/history.py +++ b/qutebrowser/browser/history.py @@ -88,7 +88,7 @@ class Entry: if not url.isValid(): raise ValueError("Invalid URL: {}".format(url.errorString())) - # https://github.com/The-Compiler/qutebrowser/issues/670 + # https://github.com/qutebrowser/qutebrowser/issues/670 atime = atime.lstrip('\0') if '-' in atime: diff --git a/qutebrowser/browser/mouse.py b/qutebrowser/browser/mouse.py index d608db466..247c31a6d 100644 --- a/qutebrowser/browser/mouse.py +++ b/qutebrowser/browser/mouse.py @@ -114,7 +114,7 @@ class MouseEventFilter(QObject): e: The QWheelEvent. """ if self._ignore_wheel_event: - # See https://github.com/The-Compiler/qutebrowser/issues/395 + # See https://github.com/qutebrowser/qutebrowser/issues/395 self._ignore_wheel_event = False return True diff --git a/qutebrowser/browser/shared.py b/qutebrowser/browser/shared.py index 3d171c696..885e2809d 100644 --- a/qutebrowser/browser/shared.py +++ b/qutebrowser/browser/shared.py @@ -158,7 +158,7 @@ def ignore_certificate_errors(url, errors, abort_on): log.webview.debug("ssl-strict is False, only warning about errors") for err in errors: # FIXME we might want to use warn here (non-fatal error) - # https://github.com/The-Compiler/qutebrowser/issues/114 + # https://github.com/qutebrowser/qutebrowser/issues/114 message.error('Certificate error: {}'.format(err)) return True elif ssl_strict is True: diff --git a/qutebrowser/browser/webelem.py b/qutebrowser/browser/webelem.py index dfcd69ef7..85c6774b2 100644 --- a/qutebrowser/browser/webelem.py +++ b/qutebrowser/browser/webelem.py @@ -161,7 +161,7 @@ class AbstractWebElement(collections.abc.MutableMapping): Skipping of small rectangles is due to elements containing other elements with "display:block" style, see - https://github.com/The-Compiler/qutebrowser/issues/1298 + https://github.com/qutebrowser/qutebrowser/issues/1298 Args: elem_geometry: The geometry of the element, or None. @@ -312,7 +312,7 @@ class AbstractWebElement(collections.abc.MutableMapping): # Click the center of the largest square fitting into the top/left # corner of the rectangle, this will help if part of the element # is hidden behind other elements - # https://github.com/The-Compiler/qutebrowser/issues/1005 + # https://github.com/qutebrowser/qutebrowser/issues/1005 rect = self.rect_on_view() if rect.width() > rect.height(): rect.setWidth(rect.height()) diff --git a/qutebrowser/browser/webengine/webengineelem.py b/qutebrowser/browser/webengine/webengineelem.py index 8e493bf9b..40d467f43 100644 --- a/qutebrowser/browser/webengine/webengineelem.py +++ b/qutebrowser/browser/webengine/webengineelem.py @@ -110,7 +110,7 @@ class WebEngineElement(webelem.AbstractWebElement): Skipping of small rectangles is due to elements containing other elements with "display:block" style, see - https://github.com/The-Compiler/qutebrowser/issues/1298 + https://github.com/qutebrowser/qutebrowser/issues/1298 Args: elem_geometry: The geometry of the element, or None. diff --git a/qutebrowser/browser/webkit/mhtml.py b/qutebrowser/browser/webkit/mhtml.py index 97da148a0..1d16a0669 100644 --- a/qutebrowser/browser/webkit/mhtml.py +++ b/qutebrowser/browser/webkit/mhtml.py @@ -349,8 +349,8 @@ class _Downloader: # Using the download manager to download host-blocked urls might crash # qute, see the comments/discussion on - # https://github.com/The-Compiler/qutebrowser/pull/962#discussion_r40256987 - # and https://github.com/The-Compiler/qutebrowser/issues/1053 + # https://github.com/qutebrowser/qutebrowser/pull/962#discussion_r40256987 + # and https://github.com/qutebrowser/qutebrowser/issues/1053 host_blocker = objreg.get('host-blocker') if host_blocker.is_blocked(url): log.downloads.debug("Skipping {}, host-blocked".format(url)) diff --git a/qutebrowser/browser/webkit/network/networkmanager.py b/qutebrowser/browser/webkit/network/networkmanager.py index 5e26893f5..caa04636d 100644 --- a/qutebrowser/browser/webkit/network/networkmanager.py +++ b/qutebrowser/browser/webkit/network/networkmanager.py @@ -434,7 +434,7 @@ class NetworkManager(QNetworkAccessManager): tab=self._tab_id) current_url = tab.url() except (KeyError, RuntimeError, TypeError): - # https://github.com/The-Compiler/qutebrowser/issues/889 + # https://github.com/qutebrowser/qutebrowser/issues/889 # Catching RuntimeError and TypeError because we could be in # the middle of the webpage shutdown here. current_url = QUrl() diff --git a/qutebrowser/browser/webkit/network/webkitqutescheme.py b/qutebrowser/browser/webkit/network/webkitqutescheme.py index cb4c68d97..61ef760bc 100644 --- a/qutebrowser/browser/webkit/network/webkitqutescheme.py +++ b/qutebrowser/browser/webkit/network/webkitqutescheme.py @@ -74,7 +74,7 @@ class JSBridge(QObject): @pyqtSlot(str, str, str) def set(self, sectname, optname, value): """Slot to set a setting from qute:settings.""" - # https://github.com/The-Compiler/qutebrowser/issues/727 + # https://github.com/qutebrowser/qutebrowser/issues/727 if ((sectname, optname) == ('content', 'allow-javascript') and value == 'false'): message.error("Refusing to disable javascript via qute:settings " diff --git a/qutebrowser/browser/webkit/rfc6266.py b/qutebrowser/browser/webkit/rfc6266.py index cc7f50f1e..89f4c78c6 100644 --- a/qutebrowser/browser/webkit/rfc6266.py +++ b/qutebrowser/browser/webkit/rfc6266.py @@ -117,7 +117,7 @@ class Language(str): """A language-tag (RFC 5646, Section 2.1). FIXME: This grammar is not 100% correct yet. - https://github.com/The-Compiler/qutebrowser/issues/105 + https://github.com/qutebrowser/qutebrowser/issues/105 """ grammar = re.compile('[A-Za-z0-9-]+') @@ -132,7 +132,7 @@ class ValueChars(str): """A value of an attribute. FIXME: Can we merge this with Value? - https://github.com/The-Compiler/qutebrowser/issues/105 + https://github.com/qutebrowser/qutebrowser/issues/105 """ grammar = re.compile('({}|{})*'.format(attr_char_re, hex_digit_re)) diff --git a/qutebrowser/browser/webkit/webkitelem.py b/qutebrowser/browser/webkit/webkitelem.py index aab3d237c..56e6baf6f 100644 --- a/qutebrowser/browser/webkit/webkitelem.py +++ b/qutebrowser/browser/webkit/webkitelem.py @@ -162,7 +162,7 @@ class WebKitElement(webelem.AbstractWebElement): # On e.g. Void Linux with musl libc, the stack size is too small # for jsc, and running JS will fail. If that happens, fall back to # the Python implementation. - # https://github.com/The-Compiler/qutebrowser/issues/1641 + # https://github.com/qutebrowser/qutebrowser/issues/1641 return None text = utils.compact_text(self._elem.toOuterXml(), 500) @@ -216,7 +216,7 @@ class WebKitElement(webelem.AbstractWebElement): Skipping of small rectangles is due to elements containing other elements with "display:block" style, see - https://github.com/The-Compiler/qutebrowser/issues/1298 + https://github.com/qutebrowser/qutebrowser/issues/1298 Args: elem_geometry: The geometry of the element, or None. diff --git a/qutebrowser/browser/webkit/webkittab.py b/qutebrowser/browser/webkit/webkittab.py index 9cb4ea68c..6c12d875c 100644 --- a/qutebrowser/browser/webkit/webkittab.py +++ b/qutebrowser/browser/webkit/webkittab.py @@ -677,7 +677,7 @@ class WebKitTab(browsertab.AbstractTab): While Qt has a bool "ok" attribute for loadFinished, it always is True when using error pages... See - https://github.com/The-Compiler/qutebrowser/issues/84 + https://github.com/qutebrowser/qutebrowser/issues/84 """ self._on_load_finished(not self._widget.page().error_occurred) @@ -690,8 +690,8 @@ class WebKitTab(browsertab.AbstractTab): def _on_frame_created(self, frame): """Connect the contentsSizeChanged signal of each frame.""" # FIXME:qtwebengine those could theoretically regress: - # https://github.com/The-Compiler/qutebrowser/issues/152 - # https://github.com/The-Compiler/qutebrowser/issues/263 + # https://github.com/qutebrowser/qutebrowser/issues/152 + # https://github.com/qutebrowser/qutebrowser/issues/263 frame.contentsSizeChanged.connect(self._on_contents_size_changed) @pyqtSlot(QSize) diff --git a/qutebrowser/browser/webkit/webview.py b/qutebrowser/browser/webkit/webview.py index 7795f3962..6494b04b8 100644 --- a/qutebrowser/browser/webkit/webview.py +++ b/qutebrowser/browser/webkit/webview.py @@ -59,7 +59,7 @@ class WebView(QWebView): super().__init__(parent) if sys.platform == 'darwin' and qtutils.version_check('5.4'): # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-42948 - # See https://github.com/The-Compiler/qutebrowser/issues/462 + # See https://github.com/qutebrowser/qutebrowser/issues/462 self.setStyle(QStyleFactory.create('Fusion')) # FIXME:qtwebengine this is only used to set the zoom factor from # the QWebPage - we should get rid of it somehow (signals?) diff --git a/qutebrowser/completion/completer.py b/qutebrowser/completion/completer.py index c5c1915ca..74c759c0d 100644 --- a/qutebrowser/completion/completer.py +++ b/qutebrowser/completion/completer.py @@ -239,7 +239,7 @@ class Completer(QObject): # This is a search or gibberish, so we don't need to complete # anything (yet) # FIXME complete searches - # https://github.com/The-Compiler/qutebrowser/issues/32 + # https://github.com/qutebrowser/qutebrowser/issues/32 completion.set_model(None) return diff --git a/qutebrowser/completion/completiondelegate.py b/qutebrowser/completion/completiondelegate.py index b48952d1c..dfb479b3f 100644 --- a/qutebrowser/completion/completiondelegate.py +++ b/qutebrowser/completion/completiondelegate.py @@ -54,7 +54,7 @@ class CompletionItemDelegate(QStyledItemDelegate): # FIXME this is horribly slow when resizing. # We should probably cache something in _get_textdoc or so, but as soon as # we implement eliding that cache probably isn't worth much anymore... - # https://github.com/The-Compiler/qutebrowser/issues/121 + # https://github.com/qutebrowser/qutebrowser/issues/121 def __init__(self, parent=None): self._painter = None @@ -173,7 +173,7 @@ class CompletionItemDelegate(QStyledItemDelegate): """ # FIXME we probably should do eliding here. See # qcommonstyle.cpp:viewItemDrawText - # https://github.com/The-Compiler/qutebrowser/issues/118 + # https://github.com/qutebrowser/qutebrowser/issues/118 text_option = QTextOption() if self._opt.features & QStyleOptionViewItem.WrapText: text_option.setWrapMode(QTextOption.WordWrap) diff --git a/qutebrowser/completion/completionwidget.py b/qutebrowser/completion/completionwidget.py index 8a8f9cfb6..294d50332 100644 --- a/qutebrowser/completion/completionwidget.py +++ b/qutebrowser/completion/completionwidget.py @@ -134,7 +134,7 @@ class CompletionView(QTreeView): self.setUniformRowHeights(True) self.hide() # FIXME set elidemode - # https://github.com/The-Compiler/qutebrowser/issues/118 + # https://github.com/qutebrowser/qutebrowser/issues/118 def __repr__(self): return utils.get_repr(self) diff --git a/qutebrowser/completion/models/configmodel.py b/qutebrowser/completion/models/configmodel.py index 2983dbe5c..4058a5f00 100644 --- a/qutebrowser/completion/models/configmodel.py +++ b/qutebrowser/completion/models/configmodel.py @@ -30,7 +30,7 @@ class SettingSectionCompletionModel(base.BaseCompletionModel): """A CompletionModel filled with settings sections.""" - # https://github.com/The-Compiler/qutebrowser/issues/545 + # https://github.com/qutebrowser/qutebrowser/issues/545 # pylint: disable=abstract-method COLUMN_WIDTHS = (20, 70, 10) @@ -52,7 +52,7 @@ class SettingOptionCompletionModel(base.BaseCompletionModel): _section: The config section this model shows. """ - # https://github.com/The-Compiler/qutebrowser/issues/545 + # https://github.com/qutebrowser/qutebrowser/issues/545 # pylint: disable=abstract-method COLUMN_WIDTHS = (20, 70, 10) @@ -108,7 +108,7 @@ class SettingValueCompletionModel(base.BaseCompletionModel): _option: The config option this model shows. """ - # https://github.com/The-Compiler/qutebrowser/issues/545 + # https://github.com/qutebrowser/qutebrowser/issues/545 # pylint: disable=abstract-method COLUMN_WIDTHS = (20, 70, 10) diff --git a/qutebrowser/completion/models/miscmodels.py b/qutebrowser/completion/models/miscmodels.py index 450549057..15ec21fc6 100644 --- a/qutebrowser/completion/models/miscmodels.py +++ b/qutebrowser/completion/models/miscmodels.py @@ -32,7 +32,7 @@ class CommandCompletionModel(base.BaseCompletionModel): """A CompletionModel filled with non-hidden commands and descriptions.""" - # https://github.com/The-Compiler/qutebrowser/issues/545 + # https://github.com/qutebrowser/qutebrowser/issues/545 # pylint: disable=abstract-method COLUMN_WIDTHS = (20, 60, 20) @@ -50,7 +50,7 @@ class HelpCompletionModel(base.BaseCompletionModel): """A CompletionModel filled with help topics.""" - # https://github.com/The-Compiler/qutebrowser/issues/545 + # https://github.com/qutebrowser/qutebrowser/issues/545 # pylint: disable=abstract-method def __init__(self, parent=None): @@ -87,7 +87,7 @@ class QuickmarkCompletionModel(base.BaseCompletionModel): """A CompletionModel filled with all quickmarks.""" - # https://github.com/The-Compiler/qutebrowser/issues/545 + # https://github.com/qutebrowser/qutebrowser/issues/545 # pylint: disable=abstract-method def __init__(self, parent=None): @@ -102,7 +102,7 @@ class BookmarkCompletionModel(base.BaseCompletionModel): """A CompletionModel filled with all bookmarks.""" - # https://github.com/The-Compiler/qutebrowser/issues/545 + # https://github.com/qutebrowser/qutebrowser/issues/545 # pylint: disable=abstract-method def __init__(self, parent=None): @@ -117,7 +117,7 @@ class SessionCompletionModel(base.BaseCompletionModel): """A CompletionModel filled with session names.""" - # https://github.com/The-Compiler/qutebrowser/issues/545 + # https://github.com/qutebrowser/qutebrowser/issues/545 # pylint: disable=abstract-method def __init__(self, parent=None): @@ -249,7 +249,7 @@ class BindCompletionModel(base.BaseCompletionModel): """A CompletionModel filled with all bindable commands and descriptions.""" - # https://github.com/The-Compiler/qutebrowser/issues/545 + # https://github.com/qutebrowser/qutebrowser/issues/545 # pylint: disable=abstract-method COLUMN_WIDTHS = (20, 60, 20) diff --git a/qutebrowser/config/config.py b/qutebrowser/config/config.py index 9e8f14621..aa9ac1ca3 100644 --- a/qutebrowser/config/config.py +++ b/qutebrowser/config/config.py @@ -233,7 +233,7 @@ def _init_misc(): # doesn't overwrite our config. # # This fixes one of the corruption issues here: - # https://github.com/The-Compiler/qutebrowser/issues/515 + # https://github.com/qutebrowser/qutebrowser/issues/515 path = os.path.join(standarddir.config(), 'qsettings') for fmt in [QSettings.NativeFormat, QSettings.IniFormat]: diff --git a/qutebrowser/keyinput/modeman.py b/qutebrowser/keyinput/modeman.py index 54487ae5c..741fe87ec 100644 --- a/qutebrowser/keyinput/modeman.py +++ b/qutebrowser/keyinput/modeman.py @@ -299,7 +299,7 @@ class ModeManager(QObject): log.modes.debug("Leaving mode {}{}".format( mode, '' if reason is None else ' (reason: {})'.format(reason))) # leaving a mode implies clearing keychain, see - # https://github.com/The-Compiler/qutebrowser/issues/1805 + # https://github.com/qutebrowser/qutebrowser/issues/1805 self.clear_keychain() self.mode = usertypes.KeyMode.normal self.left.emit(mode, self.mode, self._win_id) diff --git a/qutebrowser/mainwindow/prompt.py b/qutebrowser/mainwindow/prompt.py index 5af2f6b7c..8943d8fb8 100644 --- a/qutebrowser/mainwindow/prompt.py +++ b/qutebrowser/mainwindow/prompt.py @@ -112,7 +112,7 @@ class PromptQueue(QObject): if not sip.isdeleted(question): # the question could already be deleted, e.g. by a cancelled # download. See - # https://github.com/The-Compiler/qutebrowser/issues/415 + # https://github.com/qutebrowser/qutebrowser/issues/415 self.ask_question(question, blocking=False) def shutdown(self): @@ -153,7 +153,7 @@ class PromptQueue(QObject): if self._shutting_down: # If we're currently shutting down we have to ignore this question # to avoid segfaults - see - # https://github.com/The-Compiler/qutebrowser/issues/95 + # https://github.com/qutebrowser/qutebrowser/issues/95 log.prompt.debug("Ignoring question because we're shutting down.") question.abort() return None diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py index 0cdb5eff9..d65360359 100644 --- a/qutebrowser/mainwindow/tabbedbrowser.py +++ b/qutebrowser/mainwindow/tabbedbrowser.py @@ -270,7 +270,7 @@ class TabbedBrowser(tabwidget.TabWidget): # There are some good reasons why a URL could be empty # (target="_blank" with a download, see [1]), so we silently ignore # this. - # [1] https://github.com/The-Compiler/qutebrowser/issues/163 + # [1] https://github.com/qutebrowser/qutebrowser/issues/163 pass else: # We display a warnings for URLs which are not empty but invalid - diff --git a/qutebrowser/mainwindow/tabwidget.py b/qutebrowser/mainwindow/tabwidget.py index de65fa280..c4c4beaa5 100644 --- a/qutebrowser/mainwindow/tabwidget.py +++ b/qutebrowser/mainwindow/tabwidget.py @@ -253,7 +253,7 @@ class TabBar(QTabBar): fixing this would be a lot of effort, so we'll postpone it until we're reimplementing drag&drop for other reasons. - https://github.com/The-Compiler/qutebrowser/issues/126 + https://github.com/qutebrowser/qutebrowser/issues/126 Attributes: vertical: When the tab bar is currently vertical. diff --git a/qutebrowser/misc/earlyinit.py b/qutebrowser/misc/earlyinit.py index def252a91..66bc55ca0 100644 --- a/qutebrowser/misc/earlyinit.py +++ b/qutebrowser/misc/earlyinit.py @@ -69,7 +69,7 @@ def _missing_str(name, *, windows=None, pip=None, webengine=False): '(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'), + 'https://github.com/qutebrowser/qutebrowser/issues/1692'), ] else: lines = ['If you installed a qutebrowser package for your ' diff --git a/qutebrowser/misc/editor.py b/qutebrowser/misc/editor.py index 1705df09c..a6f2854d8 100644 --- a/qutebrowser/misc/editor.py +++ b/qutebrowser/misc/editor.py @@ -107,7 +107,7 @@ class ExternalEditor(QObject): # 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 + # https://github.com/qutebrowser/qutebrowser/issues/1767 with tempfile.NamedTemporaryFile( mode='w', prefix='qutebrowser-editor-', encoding=encoding, delete=False) as fobj: diff --git a/qutebrowser/misc/ipc.py b/qutebrowser/misc/ipc.py index 6641a9ee8..2e918ed07 100644 --- a/qutebrowser/misc/ipc.py +++ b/qutebrowser/misc/ipc.py @@ -222,7 +222,7 @@ class IPCServer(QObject): try: os.chmod(self._server.fullServerName(), 0o700) except FileNotFoundError: - # https://github.com/The-Compiler/qutebrowser/issues/1530 + # https://github.com/qutebrowser/qutebrowser/issues/1530 # The server doesn't actually exist even if ok was reported as # True, so report this as an error. raise ListenError(self._server) diff --git a/qutebrowser/misc/sessions.py b/qutebrowser/misc/sessions.py index 9a544c738..3b18c39a5 100644 --- a/qutebrowser/misc/sessions.py +++ b/qutebrowser/misc/sessions.py @@ -167,7 +167,7 @@ class SessionManager(QObject): if item.title(): data['title'] = item.title() else: - # https://github.com/The-Compiler/qutebrowser/issues/879 + # https://github.com/qutebrowser/qutebrowser/issues/879 if tab.history.current_idx() == idx: data['title'] = tab.title() else: @@ -306,7 +306,7 @@ class SessionManager(QObject): if 'zoom' in data: # The zoom was accidentally stored in 'data' instead of per-tab # earlier. - # See https://github.com/The-Compiler/qutebrowser/issues/728 + # See https://github.com/qutebrowser/qutebrowser/issues/728 user_data['zoom'] = data['zoom'] elif 'zoom' in histentry: user_data['zoom'] = histentry['zoom'] @@ -314,7 +314,7 @@ class SessionManager(QObject): if 'scroll-pos' in data: # The scroll position was accidentally stored in 'data' instead # of per-tab earlier. - # See https://github.com/The-Compiler/qutebrowser/issues/728 + # See https://github.com/qutebrowser/qutebrowser/issues/728 pos = data['scroll-pos'] user_data['scroll-pos'] = QPoint(pos['x'], pos['y']) elif 'scroll-pos' in histentry: diff --git a/qutebrowser/utils/debug.py b/qutebrowser/utils/debug.py index e342446b4..89ae62faf 100644 --- a/qutebrowser/utils/debug.py +++ b/qutebrowser/utils/debug.py @@ -137,7 +137,7 @@ def qflags_key(base, value, add_base=False, klass=None): Note: Passing a combined value (such as Qt.AlignCenter) will get the names for the individual bits (e.g. Qt.AlignVCenter | Qt.AlignHCenter). FIXME - https://github.com/The-Compiler/qutebrowser/issues/42 + https://github.com/qutebrowser/qutebrowser/issues/42 Args: base: The object the flags are in, e.g. QtCore.Qt diff --git a/qutebrowser/utils/log.py b/qutebrowser/utils/log.py index 07ec78741..e9ce3d463 100644 --- a/qutebrowser/utils/log.py +++ b/qutebrowser/utils/log.py @@ -406,9 +406,9 @@ def qt_message_handler(msg_type, context, msg): # When enabling debugging with QtWebEngine "Remote debugging server started successfully. Try pointing a " "Chromium-based browser to ", - # https://github.com/The-Compiler/qutebrowser/issues/1287 + # https://github.com/qutebrowser/qutebrowser/issues/1287 "QXcbClipboard: SelectionRequest too old", - # https://github.com/The-Compiler/qutebrowser/issues/2071 + # https://github.com/qutebrowser/qutebrowser/issues/2071 'QXcbWindow: Unhandled client message: ""', # https://codereview.qt-project.org/176831 "QObject::disconnect: Unexpected null parameter", @@ -551,7 +551,7 @@ class RAMHandler(logging.Handler): FIXME: We should do all the HTML formatter via jinja2. (probably obsolete when moving to a widget for logging, - https://github.com/The-Compiler/qutebrowser/issues/34 + https://github.com/qutebrowser/qutebrowser/issues/34 """ minlevel = LOG_LEVELS.get(level.upper(), VDEBUG_LEVEL) lines = [] diff --git a/qutebrowser/utils/objreg.py b/qutebrowser/utils/objreg.py index d05424fc3..e54502ef8 100644 --- a/qutebrowser/utils/objreg.py +++ b/qutebrowser/utils/objreg.py @@ -111,7 +111,7 @@ class ObjectRegistry(collections.UserDict): # # With older PyQt-versions (5.2.1) we'll get a "TypeError: # pyqtSignal must be bound to a QObject" instead: - # https://github.com/The-Compiler/qutebrowser/issues/257 + # https://github.com/qutebrowser/qutebrowser/issues/257 pass del partial_objs[name] diff --git a/qutebrowser/utils/urlutils.py b/qutebrowser/utils/urlutils.py index 1199c73f4..7e3827fcd 100644 --- a/qutebrowser/utils/urlutils.py +++ b/qutebrowser/utils/urlutils.py @@ -36,7 +36,7 @@ from qutebrowser.browser.network import pac # FIXME: we probably could raise some exceptions on invalid URLs -# https://github.com/The-Compiler/qutebrowser/issues/108 +# https://github.com/qutebrowser/qutebrowser/issues/108 class InvalidUrlError(ValueError): @@ -297,7 +297,7 @@ def qurl_from_user_input(urlstr): WORKAROUND - https://bugreports.qt.io/browse/QTBUG-41089 FIXME - Maybe https://codereview.qt-project.org/#/c/93851/ has a better way to solve this? - https://github.com/The-Compiler/qutebrowser/issues/109 + https://github.com/qutebrowser/qutebrowser/issues/109 Args: urlstr: The URL as string. diff --git a/qutebrowser/utils/utils.py b/qutebrowser/utils/utils.py index fbed4ede3..f3eab1eab 100644 --- a/qutebrowser/utils/utils.py +++ b/qutebrowser/utils/utils.py @@ -400,7 +400,7 @@ def keyevent_to_string(e): if sys.platform == 'darwin': # Qt swaps Ctrl/Meta on OS X, so we switch it back here so the user can # use it in the config as expected. See: - # https://github.com/The-Compiler/qutebrowser/issues/110 + # https://github.com/qutebrowser/qutebrowser/issues/110 # http://doc.qt.io/qt-5.4/osx-issues.html#special-keys modmask2str = collections.OrderedDict([ (Qt.MetaModifier, 'Ctrl'), diff --git a/scripts/dev/download_release.sh b/scripts/dev/download_release.sh index 80b68d2f0..7ec4d9159 100644 --- a/scripts/dev/download_release.sh +++ b/scripts/dev/download_release.sh @@ -14,7 +14,7 @@ fi cd "$tmpdir" mkdir windows -base="https://github.com/The-Compiler/qutebrowser/releases/download/v$1" +base="https://github.com/qutebrowser/qutebrowser/releases/download/v$1" wget "$base/qutebrowser-$1.tar.gz" || exit 1 wget "$base/qutebrowser-$1.tar.gz.asc" || exit 1 diff --git a/tests/conftest.py b/tests/conftest.py index d016f2e58..bafd1cf74 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -133,7 +133,7 @@ def pytest_collection_modifyitems(config, items): if item.get_marker('issue2183'): item.add_marker(pytest.mark.xfail( config.webengine and qtutils.version_check('5.7.1'), - reason='https://github.com/The-Compiler/qutebrowser/issues/' + reason='https://github.com/qutebrowser/qutebrowser/issues/' '2183')) if deselected: diff --git a/tests/end2end/data/downloads/issue1535.html b/tests/end2end/data/downloads/issue1535.html index aec88420b..2c147bff6 100644 --- a/tests/end2end/data/downloads/issue1535.html +++ b/tests/end2end/data/downloads/issue1535.html @@ -4,7 +4,7 @@

Cancelling a download that belongs to an mhtml download.

-

See also GitHub

+

See also GitHub

+ + =20 + + + =20 + + + =20 + + + =20 + + + =20 + + =20 + =20 + + =20 + + + + + =20 +

Welcome to the qutebrowser mhtml test page

+ =20 +
+ ...that the word qutebrowser is a word play on Qt, the + framework the browser is built with? +
+ =20 +

What is this page?

+ =20 +

This page is a test-case for the mhtml download feature of + qutebrowser. Under normal circumstances, you won't see this page, e= +xcept + if you're a qutebrowser developer or you're attending one = +of + The-Compiler's pytest demos.

+ =20 +
+ ...that this page was once a monstrosity with "this weird pixel= +ated + globe with the geocities-like background"? You can find the ol= +d + page in the old commits and indeed, it was quite atrocious. But hey= +, + every browser needs a globe... +
+ =20 +

This page references other assets and when the page is downloade= +d, + qutebrowser checks if each asset was downloaded. If some assets are + missing, the test fails and the poor developers have to search for = +the + error.

+ =20 +

Can I contribute to qutebrowser?

+ =20 +

Yes!

+ =20 +
+ ...that qutebrowser is free software? Free as in free beer= + and + free speech! Isn't that great? +
+ =20 +

...and how?

+ =20 +

See + here for more information.

+ =20 +

More useless trivia!

+ =20 +
+ ...that the font in the header is Comic Sans? +
+ =20 +
+ ...the IRC channel for qutebrowser is #qutebrowser on + irc.freenode.net +
+ =20 +
+ ...the area of a circle is =CF=80*r2? +
+ =20 +

To make this page a bit useful, I've included a chessboard, so y= +ou + can play chess. Just turn your screen 90 degrees, such that it form= +s a + flat, horizontal surface (you can skip this step if you're using a + tablet). Next, zoom the page until it fits your needs. Enjoy your r= +ound + of chess!

+ +
+ =20 + + +-----=_qute-UUID +Content-Type: image/png +Content-Transfer-Encoding: base64 +Content-Location: http://localhost:(port)/data/downloads/mhtml/complex/Background.png + +iVBORw0KGgoAAAANSUhEUgAAAAEAAABkCAIAAADITs03AAAACXBIWXMAAAsTAAALEwEAmpwYAAAA +B3RJTUUH4AICDwo6PeZczAAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUH +AAAAMElEQVQY0+3DUQ0AIAxDwaYSYK+W5t8H84ALvrjk1N2W9D+613IFUxgwwSlcRHPmAgsRBhHi +LLCEAAAAAElFTkSuQmCC +-----=_qute-UUID +Content-Type: text/css +Content-Transfer-Encoding: quoted-printable +Content-Location: http://localhost:(port)/data/downloads/mhtml/complex/external-in-extern.css + +@charset "utf-8"; + +h1, h2, h3, h4, h5, h6 { color: rgb(10, 57, 110); border-bottom: 1px dotted= + rgb(71, 71, 71); } +-----=_qute-UUID +Content-Type: image/png +Content-Transfer-Encoding: base64 +Content-Location: http://localhost:(port)/data/downloads/mhtml/complex/DYK.png + +iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA +B3RJTUUH4AICDjQ0mlkAgQAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUH +AAAgAElEQVR42u19d3xUVfr+c8690yeTXkggCYEkhI4gIiBSRKWKgugq+l0XRNR17a4iUkRwBdRd +wYIi+ltFXAQXURCQqoJSpYUSAikkgZBJT6bee8/vj5uZZJiSmcyE4vryuR9gbjv3vM953/e8533f +Q/C7I0aW5dq6SDw/SE1Zbx2HThFK2lbHQa8iUCo5KFSUcBoKTsOBiAwwi5DMEpOsEgSLCLvRhtrH +jtp3MuAsI+Q0gZAjQMgpvsVQ/nvrLXKtf8DrJ8ypkSp+cpSKjkhWk4z2OqJX0eC/641cESuLJVdo +MVQSIIcR7JVEtpUzm3bm3xlZ9QcALiON2V+ivS0i/vFUHbm3rRpZyVqiaY2PkBgwaq+AC1bmXdYw +iITgIBjbJjG2tVA89yNGplv/AECIafb27Xx88qAnkjRkahcDyYxUtH67i8wMY/cKgSkfoAqQVosC ++7xopOZH+ac/ANBieuaYfciACDK/Zzh3fYQC3OV89/v5IpYVSsHYIoUSwxdg+LzwdlX2HwAIwIj7 +JF+akaUnz3XUE8PleKNNAlaXSPihTMKMDA7xKoJ79gsotYZmADOG7YyJ8wpHaLb+AQAvtCo7Wyno +M97ICqOPtNUQTSieaZUAo40hTkWgaPhSiwScMzMIEkAI8HMFw1fFEirswMMpFFNSKGacEPFDWatI +7z2SKM0rHKn+7mpRD1cDAMhn+cK7vSPplBglFKF66IEqhheyBdQK8kem6Qj+2Y3DY4dFnDMz5N2m +9HhfmYXhqxIJW8skHKlmEFqDTQxHJIa5hSNVq/+nAfDxGWF8l3CyPE0belFfYmG4YAWq7AyLz0oo +tjB0MxAwAN/e6BtnZWaZ65+dk7A4T2w9ZcfYNjvwWMkI9akrxQP+Srx0TZGprZqovr0ukvSkIZCF +cR4mgrEagjZmhhILUC8wMAYcqXZ9EyHkUoY4rHkAgEmU7/OX8m/3LFVSN9q8jb+hCoYjKRvMi8Q6 +42tFE9uZLzcv6OV+YfIGy5h4hepM7wjSkzCABXnE+fACxGkIXjkhotIesNEGxgASgID0xvzmzoFA +CUKnc2Gxx5M3WMb8fgHwSZ46eYNlqZqSdYRAyRpGWrBH09Hc9HDQ0Wrm/SYv9PAhAcdrGZQ08IZ4 +a0fzzyCpBGRd8gbLUnySp/5dqYDlZ4UJqRryaYQCumglgYICdgngL4MF0ieC4HA1g8iAFC3B8+nN +uxOOVDM8dFDAlSACMjUlLvEGYb1lYvEodc41D4CFx+0LKq14cnuZpLyjDUW8WjauPi0QkagmSNUR +9IkgGNumdYTRG114MADFZnk6+HO5H86d1rH8A6EePCP7231nmnputPbLa1IF9F66X7E8137gUA2e +X14oKgdGEfSNIGAMMAkMJhHIrWc4Us1wuJq12AZoFuEEOFnDsKlUwgP7Bcw7JboYfU2PlpA9Pxem +3dtbQxSEUcKtTPnO8h5mb+dbT+K0xqg/XBvXKVp7ZHe5FL/+goS3u/PI0De+6uVsETuNEkbEU7zU +iWsWhQRAmBLQ8K5qgwGgDXrWm0XfZaMN9SKQP0rpV9tT19u8njMogEfSONwaB7TVAJAkAAwWu4RI +vdpnO3w9t3nhwdYLrGJiyZhE01WvAj4+ZeuRFc7tPlTDtF+ck/BEBw7pOuIyWrPCCCIVFM+kcyAN +NtpvVQwfF4gotwEPJlOMiJdhwREgTgvwlLQIvYEwH5Cv9cSsZC3wZV+KRF1TG0L+t0bV6nbBKAWi +t7RdVT26aGJ4xVWrAlacEUZeH8sfiFAS7QdnRWgoMDKeuhm8f2onM7+pcdwzguCf3Xks7cXj9oZ7 +mjK/srIS06dPR+fOnaHVahEdHY0xY8Zg7969fuveSy10vyx2UURC8Ums7G5Foo4Prh3BTXdupGrV +T0lrTG2vSgmw4LgwskcE/TaMA/2pXEK9ALyYyUHHuTJhfyVDoZnheA1DOy3BvW0pVLRxPDGzCfvy +SnA6/zz+NKwbeH0kzpw5g8GDB6OoqMj5HLPZjO+++w4bN27E559/7rVdYlU5gDYBf49UWw3zb7/C +dngvvln8CJKiw4JqR0gkASGdeQXdlfS1ZXjxXaGZIYTEBlh4xH7j0DbcT9FKWSbaGVBhY4hXuT6+ +zMow54SIFC3BxLYUKVr5fNGFcvzw82Fs/vkIjpwqAGMMY2/ujpWvT4YoiujTpw8OHTrk9f1qtRoW +i8Wj7tX1fxL1u//lUT97cM0CAKIeWgbr6WxAkkLWjtRvbSGEAisU7NKA4vHaoiuuAhYdsXa6KY7u +iFKAc1jmPIA4JXGz2GOUBIt78HgunQNXU4kPVv6A8Y8vwq1/nouFy9bh8Ml8Z4fdOaQnAOCbb75x +6/SZM2eivLwcZWVleOmll9w63ReD/bH6raeONhh4rdOOEIzbZI6nm9quqo66oipg8cn6xJ4G/kCc +mij9nUQVXSjH0pWbsW7rPoii9zl5z0xZ1a1e7bpgduedd2LOnDnO/8+fPx/Z2dlYt26d12c1lQKB +UsjawULrXCBAZ06p/C5xacktJY+0fHbQYgmw6kxFeBe9OjtRQ7T+zNcLS4yY8dZKjJoyD//dvMcn +8wGgTUw4AODgwYMuvz/66KMAgHU7D2P9T0cBAI899liz7dX1f9J5BEL+tGPD/rN+tyPERsGNfHzU +qmD8BC26cdWqVVy4Kvxoso5ENIdrQRDx7uff49M125plelPSquWpW0lJicvvXbp0AQB89PUuUEow +6qZuzt9ag/xphyK+DUb2SfPdjlYK/yDAqHcmDtz1t9m44bIBQOpz18o0LWnXnFQ7U3ABLy36DKfO +Frf4A00mV+kWExMDADied97pBIqNjW31wearHSqb6rK1w4ufoO8bh22L/t5D+Vyrq4D3s60Te4TT +u31NWSXG8NnaHfjTU28GxXwA0Gq1Lv83Go0AgM7t26Bzmjy9Kysra/VO9tWOTu2imm8Ha73jZK2E +W9vwz8w4YB7aqhLgnYO1sd0iuM8ovAdKiKKEV95ege93HAxuxFlsCNOpkZiYiFOnGgNmsrOzkZiY +iMnj+oPnOOdvrTby/WiHQh/W6u3wRT+XSdBQgRSb6Uf4JK8LHmpvaRUAxBrUP8eqiVe/qtVmx/Ov +f4qf9h0P+qPOG6sRplOjd+/eLh2/dOlSDB8+HOMapmcA8P777/t2Bwc4A/B0fbDtYK0YAlppA1ad +kwAgLdmQ8K9C4JGQq4B3sq0LekXQDG9Wfm2dBY++8kFImA8Av506BwCYMGGCy+9r1qzBq6++isrK +SpSXl2PGjBn45ptvQsZ8bxRsOy7jzGDqkI3m+0LqCfz6jCUjRa84ofWycGezCXh4+hIcO1UQsu8Y +M6g7vvyHfx44vV6Puro6jx64Jm7UlvncGp4TbDtSvr48GWMRCmB+N966aF9FzI6JcXUhkQBajt+o +oaDeRv+cd1aGlPkAsP7nozicUwSO47Bq1SokJiZ61mE8j2XLlrV6x14t7fDJTAJMTuOQpieq4emR +q0KiApaftD3UTkfaezNCP1m9BRt3Hgz5x0gSwwMzPsX5smqkp6fjyJEjePbZZ9GxY0eoVCpER0dj +9OjR2LlzJ+65555W79yg28Fa9+igI5jblcctDSupQ+Lp7eM21fcOWgVsyBNqknUkzKP1uS8bz877 +uMXRNP5Qx/ZtsHHxX9EmSn9FR1f3ia9h07tPoE1seIvuT1nTOiqAAHikA4exSe5j+fvzYsHzPZSp +LQbAG4dsi0Ymcs96OldTZ8I9j/8DFVW1If8oPqEd1J17g09MAR/TBhFqisc7cbg1kUObJkljJgE4 +UC7hlkTPgZ4Ot2+whqDjOVEGLZ57cDhGD+qGtnGRjYam1Y6DtUqv7UhZ3Xr6/662FJM7eH6vXQJe +z7Y/9tUw9fsBA2D29u38je1vqmujIR7jXWa/vQIbd+4P6ccoktOh7TsEiuSOLX6GWFmGmjXLINZU +er1GmZoBw11TAAAVy16H5ONav9rdNg3hE6dddqlkUAAf91VA4yPQeV85q5rcmY+CF2e0VxsgLmbA +WwkaovKkcn7enx1C5hOo0rsh4r6/IXz8w1C06xiULuQiYhF+z2PgImP908ehInb5j1sTKNSc78v6 +RJOIF/ZYngvICNxw+rSqSzj16Eyot0l4Y+nXoWE9xyNs5J8QNnoS+PikkPUM1RsQPnEa+Ng2fnDs +2kVAuJ+ptDdG8zMDAsC+qnaPlVqgrLG7Tvfy6xj+/Nk+lJUFH5dI1FoYxj8MVUaPVukfqtEjbOQk +gHK/S/5rKXBDFPUrdL69nuif/cUyzW9X8Mdn2BgQOaEuWUtwfTRFXj3D4UoRxt07gp9TR0TDMO4v +4CJiWnfuHhkDZUo6bHkn8XsiAuBvmTzi1cRvF3OylpsB4INmAZD8RW0XAEMco6OwnqGwXk6msOWd +glh+IeiRHz7hUVBd2OUpkUCo9xGLEEmBUEsTH5QeRjAtg0cHPQnolb2jaFK/b+uv+3WM7qBPAEjg +JnmzDM37g8+ACRs2Xmb+/yJJEqT6aoi11ZBqqyDVVcsYVWlA1BpQlQZEowMfFeemujIMBBNTOFwX +JdfACxRvGh4YHMfP+RUY4wsAhBDc5+np9vMFsJfkB/X96qw+UHboenUUR2GX53mSqRa2vJOw5Z+E +vTAHzN58dDBRKKFITIWibQcoktIQ07YdpndRwqCQ39PSpncL54Y3aBDmEQBtvzAPIkBya4x+GhYJ +3c1j/2cGu1hTAdOvP8B66reA14KZ3QZbQQ5sBXLov0WtwvuHe2HS+FuQGB/d4jZlGIjqL9vN9y0f +olnhEQCUYZJHFNdVBW1IafsMAVGorp7R30oSQDLVwbRvKyzZewAxNOVlLBYr1m/9FRt37MXEMYMx ++U8jncEwgVKXcO55ACvcp4HvnFYBbIKnm2z5J4PqMaLSQNXput/3kGcSTHu3oPLfb8ByZHfImO8i +VUQJK9duw19ffgelxsoWzSC7RtJuD2y6oHMDQHJkm0EAifB0V7CjX92lLwinuCLeMq+4DaEUYFYz +qtd+DNOeH/zS8cHSydxCPPnKEpSWVbgluzR3hCtAk8MiHnUDgETpMI8BnoId9qLcoGat6q43XpW8 +D5UWEIzng+yjwOlCWQWenv0eysoDr1VtUNKx7ipAwlBPPWgvOgMm2FvcUC4qDjQs8uoa/Zd57t5a +dL60HC+8thRWmxBQUY0YFenuAoCITyojQIhHJW3PPxFUIxWJafiDWo8Kikqx7Iv1AY2JFD0JT15R +FekEgJYqbyYMnEcJcC43SAC0v3Kj/3cuARz09YYfcSg7128JoOGAwXHqSc5pICUY5qk3qCRAqglu +kww+IbnFPb1+hArdot39ks/utuGrs41W9l3tOfxzgHu0+olKCf2/b/x/57Q2mDbhJtzctwuS4uQw +BzZhOgCg+GIVfjx4Gu+t2omT+aVuz/IWVNJc0EmguYgtMkIZw8L3VuLDRS9Ao/avXEm8FuMALHb0 +bl9Po+e+6Krgwr0oBdWGt2jkemM+ALzZX4m70+SF8LtSPTMfALIiKX56XZ7ZvvjQbdjz7xcwedwA +dEyMgIYn0PAEWrUSWrUS6clxmDxuAPZ+9iKef3C4X8x3nGvu/OWg0rJKrN/yq99SIEFFuztVAAEy +Ln1gr2gKvSm4lCuqC5dLcgdBvXv3dinj8sknnwAA7mwvO0JGpMh/f/bZZy7X9ejRw/mMFx+6Da88 +PBKUUhw7dgzTpk1DZmYmdDoddDodMjMzMW3aNBw/fhwcRzF72mg3EAA+ikD6eb61ae33P0KUJL/G +WDs9iQIAmrSkJhqMRDY9q6bAPakczhVfDBIAhqB198MPP+zyzBUrZCdW/wS55uDNibKUWLNmjct1 +Dz74oPPfrzw8EgAwd+5c9OjRA0uXLkVOTg5MJhNMJhNycnKwdOlSdO/eHfPnzwcAzJ42Gk/cO/ia +sgUuGivx054jfkmAeDWhz/xkyaC8FpkcAVL1BAPjKSakcpjXW4G2OoKikuAAQGjLN/lYeVqu1Hnf +ffe5JGZu374dJSUloIRgbHseao6grq4OmzZtapx6chzuv/9+AECnJDl4c8GCBZg5cyYkSfLhaRPx +8ssv46233pIB89hYtI2PvLYMwvU7/b42SoGRdO716oUfDlBi7nUKPJLJ445kDnENgQYXjZXBt6iF +EmDtWRFmgcFgMLjE20uShJUrV7qogQ0bNriUZxk+fDgSEhIgSgw8R1FQUICXX37ZpVmzZ89GeXk5 +ysvLMXv2bJdzL774IgoLC6HgOTx854DQcIYQUJ0BfFw7KJI6gqp1rQKAU7kFOHG6wC8pYBdpFlXx +JFlBPfOB47jgmd9CqrMD3zbU6vemBrpG+xb/xyrkBrz//vsQhMbav+PHj8esWbMQFRWFqKgozJo1 +C+PHj2/0fdjtzkTPwX0ygmaKtu9tiPrLq4icNB3hdz4Ow+gpiPy/V2AYNRmKpI4hB8GeA9n+jrP2 +VE1IpDeEqJTBbeDBbOag5vBf5MgAuPHGG12qb/z22284cUJ2UFksFmzYsKHRzWkwYNy4cQCAlDDZ +ENu61XWrnmnT5PC4b34+jrXb5Vy/Rx5xjYHdtm0bAKB9UvBha5qeQzyuhSiS0mEYNQVhtz4IwitD +BoDD2af9kgAcWCI9WimW2ZlnHihVwTVKrK8J6v6DZRJOVUo+pcCmTZtcEjInTJgAjUaDWhuDvgG/ +Z86ccbm3c+fOAICPVm3Hx2t3u/zmIMc94brWr9yuTO0Mw9hpIKqQbJWEnLPnUG+2NDvGVDzC6NLj +YtHbRwRYxVaQAJZ6eXUsCCmwMkcW3Q888ABUqkYnxxdffAHGmFfxvyG/0VFUW+uaveQo73Ii/wJO +5F1w+c1B1dXVIfTUNH/w0YmIuHFUSF4nSRKOnTjbrARQUqKlDMyQXSlh0RE7TMIlCFEGL5aEC/lB +3b8mV4RVZIiKinLR03l5edixYwe+/fZb52+pqakYNGgQAGB1biMAwsJcYxAd5V2yUhOQ1T7B5TcH +hYcHlgMohmD9f+DAvujeOT00auD46WZxp+GJmhJGwsCA01UMbxyy40SlBLMgI8RgCD4h016cG5QE +qLYC3+d7NgYff/xxVFU1LodOmjQJhBCcq5Ww54KEOqt8X4cOHVzuO35cLmIx5c4BmDyuv8tvDnLc +U13vvdqK2dy4xU8o6gPlVDE8+pe7oVQGv3naET/sAA0HJWVgzuFRWMew8LCAJ3fZ8G62gHI++Dmw +veRM0M/4oqHG/+DBg5Ge3jhCHIbgpeJ/zfFa1G7+fzibL5d2GzZsmMt1H3wgh8ePG9LTWeJl6dKl +LtcMHSrXW8orbpQMer3rgMjPb5RuO3bsCPo7jRaGiJgYjBs1JOhnFZw7D0H07RVUceApJGa49Iwg +yQbYXlPwABDKS8AspqCkwK/nJeRVy8bglClTPL6nX79+TnAsf/td2ApPYPv+HKeFz/ON4Y9r1qzB +nDlzUFlZiYqKCsyZM8fFllAoFM6Zwo79Oagzydm9aWmuS9uvvvoqysvLcfjwYTc/Q0skAAf5GHpT +35AsEFVV1/mUAArGKGWSBOblDwkLgReMMVjPnfT6joEJBLcnU/l9hHm9buUp2Rj885//DIXCXUQ6 +Rv/eY/k4Uyiv5n28dhcEQUT79u3x6quvujmCoqKiEB0d7eYImjdvHlJSUmAXRCxbuwt5JbIUuO22 +21yu+/LLLxETE4OePXvi/PnzPvjv3x+BMfznjIC4mEhkdEwJuusrq6t9Is4uSqBgrM5Xfl0o5qeW +wzvk/dgbnquhQNdIime68Xgwg0eVhSFRQzC9pwIv91Kgc4S7Z2pVjgC7xBAXF4exY13Dy5VKJe69 +9155evh9Y93+gvMVmPuR7CN46aWXMHPmTJ+LNJRSzJkzB88//7wMkg++w7kLldh+Ul4Sf/bZZxEf +H+/x3pdeeikk3tDtxSL+e1bEwH69gu73isoanxKg1s4kKjHmo5AQAR8fPBLFqouw5R1BrIZg+nUK +vD1Aib9149EpUi5ncm9HHq/0USA5jCAljOCp7jzu6cghXkOQrCdQUqDcAvxQINsCl5ZiGT16NCIj +I2GzC1i9xbVczaLPtmD+8o0AgDlz5uDw4cOYOnUq0tPTodVqodVqkZ6ejqlTp+LQoUOYOVNOpH3t +ow1YsvkUDKOm4ZPqjigzMcTHx+Pnn3/GhAkTEBkZCbVaje7du2Pp0qV45ZVXQjZrXF8oIkcTvIew +qrrWJ97qbUzkIZI6X9UCFUkZsBefDroxpr3rMXFYOlL0EQ7NIBuJElBSz5BmcE10HJrIYWhDxQ2T +AJyuETG0nfz/Xbt2uTz7gQceAAB8vysbVbXum2/OWyZHhbz00G3o1q2bm8F36Rz69X9vxzunw2AY +8zgAAqMJuOtbC74eo0bHjh3x1VdfedW7oXKJHxeiQHkFpCDiMSuqanzmpJhssPHwKQEARWJo5qWS +qRafL1uOV1/+q4t/oV5gKDExtDd4Fs2jMhzolP+urKzEp59+6jwfHR2NUaNkB8oXG/d5ff+8Zd9j +7bZDmDZhEAb17ojE2AiX8yVlVfjxtzP45JAJeTG9oUp3VX35NQx3fWvBi9crMCCJg7rJMkleNUOW +l+CVdh+1tJI7AZQaIAgAVFbV+MSeyS5aeSZKFl9XcRHxoFoDJFNN0CDIKyjGG29/jMcfvg9RkbKj +xaAg6B/vOc15dCbvMjLPnj2Lp556ysVL5zAKy6vrsXGX71Kt2WfP44kF//Hsjk3pCu31I0ETIuW6 +OR7ak1/NMG1L68f9OyEQpP1V2YwEqLEwCw9BMAG+48gUiR1hzQ1NKbhjJ3Lx3IyF+L/7xmHQgD7+ +dYQXw02r1eKZZ54BAHy+fg+EAMrROwEe1Qa6vmPAJ7THVUdBAqDeZPYpAaqtoplnoni+OR2lSMwI +GQAcDXtv2Up8ve4HdO+aie5dM9E5swM0GjVESUKZsQJ79h/BmMxbfT5nyZIlSExMhMliwztfBpa8 +StV6aK67FaqOfeSwNdaosMWK8+CiEq84/4kiOAD42lyTAbDbpHJeYuQ0EyUQjvq0AwinABPtIf3A +CxeNuLDNiM3bZKNOq1HDarVBbIja+fgZdwDo9Xp0794ds2bNwq23yuefeXM1LhgDU1GGMU+Aag2u +XsvzZ2A+sAFUGwH90AeuPABCMAX3NrbrrSIIpQU8YzjK7CII9Q4AotRC1akfLNk/teoHm8yWwKxr +ANPmfYHP1u8JvHM5pbN3xOqLMB34HvYiOQdSqY24KnIGQgIAL9+RVyHAJOAnnkn23yRRAQrfCxCa +rjfDmrMXzG69bB3gbbOnOpMVx3JLMH/599i691RQ77Dm7EH9nnUAk3DVERfcto6GMNe1C5MA7CsV +sbdUwtlSK+xm09d8+Yx2xXGvl9ZBA59Lf0SpgzprAMxHtl3WPmitxAoOsnNSrCrzzPyrQAIwqzmo ++3OFSHx4TECynuCCieGwUYLQ8KmSTagrn9GumAIAE4Xz/lSxUHe5GVx4LH4PNKkTB45c3W1klvqg +7rdoonHUKGF9vogDFxuZD8ZknsORHUzIUckuNOunJpwS+pvuD2n82pWiTpEUw5M539bTFT4kS11w +Uk4f7fm5dgEg5KgTAIRgs2Sz+9UqLiIOugET0Uo7z1++0QVH0Ci7KhHA7GZI9cGFpdGwSI/Plmx2 +EILNgMO/yixrRRt5j9f6t4GEsm1naLoPg/nIFt/z/SuYMOmPdZwRSRGlIijxxvsrSEJZUVCNIJwC +VGPw+AjRJkiEWNY6AXDx5bTS2LlFeUySOviaDrrMCroNgVh1AbbCYwEx33HuSoPA0S/pkdQdAK1M +ag64O4vHyDQeXWKpM3oZAKqsQLZRxJdbJHwWhL1N9VEepTSTJICJeRdnpJU2SgAABPRXyWrvwPmZ +XgwQ6PvfgzrCwVZw2PtVl7hxW3NziZZ4yZRcgB6UIGlkBw6v3axCrNazCo3VAoOTeQz+Sy+M76zG +vS9+DIstcAccFxbl8Rskqx0E9FcnUJwnCL4XrbbANBXloBswEeqsmy7f6PEboMFJh9Y4RnbgsHSE +GrFaArPZjPfffx+33HILYmJioFQqkZCQgNtvvx0rVqwAYwzD+2Xhy39MhroFQaJUH+2xDaLVBong +ezcAGAX8lwmCmdnFAL+MQNNzBLR9xoLj+VZlTPvUdnj04QdCJAEclbMuHwqWjpCTTFavXo327dvj +sccew9atW1FeXg673Y7S0lJs2rQJkyZNwogRI2A2mzG8XxZe+POtAX8fH+demYXZRTBBMBsF/NcN +AJidaGKU2yFaW7bcGd6pH2bPfA6dszJCznilUoGJE8bgpReeQHR0VEhHuSfqEE4wLJlDnLZ1Zjp3 +3303SktLfV6zadMmzJs3DwBw7229A3o+4RTgE9wjikSrDYxyOzC7cbt5/hJ9vVyy2UZAow64sIOa +I0iIjcHTf5uKffsPNd9IQrB81gOYeKv7x1XVmnDH0x/gfJUVOasvjbVLwtO3/guHc4qQnhzX6NK1 +Cdh5IAevvPctzhYbPb7TUSJm0HXpaJ8UBhDgrk7jIM4Y61IiRs0Dt6VyuC2Vwx1ZPHRK176YscOK +T48IXr/toR485t7sqqrKTK5wi46OxlNPPYW77roLaWlpKCsrw6xZs5wFMAA5/e21115DQnRgSSp8 +QjoIVbginDFINhsIpctdLblL+BIzq8io0GujaIBZQRwBHu2lQLuGhMz7eyh8GoGrNh/wyHynF8su +Qa0IeG9rlFbU4JZH/uUGghcfug0vT74dtJlZjihKWPbdfuhTe+Guzjw0Cs8DwRsIPDHfzQs5aRLe +ffddt+yj6upqREQ0RiqpVCpYLBZYbQKiBj/rdx9o+46HKs21byWbDfY6U4VxTtuYpsLv0t5glKMb +RLM1YP0mSsC2AtHnGnRTmnhrb0iShPvuu8+lrEpkZCT27Nnj3BreAaJLS68sX74cvWOLrAwAAA99 +SURBVHr1gk6nQ1RUFCZMmIDc3FzERxmwZemTiI3UuzA/kBIxj9zRF11iG7smISHB+e4lS5YAAEZ1 +4DG5G4+bkjgoSGM/ZETK9y1evNh5T0KCnH5WVi93zOeff47w8HBUmhkW7LahqEb20V6axNo0CcZ/ ++U+gTOzkzh+zFZSjGy7VfB6GA/tAEkVIAU49OAIMTeYC8qE8+uijzmIPDrG4bds23HDDDT7ve/31 +1zF58mQcOnQIJpMJlZWVWLNmDQYOHIiLFy8iPsqAJ+6Vs2uevn9Yi0rE9GyyBVzTekOObKT2EQQd +IylGduDw2HUKxGhkYKY3xAY2TTXr2bNxg+mvTwrOY8RKM+7ryqOtgaKwsNBtf+IXX3wRAJB7zv9K +LYqEdBCV7pLRb4ckigCY244hbgC4ODNpFwGOiebAcvtHpHFI1BO/JcDzzz+PDz/80Pn/+Ph47Nix +A7169YJNYNhyxnuy5WuvveZZ/JeWOsu7DO+XhXYJkZj9iBww2pISMSrenYEOAISrCR7oqcADPRV4 +foASh6ZqUfSkDhlR3gGQXSbibxut+NtGK17aasW/71CjrYEiPz8fgwYNQl5envOe5557zlnm5s3P +tvhvi3W62cPoN4MAxy7OTNrVLABkPUDmS6IUkBTwtz6jg4GLFi1qNOuSkrBz50507doVFoFhU67g +ZjQ1pbi4OBw8eBBlZWUuGcMAsHGjnAOQlhSDh+8cCJ7nWlwixqGGmgLAwVhvtkFUgyTwDIBG8E29 +ToGMaIra2lqMGjUKBQWNey/PmTMHCxcuBAD8c8U2/GfzAf+Mv5hU8LHtPYx+CYyS+R79BZ5+NM5K +WkkIcoUApMDpcuaSdeKNFi9e7JJEkZqaih9//BGZmZkw2xk25ggoN/l+xvz589GrVy/ExMS4Mc+h +R3UaJYY0lHcJpkTMpSqgtLQUlZVy7aTt27e72CaOBNGysjKXdHPH/dkXJWd/jewoq5i33nrLBSxz +5851Jqf8c8U2vPyu/1vSq7MGu/FFMJtBCHKNs5JW+g0AWQrQBUwSIdmsfiHgTKWIk+VSsxLgyScb +1wB4nse2bduQlpaGehvDhhwBlZbmpciQIY3Zs5cmbNbX1zsNR0d5F28lYvIrJew6VgTAe4kYAMjM +zIRGo3FTA7t373a559dff3Ub/RqNBhkZMhCPG0UADJKpEikGWRpcWuDCIfZf+2hDQMznIhOhSEiH +66qfFUwSwShd4NVj6O2EcVbiMjByTjCZXfL6fM0CVhyzY3eR2KwHzrniJQjOLBtVAE7E2NjGoJSm +JeQufX6YVuXRunaUgzlplLBh71mX3y6VJBJj4DgOXbt2dQPAL7/84hEATdPWu3XrBo7jYLYznK1g +sOX/hprN70CrUnhsW1JSEgDfSS4eF+eyhlxS559BMJkBRs4ZZyUuCxgAABjhyULGGASzf6FJjAGb +zwi4UOf/SsqsWbOQk5MDnhIMSOb8UiOBVi/zViKm0sxw7mKNy29N5+SOb/JkBzDGnAxv317Wu3v2 +7PGq/09csKB621LU71vtEldpMpku8XrK/peSMv9jAZQpvaBIdAWwYDaDMQbCk4W+BKpPr0jZrKTF +BOSoaLPBn4ghhyT470nvxuPAgQNdcvMsFgumTJkCxhgSwig6xdKQL8d7KxEToSZoF2dw+c3p2m5w +0rCGWcOlU8GcnByUl8tZw3//+98BABcuXEB+fr4LABz3HTrwG4TyQr/bbBf8KznDGeKg7XWHW8SP +aLOBgBwtm5W02OeiUXMvkDg2hTGI/koBACip9c6+zZs3Y+rUqc4KHADw008/OY2uPkkUWkXAG215 +HvkNhR28lYjpFEsxom+a26hteo+jRMylU0GH/jcYDHjooYecIPvll188SoAjucXNLEqxgJfKCaeA +7oY/ySXo3EY/RIljU5pdNWzugvLZ7fZSjlvBRBGi2RJ0nX6HMbVkyRKXQg+OqZeSIxiYwoVEAjjK +u3grEdM+kmJg17YAmi8R06NHD6cXsqCgwGlY9u/fH0qlEv3793dOQx3FIiil6N5d3pzjyOmikC8q +aXuOBRcWd8mc3wImiqAct6J8dru9QQMAAJi55q+EEKNgtQSVrtyUsrKy8NRTT7noaYcl3i6comNU +8CtxoSoRwxiDXq93SgXGGL7+Wt5B3VGVzFmdbPVqFymi1+vlsm25JV4XxVpSYVyV2gfKFNdNXiTB +DsFqASHEyMw1f/XnOX4BwLigUy0jdDoACCYTmCi1fKeOJjRz5kyn1esYPf/+978BADcmc0EDIFQl +Ys42kQIOclQIuxQATY06x/Vni42oN4cuq1idORjanuNc1/pFCULDuxmh040LOtWGDAAAYHw18SMQ +bj1jDIKpPuiPqLPJo+rNN990+f3pp59GaWkp1HzwEiBUJWKOni52swMc6uz6668HAPTt2xdqtWtV +Ucf1R0+HJuqQcArorr8Xms7D3ZbrBVO9bEMQbr3x1cSP/H1mQOutRjO9h4EUSqIIu2xoeD2ao18K +G8u9NNXRFRUVePzxx0M2WoIpEfPPL7Y16G/PALjhhhugVCphsdqhVCrRr18/jwAIhf6nmnDoB02F +Iqmbe9VvsxmSKIKBFBrN9J5AnhtYDNeihHrycsl4QPxJslnVIqXglCq/HT9NKbdcQlashLbhFFu2 +bGnRM/w9H2iJmPkfb8TrnzTuP+AAwOjRoz2+a292PgZdl47t2z2nqDvub0nbAYCPToGu7/1uq3wA +INqsDd5aWAij47EoISDxHHDEhXFe4n4QbrpjlUmyea4FnPSG96yW9/bIwafrTgooqva8OnemQsJ7 +e7zrTV3/J32Gluv6PwlKKdI6pGHUmFEwR2XhP0cFZF8UUWVhsIvyYRMkmCw25J67iOVrd6Pfgwtc +mA/ItYcmPP+hx/f8ePA0Rvx1CX486LmO0vjnlmLj7uMe2+er7TJ3OKg7DYO+/2QQpc49w8dmg3PV +lnDTjfMS9wesVloqkmJmFH8DSRoLAvAaHSjvO3I1UkMwpY8CLQjycaGLFy9i0T8Wej1vMBjQKasT +MrM6IT0j3U0vexqB/5j3OiorQrA5RqiIECgSu0LT6RZQvedy9ZJgh2Cul5lP6Trja0l3tORVLQ7j +NZ6qujsmPWIXmNRHMJmg0OpAfKQzV5oY/nPEjondFFBwoesrSilSUlPQKSsLmVmZaJPoWtmD+dHZ +N908COv++81Vw3h1xlB5fu/lA5goOC1+gO43nqq6u6WvbHkc91ddbNITp4fRMM0+gGXYzfVQaPQg +Pvz0hVUMXx6xY3QmjwhNy4QPY0CYwYDMTpnI7NQJ6ZkZLqO8JR7E/gMH4nzJBezbs+fK8J1TQNGm +M1TpgxsZ7+37RRF2c30DMkiOVGsahq+6tHiOGfRcK2Z6fhsGbh9hSAIBeLUOtJn8AI4C1yVx6J/C +Q80HCgDmdKCEkhhjWP3lf3Bw/4HLw3SlDor4TCgSssDHdnRz53oW+wIEiyz2GUExgXi9cX7q+aDa +EYqPiZlRnMkkcTdh8l50vFoD6keBIwUFMmI53NCOQ4zu6sg2zj19GhvWfYsL58+H/NlUFw1FQpbM +9MjkgELvJbsNgsXc4OhhFYRy/Y2vJZ0KGoih+riYl0v6MEnYRCCDgFOqm50iOkjJAaOyFOgYQ68K +EDDGkH/2LLKPHkPOyZOorKjwGUvo3qsUVBsFTh8Dqo8Fp48FF9G2WfHujUSbFaLN0mASsApC+dta +YvG3KgAAIOaF4kzGS1sJQxIAUIUSvJ/74BAAN6Xx6NuOw9VGkiShuroaVZWVqKqogMViAaUUvEIB +tUoFgaqw8QwFODUIrwJV6wESmu8QrGZIdlvDyEcxEegw44LgR36rAMBhE0DidwAsQzZwOPAqLfxN +Ox+VpUBWPMW1RMcuiNh4UgitFJIkCFYTmHMrGpIDKgwOVucH7Qhqdno4P/W8VG++HgT7G63WWr9X +EQsqJb83QL5aDinEBcYkwQ67ubaR+QT7pXrz9aFmfqsAAAAqFqfXGHNrBgBknWMuK1hMshHDfMcX +FldJV0N5noCOg0ViiB7GIFjMEBw7rMjcX2fMrRlQsTi9pjV41eqmd8z0oqchSfMBopanbxScSgPq +w2n0aH8l1IprowbRGaOEddnBx0hIogDRagZzlqxjFlA63Ti/7dut2f5WV7bG+W3fhsTfxBgpBGvQ +beZ6CBZTQ7kS95FgFq6d0X8gyNHPJEmWjuZ6Z38wRgoh8Te1NvMvCwAAwLggcX+5WN4ZhKx30XOm +Woh29/KwonRt6P6iKgnFVS03AES7BXbTJfYRIevLxfLOxgWhmeZdcRXgphL+XvIwmDgfQEzjtJmC +U6hAG+oP/qmPErH6q1sFFFdJ2HxSQK0lcN+zJNgg2q3OiGPHOAHhphvf8D+Y45oEgOwvOBlGiH6J +xNj9RK7a2gAEAsqrMPF6HRLDry5/QFGVhKMlIipNDNVm1lh10/+JHUS7DZJgBZNYk18hUkJWMFb3 +V3/DuK55ADgo+oVzfUGwDAzdmv7eLYlH17YaxEWowV/heq4Xaxl2nxVwrrKFop4xiIIVkmBzD/4g +OAqGKeULmo/e/V0CwAmEvxc/AcaeB1g7AOgQQ5EYTkEJEKFXIsagRLhWCXKZW7s9R8Cx8y3bE1gS +7ZAEOySPeyyQcyBkYfkbvpM2/mcA4GhLzN9LpoAJL6RE0Y7tIl1VAM8RRIUpEWNQQafmW70xe/IE +7CsIzLvHJBGSYIMk2j2GehGCXBB+gfGNxGW4KuqRX6UFf4cvLntVyUtP81T0WMJeyVOEaXiEaRQI +0/BQK0NrL9hF4MOfm98XgTEJTBQgSQKYKDaZw7t18zECOt+40HOK9h8A8EL93ywdb1CTWUpO7EII +8zplVXAUYVoeOjUPtYKDWslBFUTsmcSAD3Za3Zktl1mFJIly2rWvTSYYKkDoBgnkg8qF7pU5/gBA +IHT3Ku72YUPnKoj4ECDFE8aabTclgErBQaXkoFZQcJQ4D9rk37LfgUGUGKSGv0WJYe1vNgiSzHTG +/LQDGMyEkh0SyPKKBW3XXC1i/toHQBPq/Y8z4QaN4VmFgt2pIFI6RyVVa3Tz/gIRVnsz4eiARIE8 +RrhfCSHfG6vwX3yYaLqW+vPaLvoPYMg/yzKVKjxLGBsKJrahBBqOsqC+q87CcLRY9BRfaCJACaPk +CJHIZrPdtrb+X2ml13L/XfMA8ET93jZm6XiM4Xj048EyJSbFAlBxYApGwFOAY4QRCtawSShhYISB +QAKBcLxYPFttki4CpADAKUJwlAnst/K32xX/3vrq/wNwvhL7pbGSTwAAAABJRU5ErkJggg== +-----=_qute-UUID +Content-Type: text/css +Content-Transfer-Encoding: quoted-printable +Content-Location: http://localhost:(port)/data/downloads/mhtml/complex/base.css + +@charset "utf-8"; + +@import url("external-in-extern.css"); + +div.dyk { background-image: url("DYK.png"); background-repeat: no-repeat; m= +in-height: 128px; padding-left: 148px; margin-top: 10px; margin-bottom: 10p= +x; border: 2px solid rgb(71, 71, 71); border-radius: 64px; } +-----=_qute-UUID +Content-Type: image/png +Content-Transfer-Encoding: base64 +Content-Location: http://localhost:(port)/data/downloads/mhtml/complex/Banner.png + +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 +/a7WDh4p3SR1ryvQARUrQqSMGTMmwrXhnDlzQh3uCTd23Xu47rnLB3dVIHLfY3jnDnBG66SCvMKj +EctWJ81asKx4+YMrbBZ12HPPPcegQYMitnGUlpYyf/78BgcQjUEIwd/+9rfAZwqBhIbKBf65udtv +v71Z+UZHR7N06VKSk5MxpDz45fd7pkvdW3oy76spzF/6zeH7p17wT4fNet2sWbNYsWJFxKrX1157 +jddee63JPIKefQAj/2j5ncEy7DlU/NaZHVo+lJqaqg4bNixilW4QI0eODHrdcqmKWAlMHjFiBHa7 +PRSfN9wBfEJCAlOnTvXzjaa/Vbe+qqrodUk7Ly+vnubfFP70pz8Fu4zvvJqunqr28kvL0sTPQr0A +4vI41xu7diIBxpWaEUjN0BcYPi1MWwSpacjYdM648FKGXzKS5MR4PB4PXq8PTdPQdR2fT8Pr9eF2 ++38o/c7uxRWXX0JychJ6E472dUNit/i1zNQowYCWClYBbg00PeBMSR7vkHj2fV9/v2yopStYElqC +asPwaujSeJ0OSR6OheI57Y9nlvy7WBryRsAYN24czz///Ek5VQhqp0Dxi++vKQk9Q/IewL333hta +9fgLoF49enfKdDWkoaampobqM7JfTnVD99a6vdcDeVlZWXz99dd07NjxpAuWkpLCe++9F9wPKaXk +r40R/YmgRYsWfP3115x11ll+pc/jG//HJxblB+pwwu/reEdppetuYG9iYiLLly9vvgYVMIc++uij +AdO2fLLv9U9/G8z36scWHjSk/DRo5mwIEydODN77sdurLQc88fHxoXnjurjtttuCBFxeVF79St26 +KEJIgLvuuuuk3v2f//xnhg8f7u/shJh3KtvLqZCleZz04a5zeOscGqBVfTTbGzi0sMMIP06kTQiA +pNlHU62CPEtMlD2cx3UJLWItjGujYXcdZcuPP1FQUMjR4hLcbg/xcXHEx8eSlpZC507tychIp7a2 +ln/+ayG7du9tMEqH1+vl78/PZc4T88nNzScnpyM33noj2wo87C03KHdDYY1BmRsa3dQiFLSjB6j6 +4mUwfNRLZegosSnEDJ6KJakVvqoqj0/SunR66tFfp4lD/gF4HlBWrVrFddddV88bTFMYN25ccJHI +JiFE77B8s4HvgcQNGzZw2WWXRZgGG0OXLl0inp+amhra4N/Qxl/pNyUerKioIDExMTRtcOmllwZX +gx4VQqQ1Uf+2wNdAa5fLxaxZs3j22WfrOa9oDK1bt+b666/npptuIiYmJtg5jgPWAEVut5u4uLgG +93U2OVpVFCZPnswjjzwSXPRVA5wnhNgYVnYJ/lXWwX2gSUlJoVWoJ7tRWkrZFfgcSM/NzWXixIkN +OngPmaosFu655x5mzJgR/N2+C0wQQhh18h0FfAD+uc9Vq1ZFtKMlS5YELVOXCyEWSynfACbt2LGD +nj17RrzDbt26sWnTpqDjjHuFELMaqMfNQbP4a6+9xowZM5oVEDw2NpaZM2eGR4h5Vghx0yluL8tP +hSxNnD6wAKi2xGrDVfyt9GkXCVUNjbUMAxKtBonRNlLTWpOalobH48Xn8+/vtFgsWCwqVqsVp9OB +oihUVlUfNzK9EbaqVwBxVuieotApUUEzJF5dZWOhwTf5Om4NrEokmRpVxdSsWQi61x/wvJ5pxYe1 +RScUZzyG14uh6d+qzpTqX6sQhRB/D7zz5wYOHKhu27aNTz75hOXLl7NmzRoOHTpEZWUlGRkZDBgw +oJ6D8TAN9WCdfHOllEOBT/v06ZP4008/8eqrr7J8+XJ++OEHSkpKUBSFhIQEOnXqRP/+/RkzZgyD +Bg3i3nvv5emnnyY+Pj48jFhjA5pKQMbHx4unnnqKmTNnkpiYyMMPPxy8/u/j1H+/lPI8YIXT6ez4 +8MMPM336dBYvXsxXX33Fxo0bKSgooKKiAiEEycnJdOrUiX79+jF8+HAGDx4c7r2oGPi9EOIDKaUD +8DkcDuuTTz7JPffcwxlnnME//vEPdu3axWeffcaGDRsoKiri6NGjuN1u0tLSaNOmDUOHDuXyyy8P +7eUECoBLwsk0aGkEkmbOnMk999yD3W4PNyGW/ow2sU1KeSHweXZ2dvrq1atZtmwZb731FmvWrKGo +qIiYmBiysrIYMWIEv/vd78K1tXeBSXXJNJDvh1LKVcDABQsWMHr0aHJzc7n88st54YUXgmS6DAju +i5kFXNGlSxfriy++yB/+8Ac0TSMlJYUFCxYEyXQr8Ggj9Xg60Lafmjp1KhMmTODzzz/n448/ZvXq +1RQUFHD06FGsViupqal069aNYcOGMWnSpNBWsHAyPcXt5ZTI0sRp1BcD8EylSKhy9bdJudoWExsi +RK8O57ZWGNbOgtMS5EAR+nuMNiVSgqoq5Ocf5vUF77Bnz75GNdTn5s/liSfnk5ubR9u22cx84DZq +anyBxU/+vH0GHKqSrD6ks6PYQDMARUV6qqn6/O/oFYUNL5SSEmGxEz1gIrZWZ+CtqsArxIDyWOda +boqTv2ZhSikHAi8BXY437xOOOXPmBOcCnxFC3NxAvn2AxUDWzyjeUeA6IcTyRsp+U0ATqSvUvQEi +2tGM+juBe4A/A7EnWL484IXAO6gOy3Ma8CRgPcl6G8AbwF+FEIcbKPONAdKJb4Bo7xBC/ONntols +4C2gOcGqtUBZHhZCaE3k2SagpXZt4PKygGbrCUv/e+DvgCU/P589e/bQu3fvoO/gYuAiIcSW49Rj +KvAwcCIxQquA+4UQT/4n2suplqWJ00RD5aY4aX3wyA6QS3SPZ7xis4GUWARsOaLTLkHhjBQlRJ6N +dtSBRUbHY62gVVlKSXFxCT/8uJuczh3xeH2hmxUgK07gsKjkV0rKfBa0Qzuo2bgEo6a80VXHUvdi +a3MWlqRMdI8bDLnEitjxayfTAFGuCpj6hgNjA51oKyAukKRBUkhLSwvvJBrKd4OUMgf4fSDfHkBy +HdIoB3YBawEPEE7MFcCNjZFp4BnPBAZy94edLmsumQbycAEzpJSzgcuA84HeQIs6nZwESgLlXQd8 +AnzTiEY2P1CucHPkdmAj0AdIA1KB8NiVRcAB4FNgUVPlF0LMayB/D3D3L9EBCyFygQFSynHAxECb +CDefVwcsEx8D/xRC7G5GngeklKMDpJoddmkRcH04mQbSvxqo498zMzMtmcfiZjeLTAN5vCalfBu4 +EBgBDAjINTUsmS8wcNsKrADeFEKU/Kfay6mWpYnTRUMNIOneg70sFtsGa0y0CCe/JKdgYjcrLWNE +kx6DgxrqW282raE+8+xcnn56Pgdz8xBCEBcXy7hxl9DvnL4BH8YBX6kCvIbC/G+KOLDuI7T8rWA0 +sTrX0FFiU4nuMx5Lekd8lRVS07x9Sh/K2mSK+th8XV0N9ZNPPgl67/mtEOI1802ZOEXtLxPoEHZq +oxCiynwzJn5dGmpQfbHF7pW653nd471BtdtBShSgqFqyuUAnvo0FexOLSE9IQw1LVFlZxT//uYC3 +Fy4iK6s1bdpkEx0dTW1tDdt3HeDwwVzk8ULASYmwOnB0GoQ1vROaqwYkz1ttsXtNMTeNMA31iPk2 +TJxCC0o+YLrfM/H/g1CP3JtYkTw97yUp5QShqElKYMuCVcCafJ0uKSqZcU14wTHCAo43OVStf0pR +FLweL7t27mbnT7sizvsDrze9QE5YbNg7DMTe5mx0jwfd4y01fNpLJbMzKkwxN41gVBH8pkoTJkyY +MHESqLdEtsST+YPu1e/U3G7d0I0QOXp98G2uRrXXH1C8qSNScZRIw0AIgc1uIzYuFoloVItVFL8D +ieDR9ErzgGnYGY+j23Cc3UdgGKC5XLru1e8s8WT+YIo4JIdGnXcHnTrgn9MyYcKECRM/V0MFYK4w +Yh498nZtla+T5vbcaXU4QAisCmwp0MlJUeiWrjS8P1TKAIFKDMNAVVWcTicOh4P0Ful06NiRnJzO +qFYLSUlJ5B7IPSlHBH6WlwhbNGp8Cxydz8fasivS60Jzu5C6Njcmwfp26V3CjD9YBw35y3U4Qutp +XOYbMmHChIlfilCBg3el16Q9UPS0ofna6W4uV+1+UrWrgm8P6MTboWWcgkWpo5FKEIpKQlIi7WhP +6+wsOnRoT/uOHUlIiENKf4euG5KBg89l187dVFdXHUcTDU7KBmzJioriiEWJTsaW3Rtbdh+EYkF6 +XOgeN1LTFikW29MH70qrMcXbPIQRqhmv0YQJEyZOEk1OTCZPz+8uhHxJsdr6qVY7QlHQDEhwCvq2 +VMlKFLSMU0LBYgQCt9tFZWUlSUlJxMREoekSXdMxZKSyqCoq69euY/26tRw9UuQPQBwWLk4VoKgq +qFaEakNYnShR8agxaViS22BJ74ziiEVqXqSho/s8GD7vOinFdSWzM380RVtXqZcW/NsKyMjI4MgR +//qjjIyM8Mgf1qb2H5owYcKEiZMg1NRRz4ijH94kU+7NH4RuPK6otn6qzY5QVQwJPh1axArOyVLp +mqGGlEghBIqiYEjjuB6TrFYr5WVlbN+6jcKCAnw+H1rANVmhy0qZz4Fic6JYnQhnPJbETBRnIkgd +qeuAgdR1dK8HQ/euQ1VuL34o89/BspvirUeqhUD6a6+9xm233YbFYiHoVB8oFEK0MN+SCRMmTJwC +DTW7zzSRu2G+TJ6R310Y3C8U9TLVakMJBBY3DHBaYXIvG8lRghOasAyEYFMUBYvFgqKAphn4vF68 +Ony2T2V7kd+5hN/cayClAWGaruHzofu8SENfLBVmljyc+WOwzKZoGyTUqcBcIKXOpUL8jhcWm2/J +hAkTJk4BoYYjbXphK0PqNwnBbYrFqioWG0JRUYVkSAcLZ2WqaPrPL41NFewrMfhsp4/SWoki6pZW +IA0dQ/NiaD5dSuYqQn2maHbGIVOcJ0SqQWgmmZowYcLEf5BQAbLuK4yudmsTFcGjQlWSFNWKzWaj +f1sr57RR8Z0koQrh94pkUwU1XskXuzV+PKzXIVMB0kD3eTF0H1I3Sg3JXTEOy1sHH8wwFyCZMGHC +hInTh1ABna4xOQAAAddJREFUuKJCTWlT2V0I5TopjBsUi4UemQ6GdY0CIfzBvKH5kcIDZHqo3GB7 +oUFJjUFJrcQTcIwkEEgp/Rqp7kPqGkIqz0lpvFR8IO5H3o3XTTGaMGHChInTj1ADSL+jMMGneNup +ijqjRawYf2ZrK1F2CwkxVmKdViyKCLghlMd2vjTwcJ8OG/M0th7WcWsSXYISiGVjGBpS1zB0Dfl/ +7d2xSgNBEAbgf2Z3zzPEaAhKJAYRfApB7KwsrNTHsLWxEW3Eyk6wsBArG18gprBUsBIsBK0M6hm9 +hOjldtciZ8gjaJivnGaKKX4Yhl1vQaTPHdI9Y4OHxn65KeMTQgjx7wP1V2WnNTFX+FwYVXwUalQC +DWgF5EODYj5AbkTBaIYa2N/6gebxt8fxVQKm7PDIOXjXzULUg7y3nlQtVW7bcHj3ujspQSqEEGL4 +AhUA1s8a/NKwOae5wFBroaJNzeksk+83UEwwmhFohlG9atd6xB2Ly/uk/xcqeo8gtcFcs86dMHQd +SdSOpssdbJXkelcIIcTwBuqgpcM3bt5e0Pzi6lirhQ3r/AqRqypyU4AdZ6JQaRBnq93mF95vHpNr +Y/gZoCfnqR5FxXopPk3TmWX3cVCVEBVCCPHn/QC4F+TMsZASEwAAAABJRU5ErkJggg== +-----=_qute-UUID +Content-Type: image/png +Content-Transfer-Encoding: base64 +Content-Location: http://localhost:(port)/data/downloads/mhtml/complex/Inline.png + +iVBORw0KGgoAAAANSUhEUgAAAKAAAACgCAYAAACLz2ctAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA +B3RJTUUH4AICEBUm88fqUAAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUH +AAABdUlEQVR42u3dwQmAQAxE0UTsv+W1hxAZhfcKkDl8dk+yfc45tai7Nz9Xy/Ps+9i+qyBIgAgQ +AYIAESAIEAGCABEgCBABggARIAgQAYIAESAIkP/qqlr9acA/EvY5AXEFgwARIAgQAYIAESAIEAGC +ABEgAgQBIkAQIAIEASJAeN3tHwn7kvucgLiCESAIEAGCABEgCBABggARIAgQAYIAESAIEAGCABEg +jHgnxL7oPicgrmAECAJEgCBABAgCRIAgQAQIAkSAIEAECAJEgCBABAgj3gmxL7rPCYgrGAGCABEg +CBABggARIAgQAYIAESAIEAGCABEgCBABwoh3QuyL7nMC4gpGgCBABAgCRIAgQAQIAkSAIEAECAJE +gCBABAgCRIAw4p0Q+6L7nIC4ghEgCBABggARIAgQAYIAESAIEAGCABEgCBABggARIIx4J8S+6D4n +IK5gBAgCRIAgQAQIAkSAIEAECAJEgCBABAgCRIAgQAQIIw997mYx/U+iXAAAAABJRU5ErkJggg== +-----=_qute-UUID diff --git a/tests/end2end/data/downloads/mhtml/complex/complex.html b/tests/end2end/data/downloads/mhtml/complex/complex.html index 1f34eb85c..4b34d3fed 100644 --- a/tests/end2end/data/downloads/mhtml/complex/complex.html +++ b/tests/end2end/data/downloads/mhtml/complex/complex.html @@ -22,7 +22,7 @@ diff --git a/tests/end2end/data/downloads/mhtml/complex/complex.mht b/tests/end2end/data/downloads/mhtml/complex/complex.mht index f367a8673..0cbcc4607 100644 --- a/tests/end2end/data/downloads/mhtml/complex/complex.mht +++ b/tests/end2end/data/downloads/mhtml/complex/complex.mht @@ -32,7 +32,7 @@ serif; =20 =20 diff --git a/tests/end2end/data/downloads/mhtml/complex/not-css.qss b/tests/end2end/data/downloads/mhtml/complex/not-css.qss new file mode 100644 index 000000000..e69de29bb diff --git a/tests/end2end/data/downloads/mhtml/simple/simple-webengine.mht b/tests/end2end/data/downloads/mhtml/simple/simple-webengine.mht new file mode 100644 index 000000000..d8bfdee70 --- /dev/null +++ b/tests/end2end/data/downloads/mhtml/simple/simple-webengine.mht @@ -0,0 +1,26 @@ +From: +Subject: Simple MHTML test +Date: today +MIME-Version: 1.0 +Content-Type: multipart/related; + type="text/html"; + boundary="---=_qute-UUID" + +-----=_qute-UUID +Content-Type: text/html +Content-ID: 42 +Content-Transfer-Encoding: quoted-printable +Content-Location: http://localhost:(port)/data/downloads/mhtml/simple/simple.html + + + =20 + Simple MHTML test + + + normal link to another page + =20 + + +-----=_qute-UUID diff --git a/tests/end2end/features/downloads.feature b/tests/end2end/features/downloads.feature index 7bfb66921..d052acfb6 100644 --- a/tests/end2end/features/downloads.feature +++ b/tests/end2end/features/downloads.feature @@ -197,44 +197,41 @@ Feature: Downloading things from a website. ## mhtml downloads - @qtwebengine_todo: :download --mhtml is not implemented yet Scenario: Downloading as mhtml is available - When I open html + When I open data/title.html And I run :download --mhtml And I wait for "File successfully written." in the log - Then no crash should happen + Then the downloaded file Test title.mhtml should exist - @qtwebengine_todo: :download --mhtml is not implemented yet + @qtwebengine_skip: QtWebEngine refuses to load this Scenario: Downloading as mhtml with non-ASCII headers When I open response-headers?Content-Type=text%2Fpl%C3%A4in - And I run :download --mhtml --dest mhtml-response-headers.mht + And I run :download --mhtml --dest mhtml-response-headers.mhtml And I wait for "File successfully written." in the log - Then no crash should happen + Then the downloaded file mhtml-response-headers.mhtml should exist - @qtwebengine_todo: :download --mhtml is not implemented yet Scenario: Overwriting existing mhtml file When I set storage -> prompt-download-directory to true - And I open html + And I open data/title.html And I run :download --mhtml - And I wait for "Asking question text='Please enter a location for http://localhost:*/html' title='Save file to:'>, *" in the log + And I wait for "Asking question text='Please enter a location for http://localhost:*/data/title.html' title='Save file to:'>, *" in the log And I run :prompt-accept And I wait for "File successfully written." in the log And I run :download --mhtml - And I wait for "Asking question text='Please enter a location for http://localhost:*/html' title='Save file to:'>, *" in the log + And I wait for "Asking question text='Please enter a location for http://localhost:*/data/title.html' title='Save file to:'>, *" in the log And I run :prompt-accept And I wait for "Asking question text='* already exists. Overwrite?' title='Overwrite existing file?'>, *" in the log And I run :prompt-accept yes And I wait for "File successfully written." in the log - Then no crash should happen + Then the downloaded file Test title.mhtml should exist - @qtwebengine_todo: :download --mhtml is not implemented yet Scenario: Opening a mhtml download directly When I set storage -> prompt-download-directory to true And I open html And I run :download --mhtml And I wait for the download prompt for "*" And I directly open the download - Then "Opening *.mht* with [*python*]" should be logged + Then "Opening *.mhtml* with [*python*]" should be logged ## :download-cancel diff --git a/tests/end2end/test_mhtml_e2e.py b/tests/end2end/test_mhtml_e2e.py index 91ed693f6..397e585cc 100644 --- a/tests/end2end/test_mhtml_e2e.py +++ b/tests/end2end/test_mhtml_e2e.py @@ -27,10 +27,6 @@ import collections import pytest -pytestmark = pytest.mark.qtwebengine_todo("mhtml downloads are not " - "implemented") - - def collect_tests(): basedir = os.path.dirname(__file__) datadir = os.path.join(basedir, 'data', 'downloads', 'mhtml') @@ -40,10 +36,15 @@ def collect_tests(): def normalize_line(line): line = line.rstrip('\n') - line = re.sub('boundary="---=_qute-[0-9a-f-]+"', + line = re.sub('boundary="-+(=_qute|MultipartBoundary)-[0-9a-zA-Z-]+"', 'boundary="---=_qute-UUID"', line) - line = re.sub('^-----=_qute-[0-9a-f-]+$', '-----=_qute-UUID', line) + line = re.sub('^-+(=_qute|MultipartBoundary)-[0-9a-zA-Z-]+$', + '-----=_qute-UUID', line) line = re.sub(r'localhost:\d{1,5}', 'localhost:(port)', line) + if line.startswith('Date: '): + line = 'Date: today' + if line.startswith('Content-ID: '): + line = 'Content-ID: 42' # Depending on Python's mimetypes module/the system's mime files, .js # files could be either identified as x-javascript or just javascript @@ -82,7 +83,7 @@ def download_dir(tmpdir): @pytest.mark.parametrize('test_name', collect_tests()) -def test_mhtml(test_name, download_dir, quteproc, httpbin): +def test_mhtml(request, test_name, download_dir, quteproc, httpbin): quteproc.set_setting('storage', 'download-directory', download_dir.location) quteproc.set_setting('storage', 'prompt-download-directory', 'false') @@ -104,13 +105,17 @@ def test_mhtml(test_name, download_dir, quteproc, httpbin): # Discard all requests that were necessary to display the page httpbin.clear_data() quteproc.send_cmd(':download --mhtml --dest "{}"'.format(download_dest)) - quteproc.wait_for(category='downloads', module='mhtml', - function='_finish_file', + quteproc.wait_for(category='downloads', message='File successfully written.') - expected_file = os.path.join(test_dir, '{}.mht'.format(test_name)) + suffix = '-webengine' if request.config.webengine else '' + filename = '{}{}.mht'.format(test_name, suffix) + expected_file = os.path.join(test_dir, filename) download_dir.compare_mhtml(expected_file) + if request.config.webengine: + return + with open(os.path.join(test_dir, 'requests'), encoding='utf-8') as f: expected_requests = [] for line in f: From d13809089fc62aea7ce063d1d809930a386d5a2c Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 7 Feb 2017 00:16:33 +0100 Subject: [PATCH 298/561] Exclude socket notifier events during prompts This helps with segfaults while the prompt is shown, like those which happened during mhtml downloads with QtWebEngine. --- qutebrowser/mainwindow/prompt.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qutebrowser/mainwindow/prompt.py b/qutebrowser/mainwindow/prompt.py index 8943d8fb8..afe8a72dd 100644 --- a/qutebrowser/mainwindow/prompt.py +++ b/qutebrowser/mainwindow/prompt.py @@ -25,7 +25,7 @@ import collections import sip from PyQt5.QtCore import (pyqtSlot, pyqtSignal, Qt, QTimer, QDir, QModelIndex, - QItemSelectionModel, QObject) + QItemSelectionModel, QObject, QEventLoop) from PyQt5.QtWidgets import (QWidget, QGridLayout, QVBoxLayout, QLineEdit, QLabel, QFileSystemModel, QTreeView, QSizePolicy) @@ -184,7 +184,7 @@ class PromptQueue(QObject): question.completed.connect(loop.quit) question.completed.connect(loop.deleteLater) log.prompt.debug("Starting loop.exec_() for {}".format(question)) - loop.exec_() + loop.exec_(QEventLoop.ExcludeSocketNotifiers) log.prompt.debug("Ending loop.exec_() for {}".format(question)) log.prompt.debug("Restoring old question {}".format(old_question)) From eb7064d08366483673c5a467301e713276697ec4 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 7 Feb 2017 00:38:43 +0100 Subject: [PATCH 299/561] Fix lint --- tests/end2end/test_mhtml_e2e.py | 36 +++++++++++++++++---------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/tests/end2end/test_mhtml_e2e.py b/tests/end2end/test_mhtml_e2e.py index 397e585cc..d08b2b75d 100644 --- a/tests/end2end/test_mhtml_e2e.py +++ b/tests/end2end/test_mhtml_e2e.py @@ -82,6 +82,23 @@ def download_dir(tmpdir): return DownloadDir(tmpdir) +def _test_mhtml_requests(test_dir, test_path, httpbin): + with open(os.path.join(test_dir, 'requests'), encoding='utf-8') as f: + expected_requests = [] + for line in f: + if line.startswith('#'): + continue + path = '/{}/{}'.format(test_path, line.strip()) + expected_requests.append(httpbin.ExpectedRequest('GET', path)) + + actual_requests = httpbin.get_requests() + # Requests are not hashable, we need to convert to ExpectedRequests + actual_requests = [httpbin.ExpectedRequest.from_request(req) + for req in actual_requests] + assert (collections.Counter(actual_requests) == + collections.Counter(expected_requests)) + + @pytest.mark.parametrize('test_name', collect_tests()) def test_mhtml(request, test_name, download_dir, quteproc, httpbin): quteproc.set_setting('storage', 'download-directory', @@ -113,20 +130,5 @@ def test_mhtml(request, test_name, download_dir, quteproc, httpbin): expected_file = os.path.join(test_dir, filename) download_dir.compare_mhtml(expected_file) - if request.config.webengine: - return - - with open(os.path.join(test_dir, 'requests'), encoding='utf-8') as f: - expected_requests = [] - for line in f: - if line.startswith('#'): - continue - path = '/{}/{}'.format(test_path, line.strip()) - expected_requests.append(httpbin.ExpectedRequest('GET', path)) - - actual_requests = httpbin.get_requests() - # Requests are not hashable, we need to convert to ExpectedRequests - actual_requests = [httpbin.ExpectedRequest.from_request(req) - for req in actual_requests] - assert (collections.Counter(actual_requests) == - collections.Counter(expected_requests)) + if not request.config.webengine: + _test_mhtml_requests(test_dir, test_path, httpbin) From 5981bdbedb9d647ee8b28f393af081015666a82a Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 7 Feb 2017 22:23:35 +0100 Subject: [PATCH 300/561] Skip test failing on Travis See #2288 --- tests/end2end/features/downloads.feature | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/end2end/features/downloads.feature b/tests/end2end/features/downloads.feature index d052acfb6..54bce0413 100644 --- a/tests/end2end/features/downloads.feature +++ b/tests/end2end/features/downloads.feature @@ -210,6 +210,7 @@ Feature: Downloading things from a website. And I wait for "File successfully written." in the log Then the downloaded file mhtml-response-headers.mhtml should exist + @qtwebengine_skip: https://github.com/qutebrowser/qutebrowser/issues/2288 Scenario: Overwriting existing mhtml file When I set storage -> prompt-download-directory to true And I open data/title.html From bdcd980572d879498253e9f51214c12dd3a28ce2 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 7 Feb 2017 22:27:32 +0100 Subject: [PATCH 301/561] Fix bdd "Then" collision --- tests/end2end/features/downloads.feature | 10 +++++----- tests/end2end/features/test_downloads_bdd.py | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/end2end/features/downloads.feature b/tests/end2end/features/downloads.feature index dd256c80a..f83e75ebd 100644 --- a/tests/end2end/features/downloads.feature +++ b/tests/end2end/features/downloads.feature @@ -111,7 +111,7 @@ Feature: Downloading things from a website. And I wait for "fetch: * -> drip" in the log And I run :tab-close And I wait for "Download drip finished" in the log - Then the downloaded file drip should contain 128 bytes + Then the downloaded file drip should be 128 bytes big Scenario: Downloading a file with spaces When I open data/downloads/download with spaces.bin without waiting @@ -477,7 +477,7 @@ Feature: Downloading things from a website. And I run :download http://localhost:(port)/data/downloads/download2.bin --dest download.bin And I wait for "Entering mode KeyMode.yesno *" in the log And I run :prompt-accept no - Then the downloaded file download.bin should contain 1 bytes + Then the downloaded file download.bin should be 1 bytes big Scenario: Overwriting an existing file When I set storage -> prompt-download-directory to false @@ -487,7 +487,7 @@ Feature: Downloading things from a website. And I wait for "Entering mode KeyMode.yesno *" in the log And I run :prompt-accept yes And I wait until the download is finished - Then the downloaded file download.bin should contain 2 bytes + Then the downloaded file download.bin should be 2 bytes big @linux Scenario: Not overwriting a special file @@ -542,7 +542,7 @@ Feature: Downloading things from a website. When I set storage -> prompt-download-directory to false And I run :download http://localhost:(port)/custom/twenty-mb And I wait until the download is finished - Then the downloaded file twenty-mb should contain 20971520 bytes + Then the downloaded file twenty-mb should be 20971520 bytes big Scenario: Downloading 20MB file with late prompt confirmation When I set storage -> prompt-download-directory to true @@ -550,7 +550,7 @@ Feature: Downloading things from a website. And I wait 1s And I run :prompt-accept And I wait until the download is finished - Then the downloaded file twenty-mb should contain 20971520 bytes + Then the downloaded file twenty-mb should be 20971520 bytes big Scenario: Downloading invalid URL When I set storage -> prompt-download-directory to false diff --git a/tests/end2end/features/test_downloads_bdd.py b/tests/end2end/features/test_downloads_bdd.py index aab5e2333..cb0388ea4 100644 --- a/tests/end2end/features/test_downloads_bdd.py +++ b/tests/end2end/features/test_downloads_bdd.py @@ -88,8 +88,8 @@ def download_should_exist(filename, tmpdir): assert path.check() -@bdd.then(bdd.parsers.parse("The downloaded file {filename} should contain " - "{size} bytes")) +@bdd.then(bdd.parsers.parse("The downloaded file {filename} should be " + "{size} bytes big")) def download_size(filename, size, tmpdir): path = tmpdir / 'downloads' / filename assert path.size() == int(size) From ed67f93d2a2f522019162bb773347241145d518e Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 7 Feb 2017 22:27:50 +0100 Subject: [PATCH 302/561] Fix spelling --- CHANGELOG.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 2e603bd0d..a81f5f1b2 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -59,7 +59,7 @@ Fixed - Crash reports are now re-enabled when using QtWebEngine - Fixed crashes when closing tabs while hinting - Fixed starting on newer PyQt/sip versions with LibreSSL -- When downloading files with QtWebKit, an User-Agent header is set when possible. +- When downloading files with QtWebKit, a User-Agent header is set when possible. v0.9.1 ------ From 4b79280ade32d2c4eca355808ad00f562546b177 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 7 Feb 2017 23:23:59 +0100 Subject: [PATCH 303/561] tests: Use number hint mode to select download Other screen sizes will give us other letter hints. --- tests/end2end/features/downloads.feature | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/end2end/features/downloads.feature b/tests/end2end/features/downloads.feature index f83e75ebd..0b84fe5b5 100644 --- a/tests/end2end/features/downloads.feature +++ b/tests/end2end/features/downloads.feature @@ -596,8 +596,9 @@ Feature: Downloading things from a website. @qtwebengine_skip: We can't get the UA from the page there Scenario: user-agent when using hints - When I open / + When I set hints -> mode to number + And I open / And I run :hint links download - And I run :follow-hint d + And I press the keys "use" # user-agent And I wait until the download is finished Then the downloaded file user-agent should contain Safari/ From c198f3a6a344ca1cbfd8cc8937201b0aa14fe31b Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 7 Feb 2017 23:27:44 +0100 Subject: [PATCH 304/561] Adjust descriptions and dependencies See #1571 --- README.asciidoc | 6 +++--- doc/qutebrowser.1.asciidoc | 2 +- qutebrowser/__init__.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.asciidoc b/README.asciidoc index a33738589..d25d02a61 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -7,7 +7,7 @@ qutebrowser =========== // QUTE_WEB_HIDE -image:icons/qutebrowser-64x64.png[qutebrowser logo] *A keyboard-driven, vim-like browser based on PyQt5 and QtWebKit.* +image:icons/qutebrowser-64x64.png[qutebrowser logo] *A keyboard-driven, vim-like browser based on PyQt5 and Qt.* image:https://img.shields.io/pypi/l/qutebrowser.svg?style=flat["license badge",link="https://github.com/qutebrowser/qutebrowser/blob/master/COPYING"] image:https://img.shields.io/pypi/v/qutebrowser.svg?style=flat["version badge",link="https://pypi.python.org/pypi/qutebrowser/"] @@ -20,7 +20,7 @@ link:https://www.qutebrowser.org[website] | link:https://blog.qutebrowser.org[bl // QUTE_WEB_HIDE_END qutebrowser is a keyboard-focused browser with a minimal GUI. It's based -on Python, PyQt5 and QtWebKit and free software, licensed under the GPL. +on Python and PyQt5 and free software, licensed under the GPL. It was inspired by other browsers/addons like dwb and Vimperator/Pentadactyl. @@ -99,7 +99,7 @@ The following software and libraries are required to run qutebrowser: * http://www.python.org/[Python] 3.4 or newer * http://qt.io/[Qt] 5.2.0 or newer (5.5.1 recommended) -* QtWebKit +* QtWebKit (old or link:https://github.com/annulen/webkit/wiki[reloaded]/NG) or QtWebEngine * http://www.riverbankcomputing.com/software/pyqt/intro[PyQt] 5.2.0 or newer (5.5.1 recommended) for Python 3 * https://pypi.python.org/pypi/setuptools/[pkg_resources/setuptools] diff --git a/doc/qutebrowser.1.asciidoc b/doc/qutebrowser.1.asciidoc index b427ed012..36530bffe 100644 --- a/doc/qutebrowser.1.asciidoc +++ b/doc/qutebrowser.1.asciidoc @@ -10,7 +10,7 @@ :homepage: https://www.qutebrowser.org/ == NAME -qutebrowser - a keyboard-driven, vim-like browser based on PyQt5 and QtWebKit. +qutebrowser - a keyboard-driven, vim-like browser based on PyQt5. == SYNOPSIS *qutebrowser* ['-OPTION' ['...']] [':COMMAND' ['...']] ['URL' ['...']] diff --git a/qutebrowser/__init__.py b/qutebrowser/__init__.py index 514254066..7e2f2140a 100644 --- a/qutebrowser/__init__.py +++ b/qutebrowser/__init__.py @@ -19,7 +19,7 @@ # pylint: disable=line-too-long -"""A keyboard-driven, vim-like browser based on PyQt5 and QtWebKit.""" +"""A keyboard-driven, vim-like browser based on PyQt5.""" import os.path @@ -30,6 +30,6 @@ __maintainer__ = __author__ __email__ = "mail@qutebrowser.org" __version_info__ = (0, 9, 1) __version__ = '.'.join(str(e) for e in __version_info__) -__description__ = "A keyboard-driven, vim-like browser based on PyQt5 and QtWebKit." +__description__ = "A keyboard-driven, vim-like browser based on PyQt5." basedir = os.path.dirname(os.path.realpath(__file__)) From 7bb8c854bf5408096afa16774a9c8931d2baa189 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 7 Feb 2017 23:33:37 +0100 Subject: [PATCH 305/561] tests: Make user-agent download hinting more reliable --- tests/end2end/features/downloads.feature | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/end2end/features/downloads.feature b/tests/end2end/features/downloads.feature index 0b84fe5b5..b55673344 100644 --- a/tests/end2end/features/downloads.feature +++ b/tests/end2end/features/downloads.feature @@ -599,6 +599,7 @@ Feature: Downloading things from a website. When I set hints -> mode to number And I open / And I run :hint links download - And I press the keys "use" # user-agent + And I press the keys "us" # user-agent + And I run :follow-hint 0 And I wait until the download is finished Then the downloaded file user-agent should contain Safari/ From 8b9b49f180034635f291277051688bbed75103e9 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 8 Feb 2017 00:41:17 +0100 Subject: [PATCH 306/561] Fix message on renderer process crash --- qutebrowser/browser/webengine/webenginetab.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index 2067e2b94..dc3aec277 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -678,6 +678,11 @@ class WebEngineTab(browsertab.AbstractTab): @pyqtSlot(QWebEnginePage.RenderProcessTerminationStatus, int) def _on_render_process_terminated(self, status, exitcode): """Show an error when the renderer process terminated.""" + if (status == QWebEnginePage.AbnormalTerminationStatus and + exitcode == 256): + # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-58697 + status = QWebEnginePage.CrashedTerminationStatus + if status == QWebEnginePage.NormalTerminationStatus: pass elif status == QWebEnginePage.AbnormalTerminationStatus: From 02198c4f65204bb64c8188a44f08f575f91fc413 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 8 Feb 2017 00:57:47 +0100 Subject: [PATCH 307/561] Also check for QtWebKitWidgets in earlyinit --- qutebrowser/misc/earlyinit.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qutebrowser/misc/earlyinit.py b/qutebrowser/misc/earlyinit.py index ca1053422..36606756c 100644 --- a/qutebrowser/misc/earlyinit.py +++ b/qutebrowser/misc/earlyinit.py @@ -319,6 +319,7 @@ def check_libraries(backend): else: assert backend == 'webkit' modules['PyQt5.QtWebKit'] = _missing_str("PyQt5.QtWebKit") + modules['PyQt5.QtWebKitWidgets'] = _missing_str("PyQt5.QtWebKitWidgets") from qutebrowser.utils import log From ccb6594e07803e00bde48cc2a9bf0233bbabc4ee Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 8 Feb 2017 00:58:41 +0100 Subject: [PATCH 308/561] Ignore QPainter warning in tests This happens on Debian Jessie from time to time. --- pytest.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/pytest.ini b/pytest.ini index 8975d6b84..3e9fa1bf0 100644 --- a/pytest.ini +++ b/pytest.ini @@ -48,4 +48,5 @@ qt_log_ignore = ^load glyph failed ^Error when parsing the netrc file ^Image of format '' blocked because it is not considered safe. If you are sure it is safe to do so, you can white-list the format by setting the environment variable QTWEBKIT_IMAGEFORMAT_WHITELIST= + ^QPainter::end: Painter ended with 2 saved states xfail_strict = true From c4a74c7a349fead070f2374fcce994a467555d5b Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 8 Feb 2017 09:10:40 +0100 Subject: [PATCH 309/561] Make sure completion column widths never get negative I discovered this because of #2287 but it doesn't actually change anything there. When we don't have a third column, subtract the scrollbar width from the second one. --- qutebrowser/completion/completionwidget.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/qutebrowser/completion/completionwidget.py b/qutebrowser/completion/completionwidget.py index 294d50332..bd6b8c5be 100644 --- a/qutebrowser/completion/completionwidget.py +++ b/qutebrowser/completion/completionwidget.py @@ -150,10 +150,15 @@ class CompletionView(QTreeView): """Resize the completion columns based on column_widths.""" width = self.size().width() pixel_widths = [(width * perc // 100) for perc in self._column_widths] + if self.verticalScrollBar().isVisible(): - pixel_widths[-1] -= self.style().pixelMetric( - QStyle.PM_ScrollBarExtent) + 5 + delta = self.style().pixelMetric(QStyle.PM_ScrollBarExtent) + 5 + if pixel_widths[-1] > delta: + pixel_widths[-1] -= delta + else: + pixel_widths[-2] -= delta for i, w in enumerate(pixel_widths): + assert w >= 0, i self.setColumnWidth(i, w) def _next_idx(self, upwards): From 12ed226ce6b267f1d96f04cf887db68881e77163 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 8 Feb 2017 09:11:58 +0100 Subject: [PATCH 310/561] Set COLUM_WIDTHS for :help completion Fixes #2287 --- CHANGELOG.asciidoc | 1 + qutebrowser/completion/models/miscmodels.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index a81f5f1b2..62170d6e1 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -60,6 +60,7 @@ Fixed - Fixed crashes when closing tabs while hinting - Fixed starting on newer PyQt/sip versions with LibreSSL - When downloading files with QtWebKit, a User-Agent header is set when possible. +- Fixed showing of keybindings in the :help completion v0.9.1 ------ diff --git a/qutebrowser/completion/models/miscmodels.py b/qutebrowser/completion/models/miscmodels.py index 15ec21fc6..76c1a8997 100644 --- a/qutebrowser/completion/models/miscmodels.py +++ b/qutebrowser/completion/models/miscmodels.py @@ -53,6 +53,8 @@ class HelpCompletionModel(base.BaseCompletionModel): # https://github.com/qutebrowser/qutebrowser/issues/545 # pylint: disable=abstract-method + COLUMN_WIDTHS = (20, 60, 20) + def __init__(self, parent=None): super().__init__(parent) self._init_commands() From 87d1a2c7a31ab0918106a791984b4d3751bbfd1d Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 8 Feb 2017 09:14:54 +0100 Subject: [PATCH 311/561] Fix lint --- qutebrowser/__init__.py | 2 -- qutebrowser/misc/earlyinit.py | 3 ++- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/qutebrowser/__init__.py b/qutebrowser/__init__.py index 7e2f2140a..651e039fd 100644 --- a/qutebrowser/__init__.py +++ b/qutebrowser/__init__.py @@ -17,8 +17,6 @@ # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see . -# pylint: disable=line-too-long - """A keyboard-driven, vim-like browser based on PyQt5.""" import os.path diff --git a/qutebrowser/misc/earlyinit.py b/qutebrowser/misc/earlyinit.py index 36606756c..fc00f7c14 100644 --- a/qutebrowser/misc/earlyinit.py +++ b/qutebrowser/misc/earlyinit.py @@ -319,7 +319,8 @@ def check_libraries(backend): else: assert backend == 'webkit' modules['PyQt5.QtWebKit'] = _missing_str("PyQt5.QtWebKit") - modules['PyQt5.QtWebKitWidgets'] = _missing_str("PyQt5.QtWebKitWidgets") + modules['PyQt5.QtWebKitWidgets'] = _missing_str( + "PyQt5.QtWebKitWidgets") from qutebrowser.utils import log From ec0e95969ed7988eb5443156b9db8d4e9f7a4028 Mon Sep 17 00:00:00 2001 From: Imran Sobir Date: Wed, 8 Feb 2017 13:28:04 +0500 Subject: [PATCH 312/561] Add unit tests for qute://history. --- qutebrowser/browser/qutescheme.py | 4 +- tests/unit/browser/test_qutehistory.py | 95 ++++++++++++++++++++++++++ 2 files changed, 97 insertions(+), 2 deletions(-) create mode 100644 tests/unit/browser/test_qutehistory.py diff --git a/qutebrowser/browser/qutescheme.py b/qutebrowser/browser/qutescheme.py index 3960f24ed..1b51f405b 100644 --- a/qutebrowser/browser/qutescheme.py +++ b/qutebrowser/browser/qutescheme.py @@ -172,7 +172,7 @@ def qute_history(url): curr_date = datetime.datetime.strptime(query_date, "%Y-%m-%d") curr_date = curr_date.date() except ValueError: - log.misc.error("Invalid date passed to qute:history: " + query_date) + log.misc.debug("Invalid date passed to qute:history: " + query_date) one_day = datetime.timedelta(days=1) next_date = curr_date + one_day @@ -184,7 +184,7 @@ def qute_history(url): try: item_atime = datetime.datetime.fromtimestamp(item.atime) except (ValueError, OSError, OverflowError): - log.misc.error("Invalid timestamp {}.".format(item.atime)) + log.misc.debug("Invalid timestamp {}.".format(item.atime)) continue # Skip items not on curr_date diff --git a/tests/unit/browser/test_qutehistory.py b/tests/unit/browser/test_qutehistory.py new file mode 100644 index 000000000..43df79d26 --- /dev/null +++ b/tests/unit/browser/test_qutehistory.py @@ -0,0 +1,95 @@ +# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: + +# Copyright 2017 Imran Sobir +# +# This file is part of qutebrowser. +# +# qutebrowser is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# qutebrowser is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with qutebrowser. If not, see . + +from PyQt5.QtCore import QUrl +from qutebrowser.browser import history, qutescheme +from qutebrowser.utils import objreg +import datetime +import pytest +import tempfile + + +class TestHistoryHandler: + """Test the qute://history endpoint.""" + + @pytest.fixture(autouse=True) + def fake_objects(self, fake_save_manager): + """ Create fake web-history with history for three different days """ + temp_dir = tempfile.TemporaryDirectory() + fake_web_history = history.WebHistory(temp_dir.name, 'fake-history') + objreg.register('web-history', fake_web_history, update=True) + objreg.register('save-manager', fake_save_manager, update=True) + + # Add fake history items for different days + one_day = datetime.timedelta(days=1) + self.curr_date = datetime.datetime.today() + self.next_date = self.curr_date + one_day + self.prev_date = self.curr_date - one_day + + today = history.Entry(atime=str(self.curr_date.timestamp()), + url=QUrl('www.today.com'), title='today') + tomorrow = history.Entry(atime=str(self.next_date.timestamp()), + url=QUrl('www.tomorrow.com'), title='tomorrow') + yesterday = history.Entry(atime=str(self.prev_date.timestamp()), + url=QUrl('www.yesterday.com'), title='yesterday') + + web_history = objreg.get('web-history') + web_history._add_entry(today) + web_history._add_entry(tomorrow) + web_history._add_entry(yesterday) + web_history.save() + + def test_history_without_query(self): + """ Test qute://history shows today's history when it has no query """ + _mimetype, data = qutescheme.qute_history(QUrl("qute://history")) + key = "{}".format( + datetime.date.today().strftime("%a, %d %B %Y")) + assert key in data + + def test_history_with_bad_query(self): + """ Test qute://history shows today's history when given bad query """ + url = QUrl("qute://history?date=204-blaah") + _mimetype, data = qutescheme.qute_history(url) + key = "{}".format( + datetime.date.today().strftime("%a, %d %B %Y")) + assert key in data + + def test_history_today(self): + """ Test qute://history shows history for today """ + url = QUrl("qute://history") + _mimetype, data = qutescheme.qute_history(url) + assert "today" in data + assert "tomorrow" not in data + assert "yesterday" not in data + + def test_history_yesterday(self): + """ Test qute://history shows history for yesterday """ + url = QUrl("qute://history?date=" + self.prev_date.strftime("%Y-%m-%d")) + _mimetype, data = qutescheme.qute_history(url) + assert "today" not in data + assert "tomorrow" not in data + assert "yesterday" in data + + def test_history_tomorrow(self): + """ Test qute://history shows history for tomorrow """ + url = QUrl("qute://history?date=" + self.next_date.strftime("%Y-%m-%d")) + _mimetype, data = qutescheme.qute_history(url) + assert "today" not in data + assert "tomorrow" in data + assert "yesterday" not in data From 208ee04bdc08c94d624cc9b79513ff68db65931f Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 8 Feb 2017 09:41:55 +0100 Subject: [PATCH 313/561] Add simple tests for QtWebEngine renderer crash/kill --- pytest.ini | 1 + tests/end2end/features/misc.feature | 12 ++++++++++++ tests/end2end/fixtures/quteprocess.py | 3 ++- 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/pytest.ini b/pytest.ini index 3e9fa1bf0..e7583436b 100644 --- a/pytest.ini +++ b/pytest.ini @@ -23,6 +23,7 @@ markers = qtwebengine_osx_xfail: Tests which fail on OS X with QtWebEngine js_prompt: Tests needing to display a javascript prompt this: Used to mark tests during development + no_invalid_lines: Don't fail on unparseable lines in end2end tests qt_log_level_fail = WARNING qt_log_ignore = ^SpellCheck: .* diff --git a/tests/end2end/features/misc.feature b/tests/end2end/features/misc.feature index 7fbc77c89..0d843de14 100644 --- a/tests/end2end/features/misc.feature +++ b/tests/end2end/features/misc.feature @@ -641,3 +641,15 @@ Feature: Various utility commands. Scenario: Trying to enter command mode with :enter-mode When I run :enter-mode command Then the error "Mode command can't be entered manually!" should be shown + + ## Renderer crashes + + @qtwebkit_skip @no_invalid_lines + Scenario: Renderer crash + When I run :open -t chrome://crash + Then the error "Renderer process crashed" should be shown + + @qtwebkit_skip @no_invalid_lines + Scenario: Renderer kill + When I run :open -t chrome://kill + Then the error "Renderer process was killed" should be shown diff --git a/tests/end2end/fixtures/quteprocess.py b/tests/end2end/fixtures/quteprocess.py index ac9741636..9921a719e 100644 --- a/tests/end2end/fixtures/quteprocess.py +++ b/tests/end2end/fixtures/quteprocess.py @@ -244,7 +244,8 @@ class QuteProc(testprocess.Process): if not line.strip(): return None elif (is_ignored_qt_message(line) or - is_ignored_lowlevel_message(line)): + is_ignored_lowlevel_message(line) or + self.request.node.get_marker('no_invalid_lines')): return None else: raise From 2227c037f05771b5e22dbce6ed0ac3baebda187d Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 8 Feb 2017 09:45:12 +0100 Subject: [PATCH 314/561] Log ignored lines in end2end tests --- tests/end2end/fixtures/quteprocess.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/end2end/fixtures/quteprocess.py b/tests/end2end/fixtures/quteprocess.py index 9921a719e..7eb4a1113 100644 --- a/tests/end2end/fixtures/quteprocess.py +++ b/tests/end2end/fixtures/quteprocess.py @@ -246,6 +246,7 @@ class QuteProc(testprocess.Process): elif (is_ignored_qt_message(line) or is_ignored_lowlevel_message(line) or self.request.node.get_marker('no_invalid_lines')): + self._log("IGNORED: {}".format(line)) return None else: raise From 2469d01ce70af6d168f5d99c67e7fc5b4a5d2503 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 8 Feb 2017 10:55:05 +0100 Subject: [PATCH 315/561] Add renderer_process_terminated signal to tab API --- qutebrowser/browser/browsertab.py | 13 ++++++++++ qutebrowser/browser/webengine/webenginetab.py | 24 +++++++++---------- qutebrowser/mainwindow/tabbedbrowser.py | 16 +++++++++++++ 3 files changed, 41 insertions(+), 12 deletions(-) diff --git a/qutebrowser/browser/browsertab.py b/qutebrowser/browser/browsertab.py index 8addb293c..34d4424a3 100644 --- a/qutebrowser/browser/browsertab.py +++ b/qutebrowser/browser/browsertab.py @@ -74,6 +74,14 @@ class UnsupportedOperationError(WebTabError): """Raised when an operation is not supported with the given backend.""" +TerminationStatus = usertypes.enum('TerminationStatus', [ + 'normal', + 'abnormal', # non-zero exit status + 'crashed', # e.g. segfault + 'killed' +]) + + class TabData: """A simple namespace with a fixed set of attributes. @@ -532,6 +540,10 @@ class AbstractTab(QWidget): fullscreen_requested: Fullscreen display was requested by the page. arg: True if fullscreen should be turned on, False if it should be turned off. + renderer_process_terminated: Emitted when the underlying renderer process + terminated. + arg 0: A TerminationStatus member. + arg 1: The exit code. """ window_close_requested = pyqtSignal() @@ -548,6 +560,7 @@ class AbstractTab(QWidget): contents_size_changed = pyqtSignal(QSizeF) add_history_item = pyqtSignal(QUrl, QUrl, str) # url, requested url, title fullscreen_requested = pyqtSignal(bool) + renderer_process_terminated = pyqtSignal(TerminationStatus, int) def __init__(self, win_id, mode_manager, parent=None): self.win_id = win_id diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index dc3aec277..4c1bb9e9d 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -40,7 +40,7 @@ from qutebrowser.browser.webengine import (webview, webengineelem, tabhistory, webenginedownloads) from qutebrowser.misc import miscwidgets from qutebrowser.utils import (usertypes, qtutils, log, javascript, utils, - objreg, jinja, message) + objreg, jinja) _qute_scheme_handler = None @@ -683,17 +683,17 @@ class WebEngineTab(browsertab.AbstractTab): # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-58697 status = QWebEnginePage.CrashedTerminationStatus - if status == QWebEnginePage.NormalTerminationStatus: - pass - elif status == QWebEnginePage.AbnormalTerminationStatus: - message.error("Renderer process exited with status {}".format( - exitcode)) - elif status == QWebEnginePage.CrashedTerminationStatus: - message.error("Renderer process crashed") - elif status == QWebEnginePage.KilledTerminationStatus: - message.error("Renderer process was killed") - else: - raise ValueError("Invalid status {}".format(status)) + status_map = { + QWebEnginePage.NormalTerminationStatus: + browsertab.TerminationStatus.normal, + QWebEnginePage.AbnormalTerminationStatus: + browsertab.TerminationStatus.abnormal, + QWebEnginePage.CrashedTerminationStatus: + browsertab.TerminationStatus.crashed, + QWebEnginePage.KilledTerminationStatus: + browsertab.TerminationStatus.killed, + } + self.renderer_process_terminated.emit(status_map[status], exitcode) def _connect_signals(self): view = self._widget diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py index 27a882dc7..204dcdf7a 100644 --- a/qutebrowser/mainwindow/tabbedbrowser.py +++ b/qutebrowser/mainwindow/tabbedbrowser.py @@ -198,6 +198,8 @@ class TabbedBrowser(tabwidget.TabWidget): functools.partial(self.on_load_started, tab)) tab.window_close_requested.connect( functools.partial(self.on_window_close_requested, tab)) + tab.renderer_process_terminated.connect( + functools.partial(self._on_renderer_process_terminated, tab)) tab.new_tab_requested.connect(self.tabopen) tab.add_history_item.connect(objreg.get('web-history').add_from_tab) tab.fullscreen_requested.connect(self.page_fullscreen_requested) @@ -655,6 +657,20 @@ class TabbedBrowser(tabwidget.TabWidget): self.update_window_title() self.update_tab_title(idx) + def _on_renderer_process_terminated(self, tab, status, code): + """Show an error when a renderer process terminated.""" + if status == browsertab.TerminationStatus.normal: + pass + elif status == browsertab.TerminationStatus.abnormal: + message.error("Renderer process exited with status {}".format( + code)) + elif status == browsertab.TerminationStatus.crashed: + message.error("Renderer process crashed") + elif status == browsertab.TerminationStatus.killed: + message.error("Renderer process was killed") + else: + raise ValueError("Invalid status {}".format(status)) + def resizeEvent(self, e): """Extend resizeEvent of QWidget to emit a resized signal afterwards. From 1af951fd62b4878b2eaeb63e628b0ce5c06eeae8 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 8 Feb 2017 10:55:42 +0100 Subject: [PATCH 316/561] Work around crashes when opening URL after renderer crash Fixes #2290 --- CHANGELOG.asciidoc | 1 + qutebrowser/mainwindow/tabbedbrowser.py | 15 ++++++++++++--- tests/end2end/features/misc.feature | 13 +++++++++++++ 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 62170d6e1..43dc54ba1 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -61,6 +61,7 @@ Fixed - Fixed starting on newer PyQt/sip versions with LibreSSL - When downloading files with QtWebKit, a User-Agent header is set when possible. - Fixed showing of keybindings in the :help completion +- Worked around a segfault when opening a URL after a QtWebEngine renderer process crash v0.9.1 ------ diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py index 204dcdf7a..31eef4793 100644 --- a/qutebrowser/mainwindow/tabbedbrowser.py +++ b/qutebrowser/mainwindow/tabbedbrowser.py @@ -250,12 +250,13 @@ class TabbedBrowser(tabwidget.TabWidget): url = config.get('general', 'default-page') self.openurl(url, newtab=True) - def _remove_tab(self, tab, *, add_undo=True): + def _remove_tab(self, tab, *, add_undo=True, crashed=False): """Remove a tab from the tab list and delete it properly. Args: tab: The QWebView to be closed. add_undo: Whether the tab close can be undone. + crashed: Whether we're closing a tab with crashed renderer process. """ idx = self.indexOf(tab) if idx == -1: @@ -285,8 +286,10 @@ class TabbedBrowser(tabwidget.TabWidget): urlutils.invalid_url_error(tab.url(), "saving tab") tab.shutdown() self.removeTab(idx) - tab.layout().unwrap() - tab.deleteLater() + if not crashed: + # WORKAROUND for a segfault when we delete the crashed tab. + # see https://bugreports.qt.io/browse/QTBUG-58698 + tab.layout().unwrap() def undo(self): """Undo removing of a tab.""" @@ -671,6 +674,12 @@ class TabbedBrowser(tabwidget.TabWidget): else: raise ValueError("Invalid status {}".format(status)) + # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-58698 + # FIXME:qtwebengine can we disable this with Qt 5.8.1? + self._remove_tab(tab, crashed=True) + if self.count() == 0: + self.tabopen(QUrl('about:blank')) + def resizeEvent(self, e): """Extend resizeEvent of QWidget to emit a resized signal afterwards. diff --git a/tests/end2end/features/misc.feature b/tests/end2end/features/misc.feature index 0d843de14..fe1c3b256 100644 --- a/tests/end2end/features/misc.feature +++ b/tests/end2end/features/misc.feature @@ -653,3 +653,16 @@ Feature: Various utility commands. Scenario: Renderer kill When I run :open -t chrome://kill Then the error "Renderer process was killed" should be shown + + # https://github.com/qutebrowser/qutebrowser/issues/2290 + @qtwebkit_skip @no_invalid_lines + Scenario: Navigating to URL after renderer process is gone + When I run :tab-only + And I open data/numbers/1.txt + And I open data/numbers/2.txt in a new tab + And I run :open chrome://kill + And I wait for "Renderer process was killed" in the log + And I open data/numbers/3.txt + Then no crash should happen + And the following tabs should be open: + - data/numbers/3.txt (active) From 580648da32b43fe02c417e3b917faa39fb0acb17 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 8 Feb 2017 11:32:14 +0100 Subject: [PATCH 317/561] Allow to open chrome:// URLs in BDD tests --- tests/end2end/fixtures/quteprocess.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/end2end/fixtures/quteprocess.py b/tests/end2end/fixtures/quteprocess.py index 7eb4a1113..2ca361529 100644 --- a/tests/end2end/fixtures/quteprocess.py +++ b/tests/end2end/fixtures/quteprocess.py @@ -297,7 +297,8 @@ class QuteProc(testprocess.Process): URLs like about:... and qute:... are handled specially and returned verbatim. """ - if path.startswith('about:') or path.startswith('qute:'): + special_schemes = ['about:', 'qute:', 'chrome:'] + if any(path.startswith(scheme) for scheme in special_schemes): return path else: httpbin = self.request.getfixturevalue('httpbin') From 7c08444c3712f9f57e945fdd25e335b36b70a17a Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 8 Feb 2017 11:38:39 +0100 Subject: [PATCH 318/561] Refuse to serialize chrome:// and view-source:// tabs Fixes #2289 --- CHANGELOG.asciidoc | 1 + qutebrowser/browser/commands.py | 9 ++++++++- qutebrowser/browser/webengine/webenginetab.py | 5 +++++ qutebrowser/mainwindow/tabbedbrowser.py | 9 ++++++--- tests/end2end/features/tabs.feature | 17 +++++++++++++++++ 5 files changed, 37 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 43dc54ba1..924d2bbe1 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -62,6 +62,7 @@ Fixed - When downloading files with QtWebKit, a User-Agent header is set when possible. - Fixed showing of keybindings in the :help completion - Worked around a segfault when opening a URL after a QtWebEngine renderer process crash +- Using :undo or :tab-clone with a view-source:// or chrome:// tab is now prevented, as it segfaults v0.9.1 ------ diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 0cdc92eff..4a9d9aaa3 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -427,6 +427,11 @@ class CommandDispatcher: cmdutils.check_exclusive((bg, window), 'bw') curtab = self._current_widget() cur_title = self._tabbed_browser.page_title(self._current_index()) + try: + history = curtab.history.serialize() + except browsertab.WebTabError as e: + raise cmdexc.CommandError(e) + # The new tab could be in a new tabbed_browser (e.g. because of # tabs-are-windows being set) if window: @@ -437,13 +442,15 @@ class CommandDispatcher: new_tabbed_browser = objreg.get('tabbed-browser', scope='window', window=newtab.win_id) idx = new_tabbed_browser.indexOf(newtab) + new_tabbed_browser.set_page_title(idx, cur_title) if config.get('tabs', 'show-favicons'): new_tabbed_browser.setTabIcon(idx, curtab.icon()) if config.get('tabs', 'tabs-are-windows'): new_tabbed_browser.window().setWindowIcon(curtab.icon()) + newtab.data.keep_icon = True - newtab.history.deserialize(curtab.history.serialize()) + newtab.history.deserialize(history) newtab.zoom.set_factor(curtab.zoom.factor()) return newtab diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index 4c1bb9e9d..1fb01d614 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -384,6 +384,11 @@ class WebEngineHistory(browsertab.AbstractHistory): return self._history.canGoForward() def serialize(self): + # WORKAROUND for https://github.com/qutebrowser/qutebrowser/issues/2289 + # FIXME:qtwebengine can we get rid of this with Qt 5.8.1? + scheme = self._history.currentItem().url().scheme() + if scheme in ['view-source', 'chrome']: + raise browsertab.WebTabError("Can't serialize special URL!") return qtutils.serialize(self._history) def deserialize(self, data): diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py index 31eef4793..eba736d4a 100644 --- a/qutebrowser/mainwindow/tabbedbrowser.py +++ b/qutebrowser/mainwindow/tabbedbrowser.py @@ -268,9 +268,12 @@ class TabbedBrowser(tabwidget.TabWidget): window=self._win_id): objreg.delete('last-focused-tab', scope='window', window=self._win_id) - if tab.url().isValid(): - history_data = tab.history.serialize() - if add_undo: + if tab.url().isValid() and add_undo: + try: + history_data = tab.history.serialize() + except browsertab.WebTabError: + pass # special URL + else: entry = UndoEntry(tab.url(), history_data, idx) self._undo_stack.append(entry) elif tab.url().isEmpty(): diff --git a/tests/end2end/features/tabs.feature b/tests/end2end/features/tabs.feature index 0ba6d62b7..c7f7d7d1b 100644 --- a/tests/end2end/features/tabs.feature +++ b/tests/end2end/features/tabs.feature @@ -604,6 +604,12 @@ Feature: Tab management - url: http://localhost:*/data/title.html title: Test title + # https://github.com/qutebrowser/qutebrowser/issues/2289 + Scenario: Cloning a tab with a special URL + When I open chrome://gpu + And I run :tab-clone + Then the error "Can't serialize special URL!" should be shown + # :tab-detach Scenario: Detaching a tab @@ -759,6 +765,17 @@ Feature: Tab management - data/numbers/2.txt - data/numbers/3.txt + # https://github.com/qutebrowser/qutebrowser/issues/2289 + Scenario: Undoing a tab with a special URL + Given I have a fresh instance + When I open data/numbers/1.txt + And I open chrome://gpu in a new tab + And I run :tab-close + And I run :undo + Then the error "Nothing to undo!" should be shown + And the following tabs should be open: + - data/numbers/1.txt (active) + # last-close # FIXME:qtwebengine From 8226afd167947a83a5109e2697ac73fe8f47d039 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 8 Feb 2017 12:01:30 +0100 Subject: [PATCH 319/561] Hide more SSL warnings --- qutebrowser/utils/log.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/qutebrowser/utils/log.py b/qutebrowser/utils/log.py index e9ce3d463..7c96d4072 100644 --- a/qutebrowser/utils/log.py +++ b/qutebrowser/utils/log.py @@ -401,6 +401,8 @@ def qt_message_handler(msg_type, context, msg): "setting the environment variable QTWEBKIT_IMAGEFORMAT_WHITELIST=", # Installing Qt from the installer may cause it looking for SSL3 which # may not be available on the system + "QSslSocket: cannot resolve SSLv2_client_method", + "QSslSocket: cannot resolve SSLv2_server_method", "QSslSocket: cannot resolve SSLv3_client_method", "QSslSocket: cannot resolve SSLv3_server_method", # When enabling debugging with QtWebEngine From 0130866e89c5eb55370e4277a32e01078e1a1be6 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 8 Feb 2017 12:03:32 +0100 Subject: [PATCH 320/561] Only do mhtml import in DownloadManager.get_mhtml We also import qtnetworkdownloads on QtWebEngine without QtWebKit available, so we can't be sure we can import mhtml. --- qutebrowser/browser/qtnetworkdownloads.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qutebrowser/browser/qtnetworkdownloads.py b/qutebrowser/browser/qtnetworkdownloads.py index 2826df333..5ac689e29 100644 --- a/qutebrowser/browser/qtnetworkdownloads.py +++ b/qutebrowser/browser/qtnetworkdownloads.py @@ -29,7 +29,7 @@ from PyQt5.QtNetwork import QNetworkRequest, QNetworkReply from qutebrowser.utils import message, usertypes, log, urlutils, utils from qutebrowser.browser import downloads -from qutebrowser.browser.webkit import http, mhtml +from qutebrowser.browser.webkit import http from qutebrowser.browser.webkit.network import networkmanager @@ -388,6 +388,7 @@ class DownloadManager(downloads.AbstractDownloadManager): def get_mhtml(self, tab, target): """Download the given tab as mhtml to the given DownloadTarget.""" assert tab.backend == usertypes.Backend.QtWebKit + from qutebrowser.browser.webkit import mhtml if target is not None: mhtml.start_download_checked(target, tab=tab) From 2501eed5f53ac2b1395fc9091503225fcc7ca245 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 8 Feb 2017 12:12:36 +0100 Subject: [PATCH 321/561] Re-add tab.deleteLater() call This got removed in 1af951fd62b4878b2eaeb63e628b0ce5c06eeae8 but tab.layout().unwrap() only deletes the underlying widget, not the tab itself. --- qutebrowser/mainwindow/tabbedbrowser.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py index eba736d4a..b81a0f756 100644 --- a/qutebrowser/mainwindow/tabbedbrowser.py +++ b/qutebrowser/mainwindow/tabbedbrowser.py @@ -293,6 +293,7 @@ class TabbedBrowser(tabwidget.TabWidget): # WORKAROUND for a segfault when we delete the crashed tab. # see https://bugreports.qt.io/browse/QTBUG-58698 tab.layout().unwrap() + tab.deleteLater() def undo(self): """Undo removing of a tab.""" From 6ae7960d9a80c7df0bda79cbe473e99e24c24a56 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 8 Feb 2017 12:14:24 +0100 Subject: [PATCH 322/561] Fix lint --- qutebrowser/browser/browsertab.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qutebrowser/browser/browsertab.py b/qutebrowser/browser/browsertab.py index 34d4424a3..5f9100b75 100644 --- a/qutebrowser/browser/browsertab.py +++ b/qutebrowser/browser/browsertab.py @@ -540,8 +540,8 @@ class AbstractTab(QWidget): fullscreen_requested: Fullscreen display was requested by the page. arg: True if fullscreen should be turned on, False if it should be turned off. - renderer_process_terminated: Emitted when the underlying renderer process - terminated. + renderer_process_terminated: Emitted when the underlying renderer + process terminated. arg 0: A TerminationStatus member. arg 1: The exit code. """ From 0ab7fd45816f76077c452aeb0bcf4d6e1439538b Mon Sep 17 00:00:00 2001 From: Imran Sobir Date: Wed, 8 Feb 2017 16:18:33 +0500 Subject: [PATCH 323/561] Restore original save-manager, web-history at end of test. --- tests/unit/browser/test_qutehistory.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/unit/browser/test_qutehistory.py b/tests/unit/browser/test_qutehistory.py index 43df79d26..67c9a05f8 100644 --- a/tests/unit/browser/test_qutehistory.py +++ b/tests/unit/browser/test_qutehistory.py @@ -31,6 +31,16 @@ class TestHistoryHandler: @pytest.fixture(autouse=True) def fake_objects(self, fake_save_manager): """ Create fake web-history with history for three different days """ + try: + original_save_manager = objreg.get('save-manager') + except KeyError: + original_save_manager = None + + try: + original_web_history = objreg.get('web-history') + except KeyError: + original_web_history = None + temp_dir = tempfile.TemporaryDirectory() fake_web_history = history.WebHistory(temp_dir.name, 'fake-history') objreg.register('web-history', fake_web_history, update=True) @@ -55,6 +65,18 @@ class TestHistoryHandler: web_history._add_entry(yesterday) web_history.save() + yield + + if original_save_manager: + objreg.register('save-manager', original_save_manager, update=True) + else: + objreg.delete('save-manager') + + if original_web_history: + objreg.register('web-history', original_web_history, update=True) + else: + objreg.delete('web-history') + def test_history_without_query(self): """ Test qute://history shows today's history when it has no query """ _mimetype, data = qutescheme.qute_history(QUrl("qute://history")) From 36e2cc96282a6f102e1686b56f8068c13903f2d2 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 8 Feb 2017 12:45:09 +0100 Subject: [PATCH 324/561] Fix broken "if" when closing tab. Combining the "add_undo" with "tab.url().isValid()" was a mistake as the "else" meant something else then... --- qutebrowser/mainwindow/tabbedbrowser.py | 26 +++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py index b81a0f756..7302bc3fb 100644 --- a/qutebrowser/mainwindow/tabbedbrowser.py +++ b/qutebrowser/mainwindow/tabbedbrowser.py @@ -268,7 +268,19 @@ class TabbedBrowser(tabwidget.TabWidget): window=self._win_id): objreg.delete('last-focused-tab', scope='window', window=self._win_id) - if tab.url().isValid() and add_undo: + + if tab.url().isEmpty(): + # There are some good reasons why a URL could be empty + # (target="_blank" with a download, see [1]), so we silently ignore + # this. + # [1] https://github.com/qutebrowser/qutebrowser/issues/163 + pass + elif not tab.url().isValid(): + # We display a warning for URLs which are not empty but invalid - + # but we don't return here because we want the tab to close either + # way. + urlutils.invalid_url_error(tab.url(), "saving tab") + elif add_undo: try: history_data = tab.history.serialize() except browsertab.WebTabError: @@ -276,17 +288,7 @@ class TabbedBrowser(tabwidget.TabWidget): else: entry = UndoEntry(tab.url(), history_data, idx) self._undo_stack.append(entry) - elif tab.url().isEmpty(): - # There are some good reasons why a URL could be empty - # (target="_blank" with a download, see [1]), so we silently ignore - # this. - # [1] https://github.com/qutebrowser/qutebrowser/issues/163 - pass - else: - # We display a warnings for URLs which are not empty but invalid - - # but we don't return here because we want the tab to close either - # way. - urlutils.invalid_url_error(tab.url(), "saving tab") + tab.shutdown() self.removeTab(idx) if not crashed: From ac2fd4a36f27958b622c2b090dd5281c202bc137 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 8 Feb 2017 13:04:38 +0100 Subject: [PATCH 325/561] Skip special URL tests with QtWebKit --- tests/end2end/features/tabs.feature | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/end2end/features/tabs.feature b/tests/end2end/features/tabs.feature index c7f7d7d1b..aa6c2e828 100644 --- a/tests/end2end/features/tabs.feature +++ b/tests/end2end/features/tabs.feature @@ -605,6 +605,7 @@ Feature: Tab management title: Test title # https://github.com/qutebrowser/qutebrowser/issues/2289 + @qtwebkit_skip Scenario: Cloning a tab with a special URL When I open chrome://gpu And I run :tab-clone @@ -766,6 +767,7 @@ Feature: Tab management - data/numbers/3.txt # https://github.com/qutebrowser/qutebrowser/issues/2289 + @qtwebkit_skip Scenario: Undoing a tab with a special URL Given I have a fresh instance When I open data/numbers/1.txt From af40439f4a1a410170a4edf2f5a7b3b66fbcd53c Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 8 Feb 2017 15:27:45 +0100 Subject: [PATCH 326/561] tests: Ignore more QPainter::end warnings --- pytest.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytest.ini b/pytest.ini index e7583436b..d103cb3b8 100644 --- a/pytest.ini +++ b/pytest.ini @@ -49,5 +49,5 @@ qt_log_ignore = ^load glyph failed ^Error when parsing the netrc file ^Image of format '' blocked because it is not considered safe. If you are sure it is safe to do so, you can white-list the format by setting the environment variable QTWEBKIT_IMAGEFORMAT_WHITELIST= - ^QPainter::end: Painter ended with 2 saved states + ^QPainter::end: Painter ended with \d+ saved states xfail_strict = true From df35c9138b8831e120f0aa98e98990fb69137635 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 8 Feb 2017 16:53:03 +0100 Subject: [PATCH 327/561] Filter out uninteresting elements early in TabBarStyle Otherwise we call _tab_layout on something which is not a QStyleOptionTab, which might cause AttributeErrors. In a crash report I couldn't reproduce, it got called with a QStyleOptionMenuItem when right-clicking on the page. --- qutebrowser/mainwindow/tabwidget.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/qutebrowser/mainwindow/tabwidget.py b/qutebrowser/mainwindow/tabwidget.py index ce9a90f4d..dd9a54d5b 100644 --- a/qutebrowser/mainwindow/tabwidget.py +++ b/qutebrowser/mainwindow/tabwidget.py @@ -614,6 +614,12 @@ class TabBarStyle(QCommonStyle): p: QPainter widget: QWidget """ + if element not in [QStyle.CE_TabBarTab, QStyle.CE_TabBarTabShape, + QStyle.CE_TabBarTabLabel]: + # Let the real style draw it. + self._style.drawControl(element, opt, p, widget) + return + layouts = self._tab_layout(opt) if layouts is None: log.misc.warning("Could not get layouts for tab!") @@ -638,9 +644,7 @@ class TabBarStyle(QCommonStyle): opt.state & QStyle.State_Enabled, opt.text, QPalette.WindowText) else: - # For any other elements we just delegate the work to our real - # style. - self._style.drawControl(element, opt, p, widget) + raise ValueError("Invalid element {!r}".format(element)) def pixelMetric(self, metric, option=None, widget=None): """Override pixelMetric to not shift the selected tab. From 4d08dc5ddb626cbb47a47d4936195a1c9df243ac Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 8 Feb 2017 18:34:50 +0100 Subject: [PATCH 328/561] Point out QtWebKit-NG in version info --- qutebrowser/utils/version.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/qutebrowser/utils/version.py b/qutebrowser/utils/version.py index ca287f2af..729ba440b 100644 --- a/qutebrowser/utils/version.py +++ b/qutebrowser/utils/version.py @@ -38,7 +38,7 @@ except ImportError: # pragma: no cover qWebKitVersion = None import qutebrowser -from qutebrowser.utils import log, utils, standarddir +from qutebrowser.utils import log, utils, standarddir, usertypes, qtutils from qutebrowser.misc import objects from qutebrowser.browser import pdfjs @@ -230,7 +230,13 @@ def version(): gitver = _git_str() if gitver is not None: lines.append("Git commit: {}".format(gitver)) - lines.append("Backend: {}".format(objects.backend.name)) + + backend = objects.backend.name + if (qWebKitVersion is not None and + objects.backend == usertypes.Backend.QtWebKit and + qtutils.is_qtwebkit_ng(qWebKitVersion())): + backend = 'QtWebKit-NG' + lines.append("Backend: {}".format(backend)) if qVersion() != QT_VERSION_STR: qt_version = 'Qt: {} (compiled {})'.format(qVersion(), QT_VERSION_STR) From 3adcfddfff12fe1429d965a9467de49655e5dabb Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 8 Feb 2017 19:46:09 +0100 Subject: [PATCH 329/561] Handle LookupError when reading adblock zips --- qutebrowser/browser/adblock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutebrowser/browser/adblock.py b/qutebrowser/browser/adblock.py index 06d0cabe8..b0a9962c6 100644 --- a/qutebrowser/browser/adblock.py +++ b/qutebrowser/browser/adblock.py @@ -219,7 +219,7 @@ class HostBlocker: try: f = get_fileobj(byte_io) except (OSError, UnicodeDecodeError, zipfile.BadZipFile, - zipfile.LargeZipFile) as e: + zipfile.LargeZipFile, LookupError) as e: message.error("adblock: Error while reading {}: {} - {}".format( byte_io.name, e.__class__.__name__, e)) return From 0897e8e5ada932d07c8ddaa44bf070880c8b80eb Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 8 Feb 2017 22:17:13 +0100 Subject: [PATCH 330/561] Add pdfjs path for pdf.js in AUR --- qutebrowser/browser/pdfjs.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/qutebrowser/browser/pdfjs.py b/qutebrowser/browser/pdfjs.py index 3bc2c2dc3..d11cf3098 100644 --- a/qutebrowser/browser/pdfjs.py +++ b/qutebrowser/browser/pdfjs.py @@ -100,6 +100,8 @@ SYSTEM_PDFJS_PATHS = [ # Debian pdf.js-common # Arch Linux pdfjs (AUR) '/usr/share/pdf.js/', + # Arch Linux pdf.js (AUR) + '/usr/share/javascript/pdf.js/', # Debian libjs-pdf '/usr/share/javascript/pdf/', # fallback From b5ab3adc8cbde2d422f26f556ce352b1d3536376 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 8 Feb 2017 22:27:49 +0100 Subject: [PATCH 331/561] Fix sip.assign check --- qutebrowser/browser/webengine/webenginetab.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index 1fb01d614..2f4da748c 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -660,7 +660,7 @@ class WebEngineTab(browsertab.AbstractTab): try: # pylint: disable=no-member sip.assign(authenticator, QAuthenticator()) - except NameError: + except AttributeError: # WORKAROUND for # https://www.riverbankcomputing.com/pipermail/pyqt/2016-December/038400.html url_string = url.toDisplayString() From 859974fa99f82520c27e420d479c608976c5b830 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 8 Feb 2017 22:49:25 +0100 Subject: [PATCH 332/561] Don't crash without hint context in HintManager._start_cb --- qutebrowser/browser/hints.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/qutebrowser/browser/hints.py b/qutebrowser/browser/hints.py index e7ccb3e9d..308f686b0 100644 --- a/qutebrowser/browser/hints.py +++ b/qutebrowser/browser/hints.py @@ -572,6 +572,10 @@ class HintManager(QObject): def _start_cb(self, elems): """Initialize the elements and labels based on the context set.""" + if self._context is None: + log.hints.debug("In _start_cb without context!") + return + if elems is None: message.error("There was an error while getting hint elements") return From 038a517c5aada398263a34bf82bf39a077ef3c10 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 8 Feb 2017 23:46:37 +0100 Subject: [PATCH 333/561] Add test for QtWebKit-NG in test_version --- tests/unit/utils/test_version.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/unit/utils/test_version.py b/tests/unit/utils/test_version.py index 2e1ea1b80..ca31c7a12 100644 --- a/tests/unit/utils/test_version.py +++ b/tests/unit/utils/test_version.py @@ -646,6 +646,7 @@ class FakeQSslSocket: (True, True, False, True, True), # no style (True, False, True, False, True), # different Qt (True, False, True, True, False), # no webkit + (True, False, True, True, 'ng'), # QtWebKit-NG ]) def test_version_output(git_commit, frozen, style, equal_qt, with_webkit, stubs, monkeypatch): @@ -673,6 +674,8 @@ def test_version_output(git_commit, frozen, style, equal_qt, with_webkit, stubs.FakeQApplication(instance=None)), 'objects.backend': (usertypes.Backend.QtWebKit if with_webkit else usertypes.Backend.QtWebEngine), + 'qtutils.is_qtwebkit_ng': (lambda v: + True if with_webkit == 'ng' else False), } for attr, val in patches.items(): @@ -715,8 +718,13 @@ def test_version_output(git_commit, frozen, style, equal_qt, with_webkit, 'frozen': str(frozen), 'import_path': import_path, 'webkit': 'WEBKIT VERSION' if with_webkit else 'no', - 'backend': 'QtWebKit' if with_webkit else 'QtWebEngine', } + backends = { + True: 'QtWebKit', + False: 'QtWebEngine', + 'ng': 'QtWebKit-NG', + } + substitutions['backend'] = backends[with_webkit] expected = template.rstrip('\n').format(**substitutions) assert version.version() == expected From ce3e24163eca91c69fc799d2b5a324d7f7dadd44 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 8 Feb 2017 14:02:03 +0100 Subject: [PATCH 334/561] Add test environments using PyQt from PyPI Fixes #2083 --- .travis.yml | 15 ++++++++++++ scripts/dev/ci/travis_install.sh | 7 +++++- tox.ini | 40 ++++++++++++++++++++++++++++++++ 3 files changed, 61 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 5991fea42..ce8424f42 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,9 +21,24 @@ matrix: - os: linux env: DOCKER=ubuntu-xenial services: docker + - os: linux + language: python + python: 3.5 + env: TESTENV=py35-pyqt56 + - os: linux + language: python + python: 3.5 + env: TESTENV=py35-pyqt571 + - os: linux + language: python + python: 3.6 + env: TESTENV=py36-pyqt571 - os: osx env: TESTENV=py36 OSX=elcapitan osx_image: xcode7.3 + - os: osx + env: TESTENV=py36-pyqt571 OSX=elcapitan + osx_image: xcode7.3 # https://github.com/qutebrowser/qutebrowser/issues/2013 # - os: osx # env: TESTENV=py35 OSX=yosemite diff --git a/scripts/dev/ci/travis_install.sh b/scripts/dev/ci/travis_install.sh index 5317e2135..cf571f3fd 100644 --- a/scripts/dev/ci/travis_install.sh +++ b/scripts/dev/ci/travis_install.sh @@ -93,7 +93,9 @@ elif [[ $TRAVIS_OS_NAME == osx ]]; then sudo -H python get-pip.py brew --version - brew_install python3 qt5 pyqt5 + + brew_install python3 + [[ $TESTENV != *-pyqt* ]] && brew_install qt5 pyqt5 pip_install -r misc/requirements/requirements-tox.txt pip --version @@ -116,6 +118,9 @@ case $TESTENV in apt_install xvfb $pyqt_pkgs libpython3.4-dev check_pyqt ;; + py3*-pyqt*) + apt_install xvfb libpython3.4-dev + ;; pylint|vulture) apt_install $pyqt_pkgs libpython3.4-dev check_pyqt diff --git a/tox.ini b/tox.ini index 8d31dd961..bc7c05a54 100644 --- a/tox.ini +++ b/tox.ini @@ -22,6 +22,9 @@ commands = {envpython} scripts/link_pyqt.py --tox {envdir} {envpython} scripts/dev/run_pytest.py {posargs:tests} + +# test envs with coverage + [testenv:py36-cov] basepython = python3.6 setenv = {[testenv]setenv} @@ -52,6 +55,43 @@ commands = {envpython} scripts/dev/run_pytest.py --cov --cov-report xml --cov-report=html --cov-report= {posargs:tests} {envpython} scripts/dev/check_coverage.py {posargs} +# test envs with PyQt5 from PyPI + +[testenv:py35-pyqt56] +basepython = python3.5 +setenv = + {[testenv]setenv} + QUTE_BDD_WEBENGINE=true +passenv = {[testenv]passenv} +deps = + {[testenv]deps} + PyQt5==5.6 +commands = {envpython} scripts/dev/run_pytest.py {posargs:tests} + +[testenv:py35-pyqt571] +basepython = python3.5 +setenv = + {[testenv]setenv} + QUTE_BDD_WEBENGINE=true +passenv = {[testenv]passenv} +deps = + {[testenv]deps} + PyQt5==5.7.1 +commands = {envpython} scripts/dev/run_pytest.py {posargs:tests} + +[testenv:py36-pyqt571] +basepython = python3.6 +setenv = + {[testenv]setenv} + QUTE_BDD_WEBENGINE=true +passenv = {[testenv]passenv} +deps = + {[testenv]deps} + PyQt5==5.7.1 +commands = {envpython} scripts/dev/run_pytest.py {posargs:tests} + +# other envs + [testenv:mkvenv] basepython = python3 commands = {envpython} scripts/link_pyqt.py --tox {envdir} From 8779a1367eb8c97c69a655c86d43821654bd3ce9 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 8 Feb 2017 14:02:51 +0100 Subject: [PATCH 335/561] Fix test collection without QtWebKit --- tests/unit/browser/webkit/test_tabhistory.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/browser/webkit/test_tabhistory.py b/tests/unit/browser/webkit/test_tabhistory.py index 168621558..b99d8376d 100644 --- a/tests/unit/browser/webkit/test_tabhistory.py +++ b/tests/unit/browser/webkit/test_tabhistory.py @@ -24,7 +24,7 @@ import collections from PyQt5.QtCore import QUrl, QPoint import pytest -from qutebrowser.browser.webkit import tabhistory +tabhistory = pytest.importorskip('qutebrowser.browser.webkit.tabhistory') from qutebrowser.misc.sessions import TabHistoryItem as Item from qutebrowser.utils import qtutils From 00c743b3e84e94bac3846fd5b801dc096b10705c Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 8 Feb 2017 14:12:31 +0100 Subject: [PATCH 336/561] Relax complex mhtml check for QtWebEngine Turns out the output differs between QtWebEngine versions, probably because it dumps its parsed files. Let's just do a sanity check for the complex file instead. --- .../mhtml/complex/complex-webengine.mht | 711 ------------------ tests/end2end/test_mhtml_e2e.py | 8 +- 2 files changed, 7 insertions(+), 712 deletions(-) delete mode 100644 tests/end2end/data/downloads/mhtml/complex/complex-webengine.mht diff --git a/tests/end2end/data/downloads/mhtml/complex/complex-webengine.mht b/tests/end2end/data/downloads/mhtml/complex/complex-webengine.mht deleted file mode 100644 index 735adcfe9..000000000 --- a/tests/end2end/data/downloads/mhtml/complex/complex-webengine.mht +++ /dev/null @@ -1,711 +0,0 @@ -From: -Subject: qutebrowser mhtml test -Date: today -MIME-Version: 1.0 -Content-Type: multipart/related; - type="text/html"; - boundary="---=_qute-UUID" - ------=_qute-UUID -Content-Type: text/html -Content-ID: 42 -Content-Transfer-Encoding: quoted-printable -Content-Location: http://localhost:(port)/data/downloads/mhtml/complex/complex.html - - - =20 - qutebrowser mhtml test - =20 - - - =20 - - - =20 - - - =20 - - - =20 - - - =20 - - =20 - =20 - - =20 - - - - - =20 -

Welcome to the qutebrowser mhtml test page

- =20 -
- ...that the word qutebrowser is a word play on Qt, the - framework the browser is built with? -
- =20 -

What is this page?

- =20 -

This page is a test-case for the mhtml download feature of - qutebrowser. Under normal circumstances, you won't see this page, e= -xcept - if you're a qutebrowser developer or you're attending one = -of - The-Compiler's pytest demos.

- =20 -
- ...that this page was once a monstrosity with "this weird pixel= -ated - globe with the geocities-like background"? You can find the ol= -d - page in the old commits and indeed, it was quite atrocious. But hey= -, - every browser needs a globe... -
- =20 -

This page references other assets and when the page is downloade= -d, - qutebrowser checks if each asset was downloaded. If some assets are - missing, the test fails and the poor developers have to search for = -the - error.

- =20 -

Can I contribute to qutebrowser?

- =20 -

Yes!

- =20 -
- ...that qutebrowser is free software? Free as in free beer= - and - free speech! Isn't that great? -
- =20 -

...and how?

- =20 -

See - here for more information.

- =20 -

More useless trivia!

- =20 -
- ...that the font in the header is Comic Sans? -
- =20 -
- ...the IRC channel for qutebrowser is #qutebrowser on - irc.freenode.net -
- =20 -
- ...the area of a circle is =CF=80*r2? -
- =20 -

To make this page a bit useful, I've included a chessboard, so y= -ou - can play chess. Just turn your screen 90 degrees, such that it form= -s a - flat, horizontal surface (you can skip this step if you're using a - tablet). Next, zoom the page until it fits your needs. Enjoy your r= -ound - of chess!

- -
- =20 - - ------=_qute-UUID -Content-Type: image/png -Content-Transfer-Encoding: base64 -Content-Location: http://localhost:(port)/data/downloads/mhtml/complex/Background.png - -iVBORw0KGgoAAAANSUhEUgAAAAEAAABkCAIAAADITs03AAAACXBIWXMAAAsTAAALEwEAmpwYAAAA -B3RJTUUH4AICDwo6PeZczAAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUH -AAAAMElEQVQY0+3DUQ0AIAxDwaYSYK+W5t8H84ALvrjk1N2W9D+613IFUxgwwSlcRHPmAgsRBhHi -LLCEAAAAAElFTkSuQmCC ------=_qute-UUID -Content-Type: text/css -Content-Transfer-Encoding: quoted-printable -Content-Location: http://localhost:(port)/data/downloads/mhtml/complex/external-in-extern.css - -@charset "utf-8"; - -h1, h2, h3, h4, h5, h6 { color: rgb(10, 57, 110); border-bottom: 1px dotted= - rgb(71, 71, 71); } ------=_qute-UUID -Content-Type: image/png -Content-Transfer-Encoding: base64 -Content-Location: http://localhost:(port)/data/downloads/mhtml/complex/DYK.png - -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 ------=_qute-UUID -Content-Type: text/css -Content-Transfer-Encoding: quoted-printable -Content-Location: http://localhost:(port)/data/downloads/mhtml/complex/base.css - -@charset "utf-8"; - -@import url("external-in-extern.css"); - -div.dyk { background-image: url("DYK.png"); background-repeat: no-repeat; m= -in-height: 128px; padding-left: 148px; margin-top: 10px; margin-bottom: 10p= -x; border: 2px solid rgb(71, 71, 71); border-radius: 64px; } ------=_qute-UUID -Content-Type: image/png -Content-Transfer-Encoding: base64 -Content-Location: http://localhost:(port)/data/downloads/mhtml/complex/Banner.png - -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 -/a7WDh4p3SR1ryvQARUrQqSMGTMmwrXhnDlzQh3uCTd23Xu47rnLB3dVIHLfY3jnDnBG66SCvMKj -EctWJ81asKx4+YMrbBZ12HPPPcegQYMitnGUlpYyf/78BgcQjUEIwd/+9rfAZwqBhIbKBf65udtv -v71Z+UZHR7N06VKSk5MxpDz45fd7pkvdW3oy76spzF/6zeH7p17wT4fNet2sWbNYsWJFxKrX1157 -jddee63JPIKefQAj/2j5ncEy7DlU/NaZHVo+lJqaqg4bNixilW4QI0eODHrdcqmKWAlMHjFiBHa7 -PRSfN9wBfEJCAlOnTvXzjaa/Vbe+qqrodUk7Ly+vnubfFP70pz8Fu4zvvJqunqr28kvL0sTPQr0A -4vI41xu7diIBxpWaEUjN0BcYPi1MWwSpacjYdM648FKGXzKS5MR4PB4PXq8PTdPQdR2fT8Pr9eF2 -+38o/c7uxRWXX0JychJ6E472dUNit/i1zNQowYCWClYBbg00PeBMSR7vkHj2fV9/v2yopStYElqC -asPwaujSeJ0OSR6OheI57Y9nlvy7WBryRsAYN24czz///Ek5VQhqp0Dxi++vKQk9Q/IewL333hta -9fgLoF49enfKdDWkoaampobqM7JfTnVD99a6vdcDeVlZWXz99dd07NjxpAuWkpLCe++9F9wPKaXk -r40R/YmgRYsWfP3115x11ll+pc/jG//HJxblB+pwwu/reEdppetuYG9iYiLLly9vvgYVMIc++uij -AdO2fLLv9U9/G8z36scWHjSk/DRo5mwIEydODN77sdurLQc88fHxoXnjurjtttuCBFxeVF79St26 -KEJIgLvuuuuk3v2f//xnhg8f7u/shJh3KtvLqZCleZz04a5zeOscGqBVfTTbGzi0sMMIP06kTQiA -pNlHU62CPEtMlD2cx3UJLWItjGujYXcdZcuPP1FQUMjR4hLcbg/xcXHEx8eSlpZC507tychIp7a2 -ln/+ayG7du9tMEqH1+vl78/PZc4T88nNzScnpyM33noj2wo87C03KHdDYY1BmRsa3dQiFLSjB6j6 -4mUwfNRLZegosSnEDJ6KJakVvqoqj0/SunR66tFfp4lD/gF4HlBWrVrFddddV88bTFMYN25ccJHI -JiFE77B8s4HvgcQNGzZw2WWXRZgGG0OXLl0inp+amhra4N/Qxl/pNyUerKioIDExMTRtcOmllwZX -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 ------=_qute-UUID -Content-Type: image/png -Content-Transfer-Encoding: base64 -Content-Location: http://localhost:(port)/data/downloads/mhtml/complex/Inline.png - -iVBORw0KGgoAAAANSUhEUgAAAKAAAACgCAYAAACLz2ctAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA -B3RJTUUH4AICEBUm88fqUAAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUH -AAABdUlEQVR42u3dwQmAQAxE0UTsv+W1hxAZhfcKkDl8dk+yfc45tai7Nz9Xy/Ps+9i+qyBIgAgQ -AYIAESAIEAGCABEgCBABggARIAgQAYIAESAIkP/qqlr9acA/EvY5AXEFgwARIAgQAYIAESAIEAGC -ABEgAgQBIkAQIAIEASJAeN3tHwn7kvucgLiCESAIEAGCABEgCBABggARIAgQAYIAESAIEAGCABEg -jHgnxL7oPicgrmAECAJEgCBABAgCRIAgQAQIAkSAIEAECAJEgCBABAgj3gmxL7rPCYgrGAGCABEg -CBABggARIAgQAYIAESAIEAGCABEgCBABwoh3QuyL7nMC4gpGgCBABAgCRIAgQAQIAkSAIEAECAJE -gCBABAgCRIAw4p0Q+6L7nIC4ghEgCBABggARIAgQAYIAESAIEAGCABEgCBABggARIIx4J8S+6D4n -IK5gBAgCRIAgQAQIAkSAIEAECAJEgCBABAgCRIAgQAQIIw997mYx/U+iXAAAAABJRU5ErkJggg== ------=_qute-UUID diff --git a/tests/end2end/test_mhtml_e2e.py b/tests/end2end/test_mhtml_e2e.py index d08b2b75d..46ece8f0e 100644 --- a/tests/end2end/test_mhtml_e2e.py +++ b/tests/end2end/test_mhtml_e2e.py @@ -69,6 +69,9 @@ class DownloadDir: with open(str(files[0]), 'r', encoding='utf-8') as f: return f.readlines() + def sanity_check_mhtml(self): + assert 'Content-Type: multipart/related' in '\n'.join(self.read_file()) + def compare_mhtml(self, filename): with open(filename, 'r', encoding='utf-8') as f: expected_data = [normalize_line(line) for line in f] @@ -128,7 +131,10 @@ def test_mhtml(request, test_name, download_dir, quteproc, httpbin): suffix = '-webengine' if request.config.webengine else '' filename = '{}{}.mht'.format(test_name, suffix) expected_file = os.path.join(test_dir, filename) - download_dir.compare_mhtml(expected_file) + if os.path.exists(expected_file): + download_dir.compare_mhtml(expected_file) + else: + download_dir.sanity_check_mhtml() if not request.config.webengine: _test_mhtml_requests(test_dir, test_path, httpbin) From 01719408bc2f65665d4d3ced8902f7c832c791b6 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 8 Feb 2017 14:18:10 +0100 Subject: [PATCH 337/561] Update CONTRIBUTING --- CONTRIBUTING.asciidoc | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTING.asciidoc b/CONTRIBUTING.asciidoc index e12d9cb9a..e240015ce 100644 --- a/CONTRIBUTING.asciidoc +++ b/CONTRIBUTING.asciidoc @@ -638,6 +638,7 @@ New PyQt release * See above * Install new PyQt in Windows VM (32- and 64-bit) * Download new installer and update PyQt installer path in `ci_install.py`. +* Update `tox.ini`/`.travis.yml`/`.appveyor.yml` to test new versions qutebrowser release ~~~~~~~~~~~~~~~~~~~ From a3caba53d5207179aae4345de3cad461bed350fa Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 8 Feb 2017 14:18:19 +0100 Subject: [PATCH 338/561] Allow single-dot version tags in BDD tests --- tests/end2end/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/end2end/conftest.py b/tests/end2end/conftest.py index 2219f2720..ba744d0b3 100644 --- a/tests/end2end/conftest.py +++ b/tests/end2end/conftest.py @@ -69,7 +69,7 @@ def _get_version_tag(tag): version_re = re.compile(r""" (?Pqt|pyqt) (?P==|>|>=|<|<=|!=) - (?P\d+\.\d+\.\d+) + (?P\d+\.\d+(\.\d+)?) """, re.VERBOSE) match = version_re.match(tag) From 0d271eba87c19c36a69786ebc572fa9fe98a0624 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 8 Feb 2017 14:18:41 +0100 Subject: [PATCH 339/561] Mark tests using special URLs as Qt >= 5.8 --- tests/end2end/features/tabs.feature | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/end2end/features/tabs.feature b/tests/end2end/features/tabs.feature index aa6c2e828..fc456f5f0 100644 --- a/tests/end2end/features/tabs.feature +++ b/tests/end2end/features/tabs.feature @@ -605,7 +605,7 @@ Feature: Tab management title: Test title # https://github.com/qutebrowser/qutebrowser/issues/2289 - @qtwebkit_skip + @qtwebkit_skip @qt>=5.8 Scenario: Cloning a tab with a special URL When I open chrome://gpu And I run :tab-clone @@ -767,7 +767,7 @@ Feature: Tab management - data/numbers/3.txt # https://github.com/qutebrowser/qutebrowser/issues/2289 - @qtwebkit_skip + @qtwebkit_skip @qt>=5.8 Scenario: Undoing a tab with a special URL Given I have a fresh instance When I open data/numbers/1.txt From 708c96f3ce55f93cc7c79b6e6a91ee1f9a6fa885 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 8 Feb 2017 14:20:44 +0100 Subject: [PATCH 340/561] Fix handling of @qt>= BDD tags --- tests/end2end/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/end2end/conftest.py b/tests/end2end/conftest.py index ba744d0b3..939a377f7 100644 --- a/tests/end2end/conftest.py +++ b/tests/end2end/conftest.py @@ -90,7 +90,7 @@ def _get_version_tag(tag): version = match.group('version') if package == 'qt': - return pytest.mark.skipif(qtutils.version_check(version, op), + return pytest.mark.skipif(not qtutils.version_check(version, op), reason='Needs ' + tag) elif package == 'pyqt': major, minor, patch = [int(e) for e in version.split('.')] From cb704f149a66eaa34cb38e5819b25b6bfdb9e6a0 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 8 Feb 2017 15:19:24 +0100 Subject: [PATCH 341/561] Remove py35-pyqt56 on Travis It just segfaults, even locally... --- .travis.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index ce8424f42..a87be3d29 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,10 +21,6 @@ matrix: - os: linux env: DOCKER=ubuntu-xenial services: docker - - os: linux - language: python - python: 3.5 - env: TESTENV=py35-pyqt56 - os: linux language: python python: 3.5 From 5d2b32956be8943644fbf13efd0c94b8014bad62 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 8 Feb 2017 15:20:10 +0100 Subject: [PATCH 342/561] travis: Install geoclue for PyPI tests --- scripts/dev/ci/travis_install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/dev/ci/travis_install.sh b/scripts/dev/ci/travis_install.sh index cf571f3fd..3ac18fcb0 100644 --- a/scripts/dev/ci/travis_install.sh +++ b/scripts/dev/ci/travis_install.sh @@ -119,7 +119,7 @@ case $TESTENV in check_pyqt ;; py3*-pyqt*) - apt_install xvfb libpython3.4-dev + apt_install xvfb libpython3.4-dev geoclue ;; pylint|vulture) apt_install $pyqt_pkgs libpython3.4-dev From 314ba53014d4818855706297167cec8daefbbde4 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 8 Feb 2017 15:21:09 +0100 Subject: [PATCH 343/561] travis: Don't run check_pyqt on OS X for PyPI jobs --- scripts/dev/ci/travis_install.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/scripts/dev/ci/travis_install.sh b/scripts/dev/ci/travis_install.sh index 3ac18fcb0..b0acb8dbc 100644 --- a/scripts/dev/ci/travis_install.sh +++ b/scripts/dev/ci/travis_install.sh @@ -95,12 +95,11 @@ elif [[ $TRAVIS_OS_NAME == osx ]]; then brew --version brew_install python3 - [[ $TESTENV != *-pyqt* ]] && brew_install qt5 pyqt5 + [[ $TESTENV != *-pyqt* ]] && brew_install qt5 pyqt5 && check_pyqt pip_install -r misc/requirements/requirements-tox.txt pip --version tox --version - check_pyqt exit 0 fi From 45065756384f8f534bb71d42a2d993d2b27e0457 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 8 Feb 2017 15:24:54 +0100 Subject: [PATCH 344/561] Try PyPI envs on Appveyor --- .appveyor.yml | 3 +++ scripts/dev/ci/appveyor_install.py | 40 +++++++++++++++++++----------- 2 files changed, 28 insertions(+), 15 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index 0f970d98b..1f91531ac 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -7,6 +7,9 @@ environment: PYTHONUNBUFFERED: 1 matrix: - TESTENV: py34 + - TESTENV: py35-pyqt56 + - TESTENV: py35-pyqt571 + - TESTENV: py36-pyqt571 - TESTENV: unittests-frozen - TESTENV: pylint diff --git a/scripts/dev/ci/appveyor_install.py b/scripts/dev/ci/appveyor_install.py index 2c04304d5..ad63ee3a4 100644 --- a/scripts/dev/ci/appveyor_install.py +++ b/scripts/dev/ci/appveyor_install.py @@ -28,6 +28,7 @@ CI machines. from __future__ import print_function +import os import time import subprocess import urllib @@ -44,22 +45,26 @@ def pip_install(pkg): pkg]) -print("Getting PyQt5...") -qt_version = '5.5.1' -pyqt_version = '5.5.1' -pyqt_url = ('https://www.qutebrowser.org/pyqt/' - 'PyQt5-{}-gpl-Py3.4-Qt{}-x32.exe'.format( - pyqt_version, qt_version)) +using_pypi = '-pyqt' in os.environ['TESTENV'] -try: - urllib.urlretrieve(pyqt_url, r'C:\install-PyQt5.exe') -except (OSError, IOError): - print("Downloading PyQt failed, trying again in 10 seconds...") - time.sleep(10) - urllib.urlretrieve(pyqt_url, r'C:\install-PyQt5.exe') -print("Installing PyQt5...") -subprocess.check_call([r'C:\install-PyQt5.exe', '/S']) +if not using_pypi: + print("Getting PyQt5...") + qt_version = '5.5.1' + pyqt_version = '5.5.1' + pyqt_url = ('https://www.qutebrowser.org/pyqt/' + 'PyQt5-{}-gpl-Py3.4-Qt{}-x32.exe'.format( + pyqt_version, qt_version)) + + try: + urllib.urlretrieve(pyqt_url, r'C:\install-PyQt5.exe') + except (OSError, IOError): + print("Downloading PyQt failed, trying again in 10 seconds...") + time.sleep(10) + urllib.urlretrieve(pyqt_url, r'C:\install-PyQt5.exe') + + print("Installing PyQt5...") + subprocess.check_call([r'C:\install-PyQt5.exe', '/S']) print("Installing tox") pip_install('pip') @@ -68,5 +73,10 @@ pip_install(r'-rmisc\requirements\requirements-tox.txt') print("Linking Python...") with open(r'C:\Windows\system32\python3.bat', 'w') as f: f.write(r'@C:\Python34\python %*') +with open(r'C:\Windows\system32\python3.5.bat', 'w') as f: + f.write(r'@C:\Python35\python %*') +with open(r'C:\Windows\system32\python3.6.bat', 'w') as f: + f.write(r'@C:\Python36\python %*') -check_setup(r'C:\Python34\python') +if not using_pypi: + check_setup(r'C:\Python34\python') From 16ecc043f73e52291696d49bd270d23993bcdb9e Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 8 Feb 2017 15:56:03 +0100 Subject: [PATCH 345/561] Run unittests with QUTE_BDD_WEBENGINE --- tests/conftest.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index bcc4d4fe2..8b040361d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -116,8 +116,6 @@ def pytest_collection_modifyitems(config, items): 'test_conftest.py'] if module_root_dir == 'end2end': item.add_marker(pytest.mark.end2end) - elif os.environ.get('QUTE_BDD_WEBENGINE', ''): - deselected = True _apply_platform_markers(item) if item.get_marker('xfail_norun'): From 601110761d72cae55c56fc9395c1469774ff3d32 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 8 Feb 2017 17:04:14 +0100 Subject: [PATCH 346/561] Fix test_history.test_init without QtWebKIt --- tests/unit/browser/webkit/test_history.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/unit/browser/webkit/test_history.py b/tests/unit/browser/webkit/test_history.py index 6f5cf6492..71e4d5bec 100644 --- a/tests/unit/browser/webkit/test_history.py +++ b/tests/unit/browser/webkit/test_history.py @@ -383,8 +383,10 @@ def test_history_interface(qtbot, webview, hist_interface): @pytest.mark.parametrize('backend', [usertypes.Backend.QtWebEngine, usertypes.Backend.QtWebKit]) def test_init(backend, qapp, tmpdir, monkeypatch, fake_save_manager): - if backend == 'webkit': + if backend == usertypes.Backend.QtWebKit: pytest.importorskip('PyQt5.QtWebKitWidgets') + else: + assert backend == usertypes.Backend.QtWebEngine monkeypatch.setattr(history.standarddir, 'data', lambda: str(tmpdir)) monkeypatch.setattr(history.objects, 'backend', backend) From c0c636c3eb25a6bf18597c03a5505545ef5b780f Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 9 Feb 2017 00:46:11 +0100 Subject: [PATCH 347/561] Revert OS X / Windows changes for PyPI packages --- .travis.yml | 11 -------- scripts/dev/ci/appveyor_install.py | 40 +++++++++++------------------- scripts/dev/ci/travis_install.sh | 5 ++-- 3 files changed, 17 insertions(+), 39 deletions(-) diff --git a/.travis.yml b/.travis.yml index a87be3d29..5991fea42 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,20 +21,9 @@ matrix: - os: linux env: DOCKER=ubuntu-xenial services: docker - - os: linux - language: python - python: 3.5 - env: TESTENV=py35-pyqt571 - - os: linux - language: python - python: 3.6 - env: TESTENV=py36-pyqt571 - os: osx env: TESTENV=py36 OSX=elcapitan osx_image: xcode7.3 - - os: osx - env: TESTENV=py36-pyqt571 OSX=elcapitan - osx_image: xcode7.3 # https://github.com/qutebrowser/qutebrowser/issues/2013 # - os: osx # env: TESTENV=py35 OSX=yosemite diff --git a/scripts/dev/ci/appveyor_install.py b/scripts/dev/ci/appveyor_install.py index ad63ee3a4..2c04304d5 100644 --- a/scripts/dev/ci/appveyor_install.py +++ b/scripts/dev/ci/appveyor_install.py @@ -28,7 +28,6 @@ CI machines. from __future__ import print_function -import os import time import subprocess import urllib @@ -45,26 +44,22 @@ def pip_install(pkg): pkg]) -using_pypi = '-pyqt' in os.environ['TESTENV'] +print("Getting PyQt5...") +qt_version = '5.5.1' +pyqt_version = '5.5.1' +pyqt_url = ('https://www.qutebrowser.org/pyqt/' + 'PyQt5-{}-gpl-Py3.4-Qt{}-x32.exe'.format( + pyqt_version, qt_version)) +try: + urllib.urlretrieve(pyqt_url, r'C:\install-PyQt5.exe') +except (OSError, IOError): + print("Downloading PyQt failed, trying again in 10 seconds...") + time.sleep(10) + urllib.urlretrieve(pyqt_url, r'C:\install-PyQt5.exe') -if not using_pypi: - print("Getting PyQt5...") - qt_version = '5.5.1' - pyqt_version = '5.5.1' - pyqt_url = ('https://www.qutebrowser.org/pyqt/' - 'PyQt5-{}-gpl-Py3.4-Qt{}-x32.exe'.format( - pyqt_version, qt_version)) - - try: - urllib.urlretrieve(pyqt_url, r'C:\install-PyQt5.exe') - except (OSError, IOError): - print("Downloading PyQt failed, trying again in 10 seconds...") - time.sleep(10) - urllib.urlretrieve(pyqt_url, r'C:\install-PyQt5.exe') - - print("Installing PyQt5...") - subprocess.check_call([r'C:\install-PyQt5.exe', '/S']) +print("Installing PyQt5...") +subprocess.check_call([r'C:\install-PyQt5.exe', '/S']) print("Installing tox") pip_install('pip') @@ -73,10 +68,5 @@ pip_install(r'-rmisc\requirements\requirements-tox.txt') print("Linking Python...") with open(r'C:\Windows\system32\python3.bat', 'w') as f: f.write(r'@C:\Python34\python %*') -with open(r'C:\Windows\system32\python3.5.bat', 'w') as f: - f.write(r'@C:\Python35\python %*') -with open(r'C:\Windows\system32\python3.6.bat', 'w') as f: - f.write(r'@C:\Python36\python %*') -if not using_pypi: - check_setup(r'C:\Python34\python') +check_setup(r'C:\Python34\python') diff --git a/scripts/dev/ci/travis_install.sh b/scripts/dev/ci/travis_install.sh index b0acb8dbc..deeb44d5c 100644 --- a/scripts/dev/ci/travis_install.sh +++ b/scripts/dev/ci/travis_install.sh @@ -93,13 +93,12 @@ elif [[ $TRAVIS_OS_NAME == osx ]]; then sudo -H python get-pip.py brew --version - - brew_install python3 - [[ $TESTENV != *-pyqt* ]] && brew_install qt5 pyqt5 && check_pyqt + brew_install python3 qt5 pyqt5 pip_install -r misc/requirements/requirements-tox.txt pip --version tox --version + check_pyqt exit 0 fi From 92c3ec643503186b57539a2d77ffe19fd6e416fd Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 9 Feb 2017 01:57:23 +0100 Subject: [PATCH 348/561] Remove PyPI envs from .appveyor.yml too --- .appveyor.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index 1f91531ac..0f970d98b 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -7,9 +7,6 @@ environment: PYTHONUNBUFFERED: 1 matrix: - TESTENV: py34 - - TESTENV: py35-pyqt56 - - TESTENV: py35-pyqt571 - - TESTENV: py36-pyqt571 - TESTENV: unittests-frozen - TESTENV: pylint From 828b0c00b5d3e4cb2dd716f08f0038bdda3a3507 Mon Sep 17 00:00:00 2001 From: Imran Sobir Date: Thu, 9 Feb 2017 15:39:50 +0500 Subject: [PATCH 349/561] Rename test_qutehistory.py to test_qutescheme.py. --- tests/unit/browser/{test_qutehistory.py => test_qutescheme.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/unit/browser/{test_qutehistory.py => test_qutescheme.py} (100%) diff --git a/tests/unit/browser/test_qutehistory.py b/tests/unit/browser/test_qutescheme.py similarity index 100% rename from tests/unit/browser/test_qutehistory.py rename to tests/unit/browser/test_qutescheme.py From 4eccfd5396f7ceef14059a919b8f02521834f6a5 Mon Sep 17 00:00:00 2001 From: Imran Sobir Date: Thu, 9 Feb 2017 15:44:51 +0500 Subject: [PATCH 350/561] Style fixes. --- tests/unit/browser/test_qutescheme.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/tests/unit/browser/test_qutescheme.py b/tests/unit/browser/test_qutescheme.py index 67c9a05f8..f97194415 100644 --- a/tests/unit/browser/test_qutescheme.py +++ b/tests/unit/browser/test_qutescheme.py @@ -17,20 +17,23 @@ # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see . +import datetime +import tempfile + from PyQt5.QtCore import QUrl +import pytest + from qutebrowser.browser import history, qutescheme from qutebrowser.utils import objreg -import datetime -import pytest -import tempfile class TestHistoryHandler: + """Test the qute://history endpoint.""" @pytest.fixture(autouse=True) def fake_objects(self, fake_save_manager): - """ Create fake web-history with history for three different days """ + """Create fake web-history with history for three different days.""" try: original_save_manager = objreg.get('save-manager') except KeyError: @@ -78,14 +81,14 @@ class TestHistoryHandler: objreg.delete('web-history') def test_history_without_query(self): - """ Test qute://history shows today's history when it has no query """ + """Ensure qute://history shows today's history when it has no query.""" _mimetype, data = qutescheme.qute_history(QUrl("qute://history")) key = "{}".format( datetime.date.today().strftime("%a, %d %B %Y")) assert key in data def test_history_with_bad_query(self): - """ Test qute://history shows today's history when given bad query """ + """Ensure qute://history shows today's history when given bad query.""" url = QUrl("qute://history?date=204-blaah") _mimetype, data = qutescheme.qute_history(url) key = "{}".format( @@ -93,7 +96,7 @@ class TestHistoryHandler: assert key in data def test_history_today(self): - """ Test qute://history shows history for today """ + """Ensure qute://history shows history for today.""" url = QUrl("qute://history") _mimetype, data = qutescheme.qute_history(url) assert "today" in data @@ -101,7 +104,7 @@ class TestHistoryHandler: assert "yesterday" not in data def test_history_yesterday(self): - """ Test qute://history shows history for yesterday """ + """Ensure qute://history shows history for yesterday.""" url = QUrl("qute://history?date=" + self.prev_date.strftime("%Y-%m-%d")) _mimetype, data = qutescheme.qute_history(url) assert "today" not in data @@ -109,7 +112,7 @@ class TestHistoryHandler: assert "yesterday" in data def test_history_tomorrow(self): - """ Test qute://history shows history for tomorrow """ + """Ensure qute://history shows history for tomorrow.""" url = QUrl("qute://history?date=" + self.next_date.strftime("%Y-%m-%d")) _mimetype, data = qutescheme.qute_history(url) assert "today" not in data From 49271b7ce1328da15c1785f82ac46c2b27a71bc7 Mon Sep 17 00:00:00 2001 From: Imran Sobir Date: Thu, 9 Feb 2017 16:32:59 +0500 Subject: [PATCH 351/561] Generate and cleanup fake web-history in own fixture. --- tests/unit/browser/test_qutescheme.py | 49 +++++++++------------------ 1 file changed, 16 insertions(+), 33 deletions(-) diff --git a/tests/unit/browser/test_qutescheme.py b/tests/unit/browser/test_qutescheme.py index f97194415..c2b332a4a 100644 --- a/tests/unit/browser/test_qutescheme.py +++ b/tests/unit/browser/test_qutescheme.py @@ -31,25 +31,20 @@ class TestHistoryHandler: """Test the qute://history endpoint.""" - @pytest.fixture(autouse=True) - def fake_objects(self, fake_save_manager): - """Create fake web-history with history for three different days.""" - try: - original_save_manager = objreg.get('save-manager') - except KeyError: - original_save_manager = None - - try: - original_web_history = objreg.get('web-history') - except KeyError: - original_web_history = None - + @pytest.fixture + def fake_web_history(self, fake_save_manager): + """Create a fake web-history and register it into objreg.""" temp_dir = tempfile.TemporaryDirectory() fake_web_history = history.WebHistory(temp_dir.name, 'fake-history') - objreg.register('web-history', fake_web_history, update=True) - objreg.register('save-manager', fake_save_manager, update=True) + objreg.register('web-history', fake_web_history) - # Add fake history items for different days + yield fake_web_history + + objreg.delete('web-history') + + @pytest.fixture(autouse=True) + def generate_fake_history(self, fake_web_history): + """Create fake history for three different days.""" one_day = datetime.timedelta(days=1) self.curr_date = datetime.datetime.today() self.next_date = self.curr_date + one_day @@ -62,23 +57,11 @@ class TestHistoryHandler: yesterday = history.Entry(atime=str(self.prev_date.timestamp()), url=QUrl('www.yesterday.com'), title='yesterday') - web_history = objreg.get('web-history') - web_history._add_entry(today) - web_history._add_entry(tomorrow) - web_history._add_entry(yesterday) - web_history.save() - - yield - - if original_save_manager: - objreg.register('save-manager', original_save_manager, update=True) - else: - objreg.delete('save-manager') - - if original_web_history: - objreg.register('web-history', original_web_history, update=True) - else: - objreg.delete('web-history') + fake_web_history = objreg.get('web-history') + fake_web_history._add_entry(today) + fake_web_history._add_entry(tomorrow) + fake_web_history._add_entry(yesterday) + fake_web_history.save() def test_history_without_query(self): """Ensure qute://history shows today's history when it has no query.""" From 920fb8137716169070dcde7a80c51a1d17db954f Mon Sep 17 00:00:00 2001 From: Imran Sobir Date: Thu, 9 Feb 2017 16:39:21 +0500 Subject: [PATCH 352/561] Use pytest's tmpdir fixture. --- tests/unit/browser/test_qutescheme.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/unit/browser/test_qutescheme.py b/tests/unit/browser/test_qutescheme.py index c2b332a4a..08a3eaf1b 100644 --- a/tests/unit/browser/test_qutescheme.py +++ b/tests/unit/browser/test_qutescheme.py @@ -18,7 +18,6 @@ # along with qutebrowser. If not, see . import datetime -import tempfile from PyQt5.QtCore import QUrl import pytest @@ -32,10 +31,9 @@ class TestHistoryHandler: """Test the qute://history endpoint.""" @pytest.fixture - def fake_web_history(self, fake_save_manager): + def fake_web_history(self, fake_save_manager, tmpdir): """Create a fake web-history and register it into objreg.""" - temp_dir = tempfile.TemporaryDirectory() - fake_web_history = history.WebHistory(temp_dir.name, 'fake-history') + fake_web_history = history.WebHistory(tmpdir.dirname, 'fake-history') objreg.register('web-history', fake_web_history) yield fake_web_history From a15aa9eade99d74d4b47a8c473c331e13359322c Mon Sep 17 00:00:00 2001 From: Imran Sobir Date: Thu, 9 Feb 2017 17:18:57 +0500 Subject: [PATCH 353/561] Hide next links to future. --- qutebrowser/browser/qutescheme.py | 7 ++++--- qutebrowser/html/history.html | 8 +++++--- tests/unit/browser/test_qutescheme.py | 10 ++++++++++ 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/qutebrowser/browser/qutescheme.py b/qutebrowser/browser/qutescheme.py index 1b51f405b..602dce336 100644 --- a/qutebrowser/browser/qutescheme.py +++ b/qutebrowser/browser/qutescheme.py @@ -207,9 +207,10 @@ def qute_history(url): html = jinja.render('history.html', title='History', history=history, - curr_date=curr_date.strftime("%a, %d %B %Y"), - next_date=next_date.strftime("%Y-%m-%d"), - prev_date=prev_date.strftime("%Y-%m-%d")) + curr_date=curr_date, + next_date=next_date, + prev_date=prev_date, + today=datetime.date.today()) return 'text/html', html diff --git a/qutebrowser/html/history.html b/qutebrowser/html/history.html index 35bb79b39..e117d7e80 100644 --- a/qutebrowser/html/history.html +++ b/qutebrowser/html/history.html @@ -70,7 +70,7 @@ tr:nth-child(odd) { {% block content %} -

Browsing history {{curr_date}}

+

Browsing history {{curr_date.strftime("%a, %d %B %Y")}}

@@ -83,7 +83,9 @@ tr:nth-child(odd) {
-Previous -Next +Previous +{% if today >= next_date %} +Next +{% endif %} {% endblock %} diff --git a/tests/unit/browser/test_qutescheme.py b/tests/unit/browser/test_qutescheme.py index 08a3eaf1b..5db896c83 100644 --- a/tests/unit/browser/test_qutescheme.py +++ b/tests/unit/browser/test_qutescheme.py @@ -99,3 +99,13 @@ class TestHistoryHandler: assert "today" not in data assert "tomorrow" in data assert "yesterday" not in data + + def test_no_next_link_to_future(self): + """Ensure there's no next link pointing to the future.""" + url = QUrl("qute://history") + _mimetype, data = qutescheme.qute_history(url) + assert "Next" not in data + + url = QUrl("qute://history?date=" + self.next_date.strftime("%Y-%m-%d")) + _mimetype, data = qutescheme.qute_history(url) + assert "Next" not in data From 85d8d45e197d99ca95310270eba6633b7e550de9 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 9 Feb 2017 13:19:42 +0100 Subject: [PATCH 354/561] Ask for confirmation with :history-clear --- CHANGELOG.asciidoc | 1 + doc/help/commands.asciidoc | 5 +++++ qutebrowser/browser/history.py | 14 ++++++++++++-- tests/end2end/features/history.feature | 9 ++++++++- 4 files changed, 26 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 924d2bbe1..ff1a4aa52 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -40,6 +40,7 @@ Changed - When yanking a mailto: link via hints, the mailto: prefix is now stripped - Zoom level messages are now not stacked on top of each other anymore. - qutebrowser now automatically uses QtWebEngine if QtWebKit is unavailable +- :history-clear now asks for a confirmation, unless it's run with --force. Fixed ----- diff --git a/doc/help/commands.asciidoc b/doc/help/commands.asciidoc index d6db9f350..03345ac64 100644 --- a/doc/help/commands.asciidoc +++ b/doc/help/commands.asciidoc @@ -420,10 +420,15 @@ Start hinting. [[history-clear]] === history-clear +Syntax: +:history-clear [*--force*]+ + Clear all browsing history. Note this only clears the global history (e.g. `~/.local/share/qutebrowser/history` on Linux) but not cookies, the back/forward history of a tab, cache or other persistent data. +==== optional arguments +* +*-f*+, +*--force*+: Don't ask for confirmation. + [[home]] === home Open main startpage in current tab. diff --git a/qutebrowser/browser/history.py b/qutebrowser/browser/history.py index e4288f891..2615fd393 100644 --- a/qutebrowser/browser/history.py +++ b/qutebrowser/browser/history.py @@ -26,7 +26,7 @@ from PyQt5.QtCore import pyqtSignal, pyqtSlot, QUrl, QObject from qutebrowser.commands import cmdutils from qutebrowser.utils import (utils, objreg, standarddir, log, qtutils, - usertypes) + usertypes, message) from qutebrowser.config import config from qutebrowser.misc import lineparser, objects @@ -230,13 +230,23 @@ class WebHistory(QObject): self._saved_count = len(self._new_history) @cmdutils.register(name='history-clear', instance='web-history') - def clear(self): + def clear(self, force=False): """Clear all browsing history. Note this only clears the global history (e.g. `~/.local/share/qutebrowser/history` on Linux) but not cookies, the back/forward history of a tab, cache or other persistent data. + + Args: + force: Don't ask for confirmation. """ + if force: + self._do_clear() + else: + message.confirm_async(self._do_clear, title="Clear all browsing " + "history?") + + def _do_clear(self): self._lineparser.clear() self.history_dict.clear() self._temp_history.clear() diff --git a/tests/end2end/features/history.feature b/tests/end2end/features/history.feature index 3c35ad03f..decd71dd9 100644 --- a/tests/end2end/features/history.feature +++ b/tests/end2end/features/history.feature @@ -4,7 +4,7 @@ Feature: Page history Background: Given I open about:blank - And I run :history-clear + And I run :history-clear --force Scenario: Simple history saving When I open data/numbers/1.txt @@ -56,8 +56,15 @@ Feature: Page history Then "Changing title for idx 1 to 'about:blank'" should be logged Scenario: Clearing history + When I open data/title.html + And I run :history-clear --force + Then the history file should be empty + + Scenario: Clearing history with confirmation When I open data/title.html And I run :history-clear + And I wait for "Asking question <* title='Clear all browsing history?'>, *" in the log + And I run :prompt-accept yes Then the history file should be empty Scenario: History with yanked URL and 'add to history' flag From 97982df1cbe51546433b26b95d717d73a37b72a6 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 9 Feb 2017 13:40:44 +0100 Subject: [PATCH 355/561] Adjust history unittest --- tests/unit/browser/webkit/test_history.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/browser/webkit/test_history.py b/tests/unit/browser/webkit/test_history.py index 71e4d5bec..cc35dc4e4 100644 --- a/tests/unit/browser/webkit/test_history.py +++ b/tests/unit/browser/webkit/test_history.py @@ -220,7 +220,7 @@ def test_clear(qtbot, hist, tmpdir): hist.add_url(QUrl('http://www.qutebrowser.org/')) with qtbot.waitSignal(hist.cleared): - hist.clear() + hist._do_clear() assert not hist_file.read() assert not hist.history_dict From ccf4966eac463b6191b6f8b0827f0ad14d9dc6a4 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 9 Feb 2017 13:43:16 +0100 Subject: [PATCH 356/561] travis: Re-add accidentally removed PyPI PyQt builds --- .travis.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.travis.yml b/.travis.yml index 5991fea42..82524fcd0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,6 +21,14 @@ matrix: - os: linux env: DOCKER=ubuntu-xenial services: docker + - os: linux + language: python + python: 3.5 + env: TESTENV=py35-pyqt571 + - os: linux + language: python + python: 3.6 + env: TESTENV=py36-pyqt571 - os: osx env: TESTENV=py36 OSX=elcapitan osx_image: xcode7.3 From d21585f603a1728536fa2449b7d47270a2b86fb6 Mon Sep 17 00:00:00 2001 From: Imran Sobir Date: Thu, 9 Feb 2017 18:11:17 +0500 Subject: [PATCH 357/561] Add rel=prev, rel=next to pagination links. --- qutebrowser/html/history.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qutebrowser/html/history.html b/qutebrowser/html/history.html index e117d7e80..42c59efff 100644 --- a/qutebrowser/html/history.html +++ b/qutebrowser/html/history.html @@ -83,9 +83,9 @@ tr:nth-child(odd) { -Previous + {% if today >= next_date %} -Next + {% endif %} {% endblock %} From 100f90d9b3edd064ff4d14ee778b08e2971f2fe1 Mon Sep 17 00:00:00 2001 From: Imran Sobir Date: Thu, 9 Feb 2017 18:28:10 +0500 Subject: [PATCH 358/561] Fix pylint errors. --- tests/unit/browser/test_qutescheme.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/tests/unit/browser/test_qutescheme.py b/tests/unit/browser/test_qutescheme.py index 5db896c83..eb1ffb182 100644 --- a/tests/unit/browser/test_qutescheme.py +++ b/tests/unit/browser/test_qutescheme.py @@ -43,6 +43,7 @@ class TestHistoryHandler: @pytest.fixture(autouse=True) def generate_fake_history(self, fake_web_history): """Create fake history for three different days.""" + # pylint: disable=attribute-defined-outside-init one_day = datetime.timedelta(days=1) self.curr_date = datetime.datetime.today() self.next_date = self.curr_date + one_day @@ -62,14 +63,14 @@ class TestHistoryHandler: fake_web_history.save() def test_history_without_query(self): - """Ensure qute://history shows today's history when it has no query.""" + """Ensure qute://history shows today's history without any query.""" _mimetype, data = qutescheme.qute_history(QUrl("qute://history")) key = "{}".format( datetime.date.today().strftime("%a, %d %B %Y")) assert key in data def test_history_with_bad_query(self): - """Ensure qute://history shows today's history when given bad query.""" + """Ensure qute://history shows today's history with bad query.""" url = QUrl("qute://history?date=204-blaah") _mimetype, data = qutescheme.qute_history(url) key = "{}".format( @@ -86,7 +87,8 @@ class TestHistoryHandler: def test_history_yesterday(self): """Ensure qute://history shows history for yesterday.""" - url = QUrl("qute://history?date=" + self.prev_date.strftime("%Y-%m-%d")) + url = QUrl("qute://history?date=" + + self.prev_date.strftime("%Y-%m-%d")) _mimetype, data = qutescheme.qute_history(url) assert "today" not in data assert "tomorrow" not in data @@ -94,7 +96,8 @@ class TestHistoryHandler: def test_history_tomorrow(self): """Ensure qute://history shows history for tomorrow.""" - url = QUrl("qute://history?date=" + self.next_date.strftime("%Y-%m-%d")) + url = QUrl("qute://history?date=" + + self.next_date.strftime("%Y-%m-%d")) _mimetype, data = qutescheme.qute_history(url) assert "today" not in data assert "tomorrow" in data @@ -106,6 +109,7 @@ class TestHistoryHandler: _mimetype, data = qutescheme.qute_history(url) assert "Next" not in data - url = QUrl("qute://history?date=" + self.next_date.strftime("%Y-%m-%d")) + url = QUrl("qute://history?date=" + + self.next_date.strftime("%Y-%m-%d")) _mimetype, data = qutescheme.qute_history(url) assert "Next" not in data From 9001ec079c8f8923daea0122eb35bd719892bc10 Mon Sep 17 00:00:00 2001 From: Imran Sobir Date: Thu, 9 Feb 2017 18:37:33 +0500 Subject: [PATCH 359/561] Align history time to the right. --- qutebrowser/html/history.html | 1 + 1 file changed, 1 insertion(+) diff --git a/qutebrowser/html/history.html b/qutebrowser/html/history.html index 42c59efff..4135fc627 100644 --- a/qutebrowser/html/history.html +++ b/qutebrowser/html/history.html @@ -32,6 +32,7 @@ td.title { td.time { color: #555; + text-align: right; white-space: nowrap; } From 98e1603abb88fca834ff19d3d44cb8be52ab6588 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 9 Feb 2017 15:19:26 +0100 Subject: [PATCH 360/561] Don't make SSL resolve errors fail tests --- pytest.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/pytest.ini b/pytest.ini index d103cb3b8..bd57e7854 100644 --- a/pytest.ini +++ b/pytest.ini @@ -50,4 +50,5 @@ qt_log_ignore = ^Error when parsing the netrc file ^Image of format '' blocked because it is not considered safe. If you are sure it is safe to do so, you can white-list the format by setting the environment variable QTWEBKIT_IMAGEFORMAT_WHITELIST= ^QPainter::end: Painter ended with \d+ saved states + ^QSslSocket: cannot resolve SSLv[23]_(client|server)_method xfail_strict = true From 6e85b738972e589866bb161c4d3d5b73b6b0109f Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 9 Feb 2017 23:58:06 +0100 Subject: [PATCH 361/561] travis: Use archlinux-webengine image for QtWebEngine --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 82524fcd0..731c7cfec 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,7 +13,7 @@ matrix: env: DOCKER=archlinux services: docker - os: linux - env: DOCKER=archlinux QUTE_BDD_WEBENGINE=true + env: DOCKER=archlinux-webengine QUTE_BDD_WEBENGINE=true services: docker - os: linux env: DOCKER=archlinux-ng From 46c02bf5ae2330cb9717f86d062e689b5509e2d0 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 9 Feb 2017 18:27:32 +0100 Subject: [PATCH 362/561] Refactor qute:history unittests --- tests/unit/browser/test_qutescheme.py | 64 +++++++++++++++------------ 1 file changed, 35 insertions(+), 29 deletions(-) diff --git a/tests/unit/browser/test_qutescheme.py b/tests/unit/browser/test_qutescheme.py index eb1ffb182..f210c2e56 100644 --- a/tests/unit/browser/test_qutescheme.py +++ b/tests/unit/browser/test_qutescheme.py @@ -18,6 +18,7 @@ # along with qutebrowser. If not, see . import datetime +import collections from PyQt5.QtCore import QUrl import pytest @@ -26,40 +27,45 @@ from qutebrowser.browser import history, qutescheme from qutebrowser.utils import objreg +Dates = collections.namedtuple('Dates', ['yesterday', 'today', 'tomorrow']) + + class TestHistoryHandler: """Test the qute://history endpoint.""" + @pytest.fixture + def dates(self): + one_day = datetime.timedelta(days=1) + today = datetime.datetime.today() + tomorrow = today + one_day + yesterday = today - one_day + return Dates(yesterday, today, tomorrow) + + @pytest.fixture + def entries(self, dates): + today = history.Entry(atime=str(dates.today.timestamp()), + url=QUrl('www.today.com'), title='today') + tomorrow = history.Entry(atime=str(dates.tomorrow.timestamp()), + url=QUrl('www.tomorrow.com'), title='tomorrow') + yesterday = history.Entry(atime=str(dates.yesterday.timestamp()), + url=QUrl('www.yesterday.com'), title='yesterday') + return Dates(yesterday, today, tomorrow) + @pytest.fixture def fake_web_history(self, fake_save_manager, tmpdir): """Create a fake web-history and register it into objreg.""" - fake_web_history = history.WebHistory(tmpdir.dirname, 'fake-history') - objreg.register('web-history', fake_web_history) - - yield fake_web_history - + web_history = history.WebHistory(tmpdir.dirname, 'fake-history') + objreg.register('web-history', web_history) + yield web_history objreg.delete('web-history') @pytest.fixture(autouse=True) - def generate_fake_history(self, fake_web_history): + def fake_history(self, fake_web_history, entries): """Create fake history for three different days.""" - # pylint: disable=attribute-defined-outside-init - one_day = datetime.timedelta(days=1) - self.curr_date = datetime.datetime.today() - self.next_date = self.curr_date + one_day - self.prev_date = self.curr_date - one_day - - today = history.Entry(atime=str(self.curr_date.timestamp()), - url=QUrl('www.today.com'), title='today') - tomorrow = history.Entry(atime=str(self.next_date.timestamp()), - url=QUrl('www.tomorrow.com'), title='tomorrow') - yesterday = history.Entry(atime=str(self.prev_date.timestamp()), - url=QUrl('www.yesterday.com'), title='yesterday') - - fake_web_history = objreg.get('web-history') - fake_web_history._add_entry(today) - fake_web_history._add_entry(tomorrow) - fake_web_history._add_entry(yesterday) + fake_web_history._add_entry(entries.yesterday) + fake_web_history._add_entry(entries.today) + fake_web_history._add_entry(entries.tomorrow) fake_web_history.save() def test_history_without_query(self): @@ -85,31 +91,31 @@ class TestHistoryHandler: assert "tomorrow" not in data assert "yesterday" not in data - def test_history_yesterday(self): + def test_history_yesterday(self, dates): """Ensure qute://history shows history for yesterday.""" url = QUrl("qute://history?date=" + - self.prev_date.strftime("%Y-%m-%d")) + dates.yesterday.strftime("%Y-%m-%d")) _mimetype, data = qutescheme.qute_history(url) assert "today" not in data assert "tomorrow" not in data assert "yesterday" in data - def test_history_tomorrow(self): + def test_history_tomorrow(self, dates): """Ensure qute://history shows history for tomorrow.""" url = QUrl("qute://history?date=" + - self.next_date.strftime("%Y-%m-%d")) + dates.tomorrow.strftime("%Y-%m-%d")) _mimetype, data = qutescheme.qute_history(url) assert "today" not in data assert "tomorrow" in data assert "yesterday" not in data - def test_no_next_link_to_future(self): + def test_no_next_link_to_future(self, dates): """Ensure there's no next link pointing to the future.""" url = QUrl("qute://history") _mimetype, data = qutescheme.qute_history(url) assert "Next" not in data url = QUrl("qute://history?date=" + - self.next_date.strftime("%Y-%m-%d")) + dates.tomorrow.strftime("%Y-%m-%d")) _mimetype, data = qutescheme.qute_history(url) assert "Next" not in data From f063d4be6fcb0ac7cc9e5590321414efd063b43d Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 9 Feb 2017 21:56:06 +0100 Subject: [PATCH 363/561] Add a benchmark for qute:history --- misc/requirements/requirements-tests.txt | 1 + misc/requirements/requirements-tests.txt-raw | 1 + tests/unit/browser/test_qutescheme.py | 18 ++++++++++++++++++ 3 files changed, 20 insertions(+) diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index 1fb75ae0c..703faa2ec 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -19,6 +19,7 @@ parse-type==0.3.4 py==1.4.32 pytest==3.0.6 pytest-bdd==2.18.1 +pytest-benchmark==3.0.0 pytest-catchlog==1.2.2 pytest-cov==2.4.0 pytest-faulthandler==1.3.1 diff --git a/misc/requirements/requirements-tests.txt-raw b/misc/requirements/requirements-tests.txt-raw index 619095ad8..d0f3bec52 100644 --- a/misc/requirements/requirements-tests.txt-raw +++ b/misc/requirements/requirements-tests.txt-raw @@ -6,6 +6,7 @@ httpbin hypothesis pytest pytest-bdd +pytest-benchmark pytest-catchlog pytest-cov pytest-faulthandler diff --git a/tests/unit/browser/test_qutescheme.py b/tests/unit/browser/test_qutescheme.py index f210c2e56..5ceecb7f8 100644 --- a/tests/unit/browser/test_qutescheme.py +++ b/tests/unit/browser/test_qutescheme.py @@ -119,3 +119,21 @@ class TestHistoryHandler: dates.tomorrow.strftime("%Y-%m-%d")) _mimetype, data = qutescheme.qute_history(url) assert "Next" not in data + + def test_qute_history_benchmark(self, dates, entries, fake_web_history, + benchmark): + for i in range(100000): + entry = history.Entry( + atime=str(dates.yesterday.timestamp()), + url=QUrl('www.yesterday.com/{}'.format(i)), + title='yesterday') + fake_web_history._add_entry(entry) + fake_web_history._add_entry(entries.today) + fake_web_history._add_entry(entries.tomorrow) + + url = QUrl("qute://history") + _mimetype, data = benchmark(qutescheme.qute_history, url) + + assert "today" in data + assert "tomorrow" not in data + assert "yesterday" not in data From 46752a2c24d2a27ea0a9b627d18de6a4c1cd4c71 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 9 Feb 2017 21:56:25 +0100 Subject: [PATCH 364/561] Add some performance improvements for qute:history --- qutebrowser/browser/qutescheme.py | 35 ++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/qutebrowser/browser/qutescheme.py b/qutebrowser/browser/qutescheme.py index 602dce336..da4cbfff4 100644 --- a/qutebrowser/browser/qutescheme.py +++ b/qutebrowser/browser/qutescheme.py @@ -24,6 +24,8 @@ Module attributes: _HANDLERS: The handlers registered via decorators. """ +import sys +import time import datetime import urllib.parse @@ -178,8 +180,20 @@ def qute_history(url): next_date = curr_date + one_day prev_date = curr_date - one_day - def history_iter(): - for item in objreg.get('web-history').history_dict.values(): + def history_iter(reverse): + today_timestamp = time.mktime(datetime.date.today().timetuple()) + history = objreg.get('web-history').history_dict.values() + if reverse: + history = reversed(history) + + for item in history: + # If we can't apply the reverse performance trick below, + # at least continue as early as possible with old items. + # This gets us down from 550ms to 123ms with 500k old items on my + # machine. + if item.atime < today_timestamp and not reverse: + continue + # Convert timestamp try: item_atime = datetime.datetime.fromtimestamp(item.atime) @@ -187,6 +201,12 @@ def qute_history(url): log.misc.debug("Invalid timestamp {}.".format(item.atime)) continue + if reverse and item_atime.date() < curr_date: + # If we could reverse the history in-place, and this entry is + # older than today, only older entries will follow, so we can + # abort here. + return + # Skip items not on curr_date # Skip redirects # Skip qute:// links @@ -202,7 +222,16 @@ def qute_history(url): yield (item_url, item_title, display_atime) - history = reversed(list(history_iter())) + if sys.hexversion >= 0x03050000: + # On Python >= 3.5 we can reverse the ordereddict in-place and thus + # apply an additional performance improvement in history_iter. + # On my machine, this gets us down from 550ms to 72us with 500k old + # items. + history = list(history_iter(reverse=True)) + else: + # On Python 3.4, we can't do that, so we'd need to copy the entire + # history to a list. There, filter first and then reverse it here. + history = reversed(list(history_iter(reverse=False))) html = jinja.render('history.html', title='History', From 37c3b79b9b17e39c064c9fac18aa78411d37da23 Mon Sep 17 00:00:00 2001 From: Imran Sobir Date: Fri, 10 Feb 2017 17:47:20 +0500 Subject: [PATCH 365/561] Add :history command. --- qutebrowser/browser/commands.py | 13 +++++++++++++ tests/end2end/features/misc.feature | 18 ++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 95dd0e4f2..342cc7a2c 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -1396,6 +1396,19 @@ class CommandDispatcher: tab.dump_async(callback, plain=plain) + @cmdutils.register(instance='command-dispatcher', name='history', + scope='window') + def show_history(self, tab=True, bg=False, window=False): + r"""Show browsing history + + Args: + tab: Open in a new tab. + bg: Open in a background tab. + window: Open in a new window. + """ + url = QUrl('qute://history/') + self._open(url, tab, bg, window) + @cmdutils.register(instance='command-dispatcher', name='help', scope='window') @cmdutils.argument('topic', completion=usertypes.Completion.helptopic) diff --git a/tests/end2end/features/misc.feature b/tests/end2end/features/misc.feature index 168ea98ca..ca45055b5 100644 --- a/tests/end2end/features/misc.feature +++ b/tests/end2end/features/misc.feature @@ -290,6 +290,24 @@ Feature: Various utility commands. - about:blank - qute://help/index.html (active) + # :history + + Scenario: :history without arguments + When I run :tab-only + And I run :history + And I wait until qute://history/ is loaded + Then the following tabs should be open: + - qute://history/ (active) + + Scenario: :history with -t + When I open about:blank + And I run :tab-only + And I run :history -t + And I wait until qute://history/ is loaded + Then the following tabs should be open: + - about:blank + - qute://history/ (active) + # :home Scenario: :home with single page From b15ae974447efc5f202911c0baa4ada2cd05b212 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 11 Feb 2017 16:59:51 +0100 Subject: [PATCH 366/561] Get timestamp of curr_date instead of today --- qutebrowser/browser/qutescheme.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qutebrowser/browser/qutescheme.py b/qutebrowser/browser/qutescheme.py index da4cbfff4..d06f2ddf9 100644 --- a/qutebrowser/browser/qutescheme.py +++ b/qutebrowser/browser/qutescheme.py @@ -181,7 +181,7 @@ def qute_history(url): prev_date = curr_date - one_day def history_iter(reverse): - today_timestamp = time.mktime(datetime.date.today().timetuple()) + curr_timestamp = time.mktime(curr_date.timetuple()) history = objreg.get('web-history').history_dict.values() if reverse: history = reversed(history) @@ -191,7 +191,7 @@ def qute_history(url): # at least continue as early as possible with old items. # This gets us down from 550ms to 123ms with 500k old items on my # machine. - if item.atime < today_timestamp and not reverse: + if item.atime < curr_timestamp and not reverse: continue # Convert timestamp From 9c712929f508aca006aeae8a8b68910650506e89 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 11 Feb 2017 17:08:09 +0100 Subject: [PATCH 367/561] Update docs --- CHANGELOG.asciidoc | 1 + README.asciidoc | 1 + doc/help/commands.asciidoc | 12 ++++++++++++ 3 files changed, 14 insertions(+) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index ff1a4aa52..df0b28ad9 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -31,6 +31,7 @@ Added - Support for the HTML5 fullscreen API (e.g. youtube videos) with QtWebEngine - Support for the `general -> print-element-backgrounds` option with QtWebEngine on Qt >= 5.8 - Support for `:download --mhtml` with QtWebEngine +- New `qute:history` URL and `:history` command to show the browsing history. Changed ~~~~~~~ diff --git a/README.asciidoc b/README.asciidoc index d25d02a61..dc3b427df 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -169,6 +169,7 @@ Contributors, sorted by the number of commits in descending order: * Artur Shaik * Nathan Isom * Thorsten Wißmann +* Imran Sobir * Austin Anderson * Fritz Reichwald * Jimmy diff --git a/doc/help/commands.asciidoc b/doc/help/commands.asciidoc index 03345ac64..0731ea381 100644 --- a/doc/help/commands.asciidoc +++ b/doc/help/commands.asciidoc @@ -44,6 +44,7 @@ It is possible to run or bind multiple commands by separating them with `;;`. |<>|Toggle fullscreen mode. |<>|Show help about a command or setting. |<>|Start hinting. +|<>|Show browsing history |<>|Clear all browsing history. |<>|Open main startpage in current tab. |<>|Insert text at cursor position. @@ -418,6 +419,17 @@ Start hinting. ==== note * This command does not split arguments after the last argument and handles quotes literally. +[[history]] +=== history +Syntax: +:history [*--tab*] [*--bg*] [*--window*]+ + +Show browsing history + +==== optional arguments +* +*-t*+, +*--tab*+: Open in a new tab. +* +*-b*+, +*--bg*+: Open in a background tab. +* +*-w*+, +*--window*+: Open in a new window. + [[history-clear]] === history-clear Syntax: +:history-clear [*--force*]+ From 66cbb8aa318bd7399cab6b0886887e454610b4f6 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 11 Feb 2017 17:22:35 +0100 Subject: [PATCH 368/561] Fix lint --- qutebrowser/browser/commands.py | 7 +++---- qutebrowser/browser/qutescheme.py | 3 ++- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 489627387..5013d6730 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -1408,10 +1408,9 @@ class CommandDispatcher: tab.dump_async(callback, plain=plain) - @cmdutils.register(instance='command-dispatcher', name='history', - scope='window') - def show_history(self, tab=True, bg=False, window=False): - r"""Show browsing history + @cmdutils.register(instance='command-dispatcher', scope='window') + def history(self, tab=True, bg=False, window=False): + """Show browsing history. Args: tab: Open in a new tab. diff --git a/qutebrowser/browser/qutescheme.py b/qutebrowser/browser/qutescheme.py index d06f2ddf9..ac2abb7e4 100644 --- a/qutebrowser/browser/qutescheme.py +++ b/qutebrowser/browser/qutescheme.py @@ -163,7 +163,7 @@ def qute_bookmarks(_url): return 'text/html', html -@add_handler('history') +@add_handler('history') # noqa def qute_history(url): """Handler for qute:history. Display history.""" # Get current date from query parameter, if not given choose today. @@ -181,6 +181,7 @@ def qute_history(url): prev_date = curr_date - one_day def history_iter(reverse): + """Iterate through the history and get the items we're interested in.""" curr_timestamp = time.mktime(curr_date.timetuple()) history = objreg.get('web-history').history_dict.values() if reverse: From bd8ead2279c66526554909df5f6ce848ab496a23 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 11 Feb 2017 20:48:01 +0100 Subject: [PATCH 369/561] Detect rel-attributes on elements for :navigate --- CHANGELOG.asciidoc | 1 + qutebrowser/browser/navigate.py | 2 +- tests/end2end/data/navigate/rel.html | 12 ++++++++++++ tests/end2end/features/navigate.feature | 10 ++++++++++ 4 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 tests/end2end/data/navigate/rel.html diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index df0b28ad9..5deca80d4 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -65,6 +65,7 @@ Fixed - Fixed showing of keybindings in the :help completion - Worked around a segfault when opening a URL after a QtWebEngine renderer process crash - Using :undo or :tab-clone with a view-source:// or chrome:// tab is now prevented, as it segfaults +- `:navigate prev/next` now detects `rel` attributes on `` elements v0.9.1 ------ diff --git a/qutebrowser/browser/navigate.py b/qutebrowser/browser/navigate.py index 395d4e166..5abfbe0b4 100644 --- a/qutebrowser/browser/navigate.py +++ b/qutebrowser/browser/navigate.py @@ -72,7 +72,7 @@ def _find_prevnext(prev, elems): # First check for rel_values = ('prev', 'previous') if prev else ('next') for e in elems: - if e.tag_name() != 'link' or 'rel' not in e: + if e.tag_name() not in ['link', 'a'] or 'rel' not in e: continue if e['rel'] in rel_values: log.hints.debug("Found {!r} with rel={}".format(e, e['rel'])) diff --git a/tests/end2end/data/navigate/rel.html b/tests/end2end/data/navigate/rel.html new file mode 100644 index 000000000..48a5895b8 --- /dev/null +++ b/tests/end2end/data/navigate/rel.html @@ -0,0 +1,12 @@ + + + + + Navigate + + +

Index page

+
+ + + diff --git a/tests/end2end/features/navigate.feature b/tests/end2end/features/navigate.feature index 956229f09..4b6b7f82e 100644 --- a/tests/end2end/features/navigate.feature +++ b/tests/end2end/features/navigate.feature @@ -43,6 +43,16 @@ Feature: Using :navigate And I run :navigate next Then data/navigate/next.html should be loaded + Scenario: Navigating to previous page with rel + When I open data/navigate/rel.html + And I run :navigate prev + Then data/navigate/prev.html should be loaded + + Scenario: Navigating to next page with rel + When I open data/navigate/rel.html + And I run :navigate next + Then data/navigate/next.html should be loaded + # increment/decrement Scenario: Incrementing number in URL From b73cda22de42558d6e533f0695351535898930cc Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 11 Feb 2017 21:17:16 +0100 Subject: [PATCH 370/561] Fix lint --- doc/help/commands.asciidoc | 4 ++-- qutebrowser/browser/qutescheme.py | 2 +- qutebrowser/browser/webengine/webenginetab.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/help/commands.asciidoc b/doc/help/commands.asciidoc index 0731ea381..1885d227a 100644 --- a/doc/help/commands.asciidoc +++ b/doc/help/commands.asciidoc @@ -44,7 +44,7 @@ It is possible to run or bind multiple commands by separating them with `;;`. |<>|Toggle fullscreen mode. |<>|Show help about a command or setting. |<>|Start hinting. -|<>|Show browsing history +|<>|Show browsing history. |<>|Clear all browsing history. |<>|Open main startpage in current tab. |<>|Insert text at cursor position. @@ -423,7 +423,7 @@ Start hinting. === history Syntax: +:history [*--tab*] [*--bg*] [*--window*]+ -Show browsing history +Show browsing history. ==== optional arguments * +*-t*+, +*--tab*+: Open in a new tab. diff --git a/qutebrowser/browser/qutescheme.py b/qutebrowser/browser/qutescheme.py index ac2abb7e4..018582abf 100644 --- a/qutebrowser/browser/qutescheme.py +++ b/qutebrowser/browser/qutescheme.py @@ -181,7 +181,7 @@ def qute_history(url): prev_date = curr_date - one_day def history_iter(reverse): - """Iterate through the history and get the items we're interested in.""" + """Iterate through the history and get items we're interested in.""" curr_timestamp = time.mktime(curr_date.timetuple()) history = objreg.get('web-history').history_dict.values() if reverse: diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index 2f4da748c..0fec4ea99 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -658,7 +658,7 @@ class WebEngineTab(browsertab.AbstractTab): self.load_started]) if answer is None: try: - # pylint: disable=no-member + # pylint: disable=no-member, useless-suppression sip.assign(authenticator, QAuthenticator()) except AttributeError: # WORKAROUND for From a4f04db848d89d3060ef125e409b9ceae4eafd7e Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 11 Feb 2017 22:26:37 +0100 Subject: [PATCH 371/561] Cap scroll count to 5000 Fixes #1694 --- qutebrowser/browser/webengine/webenginetab.py | 6 +----- qutebrowser/browser/webkit/webkittab.py | 2 +- tests/end2end/features/scroll.feature | 1 - 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index 0fec4ea99..bbc0050a8 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -252,9 +252,6 @@ class WebEngineScroller(browsertab.AbstractScroller): """QtWebEngine implementations related to scrolling.""" - # FIXME:qtwebengine - # using stuff here with a big count/argument causes memory leaks and hangs - def __init__(self, tab, parent=None): super().__init__(tab, parent) self._pos_perc = (0, 0) @@ -271,8 +268,7 @@ class WebEngineScroller(browsertab.AbstractScroller): self._pos_perc = (None, None) def _key_press(self, key, count=1): - # FIXME:qtwebengine Abort scrolling if the minimum/maximum was reached. - for _ in range(count): + for _ in range(min(count, 5000)): press_evt = QKeyEvent(QEvent.KeyPress, key, Qt.NoModifier, 0, 0, 0) release_evt = QKeyEvent(QEvent.KeyRelease, key, Qt.NoModifier, 0, 0, 0) diff --git a/qutebrowser/browser/webkit/webkittab.py b/qutebrowser/browser/webkit/webkittab.py index 4a11e80a6..ac9eea466 100644 --- a/qutebrowser/browser/webkit/webkittab.py +++ b/qutebrowser/browser/webkit/webkittab.py @@ -443,7 +443,7 @@ class WebKitScroller(browsertab.AbstractScroller): # FIXME:qtwebengine needed? # self._widget.setFocus() - for _ in range(count): + for _ in range(min(count, 5000)): press_evt = QKeyEvent(QEvent.KeyPress, key, Qt.NoModifier, 0, 0, 0) release_evt = QKeyEvent(QEvent.KeyRelease, key, Qt.NoModifier, 0, 0, 0) diff --git a/tests/end2end/features/scroll.feature b/tests/end2end/features/scroll.feature index 5c1cb35f1..05f9c22e8 100644 --- a/tests/end2end/features/scroll.feature +++ b/tests/end2end/features/scroll.feature @@ -142,7 +142,6 @@ Feature: Scrolling And I wait until the scroll position changed to 0/0 Then the page should not be scrolled - @qtwebengine_skip: Causes memory leak... Scenario: Scrolling down with a very big count When I run :scroll down with count 99999999999 And I wait until the scroll position changed From d60e365e3ccea0d8ed1887040d46c0455b3a8704 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 12 Feb 2017 01:06:56 +0100 Subject: [PATCH 372/561] Don't use _click_js for target=_blank links Fixes #2311 --- qutebrowser/browser/webelem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutebrowser/browser/webelem.py b/qutebrowser/browser/webelem.py index 85c6774b2..02521f3a4 100644 --- a/qutebrowser/browser/webelem.py +++ b/qutebrowser/browser/webelem.py @@ -403,7 +403,7 @@ class AbstractWebElement(collections.abc.MutableMapping): href_tags = ['a', 'area', 'link'] if click_target == usertypes.ClickTarget.normal: - if self.tag_name() in href_tags: + if self.tag_name() in href_tags and self.get('target') != '_blank': log.webelem.debug("Clicking via JS click()") self._click_js(click_target) elif self.is_editable(strict=True): From 7c701dc6971012ba8bd9803e6f1e20169b400769 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 12 Feb 2017 02:26:46 +0100 Subject: [PATCH 373/561] Disable coverage checks for webkitelem.py --- scripts/dev/check_coverage.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/dev/check_coverage.py b/scripts/dev/check_coverage.py index bdaac4a14..eef61b127 100644 --- a/scripts/dev/check_coverage.py +++ b/scripts/dev/check_coverage.py @@ -59,8 +59,8 @@ PERFECT_FILES = [ 'qutebrowser/browser/webkit/http.py'), ('tests/unit/browser/webkit/http/test_content_disposition.py', 'qutebrowser/browser/webkit/rfc6266.py'), - ('tests/unit/browser/webkit/test_webkitelem.py', - 'qutebrowser/browser/webkit/webkitelem.py'), + # ('tests/unit/browser/webkit/test_webkitelem.py', + # 'qutebrowser/browser/webkit/webkitelem.py'), # ('tests/unit/browser/webkit/test_webkitelem.py', # 'qutebrowser/browser/webelem.py'), ('tests/unit/browser/webkit/network/test_schemehandler.py', From 2b76cca2b2d862713cd3f726e926c9ce4db51e5d Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 12 Feb 2017 21:19:58 +0100 Subject: [PATCH 374/561] Improve earlyinit error messages --- qutebrowser/misc/earlyinit.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/qutebrowser/misc/earlyinit.py b/qutebrowser/misc/earlyinit.py index fc00f7c14..96e1ed787 100644 --- a/qutebrowser/misc/earlyinit.py +++ b/qutebrowser/misc/earlyinit.py @@ -59,15 +59,16 @@ def _missing_str(name, *, windows=None, pip=None, webengine=False): webengine: Whether this is checking the QtWebEngine package """ blocks = ["Fatal error: {} is required to run qutebrowser but " - "could not be imported! Maybe it's not installed?".format(name)] + "could not be imported! Maybe it's not installed?".format(name), + "The error encountered was:
%ERROR%"] lines = ['Please search for the python3 version of {} in your ' 'distributions packages, or install it via pip.'.format(name)] blocks.append('
'.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.'), + '(like Ubuntu), so you need to start without --backend ' + 'webengine there.'), ('QtWebEngine is currently unsupported with the OS X .app, see ' 'https://github.com/qutebrowser/qutebrowser/issues/1692'), ] @@ -107,7 +108,7 @@ def _die(message, exception=None): print("Exiting because of --no-err-windows.", file=sys.stderr) else: if exception is not None: - message += '


Error:
{}'.format(exception) + message = message.replace('%ERROR%', str(exception)) msgbox = QMessageBox(QMessageBox.Critical, "qutebrowser: Fatal error!", message) msgbox.setTextFormat(Qt.RichText) @@ -226,7 +227,7 @@ def check_pyqt_core(): text = text.replace('', '') text = text.replace('', '') text = text.replace('
', '\n') - text += '\n\nError: {}'.format(e) + text = text.replace('%ERROR%', str(e)) if tkinter and '--no-err-windows' not in sys.argv: root = tkinter.Tk() root.withdraw() From 3de2bfb277f0bd8e10d4b44d8dc035af94822019 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 13 Feb 2017 11:23:29 +0100 Subject: [PATCH 375/561] Handle renderer process not starting at all --- qutebrowser/browser/browsertab.py | 3 ++- qutebrowser/browser/webengine/webenginetab.py | 2 ++ qutebrowser/mainwindow/tabbedbrowser.py | 2 ++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/qutebrowser/browser/browsertab.py b/qutebrowser/browser/browsertab.py index 5f9100b75..700bf88f2 100644 --- a/qutebrowser/browser/browsertab.py +++ b/qutebrowser/browser/browsertab.py @@ -78,7 +78,8 @@ TerminationStatus = usertypes.enum('TerminationStatus', [ 'normal', 'abnormal', # non-zero exit status 'crashed', # e.g. segfault - 'killed' + 'killed', + 'unknown', ]) diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index bbc0050a8..c5394ae7d 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -693,6 +693,8 @@ class WebEngineTab(browsertab.AbstractTab): browsertab.TerminationStatus.crashed, QWebEnginePage.KilledTerminationStatus: browsertab.TerminationStatus.killed, + -1: + browsertab.TerminationStatus.unknown, } self.renderer_process_terminated.emit(status_map[status], exitcode) diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py index 7302bc3fb..115ee6f75 100644 --- a/qutebrowser/mainwindow/tabbedbrowser.py +++ b/qutebrowser/mainwindow/tabbedbrowser.py @@ -677,6 +677,8 @@ class TabbedBrowser(tabwidget.TabWidget): message.error("Renderer process crashed") elif status == browsertab.TerminationStatus.killed: message.error("Renderer process was killed") + elif status == browsertab.TerminationStatus.unknown: + message.error("Renderer process did not start") else: raise ValueError("Invalid status {}".format(status)) From 40e1337e037197c3319bc0a8386445215c9b2b9c Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 17 Feb 2017 06:56:12 +0100 Subject: [PATCH 376/561] Adjust WebEngineEleement _js_dict in remove_blank_target Otherwise, when checking later if the element has target=_blank set, we still think it is. This is not perfect as remove_blank_target also removes it from sub-elements, but it's a good start. --- qutebrowser/browser/webengine/webengineelem.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/qutebrowser/browser/webengine/webengineelem.py b/qutebrowser/browser/webengine/webengineelem.py index 40d467f43..36f4e0e64 100644 --- a/qutebrowser/browser/webengine/webengineelem.py +++ b/qutebrowser/browser/webengine/webengineelem.py @@ -147,6 +147,8 @@ class WebEngineElement(webelem.AbstractWebElement): return QRect() def remove_blank_target(self): + if self._js_dict['attributes'].get('target') == '_blank': + self._js_dict['attributes']['target'] = '_top' js_code = javascript.assemble('webelem', 'remove_blank_target', self._id) self._tab.run_js_async(js_code) From ba21fb378469836734e4abe2674ead9da03dba14 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 17 Feb 2017 06:57:31 +0100 Subject: [PATCH 377/561] webengine: Use _click_href for hinting target=_blank links Fixes #2311 --- qutebrowser/browser/webelem.py | 7 ++++++- tests/end2end/features/hints.feature | 1 - 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/qutebrowser/browser/webelem.py b/qutebrowser/browser/webelem.py index 02521f3a4..85594ce43 100644 --- a/qutebrowser/browser/webelem.py +++ b/qutebrowser/browser/webelem.py @@ -403,7 +403,12 @@ class AbstractWebElement(collections.abc.MutableMapping): href_tags = ['a', 'area', 'link'] if click_target == usertypes.ClickTarget.normal: - if self.tag_name() in href_tags and self.get('target') != '_blank': + if self.tag_name() in href_tags and self.get('target') == '_blank': + log.webelem.debug("target _blank -> Clicking via href") + # FIXME:qtwebengine Should we use tab_bg here with + # background-tabs set? + self._click_href(usertypes.ClickTarget.tab) + elif self.tag_name() in href_tags: log.webelem.debug("Clicking via JS click()") self._click_js(click_target) elif self.is_editable(strict=True): diff --git a/tests/end2end/features/hints.feature b/tests/end2end/features/hints.feature index 5425cecb6..bb81bc7cb 100644 --- a/tests/end2end/features/hints.feature +++ b/tests/end2end/features/hints.feature @@ -18,7 +18,6 @@ Feature: Using hints Then the following tabs should be open: - data/hello.txt (active) - @qtwebengine_skip: Opens in background Scenario: Following a hint and allow to open in new tab. When I open data/hints/link_blank.html And I hint with args "links normal" and follow a From 75964099624a878e1d298f0e993a63c96731ac2b Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 17 Feb 2017 07:02:13 +0100 Subject: [PATCH 378/561] Add PyQt 5.8 PyPI test envs --- .travis.yml | 8 ++++++-- tox.ini | 22 ++++++++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 731c7cfec..d6b3b1d6b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,14 +21,18 @@ matrix: - os: linux env: DOCKER=ubuntu-xenial services: docker + - os: linux + language: python + python: 3.6 + env: TESTENV=py36-pyqt571 - os: linux language: python python: 3.5 - env: TESTENV=py35-pyqt571 + env: TESTENV=py35-pyqt58 - os: linux language: python python: 3.6 - env: TESTENV=py36-pyqt571 + env: TESTENV=py36-pyqt58 - os: osx env: TESTENV=py36 OSX=elcapitan osx_image: xcode7.3 diff --git a/tox.ini b/tox.ini index bc7c05a54..2a9ed8c24 100644 --- a/tox.ini +++ b/tox.ini @@ -90,6 +90,28 @@ deps = PyQt5==5.7.1 commands = {envpython} scripts/dev/run_pytest.py {posargs:tests} +[testenv:py35-pyqt58] +basepython = python3.5 +setenv = + {[testenv]setenv} + QUTE_BDD_WEBENGINE=true +passenv = {[testenv]passenv} +deps = + {[testenv]deps} + PyQt5==5.8.0 +commands = {envpython} scripts/dev/run_pytest.py {posargs:tests} + +[testenv:py36-pyqt58] +basepython = python3.6 +setenv = + {[testenv]setenv} + QUTE_BDD_WEBENGINE=true +passenv = {[testenv]passenv} +deps = + {[testenv]deps} + PyQt5==5.8.0 +commands = {envpython} scripts/dev/run_pytest.py {posargs:tests} + # other envs [testenv:mkvenv] From 79d22f25050c9c37a0a64cbc7d40838ba59314c9 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 17 Feb 2017 07:02:57 +0100 Subject: [PATCH 379/561] travis: Don't install libpython3.4-dev for PyPI PyQt --- scripts/dev/ci/travis_install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/dev/ci/travis_install.sh b/scripts/dev/ci/travis_install.sh index deeb44d5c..4fd5d6327 100644 --- a/scripts/dev/ci/travis_install.sh +++ b/scripts/dev/ci/travis_install.sh @@ -117,7 +117,7 @@ case $TESTENV in check_pyqt ;; py3*-pyqt*) - apt_install xvfb libpython3.4-dev geoclue + apt_install xvfb geoclue ;; pylint|vulture) apt_install $pyqt_pkgs libpython3.4-dev From abded2470a764d43afd1f367c493590065c501f9 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 17 Feb 2017 12:00:16 +0100 Subject: [PATCH 380/561] Reenable PyQt 5.6 PyPI tests To avoid segfaults we need to hardcode the sip version. We also need to write a Qt.conf as it was missing with PyQt 5.6 and QtWebEngine can't find its resources. --- .travis.yml | 4 ++++ scripts/link_pyqt.py | 35 ++++++++++++++++++++++++++--------- tox.ini | 5 ++++- 3 files changed, 34 insertions(+), 10 deletions(-) diff --git a/.travis.yml b/.travis.yml index d6b3b1d6b..1000de613 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,6 +21,10 @@ matrix: - os: linux env: DOCKER=ubuntu-xenial services: docker + - os: linux + language: python + python: 3.5 + env: TESTENV=py35-pyqt56 - os: linux language: python python: 3.6 diff --git a/scripts/link_pyqt.py b/scripts/link_pyqt.py index fffe27fb3..c93449b47 100644 --- a/scripts/link_pyqt.py +++ b/scripts/link_pyqt.py @@ -154,6 +154,15 @@ def link_pyqt(executable, venv_path): copy_or_link(path, dest) +def patch_pypi_pyqt(venv_path): + executable = get_venv_executable(venv_path) + pyqt_dir = os.path.dirname(get_lib_path(executable, 'PyQt5.QtCore')) + qt_conf = os.path.join(pyqt_dir, 'Qt', 'libexec', 'qt.conf') + with open(qt_conf, 'w', encoding='utf-8') as f: + f.write('[Paths]\n') + f.write('Prefix = ..\n') + + def copy_or_link(source, dest): """Copy or symlink source to dest.""" if os.name == 'nt': @@ -177,11 +186,15 @@ def remove(filename): os.unlink(filename) +def get_venv_executable(path): + """Get the Python executable in a virtualenv.""" + subdir = 'Scripts' if os.name == 'nt' else 'bin' + return os.path.join(path, subdir, 'python') + + def get_venv_lib_path(path): """Get the library path of a virtualenv.""" - subdir = 'Scripts' if os.name == 'nt' else 'bin' - executable = os.path.join(path, subdir, 'python') - return run_py(executable, + return run_py(get_venv_executable(path), 'from distutils.sysconfig import get_python_lib', 'print(get_python_lib())') @@ -200,15 +213,19 @@ def main(): parser.add_argument('path', help="Base path to the venv.") parser.add_argument('--tox', help="Add when called via tox.", action='store_true') + parser.add_argument('--pypi', help="Patch a PyPI-installed PyQt 5.6", + action='store_true') args = parser.parse_args() - if args.tox: - executable = get_tox_syspython(args.path) + if args.pypi: + patch_pypi_pyqt(args.path) else: - executable = sys.executable - - venv_path = get_venv_lib_path(args.path) - link_pyqt(executable, venv_path) + if args.tox: + executable = get_tox_syspython(args.path) + else: + executable = sys.executable + venv_path = get_venv_lib_path(args.path) + link_pyqt(executable, venv_path) if __name__ == '__main__': diff --git a/tox.ini b/tox.ini index 2a9ed8c24..c70d44992 100644 --- a/tox.ini +++ b/tox.ini @@ -66,7 +66,10 @@ passenv = {[testenv]passenv} deps = {[testenv]deps} PyQt5==5.6 -commands = {envpython} scripts/dev/run_pytest.py {posargs:tests} + sip==4.18.1 +commands = + {envpython} scripts/link_pyqt.py --pypi {envdir} + {envpython} scripts/dev/run_pytest.py {posargs:tests} [testenv:py35-pyqt571] basepython = python3.5 From f718b66c48d0319cd301ad364757660fbc4a844c Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 17 Feb 2017 12:13:08 +0100 Subject: [PATCH 381/561] Disable mhtml tests for Qt 5.6 --- tests/end2end/features/downloads.feature | 2 ++ tests/end2end/test_mhtml_e2e.py | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/tests/end2end/features/downloads.feature b/tests/end2end/features/downloads.feature index b55673344..1e461040d 100644 --- a/tests/end2end/features/downloads.feature +++ b/tests/end2end/features/downloads.feature @@ -197,6 +197,7 @@ Feature: Downloading things from a website. ## mhtml downloads + @qt>=5.8 Scenario: Downloading as mhtml is available When I open data/title.html And I run :download --mhtml @@ -226,6 +227,7 @@ Feature: Downloading things from a website. And I wait for "File successfully written." in the log Then the downloaded file Test title.mhtml should exist + @qt>=5.8 Scenario: Opening a mhtml download directly When I set storage -> prompt-download-directory to true And I open html diff --git a/tests/end2end/test_mhtml_e2e.py b/tests/end2end/test_mhtml_e2e.py index 46ece8f0e..8181b1ed9 100644 --- a/tests/end2end/test_mhtml_e2e.py +++ b/tests/end2end/test_mhtml_e2e.py @@ -26,6 +26,8 @@ import collections import pytest +from qutebrowser.utils import qtutils + def collect_tests(): basedir = os.path.dirname(__file__) @@ -104,6 +106,9 @@ def _test_mhtml_requests(test_dir, test_path, httpbin): @pytest.mark.parametrize('test_name', collect_tests()) def test_mhtml(request, test_name, download_dir, quteproc, httpbin): + if not qtutils.version_check('5.7'): + pytest.skip("mhtml is unsupported with Qt < 5.7") + quteproc.set_setting('storage', 'download-directory', download_dir.location) quteproc.set_setting('storage', 'prompt-download-directory', 'false') From 7a4a34c3747f1647b3e8b652e48358cb42dbbd41 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 17 Feb 2017 12:27:13 +0100 Subject: [PATCH 382/561] Fix WebEngineElement.click on Qt 5.6 --- qutebrowser/browser/webengine/webengineelem.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/qutebrowser/browser/webengine/webengineelem.py b/qutebrowser/browser/webengine/webengineelem.py index 36f4e0e64..b3bdd8016 100644 --- a/qutebrowser/browser/webengine/webengineelem.py +++ b/qutebrowser/browser/webengine/webengineelem.py @@ -25,7 +25,7 @@ from PyQt5.QtCore import QRect, Qt, QPoint from PyQt5.QtGui import QMouseEvent -from qutebrowser.utils import log, javascript +from qutebrowser.utils import log, javascript, qtutils from qutebrowser.browser import webelem @@ -159,11 +159,15 @@ class WebEngineElement(webelem.AbstractWebElement): # because it was added in Qt 5.6, but we can be sure we use that with # QtWebEngine. # pylint: disable=no-member - ev = QMouseEvent(QMouseEvent.MouseButtonPress, QPoint(0, 0), - QPoint(0, 0), QPoint(0, 0), Qt.NoButton, Qt.NoButton, - Qt.NoModifier, Qt.MouseEventSynthesizedBySystem) - # pylint: enable=no-member - self._tab.send_event(ev) + # This also seems to break stuff on Qt 5.6 for some reason... + if qtutils.version_check('5.7'): + ev = QMouseEvent(QMouseEvent.MouseButtonPress, QPoint(0, 0), + QPoint(0, 0), QPoint(0, 0), Qt.NoButton, + Qt.NoButton, Qt.NoModifier, + Qt.MouseEventSynthesizedBySystem) + # pylint: enable=no-member + self._tab.send_event(ev) + # 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) From a86170f45dbc56b62f33709f33a258a077afe6f4 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 17 Feb 2017 14:11:45 +0100 Subject: [PATCH 383/561] Drop PyQt < 5.7.1 support for QtWebEngine --- .travis.yml | 4 -- CHANGELOG.asciidoc | 1 + doc/help/settings.asciidoc | 4 +- .../browser/webengine/webenginedownloads.py | 11 +--- .../browser/webengine/webengineelem.py | 16 ++---- .../browser/webengine/webenginesettings.py | 18 +++--- qutebrowser/browser/webengine/webenginetab.py | 57 ++++--------------- qutebrowser/browser/webengine/webview.py | 44 ++++---------- qutebrowser/config/configdata.py | 3 +- qutebrowser/mainwindow/tabbedbrowser.py | 16 ------ qutebrowser/misc/earlyinit.py | 9 +-- scripts/link_pyqt.py | 35 +++--------- tests/conftest.py | 8 +-- tests/end2end/features/downloads.feature | 2 - tests/end2end/features/misc.feature | 6 +- tests/end2end/fixtures/quteprocess.py | 3 - tests/end2end/test_mhtml_e2e.py | 5 -- tox.ini | 5 +- 18 files changed, 61 insertions(+), 186 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1000de613..d6b3b1d6b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,10 +21,6 @@ matrix: - os: linux env: DOCKER=ubuntu-xenial services: docker - - os: linux - language: python - python: 3.5 - env: TESTENV=py35-pyqt56 - os: linux language: python python: 3.6 diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 5deca80d4..b06017ea1 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -36,6 +36,7 @@ Added Changed ~~~~~~~ +- PyQt/Qt 5.7.1 is now required for the QtWebEngine backend - Scrolling with the scrollwheel while holding shift now scrolls sideways - New way of clicking hints with which solves various small issues - When yanking a mailto: link via hints, the mailto: prefix is now stripped diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc index 04424d3aa..47e6ab29f 100644 --- a/doc/help/settings.asciidoc +++ b/doc/help/settings.asciidoc @@ -158,7 +158,7 @@ |<>|Whether images are automatically loaded in web pages. |<>|Enables or disables the running of JavaScript programs. |<>|Enables or disables plugins in Web pages. -|<>|Enables or disables WebGL. For QtWebEngine, Qt/PyQt >= 5.7 is required for this setting. +|<>|Enables or disables WebGL. |<>|Enable or disable support for CSS regions. |<>|Enable or disable hyperlink auditing (). |<>|Allow websites to request geolocations. @@ -1426,7 +1426,7 @@ Default: +pass:[false]+ [[content-webgl]] === webgl -Enables or disables WebGL. For QtWebEngine, Qt/PyQt >= 5.7 is required for this setting. +Enables or disables WebGL. Valid values: diff --git a/qutebrowser/browser/webengine/webenginedownloads.py b/qutebrowser/browser/webengine/webenginedownloads.py index 072d363ba..59a81df69 100644 --- a/qutebrowser/browser/webengine/webenginedownloads.py +++ b/qutebrowser/browser/webengine/webenginedownloads.py @@ -49,12 +49,8 @@ class DownloadItem(downloads.AbstractDownloadItem): def _is_page_download(self): """Check if this item is a page (i.e. mhtml) download.""" - try: - return (self._qt_item.savePageFormat() != - QWebEngineDownloadItem.UnknownSaveFormat) - except AttributeError: - # Added in Qt 5.7 - return False + return (self._qt_item.savePageFormat() != + QWebEngineDownloadItem.UnknownSaveFormat) @pyqtSlot(QWebEngineDownloadItem.DownloadState) def _on_state_changed(self, state): @@ -209,9 +205,6 @@ class DownloadManager(downloads.AbstractDownloadManager): def get_mhtml(self, tab, target): """Download the given tab as mhtml to the given target.""" assert tab.backend == usertypes.Backend.QtWebEngine - # Raises browsertab.UnsupportedOperationError on older Qt versions - # but we let the caller handle that. - tab.action.check_save_page_supported() assert self._mhtml_target is None, self._mhtml_target self._mhtml_target = target tab.action.save_page() diff --git a/qutebrowser/browser/webengine/webengineelem.py b/qutebrowser/browser/webengine/webengineelem.py index b3bdd8016..36f4e0e64 100644 --- a/qutebrowser/browser/webengine/webengineelem.py +++ b/qutebrowser/browser/webengine/webengineelem.py @@ -25,7 +25,7 @@ from PyQt5.QtCore import QRect, Qt, QPoint from PyQt5.QtGui import QMouseEvent -from qutebrowser.utils import log, javascript, qtutils +from qutebrowser.utils import log, javascript from qutebrowser.browser import webelem @@ -159,15 +159,11 @@ class WebEngineElement(webelem.AbstractWebElement): # because it was added in Qt 5.6, but we can be sure we use that with # QtWebEngine. # pylint: disable=no-member - # This also seems to break stuff on Qt 5.6 for some reason... - if qtutils.version_check('5.7'): - ev = QMouseEvent(QMouseEvent.MouseButtonPress, QPoint(0, 0), - QPoint(0, 0), QPoint(0, 0), Qt.NoButton, - Qt.NoButton, Qt.NoModifier, - Qt.MouseEventSynthesizedBySystem) - # pylint: enable=no-member - self._tab.send_event(ev) - + ev = QMouseEvent(QMouseEvent.MouseButtonPress, QPoint(0, 0), + QPoint(0, 0), QPoint(0, 0), Qt.NoButton, Qt.NoButton, + Qt.NoModifier, Qt.MouseEventSynthesizedBySystem) + # pylint: enable=no-member + self._tab.send_event(ev) # 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) diff --git a/qutebrowser/browser/webengine/webenginesettings.py b/qutebrowser/browser/webengine/webenginesettings.py index fc2386e89..a87bc4802 100644 --- a/qutebrowser/browser/webengine/webenginesettings.py +++ b/qutebrowser/browser/webengine/webenginesettings.py @@ -176,16 +176,16 @@ def shutdown(): # Missing QtWebEngine attributes: -# - ScreenCaptureEnabled (5.7) -# - Accelerated2dCanvasEnabled (5.7) -# - AutoLoadIconsForPage (5.7) -# - TouchIconsEnabled (5.7) +# - ScreenCaptureEnabled +# - Accelerated2dCanvasEnabled +# - AutoLoadIconsForPage +# - TouchIconsEnabled # - FocusOnNavigationEnabled (5.8) # - AllowRunningInsecureContent (5.8) # # Missing QtWebEngine fonts: # - FantasyFont -# - PictographFont (5.7) +# - PictographFont MAPPINGS = { @@ -209,6 +209,8 @@ MAPPINGS = { # https://bugreports.qt.io/browse/QTBUG-58650 # 'cookies-store': # PersistentCookiePolicy(), + 'webgl': + Attribute(QWebEngineSettings.WebGLEnabled), }, 'input': { 'spatial-navigation': @@ -278,12 +280,6 @@ MAPPINGS = { } } -try: - MAPPINGS['content']['webgl'] = Attribute(QWebEngineSettings.WebGLEnabled) -except AttributeError: - # Added in Qt 5.7 - pass - try: MAPPINGS['general']['print-element-backgrounds'] = Attribute( QWebEngineSettings.PrintElementBackgrounds) diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index c5394ae7d..6fb5bca26 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -90,11 +90,6 @@ class WebEngineAction(browsertab.AbstractAction): def exit_fullscreen(self): self._action(QWebEnginePage.ExitFullScreen) - def check_save_page_supported(self): - if not hasattr(QWebEnginePage, 'SavePage'): - raise browsertab.UnsupportedOperationError( - "Saving as mhtml is unsupported with Qt < 5.7") - def save_page(self): """Save the current page.""" self._action(QWebEnginePage.SavePage) @@ -105,9 +100,7 @@ class WebEnginePrinting(browsertab.AbstractPrinting): """QtWebEngine implementations related to printing.""" def check_pdf_support(self): - if not hasattr(self._widget.page(), 'printToPdf'): - raise browsertab.WebTabError( - "Printing to PDF is unsupported with QtWebEngine on Qt < 5.7") + return True def check_printer_support(self): if not hasattr(self._widget.page(), 'print'): @@ -261,11 +254,7 @@ class WebEngineScroller(browsertab.AbstractScroller): def _init_widget(self, widget): super()._init_widget(widget) page = widget.page() - try: - page.scrollPositionChanged.connect(self._update_pos) - except AttributeError: - log.stub('scrollPositionChanged, on Qt < 5.7') - self._pos_perc = (None, None) + page.scrollPositionChanged.connect(self._update_pos) def _key_press(self, key, count=1): for _ in range(min(count, 5000)): @@ -501,7 +490,6 @@ class WebEngineTab(browsertab.AbstractTab): self.backend = usertypes.Backend.QtWebEngine self._init_js() self._child_event_filter = None - self.needs_qtbug54419_workaround = False self._saved_zoom = None def _init_js(self): @@ -516,13 +504,7 @@ class WebEngineTab(browsertab.AbstractTab): script.setSourceCode(js_code) page = self._widget.page() - try: - page.runJavaScript("", QWebEngineScript.ApplicationWorld) - except TypeError: - # We're unable to pass a world to runJavaScript - script.setWorldId(QWebEngineScript.MainWorld) - else: - script.setWorldId(QWebEngineScript.ApplicationWorld) + script.setWorldId(QWebEngineScript.ApplicationWorld) # FIXME:qtwebengine what about runsOnSubFrames? page.scripts().insert(script) @@ -567,19 +549,10 @@ class WebEngineTab(browsertab.AbstractTab): else: world_id = _JS_WORLD_MAP[world] - try: - if callback is None: - self._widget.page().runJavaScript(code, world_id) - else: - self._widget.page().runJavaScript(code, world_id, callback) - except TypeError: - if world is not None and world != usertypes.JsWorld.jseval: - log.webview.warning("Ignoring world ID on Qt < 5.7") - # Qt < 5.7 - if callback is None: - self._widget.page().runJavaScript(code) - else: - self._widget.page().runJavaScript(code, callback) + if callback is None: + self._widget.page().runJavaScript(code, world_id) + else: + self._widget.page().runJavaScript(code, world_id, callback) def shutdown(self): self.shutting_down.emit() @@ -602,11 +575,7 @@ class WebEngineTab(browsertab.AbstractTab): return self._widget.title() def icon(self): - try: - return self._widget.icon() - except AttributeError: - log.stub('on Qt < 5.7') - return QIcon() + return self._widget.icon() def set_html(self, html, base_url=None): # FIXME:qtwebengine @@ -712,19 +681,13 @@ class WebEngineTab(browsertab.AbstractTab): page.certificate_error.connect(self._on_ssl_errors) page.authenticationRequired.connect(self._on_authentication_required) page.fullScreenRequested.connect(self._on_fullscreen_requested) - try: - page.contentsSizeChanged.connect(self.contents_size_changed) - except AttributeError: - log.stub('contentsSizeChanged, on Qt < 5.7') + page.contentsSizeChanged.connect(self.contents_size_changed) view.titleChanged.connect(self.title_changed) view.urlChanged.connect(self._on_url_changed) view.renderProcessTerminated.connect( self._on_render_process_terminated) - try: - view.iconChanged.connect(self.icon_changed) - except AttributeError: - log.stub('iconChanged, on Qt < 5.7') + view.iconChanged.connect(self.icon_changed) def event_target(self): return self._widget.focusProxy() diff --git a/qutebrowser/browser/webengine/webview.py b/qutebrowser/browser/webengine/webview.py index 2d9c2eb0a..a861b9219 100644 --- a/qutebrowser/browser/webengine/webview.py +++ b/qutebrowser/browser/webengine/webview.py @@ -71,7 +71,7 @@ class WebEngineView(QWebEngineView): A window without decoration. QWebEnginePage::WebBrowserBackgroundTab: A web browser tab without hiding the current visible - WebEngineView. (Added in Qt 5.7) + WebEngineView. Return: The new QWebEngineView object. @@ -82,13 +82,6 @@ class WebEngineView(QWebEngineView): log.webview.debug("createWindow with type {}, background_tabs " "{}".format(debug_type, background_tabs)) - try: - background_tab_wintype = QWebEnginePage.WebBrowserBackgroundTab - except AttributeError: - # This is unavailable with an older PyQt, but we still might get - # this with a newer Qt... - background_tab_wintype = 0x0003 - if wintype == QWebEnginePage.WebBrowserWindow: # Shift-Alt-Click target = usertypes.ClickTarget.window @@ -103,7 +96,7 @@ class WebEngineView(QWebEngineView): target = usertypes.ClickTarget.tab else: target = usertypes.ClickTarget.tab_bg - elif wintype == background_tab_wintype: + elif wintype == QWebEnginePage.WebBrowserBackgroundTab: # Middle-click / Ctrl-Click if background_tabs: target = usertypes.ClickTarget.tab_bg @@ -113,15 +106,6 @@ class WebEngineView(QWebEngineView): raise ValueError("Invalid wintype {}".format(debug_type)) tab = shared.get_tab(self._win_id, target) - - # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-54419 - vercheck = qtutils.version_check - qtbug54419_fixed = ((vercheck('5.6.2') and not vercheck('5.7.0')) or - qtutils.version_check('5.7.1') or - os.environ.get('QUTE_QTBUG54419_PATCHED', '')) - if not qtbug54419_fixed: - tab.needs_qtbug54419_workaround = True - return tab._widget # pylint: disable=protected-access @@ -261,20 +245,16 @@ class WebEnginePage(QWebEnginePage): except shared.CallSuper: return super().javaScriptConfirm(url, js_msg) - if PYQT_VERSION > 0x050700: - # WORKAROUND - # Can't override javaScriptPrompt with older PyQt versions - # https://www.riverbankcomputing.com/pipermail/pyqt/2016-November/038293.html - def javaScriptPrompt(self, url, js_msg, default): - """Override javaScriptPrompt to use qutebrowser prompts.""" - if self._is_shutting_down: - return (False, "") - try: - return shared.javascript_prompt(url, js_msg, default, - abort_on=[self.loadStarted, - self.shutting_down]) - except shared.CallSuper: - return super().javaScriptPrompt(url, js_msg, default) + def javaScriptPrompt(self, url, js_msg, default): + """Override javaScriptPrompt to use qutebrowser prompts.""" + if self._is_shutting_down: + return (False, "") + try: + return shared.javascript_prompt(url, js_msg, default, + abort_on=[self.loadStarted, + self.shutting_down]) + except shared.CallSuper: + return super().javaScriptPrompt(url, js_msg, default) def javaScriptAlert(self, url, js_msg): """Override javaScriptAlert to use qutebrowser prompts.""" diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index 2ec593378..0180537b0 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -823,8 +823,7 @@ def data(readonly=False): ('webgl', SettingValue(typ.Bool(), 'true'), - "Enables or disables WebGL. For QtWebEngine, Qt/PyQt >= 5.7 is " - "required for this setting."), + "Enables or disables WebGL."), ('css-regions', SettingValue(typ.Bool(), 'true', diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py index 115ee6f75..bafd178f1 100644 --- a/qutebrowser/mainwindow/tabbedbrowser.py +++ b/qutebrowser/mainwindow/tabbedbrowser.py @@ -539,22 +539,6 @@ class TabbedBrowser(tabwidget.TabWidget): # We can get signals for tabs we already deleted... return - # If needed, re-open the tab as a workaround for QTBUG-54419. - # See https://bugreports.qt.io/browse/QTBUG-54419 - if (tab.backend == usertypes.Backend.QtWebEngine and - tab.needs_qtbug54419_workaround and url.isValid()): - log.misc.debug("Doing QTBUG-54419 workaround for {}, " - "url {}".format(tab, url)) - background = self.currentIndex() != idx - self.setUpdatesEnabled(False) - try: - self.tabopen(url, background=background, idx=idx, - ignore_tabs_are_windows=True) - self.close_tab(tab, add_undo=False) - finally: - self.setUpdatesEnabled(True) - tab.needs_qtbug54419_workaround = False - @pyqtSlot(browsertab.AbstractTab, QIcon) def on_icon_changed(self, tab, icon): """Set the icon of a tab. diff --git a/qutebrowser/misc/earlyinit.py b/qutebrowser/misc/earlyinit.py index 96e1ed787..5e71e8bb1 100644 --- a/qutebrowser/misc/earlyinit.py +++ b/qutebrowser/misc/earlyinit.py @@ -262,15 +262,16 @@ def get_backend(args): def check_qt_version(backend): """Check if the Qt version is recent enough.""" - from PyQt5.QtCore import qVersion + from PyQt5.QtCore import qVersion, PYQT_VERSION from qutebrowser.utils import qtutils if qtutils.version_check('5.2.0', operator.lt): text = ("Fatal error: Qt and PyQt >= 5.2.0 are required, but {} is " "installed.".format(qVersion())) _die(text) - elif backend == 'webengine' and qtutils.version_check('5.6.0', - operator.lt): - text = ("Fatal error: Qt and PyQt >= 5.6.0 are required for " + elif (backend == 'webengine' and ( + qtutils.version_check('5.7.0', operator.lt) or + PYQT_VERSION < 0x050701)): + text = ("Fatal error: Qt and PyQt >= 5.7.1 are required for " "QtWebEngine support, but {} is installed.".format(qVersion())) _die(text) diff --git a/scripts/link_pyqt.py b/scripts/link_pyqt.py index c93449b47..fffe27fb3 100644 --- a/scripts/link_pyqt.py +++ b/scripts/link_pyqt.py @@ -154,15 +154,6 @@ def link_pyqt(executable, venv_path): copy_or_link(path, dest) -def patch_pypi_pyqt(venv_path): - executable = get_venv_executable(venv_path) - pyqt_dir = os.path.dirname(get_lib_path(executable, 'PyQt5.QtCore')) - qt_conf = os.path.join(pyqt_dir, 'Qt', 'libexec', 'qt.conf') - with open(qt_conf, 'w', encoding='utf-8') as f: - f.write('[Paths]\n') - f.write('Prefix = ..\n') - - def copy_or_link(source, dest): """Copy or symlink source to dest.""" if os.name == 'nt': @@ -186,15 +177,11 @@ def remove(filename): os.unlink(filename) -def get_venv_executable(path): - """Get the Python executable in a virtualenv.""" - subdir = 'Scripts' if os.name == 'nt' else 'bin' - return os.path.join(path, subdir, 'python') - - def get_venv_lib_path(path): """Get the library path of a virtualenv.""" - return run_py(get_venv_executable(path), + subdir = 'Scripts' if os.name == 'nt' else 'bin' + executable = os.path.join(path, subdir, 'python') + return run_py(executable, 'from distutils.sysconfig import get_python_lib', 'print(get_python_lib())') @@ -213,19 +200,15 @@ def main(): parser.add_argument('path', help="Base path to the venv.") parser.add_argument('--tox', help="Add when called via tox.", action='store_true') - parser.add_argument('--pypi', help="Patch a PyPI-installed PyQt 5.6", - action='store_true') args = parser.parse_args() - if args.pypi: - patch_pypi_pyqt(args.path) + if args.tox: + executable = get_tox_syspython(args.path) else: - if args.tox: - executable = get_tox_syspython(args.path) - else: - executable = sys.executable - venv_path = get_venv_lib_path(args.path) - link_pyqt(executable, venv_path) + executable = sys.executable + + venv_path = get_venv_lib_path(args.path) + link_pyqt(executable, venv_path) if __name__ == '__main__': diff --git a/tests/conftest.py b/tests/conftest.py index 8b040361d..f782e810e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -120,13 +120,9 @@ def pytest_collection_modifyitems(config, items): _apply_platform_markers(item) if item.get_marker('xfail_norun'): item.add_marker(pytest.mark.xfail(run=False)) - if item.get_marker('js_prompt'): - if config.webengine: - js_prompt_pyqt_version = 0x050700 - else: - js_prompt_pyqt_version = 0x050300 + if item.get_marker('js_prompt') and not config.webengine: item.add_marker(pytest.mark.skipif( - PYQT_VERSION <= js_prompt_pyqt_version, + PYQT_VERSION <= 0x050300, reason='JS prompts are not supported with this PyQt version')) if deselected: diff --git a/tests/end2end/features/downloads.feature b/tests/end2end/features/downloads.feature index 1e461040d..b55673344 100644 --- a/tests/end2end/features/downloads.feature +++ b/tests/end2end/features/downloads.feature @@ -197,7 +197,6 @@ Feature: Downloading things from a website. ## mhtml downloads - @qt>=5.8 Scenario: Downloading as mhtml is available When I open data/title.html And I run :download --mhtml @@ -227,7 +226,6 @@ Feature: Downloading things from a website. And I wait for "File successfully written." in the log Then the downloaded file Test title.mhtml should exist - @qt>=5.8 Scenario: Opening a mhtml download directly When I set storage -> prompt-download-directory to true And I open html diff --git a/tests/end2end/features/misc.feature b/tests/end2end/features/misc.feature index 40b405d9e..3edcfce4d 100644 --- a/tests/end2end/features/misc.feature +++ b/tests/end2end/features/misc.feature @@ -80,7 +80,7 @@ Feature: Various utility commands. And I wait for the javascript message "Hello from JS!" Then "Ignoring world ID 1" should be logged - @qtwebkit_skip @pyqt>=5.7.0 + @qtwebkit_skip Scenario: :jseval uses separate world without --world When I set general -> log-javascript-console to info And I open data/misc/jseval.html @@ -88,14 +88,14 @@ Feature: Various utility commands. Then the javascript message "Hello from the page!" should not be logged And the javascript message "Uncaught ReferenceError: do_log is not defined" should be logged - @qtwebkit_skip @pyqt>=5.7.0 + @qtwebkit_skip Scenario: :jseval using the main world When I set general -> log-javascript-console to info And I open data/misc/jseval.html And I run :jseval --world 0 do_log() Then the javascript message "Hello from the page!" should be logged - @qtwebkit_skip @pyqt>=5.7.0 + @qtwebkit_skip Scenario: :jseval using the main world as name When I set general -> log-javascript-console to info And I open data/misc/jseval.html diff --git a/tests/end2end/fixtures/quteprocess.py b/tests/end2end/fixtures/quteprocess.py index 2ca361529..0f4acaaf6 100644 --- a/tests/end2end/fixtures/quteprocess.py +++ b/tests/end2end/fixtures/quteprocess.py @@ -334,9 +334,6 @@ class QuteProc(testprocess.Process): # pylint: disable=no-name-in-module,useless-suppression from PyQt5.QtWebEngineWidgets import QWebEnginePage # pylint: enable=no-name-in-module,useless-suppression - if not hasattr(QWebEnginePage, 'scrollPositionChanged'): - # Qt < 5.7 - pytest.skip("QWebEnginePage.scrollPositionChanged missing") if x is None and y is None: point = 'PyQt5.QtCore.QPoint(*, *)' # not counting 0/0 here elif x == '0' and y == '0': diff --git a/tests/end2end/test_mhtml_e2e.py b/tests/end2end/test_mhtml_e2e.py index 8181b1ed9..46ece8f0e 100644 --- a/tests/end2end/test_mhtml_e2e.py +++ b/tests/end2end/test_mhtml_e2e.py @@ -26,8 +26,6 @@ import collections import pytest -from qutebrowser.utils import qtutils - def collect_tests(): basedir = os.path.dirname(__file__) @@ -106,9 +104,6 @@ def _test_mhtml_requests(test_dir, test_path, httpbin): @pytest.mark.parametrize('test_name', collect_tests()) def test_mhtml(request, test_name, download_dir, quteproc, httpbin): - if not qtutils.version_check('5.7'): - pytest.skip("mhtml is unsupported with Qt < 5.7") - quteproc.set_setting('storage', 'download-directory', download_dir.location) quteproc.set_setting('storage', 'prompt-download-directory', 'false') diff --git a/tox.ini b/tox.ini index c70d44992..2a9ed8c24 100644 --- a/tox.ini +++ b/tox.ini @@ -66,10 +66,7 @@ passenv = {[testenv]passenv} deps = {[testenv]deps} PyQt5==5.6 - sip==4.18.1 -commands = - {envpython} scripts/link_pyqt.py --pypi {envdir} - {envpython} scripts/dev/run_pytest.py {posargs:tests} +commands = {envpython} scripts/dev/run_pytest.py {posargs:tests} [testenv:py35-pyqt571] basepython = python3.5 From 006378923e094ed7fe8784046c07930dd2d29ca8 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 17 Feb 2017 14:45:36 +0100 Subject: [PATCH 384/561] Skip geolocation tests on Qt 5.8 https://bugreports.qt.io/browse/QTBUG-58985 --- tests/end2end/features/prompts.feature | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/end2end/features/prompts.feature b/tests/end2end/features/prompts.feature index 1b6bcd62f..8b687abcc 100644 --- a/tests/end2end/features/prompts.feature +++ b/tests/end2end/features/prompts.feature @@ -213,14 +213,14 @@ Feature: Prompts And I run :click-element id button Then the javascript message "geolocation permission denied" should be logged - @ci @not_osx + @ci @not_osx @qt!=5.8 Scenario: Always accepting geolocation When I set content -> geolocation to true And I open data/prompt/geolocation.html in a new tab And I run :click-element id button Then the javascript message "geolocation permission denied" should not be logged - @ci @not_osx + @ci @not_osx @qt!=5.8 Scenario: geolocation with ask -> true When I set content -> geolocation to ask And I open data/prompt/geolocation.html in a new tab From b5dd69f4a6e06ab85023fffcb9aa0b588c06bcf4 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 17 Feb 2017 15:43:25 +0100 Subject: [PATCH 385/561] Add some logging for clicking --- qutebrowser/browser/webelem.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/qutebrowser/browser/webelem.py b/qutebrowser/browser/webelem.py index 85594ce43..376db1142 100644 --- a/qutebrowser/browser/webelem.py +++ b/qutebrowser/browser/webelem.py @@ -397,6 +397,9 @@ class AbstractWebElement(collections.abc.MutableMapping): to simulate. force_event: Force generating a fake mouse event. """ + log.webelem.debug("Clicking {!r} with click_target {}, force_event {}" + .format(self, click_target, force_event)) + if force_event: self._click_fake_event(click_target) return From 0deb422cfdcb0432eac9a1d53120cb5c4da991e1 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 17 Feb 2017 15:44:25 +0100 Subject: [PATCH 386/561] Remove unused imports --- qutebrowser/browser/webengine/webenginetab.py | 2 +- qutebrowser/browser/webengine/webview.py | 7 +++---- tests/end2end/fixtures/quteprocess.py | 4 ---- 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index 6fb5bca26..88c693244 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -26,7 +26,7 @@ import functools import sip from PyQt5.QtCore import pyqtSlot, Qt, QEvent, QPoint, QUrl, QTimer -from PyQt5.QtGui import QKeyEvent, QIcon +from PyQt5.QtGui import QKeyEvent from PyQt5.QtNetwork import QAuthenticator # pylint: disable=no-name-in-module,import-error,useless-suppression from PyQt5.QtWidgets import QApplication diff --git a/qutebrowser/browser/webengine/webview.py b/qutebrowser/browser/webengine/webview.py index a861b9219..90b9a70fb 100644 --- a/qutebrowser/browser/webengine/webview.py +++ b/qutebrowser/browser/webengine/webview.py @@ -19,10 +19,9 @@ """The main browser widget for QtWebEngine.""" -import os import functools -from PyQt5.QtCore import pyqtSignal, pyqtSlot, QUrl, PYQT_VERSION +from PyQt5.QtCore import pyqtSignal, pyqtSlot, QUrl from PyQt5.QtGui import QPalette # pylint: disable=no-name-in-module,import-error,useless-suppression from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEnginePage @@ -31,8 +30,8 @@ from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEnginePage from qutebrowser.browser import shared from qutebrowser.browser.webengine import certificateerror from qutebrowser.config import config -from qutebrowser.utils import (log, debug, usertypes, qtutils, jinja, urlutils, - message, objreg) +from qutebrowser.utils import (log, debug, usertypes, jinja, urlutils, message, + objreg) class WebEngineView(QWebEngineView): diff --git a/tests/end2end/fixtures/quteprocess.py b/tests/end2end/fixtures/quteprocess.py index 0f4acaaf6..d7410b3bc 100644 --- a/tests/end2end/fixtures/quteprocess.py +++ b/tests/end2end/fixtures/quteprocess.py @@ -330,10 +330,6 @@ class QuteProc(testprocess.Process): if (x is None and y is not None) or (y is None and x is not None): raise ValueError("Either both x/y or neither must be given!") - if self.request.config.webengine: - # pylint: disable=no-name-in-module,useless-suppression - from PyQt5.QtWebEngineWidgets import QWebEnginePage - # pylint: enable=no-name-in-module,useless-suppression if x is None and y is None: point = 'PyQt5.QtCore.QPoint(*, *)' # not counting 0/0 here elif x == '0' and y == '0': From dcdfc116a4dc7cb47b1fdfd9a7c3d33735da1a1c Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 17 Feb 2017 15:52:05 +0100 Subject: [PATCH 387/561] Fix indent --- qutebrowser/browser/webengine/webview.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutebrowser/browser/webengine/webview.py b/qutebrowser/browser/webengine/webview.py index 90b9a70fb..a6bdb12f4 100644 --- a/qutebrowser/browser/webengine/webview.py +++ b/qutebrowser/browser/webengine/webview.py @@ -251,7 +251,7 @@ class WebEnginePage(QWebEnginePage): try: return shared.javascript_prompt(url, js_msg, default, abort_on=[self.loadStarted, - self.shutting_down]) + self.shutting_down]) except shared.CallSuper: return super().javaScriptPrompt(url, js_msg, default) From a767e33f1592f5e8a71fa1d45a2b93f39314e12c Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 17 Feb 2017 15:52:12 +0100 Subject: [PATCH 388/561] Re-add title setting code --- qutebrowser/mainwindow/tabbedbrowser.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py index bafd178f1..60df43f9d 100644 --- a/qutebrowser/mainwindow/tabbedbrowser.py +++ b/qutebrowser/mainwindow/tabbedbrowser.py @@ -539,6 +539,9 @@ class TabbedBrowser(tabwidget.TabWidget): # We can get signals for tabs we already deleted... return + if not self.page_title(idx): + self.set_page_title(idx, url.toDisplayString()) + @pyqtSlot(browsertab.AbstractTab, QIcon) def on_icon_changed(self, tab, icon): """Set the icon of a tab. From 6ac7e61e0e253e6c6b990a5c653a67578bf2f705 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 17 Feb 2017 21:32:40 +0100 Subject: [PATCH 389/561] Add testcase for #2311 --- .../data/hints/html/target_blank_js.html | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 tests/end2end/data/hints/html/target_blank_js.html diff --git a/tests/end2end/data/hints/html/target_blank_js.html b/tests/end2end/data/hints/html/target_blank_js.html new file mode 100644 index 000000000..277d17f66 --- /dev/null +++ b/tests/end2end/data/hints/html/target_blank_js.html @@ -0,0 +1,28 @@ + + + + + + + + + + Link where we insert target=_blank via JS + + + + Follow me! + + From ac9b0e5c598b18495160fcee2c588a78507996a1 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 17 Feb 2017 22:07:23 +0100 Subject: [PATCH 390/561] bdd: Allow checking open tabs without (active) --- tests/end2end/features/conftest.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/end2end/features/conftest.py b/tests/end2end/features/conftest.py index 789fda108..01afa499f 100644 --- a/tests/end2end/features/conftest.py +++ b/tests/end2end/features/conftest.py @@ -536,6 +536,9 @@ def check_open_tabs(quteproc, request, tabs): assert len(session['windows']) == 1 assert len(session['windows'][0]['tabs']) == len(tabs) + # If we don't have (active) anywhere, don't check it + has_active = any(line.endswith(active_suffix) for line in tabs) + for i, line in enumerate(tabs): line = line.strip() assert line.startswith('- ') @@ -551,7 +554,7 @@ def check_open_tabs(quteproc, request, tabs): assert session_tab['history'][-1]['url'] == quteproc.path_to_url(path) if active: assert session_tab['active'] - else: + elif has_active: assert 'active' not in session_tab From 399b02a36712d5d73d9ca49c2ab2a9acd4a110d4 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 17 Feb 2017 22:07:59 +0100 Subject: [PATCH 391/561] Fix subtle issue in hint bdd tests - We need to clean open tabs to avoid reusing target=_blank child tabs - We don't check the active tab with target=_blank anymore - Remove some weird :tab-close --- tests/end2end/features/hints.feature | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/end2end/features/hints.feature b/tests/end2end/features/hints.feature index bb81bc7cb..516ca0979 100644 --- a/tests/end2end/features/hints.feature +++ b/tests/end2end/features/hints.feature @@ -1,5 +1,9 @@ Feature: Using hints + # https://bugreports.qt.io/browse/QTBUG-58381 + Background: + Given I clean up open tabs + Scenario: Using :follow-hint outside of hint mode (issue 1105) When I run :follow-hint Then the error "follow-hint: This command is only allowed in hint mode, not normal." should be shown @@ -24,11 +28,10 @@ Feature: Using hints And I wait until data/hello.txt is loaded Then the following tabs should be open: - data/hints/link_blank.html - - data/hello.txt (active) + - data/hello.txt Scenario: Following a hint to link with sub-element and force to open in current tab. When I open data/hints/link_span.html - And I run :tab-close And I hint with args "links current" and follow a And I wait until data/hello.txt is loaded Then the following tabs should be open: From 410b56447aa2b5d7dc0991526c1f79c9364382ff Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 17 Feb 2017 22:08:46 +0100 Subject: [PATCH 392/561] Temporarily set JavascriptCanOpenWindows for hints This partially reverts ba21fb378469836734e4abe2674ead9da03dba14. Fixes #2311. --- qutebrowser/browser/webelem.py | 7 +----- .../browser/webengine/webengineelem.py | 22 +++++++++++++++++-- qutebrowser/browser/webkit/webkitelem.py | 13 ++++++----- 3 files changed, 28 insertions(+), 14 deletions(-) diff --git a/qutebrowser/browser/webelem.py b/qutebrowser/browser/webelem.py index 376db1142..57d75a2fd 100644 --- a/qutebrowser/browser/webelem.py +++ b/qutebrowser/browser/webelem.py @@ -406,12 +406,7 @@ class AbstractWebElement(collections.abc.MutableMapping): href_tags = ['a', 'area', 'link'] if click_target == usertypes.ClickTarget.normal: - if self.tag_name() in href_tags and self.get('target') == '_blank': - log.webelem.debug("target _blank -> Clicking via href") - # FIXME:qtwebengine Should we use tab_bg here with - # background-tabs set? - self._click_href(usertypes.ClickTarget.tab) - elif self.tag_name() in href_tags: + if self.tag_name() in href_tags: log.webelem.debug("Clicking via JS click()") self._click_js(click_target) elif self.is_editable(strict=True): diff --git a/qutebrowser/browser/webengine/webengineelem.py b/qutebrowser/browser/webengine/webengineelem.py index 36f4e0e64..4df7f87b6 100644 --- a/qutebrowser/browser/webengine/webengineelem.py +++ b/qutebrowser/browser/webengine/webengineelem.py @@ -22,8 +22,10 @@ """QtWebEngine specific part of the web element API.""" -from PyQt5.QtCore import QRect, Qt, QPoint +from PyQt5.QtCore import QRect, Qt, QPoint, QEventLoop from PyQt5.QtGui import QMouseEvent +from PyQt5.QtWidgets import QApplication +from PyQt5.QtWebEngineWidgets import QWebEngineSettings from qutebrowser.utils import log, javascript from qutebrowser.browser import webelem @@ -169,5 +171,21 @@ class WebEngineElement(webelem.AbstractWebElement): self._tab.run_js_async(js_code) def _click_js(self, _click_target): + settings = QWebEngineSettings.globalSettings() + attribute = QWebEngineSettings.JavascriptCanOpenWindows + could_open_windows = settings.testAttribute(attribute) + settings.setAttribute(attribute, True) + + # Get QtWebEngine do apply the settings + # (it does so with a 0ms QTimer...) + # This is also used in Qt's tests: + # https://github.com/qt/qtwebengine/commit/5e572e88efa7ba7c2b9138ec19e606d3e345ac90 + qapp = QApplication.instance() + qapp.processEvents(QEventLoop.ExcludeSocketNotifiers | + QEventLoop.ExcludeUserInputEvents) + + def reset_setting(_arg): + settings.setAttribute(attribute, could_open_windows) + js_code = javascript.assemble('webelem', 'click', self._id) - self._tab.run_js_async(js_code) + self._tab.run_js_async(js_code, reset_setting) diff --git a/qutebrowser/browser/webkit/webkitelem.py b/qutebrowser/browser/webkit/webkitelem.py index 56e6baf6f..5813cf8f6 100644 --- a/qutebrowser/browser/webkit/webkitelem.py +++ b/qutebrowser/browser/webkit/webkitelem.py @@ -20,7 +20,7 @@ """QtWebKit specific part of the web element API.""" from PyQt5.QtCore import QRect -from PyQt5.QtWebKit import QWebElement +from PyQt5.QtWebKit import QWebElement, QWebSettings from qutebrowser.config import config from qutebrowser.utils import log, utils, javascript @@ -301,11 +301,12 @@ class WebKitElement(webelem.AbstractWebElement): self._elem.evaluateJavaScript('this.focus();') def _click_js(self, click_target): - if self.get('target') == '_blank': - # QtWebKit does nothing in this case for some reason... - self._click_fake_event(click_target) - else: - self._elem.evaluateJavaScript('this.click();') + settings = QWebSettings.globalSettings() + attribute = QWebSettings.JavascriptCanOpenWindows + could_open_windows = settings.testAttribute(attribute) + settings.setAttribute(attribute, True) + self._elem.evaluateJavaScript('this.click();') + settings.setAttribute(attribute, could_open_windows) def _click_fake_event(self, click_target): self._tab.data.override_target = click_target From 4e74fff5e8d0ec3ae4f2ce61b8c039631f76de53 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 18 Feb 2017 00:10:13 +0100 Subject: [PATCH 393/561] Test/lint fixes --- qutebrowser/browser/webengine/webengineelem.py | 2 ++ scripts/dev/check_coverage.py | 1 + scripts/dev/freeze_tests.py | 3 ++- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/qutebrowser/browser/webengine/webengineelem.py b/qutebrowser/browser/webengine/webengineelem.py index 4df7f87b6..37a1c0a00 100644 --- a/qutebrowser/browser/webengine/webengineelem.py +++ b/qutebrowser/browser/webengine/webengineelem.py @@ -25,7 +25,9 @@ from PyQt5.QtCore import QRect, Qt, QPoint, QEventLoop from PyQt5.QtGui import QMouseEvent from PyQt5.QtWidgets import QApplication +# pylint: disable=no-name-in-module,import-error,useless-suppression from PyQt5.QtWebEngineWidgets import QWebEngineSettings +# pylint: enable=no-name-in-module,import-error,useless-suppression from qutebrowser.utils import log, javascript from qutebrowser.browser import webelem diff --git a/scripts/dev/check_coverage.py b/scripts/dev/check_coverage.py index eef61b127..c876acd1a 100644 --- a/scripts/dev/check_coverage.py +++ b/scripts/dev/check_coverage.py @@ -166,6 +166,7 @@ PERFECT_FILES = [ WHITELISTED_FILES = [ 'qutebrowser/browser/webkit/webkitinspector.py', 'qutebrowser/keyinput/macros.py', + 'qutebrowser/browser/webkit/webkitelem.py', ] diff --git a/scripts/dev/freeze_tests.py b/scripts/dev/freeze_tests.py index 9f9e2bbd2..3c7ebb761 100755 --- a/scripts/dev/freeze_tests.py +++ b/scripts/dev/freeze_tests.py @@ -55,7 +55,8 @@ def get_build_exe_options(): opts = freeze.get_build_exe_options(skip_html=True) opts['includes'] += pytest.freeze_includes() opts['includes'] += ['unittest.mock', 'PyQt5.QtTest', 'hypothesis', 'bs4', - 'httpbin', 'jinja2.ext', 'cheroot', 'pstats', 'queue'] + 'httpbin', 'jinja2.ext', 'cheroot', 'pstats', 'queue', + 'pytest-benchmark'] httpbin_dir = os.path.dirname(httpbin.__file__) opts['include_files'] += [ From 0b94f2ed8cc90621a50e3de6548d7dfba3455e6f Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 18 Feb 2017 00:46:55 +0100 Subject: [PATCH 394/561] Fix pytest-benchmark package name --- scripts/dev/freeze_tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/dev/freeze_tests.py b/scripts/dev/freeze_tests.py index 3c7ebb761..dede962b3 100755 --- a/scripts/dev/freeze_tests.py +++ b/scripts/dev/freeze_tests.py @@ -56,7 +56,7 @@ def get_build_exe_options(): opts['includes'] += pytest.freeze_includes() opts['includes'] += ['unittest.mock', 'PyQt5.QtTest', 'hypothesis', 'bs4', 'httpbin', 'jinja2.ext', 'cheroot', 'pstats', 'queue', - 'pytest-benchmark'] + 'pytest_benchmark'] httpbin_dir = os.path.dirname(httpbin.__file__) opts['include_files'] += [ From 687f28845361b7fb84142fb63fc461fabc679acf Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 19 Feb 2017 12:58:58 +0100 Subject: [PATCH 395/561] Update PyInstaller env We now use PyQt5 from PyPI and apply xoviat's patch for PyQt. --- misc/requirements/requirements-pyinstaller.txt | 4 +++- misc/requirements/requirements-pyinstaller.txt-raw | 3 ++- tox.ini | 5 +++++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/misc/requirements/requirements-pyinstaller.txt b/misc/requirements/requirements-pyinstaller.txt index b4407f32b..88c81b7eb 100644 --- a/misc/requirements/requirements-pyinstaller.txt +++ b/misc/requirements/requirements-pyinstaller.txt @@ -1,3 +1,5 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py --e git+https://github.com/pyinstaller/pyinstaller.git#egg=PyInstaller +-e git+https://github.com/pyinstaller/pyinstaller.git@develop#egg=PyInstaller +PyQt5==5.8 +sip==4.19.1 diff --git a/misc/requirements/requirements-pyinstaller.txt-raw b/misc/requirements/requirements-pyinstaller.txt-raw index 7a4e021d9..bf06fc84e 100644 --- a/misc/requirements/requirements-pyinstaller.txt-raw +++ b/misc/requirements/requirements-pyinstaller.txt-raw @@ -1,4 +1,5 @@ -e git+https://github.com/pyinstaller/pyinstaller.git@develop#egg=PyInstaller +PyQt5 # remove @commit-id for scm installs -#@ replace: @.*# # \ No newline at end of file +#@ replace: @.*# @develop# \ No newline at end of file diff --git a/tox.ini b/tox.ini index 2a9ed8c24..a4e241b21 100644 --- a/tox.ini +++ b/tox.ini @@ -258,7 +258,12 @@ deps = -r{toxinidir}/misc/requirements/requirements-pip.txt -r{toxinidir}/requirements.txt -r{toxinidir}/misc/requirements/requirements-pyinstaller.txt +whitelist_externals = + patch + wget commands = + wget https://patch-diff.githubusercontent.com/raw/pyinstaller/pyinstaller/pull/2403.patch -O {envtmpdir}/pyqt.patch -o/dev/null + patch -f -p1 -d {envdir}/src/pyinstaller -i {envtmpdir}/pyqt.patch {envpython} scripts/link_pyqt.py --tox {envdir} {envbindir}/pyinstaller --noconfirm misc/qutebrowser.spec From 00a8e793118d3e66c43449d75277e394e3a88633 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 19 Feb 2017 13:05:58 +0100 Subject: [PATCH 396/561] PyInstaller env: Remove link_pyqt call --- tox.ini | 1 - 1 file changed, 1 deletion(-) diff --git a/tox.ini b/tox.ini index a4e241b21..0aed82488 100644 --- a/tox.ini +++ b/tox.ini @@ -264,7 +264,6 @@ whitelist_externals = commands = wget https://patch-diff.githubusercontent.com/raw/pyinstaller/pyinstaller/pull/2403.patch -O {envtmpdir}/pyqt.patch -o/dev/null patch -f -p1 -d {envdir}/src/pyinstaller -i {envtmpdir}/pyqt.patch - {envpython} scripts/link_pyqt.py --tox {envdir} {envbindir}/pyinstaller --noconfirm misc/qutebrowser.spec [testenv:eslint] From 01db59ff36122ea6254eff3606a8bc6f00a80ce3 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 19 Feb 2017 13:15:15 +0100 Subject: [PATCH 397/561] Handle all uerscript errors when hinting with userscript-target --- CHANGELOG.asciidoc | 1 + qutebrowser/browser/hints.py | 2 +- tests/end2end/features/hints.feature | 5 +++++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index b06017ea1..da50a87af 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -67,6 +67,7 @@ Fixed - Worked around a segfault when opening a URL after a QtWebEngine renderer process crash - Using :undo or :tab-clone with a view-source:// or chrome:// tab is now prevented, as it segfaults - `:navigate prev/next` now detects `rel` attributes on `` elements +- Fixed a crash when hinting with target `userscript` and spawning a non-existing script v0.9.1 ------ diff --git a/qutebrowser/browser/hints.py b/qutebrowser/browser/hints.py index 308f686b0..144b13f76 100644 --- a/qutebrowser/browser/hints.py +++ b/qutebrowser/browser/hints.py @@ -316,7 +316,7 @@ class HintActions: try: userscripts.run_async(context.tab, cmd, *args, win_id=self._win_id, env=env) - except userscripts.UnsupportedError as e: + except userscripts.Error as e: raise HintingError(str(e)) def spawn(self, url, context): diff --git a/tests/end2end/features/hints.feature b/tests/end2end/features/hints.feature index 516ca0979..772ffec55 100644 --- a/tests/end2end/features/hints.feature +++ b/tests/end2end/features/hints.feature @@ -81,6 +81,11 @@ Feature: Using hints And I hint with args "all userscript (testdata)/userscripts/echo_hint_text" and follow a Then the message "Follow me!" should be shown + Scenario: Using :hint userscript with a script which doesn't exist + When I open data/hints/html/simple.html + And I hint with args "all userscript (testdata)/does_not_exist" and follow a + Then the error "Userscript '*' not found" should be shown + Scenario: Yanking to clipboard When I run :debug-set-fake-clipboard And I open data/hints/html/simple.html From 5b7090e40236f428ee55592b38c03f1cc3e5bf60 Mon Sep 17 00:00:00 2001 From: Tomasz Kramkowski Date: Sat, 18 Feb 2017 19:04:25 +0000 Subject: [PATCH 398/561] browser/navigate: _find_nextprev allow for space sep rel attribs The _find_nextprev function of browser/navigate.py only checks to see if the rell attribute equals 'prev', 'previous', or 'next'. This patch changes this to check for a set intersection between {'prev', 'previous'} or {'next'} and the set of the space separated list of the rel attribute. --- qutebrowser/browser/navigate.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qutebrowser/browser/navigate.py b/qutebrowser/browser/navigate.py index 5abfbe0b4..aacec9d3c 100644 --- a/qutebrowser/browser/navigate.py +++ b/qutebrowser/browser/navigate.py @@ -70,11 +70,11 @@ def path_up(url, count): def _find_prevnext(prev, elems): """Find a prev/next element in the given list of elements.""" # First check for - rel_values = ('prev', 'previous') if prev else ('next') + rel_values = {'prev', 'previous'} if prev else {'next'} for e in elems: if e.tag_name() not in ['link', 'a'] or 'rel' not in e: continue - if e['rel'] in rel_values: + if set(e['rel'].split(' ')) & rel_values: log.hints.debug("Found {!r} with rel={}".format(e, e['rel'])) return e From 1fcba72958d61077c8dab804114546a9b02ff29e Mon Sep 17 00:00:00 2001 From: Tomasz Kramkowski Date: Sun, 19 Feb 2017 12:32:19 +0000 Subject: [PATCH 399/561] tests navigate: Add tests for rel attributes with nofollow Add two extra tests for checking navigation on pages with rel "next" and "prev" links which are also rel "nofollow" to test for the correct functionality of navigating pages with rel "next" and "prev" links with multiple rel attributes. --- tests/end2end/data/navigate/rel_nofollow.html | 12 ++++++++++++ tests/end2end/features/navigate.feature | 10 ++++++++++ 2 files changed, 22 insertions(+) create mode 100644 tests/end2end/data/navigate/rel_nofollow.html diff --git a/tests/end2end/data/navigate/rel_nofollow.html b/tests/end2end/data/navigate/rel_nofollow.html new file mode 100644 index 000000000..d6f7fd3e8 --- /dev/null +++ b/tests/end2end/data/navigate/rel_nofollow.html @@ -0,0 +1,12 @@ + + + + + Navigate + + +

Index page

+
bla + blub + + diff --git a/tests/end2end/features/navigate.feature b/tests/end2end/features/navigate.feature index 4b6b7f82e..5153400a4 100644 --- a/tests/end2end/features/navigate.feature +++ b/tests/end2end/features/navigate.feature @@ -53,6 +53,16 @@ Feature: Using :navigate And I run :navigate next Then data/navigate/next.html should be loaded + Scenario: Navigating to previous page with rel nofollow + When I open data/navigate/rel_nofollow.html + And I run :navigate prev + Then data/navigate/prev.html should be loaded + + Scenario: Navigating to next page with rel nofollow + When I open data/navigate/rel_nofollow.html + And I run :navigate next + Then data/navigate/next.html should be loaded + # increment/decrement Scenario: Incrementing number in URL From 5b4b226186c8d23f3a33a0e86eb3f26e17d34981 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 19 Feb 2017 14:02:40 +0100 Subject: [PATCH 400/561] Add Qt library/data paths to version info --- qutebrowser/utils/version.py | 7 ++++++- tests/unit/utils/test_version.py | 2 ++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/qutebrowser/utils/version.py b/qutebrowser/utils/version.py index 729ba440b..784c68cff 100644 --- a/qutebrowser/utils/version.py +++ b/qutebrowser/utils/version.py @@ -28,7 +28,8 @@ import subprocess import importlib import collections -from PyQt5.QtCore import QT_VERSION_STR, PYQT_VERSION_STR, qVersion +from PyQt5.QtCore import (QT_VERSION_STR, PYQT_VERSION_STR, qVersion, + QLibraryInfo) from PyQt5.QtNetwork import QSslSocket from PyQt5.QtWidgets import QApplication @@ -278,6 +279,10 @@ def version(): platform.architecture()[0]), 'Frozen: {}'.format(hasattr(sys, 'frozen')), "Imported from {}".format(importpath), + "Qt library executable path: {}, data path: {}".format( + QLibraryInfo.location(QLibraryInfo.LibraryExecutablesPath), + QLibraryInfo.location(QLibraryInfo.DataPath) + ) ] lines += _os_info() diff --git a/tests/unit/utils/test_version.py b/tests/unit/utils/test_version.py index ca31c7a12..7b821bba6 100644 --- a/tests/unit/utils/test_version.py +++ b/tests/unit/utils/test_version.py @@ -676,6 +676,7 @@ def test_version_output(git_commit, frozen, style, equal_qt, with_webkit, else usertypes.Backend.QtWebEngine), 'qtutils.is_qtwebkit_ng': (lambda v: True if with_webkit == 'ng' else False), + 'QLibraryInfo.location': (lambda _loc: 'QT PATH') } for attr, val in patches.items(): @@ -703,6 +704,7 @@ def test_version_output(git_commit, frozen, style, equal_qt, with_webkit, Platform: PLATFORM, ARCHITECTURE Frozen: {frozen} Imported from {import_path} + Qt library executable path: QT PATH, data path: QT PATH OS INFO 1 OS INFO 2 From 9a638b2dba1db56cc152280fa6be0f972aeaf261 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 19 Feb 2017 15:41:20 +0100 Subject: [PATCH 401/561] Patch OS X .app to work with QtWebEngine --- scripts/dev/build_release.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/scripts/dev/build_release.py b/scripts/dev/build_release.py index 95241e5ac..f1fc7e11f 100755 --- a/scripts/dev/build_release.py +++ b/scripts/dev/build_release.py @@ -23,6 +23,7 @@ import os import sys +import glob import os.path import shutil import subprocess @@ -90,12 +91,40 @@ def smoke_test(executable): '--temp-basedir', 'about:blank', ':later 500 quit']) +def patch_osx_app(): + """Patch .app for https://github.com/pyinstaller/pyinstaller/issues/2276""" + app_path = os.path.join('dist', 'qutebrowser.app') + qtwe_core_dir = os.path.join('.tox', 'pyinstaller', 'lib', 'python3.6', + 'site-packages', 'PyQt5', 'Qt', 'lib', + 'QtWebengineCore.framework') + # Copy QtWebEngineProcess.app + proc_app = 'QtWebEngineProcess.app' + shutil.copytree(os.path.join(qtwe_core_dir, 'Helpers', proc_app), + os.path.join(app_path, 'Contents', 'MacOS', proc_app)) + # Copy resources + for f in glob.glob(os.path.join(qtwe_core_dir, 'Resources', '*')): + dest = os.path.join(app_path, 'Contents', 'Resources') + if os.path.isdir(f): + shutil.copytree(f, os.path.join(dest, f)) + else: + shutil.copy(f, dest) + # Link dependencies + for lib in ['QtCore', 'QtWebEngineCore', 'QtQuick', 'QtQml', 'QtNetwork', + 'QtGui', 'QtWebChannel', 'QtPositioning']: + dest = os.path.join(app_path, lib + '.framework', 'Versions', '5') + os.makedirs(dest) + os.symlink(os.path.join(os.pardir, os.pardir, os.pardir, 'Contents', 'MacOS', lib), + os.path.join(dest, lib)) + + def build_osx(): """Build OS X .dmg/.app.""" utils.print_title("Updating 3rdparty content") update_3rdparty.update_pdfjs() utils.print_title("Building .app via pyinstaller") call_tox('pyinstaller', '-r') + utils.print_title("Patching .app") + patch_osx_app() utils.print_title("Building .dmg") subprocess.check_call(['make', '-f', 'scripts/dev/Makefile-dmg']) utils.print_title("Cleaning up...") From a8b27eb2719f17ca59b27fb0e0f47267075f4b29 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 19 Feb 2017 19:44:50 +0100 Subject: [PATCH 402/561] Fix lint --- scripts/dev/build_release.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/scripts/dev/build_release.py b/scripts/dev/build_release.py index f1fc7e11f..fb8fe000d 100755 --- a/scripts/dev/build_release.py +++ b/scripts/dev/build_release.py @@ -92,7 +92,10 @@ def smoke_test(executable): def patch_osx_app(): - """Patch .app for https://github.com/pyinstaller/pyinstaller/issues/2276""" + """Patch .app to copy missing data and link some libs. + + See https://github.com/pyinstaller/pyinstaller/issues/2276 + """ app_path = os.path.join('dist', 'qutebrowser.app') qtwe_core_dir = os.path.join('.tox', 'pyinstaller', 'lib', 'python3.6', 'site-packages', 'PyQt5', 'Qt', 'lib', @@ -113,7 +116,8 @@ def patch_osx_app(): 'QtGui', 'QtWebChannel', 'QtPositioning']: dest = os.path.join(app_path, lib + '.framework', 'Versions', '5') os.makedirs(dest) - os.symlink(os.path.join(os.pardir, os.pardir, os.pardir, 'Contents', 'MacOS', lib), + os.symlink(os.path.join(os.pardir, os.pardir, os.pardir, 'Contents', + 'MacOS', lib), os.path.join(dest, lib)) From f772ccb203340819637af53eb2de308ddef0a220 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 19 Feb 2017 20:03:27 +0100 Subject: [PATCH 403/561] QtWebEngine: Fall back to events for hints if JS is broken *cough* musl *cough* --- qutebrowser/browser/webkit/webkitelem.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/qutebrowser/browser/webkit/webkitelem.py b/qutebrowser/browser/webkit/webkitelem.py index 5813cf8f6..db926e64b 100644 --- a/qutebrowser/browser/webkit/webkitelem.py +++ b/qutebrowser/browser/webkit/webkitelem.py @@ -298,15 +298,21 @@ class WebKitElement(webelem.AbstractWebElement): elem = elem._parent() # pylint: disable=protected-access def _click_editable(self): - self._elem.evaluateJavaScript('this.focus();') + ok = self._elem.evaluateJavaScript('this.focus(); true;') + if not ok: + log.webelem.debug("Failed to focus via JS, falling back to event") + self._click_fake_event(click_target) def _click_js(self, click_target): settings = QWebSettings.globalSettings() attribute = QWebSettings.JavascriptCanOpenWindows could_open_windows = settings.testAttribute(attribute) settings.setAttribute(attribute, True) - self._elem.evaluateJavaScript('this.click();') + ok = self._elem.evaluateJavaScript('this.click(); true;') settings.setAttribute(attribute, could_open_windows) + if not ok: + log.webelem.debug("Failed to click via JS, falling back to event") + self._click_fake_event(click_target) def _click_fake_event(self, click_target): self._tab.data.override_target = click_target From a34bc929ac02d4b210d322e0bf745af31c6ae389 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 19 Feb 2017 20:11:46 +0100 Subject: [PATCH 404/561] Fix handling of pytest-benchmark for frozen tests --- scripts/dev/freeze_tests.py | 3 +-- scripts/dev/run_frozen_tests.py | 4 +++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/scripts/dev/freeze_tests.py b/scripts/dev/freeze_tests.py index dede962b3..9f9e2bbd2 100755 --- a/scripts/dev/freeze_tests.py +++ b/scripts/dev/freeze_tests.py @@ -55,8 +55,7 @@ def get_build_exe_options(): opts = freeze.get_build_exe_options(skip_html=True) opts['includes'] += pytest.freeze_includes() opts['includes'] += ['unittest.mock', 'PyQt5.QtTest', 'hypothesis', 'bs4', - 'httpbin', 'jinja2.ext', 'cheroot', 'pstats', 'queue', - 'pytest_benchmark'] + 'httpbin', 'jinja2.ext', 'cheroot', 'pstats', 'queue'] httpbin_dir = os.path.dirname(httpbin.__file__) opts['include_files'] += [ diff --git a/scripts/dev/run_frozen_tests.py b/scripts/dev/run_frozen_tests.py index 8afa5b634..ac1eb9488 100644 --- a/scripts/dev/run_frozen_tests.py +++ b/scripts/dev/run_frozen_tests.py @@ -31,8 +31,10 @@ import pytest_faulthandler import pytest_xvfb import pytest_rerunfailures import pytest_warnings +import pytest_benchmark sys.exit(pytest.main(plugins=[pytestqt.plugin, pytest_mock, pytest_catchlog, pytest_instafail, pytest_faulthandler, pytest_xvfb, - pytest_rerunfailures, pytest_warnings])) + pytest_rerunfailures, pytest_warnings, + pytest_benchmark])) From 53ec3e8a439f7ffbd8dda4d2378cefbc50d10bb2 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 19 Feb 2017 20:23:54 +0100 Subject: [PATCH 405/561] Update docs --- CHANGELOG.asciidoc | 3 ++- README.asciidoc | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index da50a87af..5075cf505 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -66,7 +66,8 @@ Fixed - Fixed showing of keybindings in the :help completion - Worked around a segfault when opening a URL after a QtWebEngine renderer process crash - Using :undo or :tab-clone with a view-source:// or chrome:// tab is now prevented, as it segfaults -- `:navigate prev/next` now detects `rel` attributes on `` elements +- `:navigate prev/next` now detects `rel` attributes on `` elements, and + handles multiple `rel` attributes correctly. - Fixed a crash when hinting with target `userscript` and spawning a non-existing script v0.9.1 diff --git a/README.asciidoc b/README.asciidoc index dc3b427df..a834171d3 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -187,6 +187,7 @@ Contributors, sorted by the number of commits in descending order: * knaggita * Oliver Caldwell * Julian Weigt +* Tomasz Kramkowski * Sebastian Frysztak * Nikolay Amiantov * Jonas Schürmann @@ -196,7 +197,6 @@ Contributors, sorted by the number of commits in descending order: * Julie Engel * skinnay * Zach-Button -* Tomasz Kramkowski * Samuel Walladge * Peter Rice * Ismail S From eb4d699be3b987613f685e99fde7f38bed0ea95e Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 19 Feb 2017 20:46:43 +0100 Subject: [PATCH 406/561] Add missing click_target arg to _click_editable --- qutebrowser/browser/webelem.py | 4 ++-- qutebrowser/browser/webengine/webengineelem.py | 2 +- qutebrowser/browser/webkit/webkitelem.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/qutebrowser/browser/webelem.py b/qutebrowser/browser/webelem.py index 57d75a2fd..fdae79f17 100644 --- a/qutebrowser/browser/webelem.py +++ b/qutebrowser/browser/webelem.py @@ -359,7 +359,7 @@ class AbstractWebElement(collections.abc.MutableMapping): self._tab.caret.move_to_end_of_document() QTimer.singleShot(0, after_click) - def _click_editable(self): + def _click_editable(self, click_target): """Fake a click on an editable input field.""" raise NotImplementedError @@ -411,7 +411,7 @@ class AbstractWebElement(collections.abc.MutableMapping): self._click_js(click_target) elif self.is_editable(strict=True): log.webelem.debug("Clicking via JS focus()") - self._click_editable() + self._click_editable(click_target) modeman.enter(self._tab.win_id, usertypes.KeyMode.insert, 'clicking input') else: diff --git a/qutebrowser/browser/webengine/webengineelem.py b/qutebrowser/browser/webengine/webengineelem.py index 37a1c0a00..c82b2df11 100644 --- a/qutebrowser/browser/webengine/webengineelem.py +++ b/qutebrowser/browser/webengine/webengineelem.py @@ -157,7 +157,7 @@ class WebEngineElement(webelem.AbstractWebElement): self._id) self._tab.run_js_async(js_code) - def _click_editable(self): + def _click_editable(self, click_target): # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-58515 # pylint doesn't know about Qt.MouseEventSynthesizedBySystem # because it was added in Qt 5.6, but we can be sure we use that with diff --git a/qutebrowser/browser/webkit/webkitelem.py b/qutebrowser/browser/webkit/webkitelem.py index db926e64b..d94e6996a 100644 --- a/qutebrowser/browser/webkit/webkitelem.py +++ b/qutebrowser/browser/webkit/webkitelem.py @@ -297,7 +297,7 @@ class WebKitElement(webelem.AbstractWebElement): break elem = elem._parent() # pylint: disable=protected-access - def _click_editable(self): + def _click_editable(self, click_target): ok = self._elem.evaluateJavaScript('this.focus(); true;') if not ok: log.webelem.debug("Failed to focus via JS, falling back to event") From 65cfb4ecfee7ef62c2c438fd4cfd438c39f26793 Mon Sep 17 00:00:00 2001 From: Julie Engel Date: Wed, 15 Feb 2017 11:59:02 +0100 Subject: [PATCH 407/561] make paths relative --- www/qute.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/www/qute.css b/www/qute.css index d8367603b..2de633b18 100644 --- a/www/qute.css +++ b/www/qute.css @@ -216,6 +216,6 @@ table td { - - + +