From d04fc291632a50058e7bbfabe33390175176dac6 Mon Sep 17 00:00:00 2001 From: Marc Jauvin Date: Mon, 12 Feb 2018 18:10:22 -0500 Subject: [PATCH 001/799] save input_mode when entering/leaving mode instead of when changing tab --- qutebrowser/keyinput/modeman.py | 10 ++++++++++ qutebrowser/mainwindow/tabbedbrowser.py | 15 ++++----------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/qutebrowser/keyinput/modeman.py b/qutebrowser/keyinput/modeman.py index e32830f50..5204c8068 100644 --- a/qutebrowser/keyinput/modeman.py +++ b/qutebrowser/keyinput/modeman.py @@ -267,6 +267,11 @@ class ModeManager(QObject): raise cmdexc.CommandError( "Mode {} can't be entered manually!".format(mode)) + if (config.val.tabs.mode_on_change == 'restore' and + m.name in ['insert', 'passthrough']): + window = objreg.get('main-window', scope='window', + window=self._win_id) + window.tabbed_browser.currentWidget().data.input_mode = m self.enter(m, 'command') @pyqtSlot(usertypes.KeyMode, str, bool) @@ -301,6 +306,11 @@ class ModeManager(QObject): """Leave the mode we're currently in.""" if self.mode == usertypes.KeyMode.normal: raise ValueError("Can't leave normal mode!") + if config.val.tabs.mode_on_change == 'restore': + window = objreg.get('main-window', scope='window', + window=self._win_id) + tab = window.tabbed_browser.currentWidget() + tab.data.input_mode = usertypes.KeyMode.normal self.leave(self.mode, 'leave current') def eventFilter(self, event): diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py index 8cee35524..94fd05b83 100644 --- a/qutebrowser/mainwindow/tabbedbrowser.py +++ b/qutebrowser/mainwindow/tabbedbrowser.py @@ -647,8 +647,6 @@ class TabbedBrowser(tabwidget.TabWidget): def on_current_changed(self, idx): """Set last-focused-tab and leave hinting mode when focus changed.""" mode_on_change = config.val.tabs.mode_on_change - modes_to_save = [usertypes.KeyMode.insert, - usertypes.KeyMode.passthrough] if idx == -1 or self.shutting_down: # closing the last tab (before quitting) or shutting down return @@ -657,23 +655,18 @@ class TabbedBrowser(tabwidget.TabWidget): log.webview.debug("on_current_changed got called with invalid " "index {}".format(idx)) return - if self._now_focused is not None and mode_on_change == 'restore': - current_mode = modeman.instance(self._win_id).mode - if current_mode not in modes_to_save: - current_mode = usertypes.KeyMode.normal - self._now_focused.data.input_mode = current_mode log.modes.debug("Current tab changed, focusing {!r}".format(tab)) tab.setFocus() modes_to_leave = [usertypes.KeyMode.hint, usertypes.KeyMode.caret] - if mode_on_change != 'persist': - modes_to_leave += modes_to_save + if mode_on_change == 'normal': + modes_to_leave += [usertypes.KeyMode.insert, + usertypes.KeyMode.passthrough] for mode in modes_to_leave: modeman.leave(self._win_id, mode, 'tab changed', maybe=True) if mode_on_change == 'restore': - modeman.enter(self._win_id, tab.data.input_mode, - 'restore input mode for tab') + modeman.enter(self._win_id, tab.data.input_mode, 'restore') if self._now_focused is not None: objreg.register('last-focused-tab', self._now_focused, update=True, scope='window', window=self._win_id) From 6214c38d7eaf1061018e2cec8d784c905c3d1544 Mon Sep 17 00:00:00 2001 From: Marc Jauvin Date: Mon, 12 Feb 2018 18:11:32 -0500 Subject: [PATCH 002/799] add input_mode tests for tabs.mode_on_change --- tests/end2end/features/keyinput.feature | 45 +++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/tests/end2end/features/keyinput.feature b/tests/end2end/features/keyinput.feature index ee5b667e8..08d368a71 100644 --- a/tests/end2end/features/keyinput.feature +++ b/tests/end2end/features/keyinput.feature @@ -162,3 +162,48 @@ Feature: Keyboard input And I press the key "c" Then the message "foo 3" should be shown And the message "foo 3" should be shown + + Scenario: tabs.mode_on_change_restore + Given I clean up open tabs + And I set tabs.mode_on_change to restore + When I open about:blank + And I run :enter-mode insert + And I open about:blank in a new tab + And I run :enter-mode passthrough + And I run :tab-focus 1 + Then "Entering mode KeyMode.insert (reason: command)" should be logged + And "Leaving mode KeyMode.insert (reason: tab changed)" should not be logged + And "Entering mode KeyMode.normal (reason: restore)" should be logged + And "Entering mode KeyMode.passthrough (reason: command)" should be logged + And "Leaving mode KeyMode.passthrough (reason: tab changed)" should not be logged + And "Entering mode KeyMode.insert (reason: restore)" should be logged + + Scenario: tabs.mode_on_change_persist + Given I clean up open tabs + And I set tabs.mode_on_change to persist + When I open about:blank + And I run :enter-mode insert + And I open about:blank in a new tab + And I run :enter-mode passthrough + And I run :tab-focus 1 + Then "Entering mode KeyMode.insert (reason: command)" should be logged + And "Leaving mode KeyMode.insert (reason: tab changed)" should not be logged + And "Entering mode KeyMode.normal (reason: restore)" should not be logged + And "Entering mode KeyMode.passthrough (reason: command)" should be logged + And "Leaving mode KeyMode.passthrough (reason: tab changed)" should not be logged + And "Entering mode KeyMode.insert (reason: restore)" should not be logged + + Scenario: tabs.mode_on_change_normal + Given I clean up open tabs + And I set tabs.mode_on_change to normal + When I open about:blank + And I run :enter-mode insert + And I open about:blank in a new tab + And I run :enter-mode passthrough + And I run :tab-focus 1 + Then "Entering mode KeyMode.insert (reason: command)" should be logged + And "Leaving mode KeyMode.insert (reason: tab changed)" should be logged + And "Entering mode KeyMode.normal (reason: restore)" should not be logged + And "Entering mode KeyMode.passthrough (reason: command)" should be logged + And "Leaving mode KeyMode.passthrough (reason: tab changed)" should be logged + And "Entering mode KeyMode.insert (reason: restore)" should not be logged From fbb78f11330655dd0a68295cee8680cd2d71a18b Mon Sep 17 00:00:00 2001 From: Marc Jauvin Date: Mon, 12 Feb 2018 21:57:53 -0500 Subject: [PATCH 003/799] hook into modeman's entered and left signals - save previous mode when a prompt is shown - restore previous mode when finished prompting --- qutebrowser/keyinput/modeman.py | 21 ++++++++++----------- qutebrowser/mainwindow/mainwindow.py | 1 + qutebrowser/mainwindow/tabbedbrowser.py | 10 ++++++++++ 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/qutebrowser/keyinput/modeman.py b/qutebrowser/keyinput/modeman.py index 5204c8068..585742d36 100644 --- a/qutebrowser/keyinput/modeman.py +++ b/qutebrowser/keyinput/modeman.py @@ -115,6 +115,7 @@ class ModeManager(QObject): Attributes: mode: The mode we're currently in. _win_id: The window ID of this ModeManager + _prev_mode: mode before a prompt poped up _parsers: A dictionary of modes and their keyparsers. _forward_unbound_keys: If we should forward unbound keys. _releaseevents_to_pass: A set of KeyEvents where the keyPressEvent was @@ -138,6 +139,7 @@ class ModeManager(QObject): super().__init__(parent) self._win_id = win_id self._parsers = {} + self._prev_mode = usertypes.KeyMode.normal self.mode = usertypes.KeyMode.normal self._releaseevents_to_pass = set() @@ -247,6 +249,11 @@ class ModeManager(QObject): return log.modes.debug("Overriding mode {}.".format(self.mode)) self.left.emit(self.mode, mode, self._win_id) + if mode in prompt_modes: + # save previous mode when being prompted + self._prev_mode = self.mode + else: + self._prev_mode = usertypes.KeyMode.normal self.mode = mode self.entered.emit(mode, self._win_id) @@ -266,12 +273,6 @@ class ModeManager(QObject): usertypes.KeyMode.yesno, usertypes.KeyMode.prompt]: raise cmdexc.CommandError( "Mode {} can't be entered manually!".format(mode)) - - if (config.val.tabs.mode_on_change == 'restore' and - m.name in ['insert', 'passthrough']): - window = objreg.get('main-window', scope='window', - window=self._win_id) - window.tabbed_browser.currentWidget().data.input_mode = m self.enter(m, 'command') @pyqtSlot(usertypes.KeyMode, str, bool) @@ -299,6 +300,9 @@ class ModeManager(QObject): self.clear_keychain() self.mode = usertypes.KeyMode.normal self.left.emit(mode, self.mode, self._win_id) + if mode in [usertypes.KeyMode.prompt, usertypes.KeyMode.yesno]: + self.enter(self._prev_mode, + reason='restore mode before {}'.format(mode.name)) @cmdutils.register(instance='mode-manager', name='leave-mode', not_modes=[usertypes.KeyMode.normal], scope='window') @@ -306,11 +310,6 @@ class ModeManager(QObject): """Leave the mode we're currently in.""" if self.mode == usertypes.KeyMode.normal: raise ValueError("Can't leave normal mode!") - if config.val.tabs.mode_on_change == 'restore': - window = objreg.get('main-window', scope='window', - window=self._win_id) - tab = window.tabbed_browser.currentWidget() - tab.data.input_mode = usertypes.KeyMode.normal self.leave(self.mode, 'leave current') def eventFilter(self, event): diff --git a/qutebrowser/mainwindow/mainwindow.py b/qutebrowser/mainwindow/mainwindow.py index 05482a1d5..18b349d5d 100644 --- a/qutebrowser/mainwindow/mainwindow.py +++ b/qutebrowser/mainwindow/mainwindow.py @@ -484,6 +484,7 @@ class MainWindow(QWidget): self.tabbed_browser.cur_fullscreen_requested.connect(status.maybe_hide) # command input / completion + mode_manager.entered.connect(self.tabbed_browser.on_mode_entered) mode_manager.left.connect(self.tabbed_browser.on_mode_left) cmd.clear_completion_selection.connect( completion_obj.on_clear_completion_selection) diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py index 94fd05b83..7d60369d3 100644 --- a/qutebrowser/mainwindow/tabbedbrowser.py +++ b/qutebrowser/mainwindow/tabbedbrowser.py @@ -631,6 +631,14 @@ class TabbedBrowser(tabwidget.TabWidget): if config.val.tabs.tabs_are_windows: self.window().setWindowIcon(icon) + @pyqtSlot(usertypes.KeyMode) + def on_mode_entered(self, mode): + """Save input mode when tabs.mode_on_change = restore.""" + input_modes = [usertypes.KeyMode.insert, usertypes.KeyMode.passthrough] + if (mode in input_modes and + config.val.tabs.mode_on_change == 'restore'): + self.currentWidget().data.input_mode = mode + @pyqtSlot(usertypes.KeyMode) def on_mode_left(self, mode): """Give focus to current tab if command mode was left.""" @@ -642,6 +650,8 @@ class TabbedBrowser(tabwidget.TabWidget): if widget is None: return widget.setFocus() + elif config.val.tabs.mode_on_change == 'restore': + self.currentWidget().data.input_mode = usertypes.KeyMode.normal @pyqtSlot(int) def on_current_changed(self, idx): From e38df261cb38d389598a26b6f6c61476ddfa52d8 Mon Sep 17 00:00:00 2001 From: Marc Jauvin Date: Tue, 13 Feb 2018 13:00:44 -0500 Subject: [PATCH 004/799] skip this test for qt>=5.10 until the log problem gets resolved --- tests/end2end/features/keyinput.feature | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/end2end/features/keyinput.feature b/tests/end2end/features/keyinput.feature index 08d368a71..29e9f94fa 100644 --- a/tests/end2end/features/keyinput.feature +++ b/tests/end2end/features/keyinput.feature @@ -178,6 +178,8 @@ Feature: Keyboard input And "Leaving mode KeyMode.passthrough (reason: tab changed)" should not be logged And "Entering mode KeyMode.insert (reason: restore)" should be logged + # we skip this for now because the logs are still present from the previous test with qt>=5.10 + @qt<5.10 Scenario: tabs.mode_on_change_persist Given I clean up open tabs And I set tabs.mode_on_change to persist From f94e12008a084cf0a44c330cd877edb6979445f3 Mon Sep 17 00:00:00 2001 From: Marc Jauvin Date: Tue, 13 Feb 2018 16:23:56 -0500 Subject: [PATCH 005/799] fix the tests by clearing history --- tests/end2end/features/keyinput.feature | 36 ++++++++++++------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/tests/end2end/features/keyinput.feature b/tests/end2end/features/keyinput.feature index 29e9f94fa..9c709ae09 100644 --- a/tests/end2end/features/keyinput.feature +++ b/tests/end2end/features/keyinput.feature @@ -163,25 +163,25 @@ Feature: Keyboard input Then the message "foo 3" should be shown And the message "foo 3" should be shown - Scenario: tabs.mode_on_change_restore - Given I clean up open tabs - And I set tabs.mode_on_change to restore + # :test all tabs.mode_on_change modes + + Scenario: mode on change normal + Given I run :history-clear --force + And I set tabs.mode_on_change to normal When I open about:blank And I run :enter-mode insert And I open about:blank in a new tab And I run :enter-mode passthrough And I run :tab-focus 1 Then "Entering mode KeyMode.insert (reason: command)" should be logged - And "Leaving mode KeyMode.insert (reason: tab changed)" should not be logged - And "Entering mode KeyMode.normal (reason: restore)" should be logged + And "Leaving mode KeyMode.insert (reason: tab changed)" should be logged + And "Entering mode KeyMode.normal (reason: restore)" should not be logged And "Entering mode KeyMode.passthrough (reason: command)" should be logged - And "Leaving mode KeyMode.passthrough (reason: tab changed)" should not be logged - And "Entering mode KeyMode.insert (reason: restore)" should be logged + And "Leaving mode KeyMode.passthrough (reason: tab changed)" should be logged + And "Entering mode KeyMode.insert (reason: restore)" should not be logged - # we skip this for now because the logs are still present from the previous test with qt>=5.10 - @qt<5.10 - Scenario: tabs.mode_on_change_persist - Given I clean up open tabs + Scenario: mode on change persist + Given I run :history-clear --force And I set tabs.mode_on_change to persist When I open about:blank And I run :enter-mode insert @@ -195,17 +195,17 @@ Feature: Keyboard input And "Leaving mode KeyMode.passthrough (reason: tab changed)" should not be logged And "Entering mode KeyMode.insert (reason: restore)" should not be logged - Scenario: tabs.mode_on_change_normal - Given I clean up open tabs - And I set tabs.mode_on_change to normal + Scenario: mode on change restore + Given I run :history-clear --force + And I set tabs.mode_on_change to restore When I open about:blank And I run :enter-mode insert And I open about:blank in a new tab And I run :enter-mode passthrough And I run :tab-focus 1 Then "Entering mode KeyMode.insert (reason: command)" should be logged - And "Leaving mode KeyMode.insert (reason: tab changed)" should be logged - And "Entering mode KeyMode.normal (reason: restore)" should not be logged + And "Leaving mode KeyMode.insert (reason: tab changed)" should not be logged + And "Entering mode KeyMode.normal (reason: restore)" should be logged And "Entering mode KeyMode.passthrough (reason: command)" should be logged - And "Leaving mode KeyMode.passthrough (reason: tab changed)" should be logged - And "Entering mode KeyMode.insert (reason: restore)" should not be logged + And "Leaving mode KeyMode.passthrough (reason: tab changed)" should not be logged + And "Entering mode KeyMode.insert (reason: restore)" should be logged From 9b8a182a78a2ced11063ca3b44faa1ff1f5696bb Mon Sep 17 00:00:00 2001 From: Marc Jauvin Date: Tue, 13 Feb 2018 17:03:01 -0500 Subject: [PATCH 006/799] history-clear does nothing to help here --- tests/end2end/features/keyinput.feature | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/tests/end2end/features/keyinput.feature b/tests/end2end/features/keyinput.feature index 9c709ae09..fb2e546b7 100644 --- a/tests/end2end/features/keyinput.feature +++ b/tests/end2end/features/keyinput.feature @@ -166,8 +166,7 @@ Feature: Keyboard input # :test all tabs.mode_on_change modes Scenario: mode on change normal - Given I run :history-clear --force - And I set tabs.mode_on_change to normal + Given I set tabs.mode_on_change to normal When I open about:blank And I run :enter-mode insert And I open about:blank in a new tab @@ -181,8 +180,7 @@ Feature: Keyboard input And "Entering mode KeyMode.insert (reason: restore)" should not be logged Scenario: mode on change persist - Given I run :history-clear --force - And I set tabs.mode_on_change to persist + Given I set tabs.mode_on_change to persist When I open about:blank And I run :enter-mode insert And I open about:blank in a new tab @@ -196,8 +194,7 @@ Feature: Keyboard input And "Entering mode KeyMode.insert (reason: restore)" should not be logged Scenario: mode on change restore - Given I run :history-clear --force - And I set tabs.mode_on_change to restore + Given I set tabs.mode_on_change to restore When I open about:blank And I run :enter-mode insert And I open about:blank in a new tab From 8a3049f09bad2cecb5be6eb3e0d39b649faa439e Mon Sep 17 00:00:00 2001 From: Marc Jauvin Date: Wed, 14 Feb 2018 08:33:32 -0500 Subject: [PATCH 007/799] make sure there IS a current widget before using it --- qutebrowser/mainwindow/tabbedbrowser.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py index 7d60369d3..a5cf7958e 100644 --- a/qutebrowser/mainwindow/tabbedbrowser.py +++ b/qutebrowser/mainwindow/tabbedbrowser.py @@ -637,7 +637,9 @@ class TabbedBrowser(tabwidget.TabWidget): input_modes = [usertypes.KeyMode.insert, usertypes.KeyMode.passthrough] if (mode in input_modes and config.val.tabs.mode_on_change == 'restore'): - self.currentWidget().data.input_mode = mode + tab = self.currentWidget() + if tab != 0: + tab.data.input_mode = mode @pyqtSlot(usertypes.KeyMode) def on_mode_left(self, mode): @@ -651,7 +653,9 @@ class TabbedBrowser(tabwidget.TabWidget): return widget.setFocus() elif config.val.tabs.mode_on_change == 'restore': - self.currentWidget().data.input_mode = usertypes.KeyMode.normal + tab = self.currentWidget() + if tab != 0: + tab.data.input_mode = usertypes.KeyMode.normal @pyqtSlot(int) def on_current_changed(self, idx): From b791dd3eb4966a41e6cebae76b9613b558eaaa20 Mon Sep 17 00:00:00 2001 From: Marc Jauvin Date: Wed, 14 Feb 2018 09:44:40 -0500 Subject: [PATCH 008/799] no restore while in prompt modes on tab change --- qutebrowser/mainwindow/tabbedbrowser.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py index a5cf7958e..64fdfed10 100644 --- a/qutebrowser/mainwindow/tabbedbrowser.py +++ b/qutebrowser/mainwindow/tabbedbrowser.py @@ -673,13 +673,18 @@ class TabbedBrowser(tabwidget.TabWidget): log.modes.debug("Current tab changed, focusing {!r}".format(tab)) tab.setFocus() + prompt_modes = [usertypes.KeyMode.prompt, usertypes.KeyMode.yesno] modes_to_leave = [usertypes.KeyMode.hint, usertypes.KeyMode.caret] + + mm_instance = modeman.instance(self._win_id) + current_mode = mm_instance.mode + if mode_on_change == 'normal': modes_to_leave += [usertypes.KeyMode.insert, usertypes.KeyMode.passthrough] for mode in modes_to_leave: modeman.leave(self._win_id, mode, 'tab changed', maybe=True) - if mode_on_change == 'restore': + if mode_on_change == 'restore' and current_mode not in prompt_modes: modeman.enter(self._win_id, tab.data.input_mode, 'restore') if self._now_focused is not None: objreg.register('last-focused-tab', self._now_focused, update=True, From 620a966d1e9d5e96382eeaf62e1368588bf7c0a3 Mon Sep 17 00:00:00 2001 From: Marc Jauvin Date: Wed, 14 Feb 2018 09:58:23 -0500 Subject: [PATCH 009/799] add debug logs and adjust tests to use them --- qutebrowser/mainwindow/tabbedbrowser.py | 5 ++++- tests/end2end/features/keyinput.feature | 27 +++++++++++-------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py index 64fdfed10..00da9ea86 100644 --- a/qutebrowser/mainwindow/tabbedbrowser.py +++ b/qutebrowser/mainwindow/tabbedbrowser.py @@ -678,7 +678,8 @@ class TabbedBrowser(tabwidget.TabWidget): mm_instance = modeman.instance(self._win_id) current_mode = mm_instance.mode - + log.modes.debug("{}: mode before tab change: {}." + .format(mode_on_change, current_mode.name)) if mode_on_change == 'normal': modes_to_leave += [usertypes.KeyMode.insert, usertypes.KeyMode.passthrough] @@ -689,6 +690,8 @@ class TabbedBrowser(tabwidget.TabWidget): if self._now_focused is not None: objreg.register('last-focused-tab', self._now_focused, update=True, scope='window', window=self._win_id) + log.modes.debug("{}: mode after tab change: {}." + .format(mode_on_change, mm_instance.mode.name)) self._now_focused = tab self.current_tab_changed.emit(tab) QTimer.singleShot(0, self._update_window_title) diff --git a/tests/end2end/features/keyinput.feature b/tests/end2end/features/keyinput.feature index fb2e546b7..db21831b6 100644 --- a/tests/end2end/features/keyinput.feature +++ b/tests/end2end/features/keyinput.feature @@ -167,42 +167,39 @@ Feature: Keyboard input Scenario: mode on change normal Given I set tabs.mode_on_change to normal + And I clean up open tabs When I open about:blank And I run :enter-mode insert And I open about:blank in a new tab - And I run :enter-mode passthrough - And I run :tab-focus 1 Then "Entering mode KeyMode.insert (reason: command)" should be logged And "Leaving mode KeyMode.insert (reason: tab changed)" should be logged - And "Entering mode KeyMode.normal (reason: restore)" should not be logged - And "Entering mode KeyMode.passthrough (reason: command)" should be logged - And "Leaving mode KeyMode.passthrough (reason: tab changed)" should be logged - And "Entering mode KeyMode.insert (reason: restore)" should not be logged + And "normal: mode before tab change: insert." should be logged + And "normal: mode after tab change: normal." should be logged Scenario: mode on change persist Given I set tabs.mode_on_change to persist + And I clean up open tabs When I open about:blank And I run :enter-mode insert And I open about:blank in a new tab - And I run :enter-mode passthrough - And I run :tab-focus 1 Then "Entering mode KeyMode.insert (reason: command)" should be logged - And "Leaving mode KeyMode.insert (reason: tab changed)" should not be logged - And "Entering mode KeyMode.normal (reason: restore)" should not be logged - And "Entering mode KeyMode.passthrough (reason: command)" should be logged - And "Leaving mode KeyMode.passthrough (reason: tab changed)" should not be logged - And "Entering mode KeyMode.insert (reason: restore)" should not be logged + And "persist: mode before tab change: insert." should be logged + And "persist: mode after tab change: insert." should be logged Scenario: mode on change restore Given I set tabs.mode_on_change to restore + And I clean up open tabs When I open about:blank And I run :enter-mode insert And I open about:blank in a new tab And I run :enter-mode passthrough And I run :tab-focus 1 Then "Entering mode KeyMode.insert (reason: command)" should be logged - And "Leaving mode KeyMode.insert (reason: tab changed)" should not be logged + And "restore: mode before tab change: insert." should be logged And "Entering mode KeyMode.normal (reason: restore)" should be logged + And "restore: mode after tab change: normal." should be logged And "Entering mode KeyMode.passthrough (reason: command)" should be logged - And "Leaving mode KeyMode.passthrough (reason: tab changed)" should not be logged + And "restore: mode before tab change: passthrough." should be logged And "Entering mode KeyMode.insert (reason: restore)" should be logged + And "restore: mode after tab change: insert." should be logged + From 872cff2ae18b908a4c4c2f6f6ffa1a5911b98729 Mon Sep 17 00:00:00 2001 From: Marc Jauvin Date: Fri, 16 Feb 2018 14:03:11 -0500 Subject: [PATCH 010/799] make sure tab is not None either, had a crash because of this --- qutebrowser/mainwindow/tabbedbrowser.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py index 00da9ea86..389cc1dc4 100644 --- a/qutebrowser/mainwindow/tabbedbrowser.py +++ b/qutebrowser/mainwindow/tabbedbrowser.py @@ -638,7 +638,7 @@ class TabbedBrowser(tabwidget.TabWidget): if (mode in input_modes and config.val.tabs.mode_on_change == 'restore'): tab = self.currentWidget() - if tab != 0: + if tab and tab is not None: tab.data.input_mode = mode @pyqtSlot(usertypes.KeyMode) @@ -654,7 +654,7 @@ class TabbedBrowser(tabwidget.TabWidget): widget.setFocus() elif config.val.tabs.mode_on_change == 'restore': tab = self.currentWidget() - if tab != 0: + if tab and tab is not None: tab.data.input_mode = usertypes.KeyMode.normal @pyqtSlot(int) From 5992688926d6a9bccaa10d5ab4080e549d4cbf0c Mon Sep 17 00:00:00 2001 From: Marc Jauvin Date: Wed, 7 Mar 2018 11:43:17 -0500 Subject: [PATCH 011/799] Save input modes when mode_on_change=='restore' --- qutebrowser/keyinput/modeman.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/qutebrowser/keyinput/modeman.py b/qutebrowser/keyinput/modeman.py index 585742d36..3f706d43c 100644 --- a/qutebrowser/keyinput/modeman.py +++ b/qutebrowser/keyinput/modeman.py @@ -236,6 +236,7 @@ class ModeManager(QObject): if mode not in self._parsers: raise ValueError("No keyparser for mode {}".format(mode)) prompt_modes = (usertypes.KeyMode.prompt, usertypes.KeyMode.yesno) + input_modes = [usertypes.KeyMode.insert, usertypes.KeyMode.passthrough] if self.mode == mode or (self.mode in prompt_modes and mode in prompt_modes): log.modes.debug("Ignoring request as we're in mode {} " @@ -249,7 +250,8 @@ class ModeManager(QObject): return log.modes.debug("Overriding mode {}.".format(self.mode)) self.left.emit(self.mode, mode, self._win_id) - if mode in prompt_modes: + if (mode in prompt_modes and self.mode in input_modes and + config.val.tabs.mode_on_change == 'restore'): # save previous mode when being prompted self._prev_mode = self.mode else: @@ -273,6 +275,7 @@ class ModeManager(QObject): 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) From 7c2802e84312904f83e24e2ef336d3731e863bde Mon Sep 17 00:00:00 2001 From: Marc Jauvin Date: Wed, 7 Mar 2018 11:46:14 -0500 Subject: [PATCH 012/799] beautify code as requested --- qutebrowser/mainwindow/tabbedbrowser.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py index 389cc1dc4..7914aca63 100644 --- a/qutebrowser/mainwindow/tabbedbrowser.py +++ b/qutebrowser/mainwindow/tabbedbrowser.py @@ -635,10 +635,9 @@ class TabbedBrowser(tabwidget.TabWidget): def on_mode_entered(self, mode): """Save input mode when tabs.mode_on_change = restore.""" input_modes = [usertypes.KeyMode.insert, usertypes.KeyMode.passthrough] - if (mode in input_modes and - config.val.tabs.mode_on_change == 'restore'): + if mode in input_modes and config.val.tabs.mode_on_change == 'restore': tab = self.currentWidget() - if tab and tab is not None: + if tab is not None: tab.data.input_mode = mode @pyqtSlot(usertypes.KeyMode) @@ -654,7 +653,7 @@ class TabbedBrowser(tabwidget.TabWidget): widget.setFocus() elif config.val.tabs.mode_on_change == 'restore': tab = self.currentWidget() - if tab and tab is not None: + if tab is not None: tab.data.input_mode = usertypes.KeyMode.normal @pyqtSlot(int) From c9f6cd507b55dabe4d4d8f7841955837a634ff20 Mon Sep 17 00:00:00 2001 From: Marc Jauvin Date: Tue, 13 Mar 2018 23:31:48 -0400 Subject: [PATCH 013/799] address requested changes - add INPUT_MODES & PROMPT_MODES constants in modeman - use those in tabbedbrowser and modeman - fix debug logs format to be more human readable - fix associated tests for new debug logs --- qutebrowser/keyinput/modeman.py | 16 ++++++------ qutebrowser/mainwindow/tabbedbrowser.py | 34 +++++++++++-------------- tests/end2end/features/keyinput.feature | 19 +++++++------- 3 files changed, 33 insertions(+), 36 deletions(-) diff --git a/qutebrowser/keyinput/modeman.py b/qutebrowser/keyinput/modeman.py index 3f706d43c..4e9d78fb0 100644 --- a/qutebrowser/keyinput/modeman.py +++ b/qutebrowser/keyinput/modeman.py @@ -30,6 +30,9 @@ from qutebrowser.config import config from qutebrowser.commands import cmdexc, cmdutils from qutebrowser.utils import usertypes, log, objreg, utils +INPUT_MODES = [usertypes.KeyMode.insert, usertypes.KeyMode.passthrough] +PROMPT_MODES = [usertypes.KeyMode.prompt, usertypes.KeyMode.yesno] + @attr.s(frozen=True) class KeyEvent: @@ -115,7 +118,7 @@ class ModeManager(QObject): Attributes: mode: The mode we're currently in. _win_id: The window ID of this ModeManager - _prev_mode: mode before a prompt poped up + _prev_mode: Mode before a prompt popped up _parsers: A dictionary of modes and their keyparsers. _forward_unbound_keys: If we should forward unbound keys. _releaseevents_to_pass: A set of KeyEvents where the keyPressEvent was @@ -235,10 +238,8 @@ class ModeManager(QObject): mode, '' if reason is None else ' (reason: {})'.format(reason))) if mode not in self._parsers: raise ValueError("No keyparser for mode {}".format(mode)) - prompt_modes = (usertypes.KeyMode.prompt, usertypes.KeyMode.yesno) - input_modes = [usertypes.KeyMode.insert, usertypes.KeyMode.passthrough] - if self.mode == mode or (self.mode in prompt_modes and - mode in prompt_modes): + if self.mode == mode or (self.mode in PROMPT_MODES and + mode in PROMPT_MODES): log.modes.debug("Ignoring request as we're in mode {} " "already.".format(self.mode)) return @@ -250,9 +251,8 @@ class ModeManager(QObject): return log.modes.debug("Overriding mode {}.".format(self.mode)) self.left.emit(self.mode, mode, self._win_id) - if (mode in prompt_modes and self.mode in input_modes and + if (mode in PROMPT_MODES and self.mode in INPUT_MODES and config.val.tabs.mode_on_change == 'restore'): - # save previous mode when being prompted self._prev_mode = self.mode else: self._prev_mode = usertypes.KeyMode.normal @@ -303,7 +303,7 @@ class ModeManager(QObject): self.clear_keychain() self.mode = usertypes.KeyMode.normal self.left.emit(mode, self.mode, self._win_id) - if mode in [usertypes.KeyMode.prompt, usertypes.KeyMode.yesno]: + if mode in PROMPT_MODES: self.enter(self._prev_mode, reason='restore mode before {}'.format(mode.name)) diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py index 7914aca63..df5ab5584 100644 --- a/qutebrowser/mainwindow/tabbedbrowser.py +++ b/qutebrowser/mainwindow/tabbedbrowser.py @@ -634,8 +634,8 @@ class TabbedBrowser(tabwidget.TabWidget): @pyqtSlot(usertypes.KeyMode) def on_mode_entered(self, mode): """Save input mode when tabs.mode_on_change = restore.""" - input_modes = [usertypes.KeyMode.insert, usertypes.KeyMode.passthrough] - if mode in input_modes and config.val.tabs.mode_on_change == 'restore': + if (config.val.tabs.mode_on_change == 'restore' and + mode in modeman.INPUT_MODES): tab = self.currentWidget() if tab is not None: tab.data.input_mode = mode @@ -643,18 +643,15 @@ class TabbedBrowser(tabwidget.TabWidget): @pyqtSlot(usertypes.KeyMode) def on_mode_left(self, mode): """Give focus to current tab if command mode was left.""" - if mode in [usertypes.KeyMode.command, usertypes.KeyMode.prompt, - usertypes.KeyMode.yesno]: - widget = self.currentWidget() + widget = self.currentWidget() + if widget is None: + return + if mode in [usertypes.KeyMode.command] + modeman.PROMPT_MODES: log.modes.debug("Left status-input mode, focusing {!r}".format( widget)) - if widget is None: - return widget.setFocus() - elif config.val.tabs.mode_on_change == 'restore': - tab = self.currentWidget() - if tab is not None: - tab.data.input_mode = usertypes.KeyMode.normal + if config.val.tabs.mode_on_change == 'restore': + widget.data.input_mode = usertypes.KeyMode.normal @pyqtSlot(int) def on_current_changed(self, idx): @@ -672,25 +669,24 @@ class TabbedBrowser(tabwidget.TabWidget): log.modes.debug("Current tab changed, focusing {!r}".format(tab)) tab.setFocus() - prompt_modes = [usertypes.KeyMode.prompt, usertypes.KeyMode.yesno] modes_to_leave = [usertypes.KeyMode.hint, usertypes.KeyMode.caret] mm_instance = modeman.instance(self._win_id) current_mode = mm_instance.mode - log.modes.debug("{}: mode before tab change: {}." - .format(mode_on_change, current_mode.name)) + log.modes.debug("Mode before tab change: {} (mode_on_change = {})" + .format(current_mode.name, mode_on_change)) if mode_on_change == 'normal': - modes_to_leave += [usertypes.KeyMode.insert, - usertypes.KeyMode.passthrough] + modes_to_leave += modeman.INPUT_MODES for mode in modes_to_leave: modeman.leave(self._win_id, mode, 'tab changed', maybe=True) - if mode_on_change == 'restore' and current_mode not in prompt_modes: + if (mode_on_change == 'restore' and + current_mode not in modeman.PROMPT_MODES): modeman.enter(self._win_id, tab.data.input_mode, 'restore') if self._now_focused is not None: objreg.register('last-focused-tab', self._now_focused, update=True, scope='window', window=self._win_id) - log.modes.debug("{}: mode after tab change: {}." - .format(mode_on_change, mm_instance.mode.name)) + log.modes.debug("Mode after tab change: {} (mode_on_change = {})" + .format(current_mode.name, mode_on_change)) self._now_focused = tab self.current_tab_changed.emit(tab) QTimer.singleShot(0, self._update_window_title) diff --git a/tests/end2end/features/keyinput.feature b/tests/end2end/features/keyinput.feature index db21831b6..5a857002a 100644 --- a/tests/end2end/features/keyinput.feature +++ b/tests/end2end/features/keyinput.feature @@ -163,7 +163,7 @@ Feature: Keyboard input Then the message "foo 3" should be shown And the message "foo 3" should be shown - # :test all tabs.mode_on_change modes + # test all tabs.mode_on_change modes Scenario: mode on change normal Given I set tabs.mode_on_change to normal @@ -173,8 +173,8 @@ Feature: Keyboard input And I open about:blank in a new tab Then "Entering mode KeyMode.insert (reason: command)" should be logged And "Leaving mode KeyMode.insert (reason: tab changed)" should be logged - And "normal: mode before tab change: insert." should be logged - And "normal: mode after tab change: normal." should be logged + And "Mode before tab change: insert (mode_on_change = normal)" should be logged + And "Mode after tab change: normal (mode_on_change = normal)" should be logged Scenario: mode on change persist Given I set tabs.mode_on_change to persist @@ -183,8 +183,9 @@ Feature: Keyboard input And I run :enter-mode insert And I open about:blank in a new tab Then "Entering mode KeyMode.insert (reason: command)" should be logged - And "persist: mode before tab change: insert." should be logged - And "persist: mode after tab change: insert." should be logged + And "Leaving mode KeyMode.insert (reason: tab changed)" should not be logged + And "Mode before tab change: insert (mode_on_change = persist)" should be logged + And "Mode after tab change: insert (mode_on_change = persist)" should be logged Scenario: mode on change restore Given I set tabs.mode_on_change to restore @@ -195,11 +196,11 @@ Feature: Keyboard input And I run :enter-mode passthrough And I run :tab-focus 1 Then "Entering mode KeyMode.insert (reason: command)" should be logged - And "restore: mode before tab change: insert." should be logged + And "Mode before tab change: insert (mode_on_change = restore)" should be logged And "Entering mode KeyMode.normal (reason: restore)" should be logged - And "restore: mode after tab change: normal." should be logged + And "Mode after tab change: normal (mode_on_change = restore)" should be logged And "Entering mode KeyMode.passthrough (reason: command)" should be logged - And "restore: mode before tab change: passthrough." should be logged + And "Mode before tab change: passthrough (mode_on_change = restore)" should be logged And "Entering mode KeyMode.insert (reason: restore)" should be logged - And "restore: mode after tab change: insert." should be logged + And "Mode after tab change: insert (mode_on_change = restore)" should be logged From 262cea8e7513edbeb3e6ce6d6f24b41414acb723 Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Wed, 9 May 2018 11:02:42 -0700 Subject: [PATCH 014/799] Restore focus to webview after clicking tab-bg --- qutebrowser/browser/hints.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/qutebrowser/browser/hints.py b/qutebrowser/browser/hints.py index 715a4a4db..147bf23af 100644 --- a/qutebrowser/browser/hints.py +++ b/qutebrowser/browser/hints.py @@ -210,10 +210,10 @@ class HintActions: else: target_mapping[Target.tab] = usertypes.ClickTarget.tab + tabbed_browser = objreg.get('tabbed-browser', scope='window', + window=self._win_id) if context.target in [Target.normal, Target.current]: # Set the pre-jump mark ', so we can jump back here after following - tabbed_browser = objreg.get('tabbed-browser', scope='window', - window=self._win_id) tabbed_browser.set_mark("'") try: @@ -227,6 +227,10 @@ class HintActions: except webelem.Error as e: raise HintingError(str(e)) + if context.target == Target.tab_bg: + # We lost focus when clicking in the background, get it back. + tabbed_browser.widget.currentWidget().setFocus() + def yank(self, url, context): """Yank an element to the clipboard or primary selection. From bc9f178a080688df93293b26a7bcab596ce08289 Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Wed, 9 May 2018 15:34:01 -0700 Subject: [PATCH 015/799] Add test for tab-bg focus --- tests/end2end/data/hints/link_input.html | 25 ++++++++++++++++++++++++ tests/end2end/features/hints.feature | 11 +++++++++++ 2 files changed, 36 insertions(+) create mode 100644 tests/end2end/data/hints/link_input.html diff --git a/tests/end2end/data/hints/link_input.html b/tests/end2end/data/hints/link_input.html new file mode 100644 index 000000000..56d02433c --- /dev/null +++ b/tests/end2end/data/hints/link_input.html @@ -0,0 +1,25 @@ + + + + + + Simple link and input + + + + Follow me! +
+ With padding: +
+ With existing text (logs to JS):: +
+ + diff --git a/tests/end2end/features/hints.feature b/tests/end2end/features/hints.feature index c3b857bc4..7805082ad 100644 --- a/tests/end2end/features/hints.feature +++ b/tests/end2end/features/hints.feature @@ -203,6 +203,17 @@ Feature: Using hints And I run :fake-key new Then the javascript message "contents: existingnew" should be logged + Scenario: Typing input with existing text after opening a bg tab + When I open data/hints/link_input.html + And I run :click-element id qute-input-existing + And I wait for "Entering mode KeyMode.insert *" in the log + And I run :leave-mode + And I hint with args "all tab-bg" and follow a + And I wait until data/hello.txt is loaded + And I run :enter-mode insert + And I run :fake-key -g new + Then the javascript message "contents: existingnew" should be logged + ### iframes Scenario: Using :follow-hint inside an iframe When I open data/hints/iframe.html From 538b4fafdb2f3af6dbc7790a90542c7db2ec8856 Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Thu, 10 May 2018 09:30:28 -0700 Subject: [PATCH 016/799] Revert "Restore focus to webview after clicking tab-bg" This reverts commit 262cea8e7513edbeb3e6ce6d6f24b41414acb723. --- qutebrowser/browser/hints.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/qutebrowser/browser/hints.py b/qutebrowser/browser/hints.py index 147bf23af..715a4a4db 100644 --- a/qutebrowser/browser/hints.py +++ b/qutebrowser/browser/hints.py @@ -210,10 +210,10 @@ class HintActions: else: target_mapping[Target.tab] = usertypes.ClickTarget.tab - tabbed_browser = objreg.get('tabbed-browser', scope='window', - window=self._win_id) if context.target in [Target.normal, Target.current]: # Set the pre-jump mark ', so we can jump back here after following + tabbed_browser = objreg.get('tabbed-browser', scope='window', + window=self._win_id) tabbed_browser.set_mark("'") try: @@ -227,10 +227,6 @@ class HintActions: except webelem.Error as e: raise HintingError(str(e)) - if context.target == Target.tab_bg: - # We lost focus when clicking in the background, get it back. - tabbed_browser.widget.currentWidget().setFocus() - def yank(self, url, context): """Yank an element to the clipboard or primary selection. From 95093b82c9d7dfe1342e7db4b5fd2df3d3b8d8c5 Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Thu, 10 May 2018 09:34:38 -0700 Subject: [PATCH 017/799] Refocus webview after spawning a background tab --- qutebrowser/mainwindow/tabbedbrowser.py | 2 ++ tests/end2end/features/hints.feature | 11 ----------- tests/end2end/features/tabs.feature | 22 ++++++++++++++++++++++ 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py index 2d674e280..28c754a97 100644 --- a/qutebrowser/mainwindow/tabbedbrowser.py +++ b/qutebrowser/mainwindow/tabbedbrowser.py @@ -487,6 +487,8 @@ class TabbedBrowser(QWidget): tab.resize(self.widget.currentWidget().size()) self.widget.tab_index_changed.emit(self.widget.currentIndex(), self.widget.count()) + # Refocus webview in case we lost it by spawning a bg tab + self.widget.currentWidget().setFocus() else: self.widget.setCurrentWidget(tab) # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-68076 diff --git a/tests/end2end/features/hints.feature b/tests/end2end/features/hints.feature index 7805082ad..c3b857bc4 100644 --- a/tests/end2end/features/hints.feature +++ b/tests/end2end/features/hints.feature @@ -203,17 +203,6 @@ Feature: Using hints And I run :fake-key new Then the javascript message "contents: existingnew" should be logged - Scenario: Typing input with existing text after opening a bg tab - When I open data/hints/link_input.html - And I run :click-element id qute-input-existing - And I wait for "Entering mode KeyMode.insert *" in the log - And I run :leave-mode - And I hint with args "all tab-bg" and follow a - And I wait until data/hello.txt is loaded - And I run :enter-mode insert - And I run :fake-key -g new - Then the javascript message "contents: existingnew" should be logged - ### iframes Scenario: Using :follow-hint inside an iframe When I open data/hints/iframe.html diff --git a/tests/end2end/features/tabs.feature b/tests/end2end/features/tabs.feature index 1841ef9c9..a19bc7a97 100644 --- a/tests/end2end/features/tabs.feature +++ b/tests/end2end/features/tabs.feature @@ -1250,3 +1250,25 @@ Feature: Tab management Then the following tabs should be open: - data/numbers/1.txt - data/numbers/2.txt (pinned) (active) + + + Scenario: Focused webview after clicking link in bg + When I open data/hints/link_input.html + And I run :click-element id qute-input-existing + And I wait for "Entering mode KeyMode.insert *" in the log + And I run :leave-mode + And I hint with args "all tab-bg" and follow a + And I wait until data/hello.txt is loaded + And I run :enter-mode insert + And I run :fake-key -g new + Then the javascript message "contents: existingnew" should be logged + + Scenario: Focused webview after opening link in bg + When I open data/hints/link_input.html + And I run :click-element id qute-input-existing + And I wait for "Entering mode KeyMode.insert *" in the log + And I run :leave-mode + And I open data/hello.txt in a new background tab + And I run :enter-mode insert + And I run :fake-key -g new + Then the javascript message "contents: existingnew" should be logged From 71d55e92135aba9f11db3691028dab5e59b4e529 Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Fri, 11 May 2018 08:45:43 -0700 Subject: [PATCH 018/799] Refocus command prompt after a new tab is opened when in command mode --- qutebrowser/mainwindow/tabbedbrowser.py | 11 ++++++++++- tests/end2end/features/tabs.feature | 16 ++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py index 28c754a97..c473f914d 100644 --- a/qutebrowser/mainwindow/tabbedbrowser.py +++ b/qutebrowser/mainwindow/tabbedbrowser.py @@ -22,7 +22,7 @@ import functools import attr -from PyQt5.QtWidgets import QSizePolicy, QWidget +from PyQt5.QtWidgets import QSizePolicy, QWidget, QApplication from PyQt5.QtCore import pyqtSignal, pyqtSlot, QTimer, QUrl from PyQt5.QtGui import QIcon @@ -458,6 +458,8 @@ class TabbedBrowser(QWidget): "related {}, idx {}".format( url, background, related, idx)) + prev_focus = QApplication.focusWidget() + if (config.val.tabs.tabs_are_windows and self.widget.count() > 0 and not ignore_tabs_are_windows): window = mainwindow.MainWindow(private=self.private) @@ -494,6 +496,13 @@ class TabbedBrowser(QWidget): # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-68076 tab.setFocus() + mode = modeman.instance(self._win_id).mode + if mode in [usertypes.KeyMode.command, usertypes.KeyMode.prompt, + usertypes.KeyMode.yesno]: + # If we were in a command prompt, restore old focus + # The above commands need to be run to switch tabs + prev_focus.setFocus() + tab.show() self.new_tab.emit(tab, idx) return tab diff --git a/tests/end2end/features/tabs.feature b/tests/end2end/features/tabs.feature index a19bc7a97..5f5ae5d29 100644 --- a/tests/end2end/features/tabs.feature +++ b/tests/end2end/features/tabs.feature @@ -1272,3 +1272,19 @@ Feature: Tab management And I run :enter-mode insert And I run :fake-key -g new Then the javascript message "contents: existingnew" should be logged + + Scenario: Focused prompt after opening link in bg + When I open data/hints/link_input.html + When I run :set-cmd-text -s :message-info + And I open data/hello.txt in a new background tab + And I run :fake-key -g hello-world + And I run :command-accept + Then the message "hello-world" should be shown + + Scenario: Focused prompt after opening link in fg + When I open data/hints/link_input.html + When I run :set-cmd-text -s :message-info + And I open data/hello.txt in a new tab + And I run :fake-key -g hello-world + And I run :command-accept + Then the message "hello-world" should be shown From 77c8575a882d9b35054164654ba03021fe3b6ba0 Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Wed, 16 May 2018 12:45:01 -0700 Subject: [PATCH 019/799] Only apply workaround for QTBUG-68076 on non-background tabs Previously, we would focus webviews even if they were in the background to work around https://bugreports.qt.io/browse/QTBUG-68076. This adjusts that to only occur when needed. --- qutebrowser/browser/browsertab.py | 9 ++++++++- qutebrowser/browser/webengine/webenginetab.py | 11 +++-------- qutebrowser/browser/webkit/webkittab.py | 4 +++- qutebrowser/mainwindow/tabbedbrowser.py | 7 ++++--- 4 files changed, 18 insertions(+), 13 deletions(-) diff --git a/qutebrowser/browser/browsertab.py b/qutebrowser/browser/browsertab.py index 2a9662eef..4d4f6380a 100644 --- a/qutebrowser/browser/browsertab.py +++ b/qutebrowser/browser/browsertab.py @@ -847,7 +847,14 @@ class AbstractTab(QWidget): if predict: self.predicted_navigation.emit(url) - def openurl(self, url, *, predict=True): + def openurl(self, url, *, predict=True, background=False): + """Open the given URL in this tab. + + Arguments: + url: The QUrl to open. + predict: If set to False, predicted_navigation is not emitted. + background: Whether the tab is being opened in the background. + """ raise NotImplementedError def reload(self, *, force=False): diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index fb04dc120..f0e2b85b6 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -782,15 +782,10 @@ class WebEngineTab(browsertab.AbstractTab): self.zoom.set_factor(self._saved_zoom) self._saved_zoom = None - def openurl(self, url, *, predict=True): - """Open the given URL in this tab. - - Arguments: - url: The QUrl to open. - predict: If set to False, predicted_navigation is not emitted. - """ + def openurl(self, url, *, predict=True, background=False): # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-68076 - self._widget.setFocus() + if not background: + self._widget.setFocus() self._saved_zoom = self.zoom.factor() self._openurl_prepare(url, predict=predict) self._widget.load(url) diff --git a/qutebrowser/browser/webkit/webkittab.py b/qutebrowser/browser/webkit/webkittab.py index 9d5305f10..352372ba4 100644 --- a/qutebrowser/browser/webkit/webkittab.py +++ b/qutebrowser/browser/webkit/webkittab.py @@ -673,9 +673,11 @@ class WebKitTab(browsertab.AbstractTab): settings = widget.settings() settings.setAttribute(QWebSettings.PrivateBrowsingEnabled, True) - def openurl(self, url, *, predict=True): + # pylint: disable=unused-argument + def openurl(self, url, *, predict=True, background=False): self._openurl_prepare(url, predict=predict) self._widget.openurl(url) + # pylint: enable=unused-argument def url(self, requested=False): frame = self._widget.page().mainFrame() diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py index c473f914d..0f9efc014 100644 --- a/qutebrowser/mainwindow/tabbedbrowser.py +++ b/qutebrowser/mainwindow/tabbedbrowser.py @@ -477,11 +477,12 @@ class TabbedBrowser(QWidget): idx = self._get_new_tab_idx(related) self.widget.insertTab(idx, tab, "") - if url is not None: - tab.openurl(url) - if background is None: background = config.val.tabs.background + + if url is not None: + tab.openurl(url, background=background) + if background: # Make sure the background tab has the correct initial size. # With a foreground tab, it's going to be resized correctly by the From 2f76ef1e53d609f14e5b57d548711fb286f3b487 Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Tue, 22 May 2018 23:17:43 -0700 Subject: [PATCH 020/799] Revert "Only apply workaround for QTBUG-68076 on non-background tabs" This reverts commit 77c8575a882d9b35054164654ba03021fe3b6ba0. --- qutebrowser/browser/browsertab.py | 9 +-------- qutebrowser/browser/webengine/webenginetab.py | 11 ++++++++--- qutebrowser/browser/webkit/webkittab.py | 4 +--- qutebrowser/mainwindow/tabbedbrowser.py | 7 +++---- 4 files changed, 13 insertions(+), 18 deletions(-) diff --git a/qutebrowser/browser/browsertab.py b/qutebrowser/browser/browsertab.py index 4d4f6380a..2a9662eef 100644 --- a/qutebrowser/browser/browsertab.py +++ b/qutebrowser/browser/browsertab.py @@ -847,14 +847,7 @@ class AbstractTab(QWidget): if predict: self.predicted_navigation.emit(url) - def openurl(self, url, *, predict=True, background=False): - """Open the given URL in this tab. - - Arguments: - url: The QUrl to open. - predict: If set to False, predicted_navigation is not emitted. - background: Whether the tab is being opened in the background. - """ + def openurl(self, url, *, predict=True): raise NotImplementedError def reload(self, *, force=False): diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index f0e2b85b6..fb04dc120 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -782,10 +782,15 @@ class WebEngineTab(browsertab.AbstractTab): self.zoom.set_factor(self._saved_zoom) self._saved_zoom = None - def openurl(self, url, *, predict=True, background=False): + def openurl(self, url, *, predict=True): + """Open the given URL in this tab. + + Arguments: + url: The QUrl to open. + predict: If set to False, predicted_navigation is not emitted. + """ # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-68076 - if not background: - self._widget.setFocus() + self._widget.setFocus() self._saved_zoom = self.zoom.factor() self._openurl_prepare(url, predict=predict) self._widget.load(url) diff --git a/qutebrowser/browser/webkit/webkittab.py b/qutebrowser/browser/webkit/webkittab.py index 352372ba4..9d5305f10 100644 --- a/qutebrowser/browser/webkit/webkittab.py +++ b/qutebrowser/browser/webkit/webkittab.py @@ -673,11 +673,9 @@ class WebKitTab(browsertab.AbstractTab): settings = widget.settings() settings.setAttribute(QWebSettings.PrivateBrowsingEnabled, True) - # pylint: disable=unused-argument - def openurl(self, url, *, predict=True, background=False): + def openurl(self, url, *, predict=True): self._openurl_prepare(url, predict=predict) self._widget.openurl(url) - # pylint: enable=unused-argument def url(self, requested=False): frame = self._widget.page().mainFrame() diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py index 0f9efc014..c473f914d 100644 --- a/qutebrowser/mainwindow/tabbedbrowser.py +++ b/qutebrowser/mainwindow/tabbedbrowser.py @@ -477,12 +477,11 @@ class TabbedBrowser(QWidget): idx = self._get_new_tab_idx(related) self.widget.insertTab(idx, tab, "") + if url is not None: + tab.openurl(url) + if background is None: background = config.val.tabs.background - - if url is not None: - tab.openurl(url, background=background) - if background: # Make sure the background tab has the correct initial size. # With a foreground tab, it's going to be resized correctly by the From 28fce9a7cb6b0e772b67a6227e3f61fc11ac8e31 Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Sat, 26 May 2018 13:52:57 -0700 Subject: [PATCH 021/799] Add support for opening background tabs in 5.11 Adding more workarounds to 442bdd4a4f153c9c5b728, *sigh* --- qutebrowser/browser/mouse.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/qutebrowser/browser/mouse.py b/qutebrowser/browser/mouse.py index 5320bfa6b..a59901e62 100644 --- a/qutebrowser/browser/mouse.py +++ b/qutebrowser/browser/mouse.py @@ -22,7 +22,7 @@ from PyQt5.QtCore import QObject, QEvent, Qt, QTimer from qutebrowser.config import config -from qutebrowser.utils import message, log, usertypes, qtutils +from qutebrowser.utils import message, log, usertypes, qtutils, objreg from qutebrowser.keyinput import modeman @@ -57,7 +57,22 @@ class ChildEventFilter(QObject): if qtutils.version_check('5.11', compiled=False, exact=True): # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-68076 - QTimer.singleShot(0, self._widget.setFocus) + try: + win_id = self._widget._win_id + PASSTHROUGH_MODES = [usertypes.KeyMode.command, + usertypes.KeyMode.prompt, + usertypes.KeyMode.yesno] + if modeman.instance(win_id).mode not in PASSTHROUGH_MODES: + tabbed_browser = objreg.get('tabbed-browser', scope='window', + window=win_id) + current_index = tabbed_browser.widget.currentIndex() + widget_index = self._widget.parent().tab_id + if current_index == widget_index: + QTimer.singleShot(0, self._widget.setFocus) + except: + # Something failed, let's just setFocus + QTimer.singleShot(0, self._widget.setFocus) + elif event.type() == QEvent.ChildRemoved: child = event.child() log.mouse.debug("{}: removed child {}".format(obj, child)) From 5d38d28feeb4597db8844e36ea5784505b2655ca Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Sun, 27 May 2018 17:09:18 -0700 Subject: [PATCH 022/799] Fix incorrect usage of tab_id --- qutebrowser/browser/mouse.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/qutebrowser/browser/mouse.py b/qutebrowser/browser/mouse.py index a59901e62..d7c2e2dc2 100644 --- a/qutebrowser/browser/mouse.py +++ b/qutebrowser/browser/mouse.py @@ -58,15 +58,22 @@ class ChildEventFilter(QObject): if qtutils.version_check('5.11', compiled=False, exact=True): # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-68076 try: + # pylint: disable=protected-access win_id = self._widget._win_id - PASSTHROUGH_MODES = [usertypes.KeyMode.command, - usertypes.KeyMode.prompt, - usertypes.KeyMode.yesno] - if modeman.instance(win_id).mode not in PASSTHROUGH_MODES: - tabbed_browser = objreg.get('tabbed-browser', scope='window', + # pylint: enable=protected-access + passthrough_modes = [usertypes.KeyMode.command, + usertypes.KeyMode.prompt, + usertypes.KeyMode.yesno] + if modeman.instance(win_id).mode not in passthrough_modes: + tabbed_browser = objreg.get('tabbed-browser', + scope='window', window=win_id) current_index = tabbed_browser.widget.currentIndex() - widget_index = self._widget.parent().tab_id + try: + widget_index = tabbed_browser.widget.indexOf( + self._widget.parent()) + except RuntimeError: + widget_index = -1 if current_index == widget_index: QTimer.singleShot(0, self._widget.setFocus) except: From e5b655256821dff1f3f67c974c3922b21eebf563 Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Wed, 13 Jun 2018 15:37:32 -0700 Subject: [PATCH 023/799] Clean up and simplify some logic --- qutebrowser/browser/mouse.py | 40 ++++++++----------- qutebrowser/browser/webengine/webenginetab.py | 2 +- 2 files changed, 18 insertions(+), 24 deletions(-) diff --git a/qutebrowser/browser/mouse.py b/qutebrowser/browser/mouse.py index d7c2e2dc2..24ced7d47 100644 --- a/qutebrowser/browser/mouse.py +++ b/qutebrowser/browser/mouse.py @@ -40,11 +40,12 @@ class ChildEventFilter(QObject): _widget: The widget expected to send out childEvents. """ - def __init__(self, eventfilter, widget, parent=None): + def __init__(self, eventfilter, widget, win_id, parent=None): super().__init__(parent) self._filter = eventfilter assert widget is not None self._widget = widget + self._win_id = win_id def eventFilter(self, obj, event): """Act on ChildAdded events.""" @@ -57,28 +58,21 @@ class ChildEventFilter(QObject): if qtutils.version_check('5.11', compiled=False, exact=True): # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-68076 - try: - # pylint: disable=protected-access - win_id = self._widget._win_id - # pylint: enable=protected-access - passthrough_modes = [usertypes.KeyMode.command, - usertypes.KeyMode.prompt, - usertypes.KeyMode.yesno] - if modeman.instance(win_id).mode not in passthrough_modes: - tabbed_browser = objreg.get('tabbed-browser', - scope='window', - window=win_id) - current_index = tabbed_browser.widget.currentIndex() - try: - widget_index = tabbed_browser.widget.indexOf( - self._widget.parent()) - except RuntimeError: - widget_index = -1 - if current_index == widget_index: - QTimer.singleShot(0, self._widget.setFocus) - except: - # Something failed, let's just setFocus - QTimer.singleShot(0, self._widget.setFocus) + pass_modes = [usertypes.KeyMode.command, + usertypes.KeyMode.prompt, + usertypes.KeyMode.yesno] + if modeman.instance(self._win_id).mode not in pass_modes: + tabbed_browser = objreg.get('tabbed-browser', + scope='window', + window=self._win_id) + current_index = tabbed_browser.widget.currentIndex() + try: + widget_index = tabbed_browser.widget.indexOf( + self._widget.parent()) + except RuntimeError: + widget_index = -1 + if current_index == widget_index: + QTimer.singleShot(0, self._widget.setFocus) elif event.type() == QEvent.ChildRemoved: child = event.child() diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index 4ce8b8bdd..d8ccd857c 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -769,7 +769,7 @@ class WebEngineTab(browsertab.AbstractTab): fp.installEventFilter(self._mouse_event_filter) self._child_event_filter = mouse.ChildEventFilter( eventfilter=self._mouse_event_filter, widget=self._widget, - parent=self) + win_id=self.win_id, parent=self) self._widget.installEventFilter(self._child_event_filter) @pyqtSlot() From 868cd115be9d29873caea5b391bf307685331394 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 14 Jun 2018 14:50:45 +0200 Subject: [PATCH 024/799] Remove old focus handling code --- qutebrowser/browser/webkit/webview.py | 32 +-------------------------- 1 file changed, 1 insertion(+), 31 deletions(-) diff --git a/qutebrowser/browser/webkit/webview.py b/qutebrowser/browser/webkit/webview.py index 79da9778c..3d56366c3 100644 --- a/qutebrowser/browser/webkit/webview.py +++ b/qutebrowser/browser/webkit/webview.py @@ -19,7 +19,7 @@ """The main browser widgets.""" -from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QUrl +from PyQt5.QtCore import pyqtSignal, Qt, QUrl from PyQt5.QtGui import QPalette from PyQt5.QtWidgets import QStyleFactory from PyQt5.QtWebKit import QWebSettings @@ -78,10 +78,6 @@ class WebView(QWebView): self.setPage(page) - mode_manager = objreg.get('mode-manager', scope='window', - window=win_id) - mode_manager.entered.connect(self.on_mode_entered) - mode_manager.left.connect(self.on_mode_left) config.instance.changed.connect(self._set_bg_color) def __repr__(self): @@ -130,32 +126,6 @@ class WebView(QWebView): """ self.load(url) - @pyqtSlot(usertypes.KeyMode) - def on_mode_entered(self, mode): - """Ignore attempts to focus the widget if in any status-input mode. - - FIXME:qtwebengine - For QtWebEngine, doing the same has no effect, so we do it in here. - """ - if mode in [usertypes.KeyMode.command, usertypes.KeyMode.prompt, - usertypes.KeyMode.yesno]: - log.webview.debug("Ignoring focus because mode {} was " - "entered.".format(mode)) - self.setFocusPolicy(Qt.NoFocus) - - @pyqtSlot(usertypes.KeyMode) - def on_mode_left(self, mode): - """Restore focus policy if status-input modes were left. - - FIXME:qtwebengine - For QtWebEngine, doing the same has no effect, so we do it in here. - """ - if mode in [usertypes.KeyMode.command, usertypes.KeyMode.prompt, - usertypes.KeyMode.yesno]: - log.webview.debug("Restoring focus policy because mode {} was " - "left.".format(mode)) - self.setFocusPolicy(Qt.WheelFocus) - def createWindow(self, wintype): """Called by Qt when a page wants to create a new window. From 7592186181cc2fb49b6498b3be9c1f683bfd43e1 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 14 Jun 2018 14:54:48 +0200 Subject: [PATCH 025/799] Update changelog --- doc/changelog.asciidoc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index e09c81ba7..4ea9a2d84 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -86,6 +86,11 @@ Changed - QtWebEngine: Various improvements to make the cursor more visible in caret browsing. +Fixed +~~~~~ + +- Various subtle keyboard focus issues. + Removed ~~~~~~~ From e4e982c0a76c3de8de2afc3e7b68ef30321ab520 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 14 Jun 2018 14:57:55 +0200 Subject: [PATCH 026/799] Remove unused variable --- qutebrowser/misc/crashsignal.py | 1 - 1 file changed, 1 deletion(-) diff --git a/qutebrowser/misc/crashsignal.py b/qutebrowser/misc/crashsignal.py index 26199a85c..2ca0dac1e 100644 --- a/qutebrowser/misc/crashsignal.py +++ b/qutebrowser/misc/crashsignal.py @@ -206,7 +206,6 @@ class CrashHandler(QObject): gracefully. """ exc = (exctype, excvalue, tb) - qapp = QApplication.instance() if not self._quitter.quit_status['crash']: log.misc.error("ARGH, there was an exception while the crash " From 11fce30ed0e30c0346c943127a49c8bce39a569a Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 14 Jun 2018 15:56:48 +0200 Subject: [PATCH 027/799] Stabilize mode_on_change tests --- tests/end2end/features/keyinput.feature | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/tests/end2end/features/keyinput.feature b/tests/end2end/features/keyinput.feature index 6dc7fa247..c0a9a3314 100644 --- a/tests/end2end/features/keyinput.feature +++ b/tests/end2end/features/keyinput.feature @@ -166,9 +166,10 @@ Feature: Keyboard input Scenario: mode on change normal Given I set tabs.mode_on_change to normal And I clean up open tabs - When I open about:blank + When I open data/hello.txt And I run :enter-mode insert - And I open about:blank in a new tab + And I open data/hello2.txt in a new background tab + And I run :tab-focus 2 Then "Entering mode KeyMode.insert (reason: command)" should be logged And "Leaving mode KeyMode.insert (reason: tab changed)" should be logged And "Mode before tab change: insert (mode_on_change = normal)" should be logged @@ -177,9 +178,10 @@ Feature: Keyboard input Scenario: mode on change persist Given I set tabs.mode_on_change to persist And I clean up open tabs - When I open about:blank + When I open data/hello.txt And I run :enter-mode insert - And I open about:blank in a new tab + And I open data/hello2.txt in a new background tab + And I run :tab-focus 2 Then "Entering mode KeyMode.insert (reason: command)" should be logged And "Leaving mode KeyMode.insert (reason: tab changed)" should not be logged And "Mode before tab change: insert (mode_on_change = persist)" should be logged @@ -188,9 +190,10 @@ Feature: Keyboard input Scenario: mode on change restore Given I set tabs.mode_on_change to restore And I clean up open tabs - When I open about:blank + When I open data/hello.txt And I run :enter-mode insert - And I open about:blank in a new tab + And I open data/hello2.txt in a new background tab + And I run :tab-focus 2 And I run :enter-mode passthrough And I run :tab-focus 1 Then "Entering mode KeyMode.insert (reason: command)" should be logged @@ -201,4 +204,3 @@ Feature: Keyboard input And "Mode before tab change: passthrough (mode_on_change = restore)" should be logged And "Entering mode KeyMode.insert (reason: restore)" should be logged And "Mode after tab change: insert (mode_on_change = restore)" should be logged - From 4dddc077534fb201954d4c240d0bbfd1df392816 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 14 Jun 2018 16:07:39 +0200 Subject: [PATCH 028/799] Make sure modeman.enter(KeyMode.normal) does something sensible --- qutebrowser/keyinput/modeman.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/qutebrowser/keyinput/modeman.py b/qutebrowser/keyinput/modeman.py index ffe780333..a2b23574b 100644 --- a/qutebrowser/keyinput/modeman.py +++ b/qutebrowser/keyinput/modeman.py @@ -241,6 +241,11 @@ class ModeManager(QObject): """ if not isinstance(mode, usertypes.KeyMode): raise TypeError("Mode {} is no KeyMode member!".format(mode)) + + if mode == usertypes.KeyMode.normal: + self.leave(self.mode, reason='enter normal: {}'.format(reason)) + return + log.modes.debug("Entering mode {}{}".format( mode, '' if reason is None else ' (reason: {})'.format(reason))) if mode not in self._parsers: From 07cf2f5b60efc7912dad1ed57299c7adec7cc466 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 14 Jun 2018 16:09:30 +0200 Subject: [PATCH 029/799] Unconditionally restore mode after prompt --- qutebrowser/keyinput/modeman.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/qutebrowser/keyinput/modeman.py b/qutebrowser/keyinput/modeman.py index a2b23574b..c06f18a1c 100644 --- a/qutebrowser/keyinput/modeman.py +++ b/qutebrowser/keyinput/modeman.py @@ -263,11 +263,12 @@ class ModeManager(QObject): return log.modes.debug("Overriding mode {}.".format(self.mode)) self.left.emit(self.mode, mode, self._win_id) - if (mode in PROMPT_MODES and self.mode in INPUT_MODES and - config.val.tabs.mode_on_change == 'restore'): + + if mode in PROMPT_MODES and self.mode in INPUT_MODES: self._prev_mode = self.mode else: self._prev_mode = usertypes.KeyMode.normal + self.mode = mode self.entered.emit(mode, self._win_id) From f4386fa9ea4c5ab6c37d023515ad0c29c702423e Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 14 Jun 2018 16:35:16 +0200 Subject: [PATCH 030/799] Update changelog --- doc/changelog.asciidoc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index 4ea9a2d84..b6dfa98b2 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -85,6 +85,8 @@ Changed - Improved error messages when a setting needs a newer Qt version. - QtWebEngine: Various improvements to make the cursor more visible in caret browsing. +- When a prompt is opened in insert/passthrough mode, the mode is restored + after closing the prompt. Fixed ~~~~~ From 746c2986f038c3c311397db6412aa3b645d4651b Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 14 Jun 2018 16:42:24 +0200 Subject: [PATCH 031/799] Fix test_stylesheet on Qt 5.11 --- tests/helpers/fixtures.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/helpers/fixtures.py b/tests/helpers/fixtures.py index b183933d2..ec562c3b4 100644 --- a/tests/helpers/fixtures.py +++ b/tests/helpers/fixtures.py @@ -165,7 +165,11 @@ def webkit_tab(qtbot, tab_registry, cookiejar_and_cache, mode_manager, @pytest.fixture def webengine_tab(qtbot, tab_registry, fake_args, mode_manager, session_manager_stub, greasemonkey_manager, - redirect_webengine_data): + redirect_webengine_data, tabbed_browser_stubs): + tabwidget = tabbed_browser_stubs[0].widget + tabwidget.current_index = 0 + tabwidget.index_of = 0 + webenginetab = pytest.importorskip( 'qutebrowser.browser.webengine.webenginetab') tab = webenginetab.WebEngineTab(win_id=0, mode_manager=mode_manager, From b1b06fcb4322d46b65819ce89311224965715c6d Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 14 Jun 2018 17:42:33 +0200 Subject: [PATCH 032/799] Fix restore test --- tests/end2end/features/keyinput.feature | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/end2end/features/keyinput.feature b/tests/end2end/features/keyinput.feature index c0a9a3314..f681012f1 100644 --- a/tests/end2end/features/keyinput.feature +++ b/tests/end2end/features/keyinput.feature @@ -198,7 +198,6 @@ Feature: Keyboard input And I run :tab-focus 1 Then "Entering mode KeyMode.insert (reason: command)" should be logged And "Mode before tab change: insert (mode_on_change = restore)" should be logged - And "Entering mode KeyMode.normal (reason: restore)" should be logged And "Mode after tab change: normal (mode_on_change = restore)" should be logged And "Entering mode KeyMode.passthrough (reason: command)" should be logged And "Mode before tab change: passthrough (mode_on_change = restore)" should be logged From 7654467f3610612b76872da4afd6b39214d24781 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 14 Jun 2018 17:43:20 +0200 Subject: [PATCH 033/799] Remove unused import --- qutebrowser/misc/crashsignal.py | 1 - 1 file changed, 1 deletion(-) diff --git a/qutebrowser/misc/crashsignal.py b/qutebrowser/misc/crashsignal.py index 2ca0dac1e..196613d62 100644 --- a/qutebrowser/misc/crashsignal.py +++ b/qutebrowser/misc/crashsignal.py @@ -36,7 +36,6 @@ except ImportError: import attr from PyQt5.QtCore import (pyqtSlot, qInstallMessageHandler, QObject, QSocketNotifier, QTimer, QUrl) -from PyQt5.QtWidgets import QApplication from qutebrowser.commands import cmdutils from qutebrowser.misc import earlyinit, crashdialog, ipc From e2ef39e8728f54c9de0aa4c21f1e790f4721e638 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 14 Jun 2018 23:29:16 +0200 Subject: [PATCH 034/799] Add Comment to .desktop file --- misc/qutebrowser.desktop | 1 + 1 file changed, 1 insertion(+) diff --git a/misc/qutebrowser.desktop b/misc/qutebrowser.desktop index 96cbda392..5243b0c17 100644 --- a/misc/qutebrowser.desktop +++ b/misc/qutebrowser.desktop @@ -1,6 +1,7 @@ [Desktop Entry] Name=qutebrowser GenericName=Web Browser +Comment=A keyboard-driven, vim-like browser based on PyQt5 Icon=qutebrowser Type=Application Categories=Network;WebBrowser; From 7b7e0c93f5b627480cee0a7c110d24cfbbeff3b7 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 16 Jun 2018 11:54:53 +0200 Subject: [PATCH 035/799] Update Chromium version in changelog --- doc/changelog.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index b6dfa98b2..35be61392 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -56,7 +56,7 @@ Changed ~~~~~~~ - The Windows/macOS releases now bundle Qt 5.11.1 which is based on - Chromium 65.0.3325.151 with security fixes up to Chromium 67.0.3396.79. + Chromium 65.0.3325.151 with security fixes up to Chromium 67.0.3396.87. - New short flags for commandline arguments: `-B` and `-T` for `--basedir` and `--temp-basedir`; `-d` and `-D` for `--debug` and `--debug-flag`. - Deleting history items via `:history-clear` or `:completion-item-del` now From a62aeb4abebd9a951b9bf915a4823e64ab71f10d Mon Sep 17 00:00:00 2001 From: Philip Date: Sun, 17 Jun 2018 05:33:53 +0200 Subject: [PATCH 036/799] Added support for searchengines listing in :open dialogue. Added settings for selecting what categories are shown in the :open dialogue. --- doc/help/settings.asciidoc | 13 +++++++++++++ qutebrowser/completion/models/urlmodel.py | 15 +++++++++++---- qutebrowser/config/configdata.yml | 13 +++++++++++++ 3 files changed, 37 insertions(+), 4 deletions(-) diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc index 9143a4c41..8bac566da 100644 --- a/doc/help/settings.asciidoc +++ b/doc/help/settings.asciidoc @@ -267,6 +267,7 @@ |<>|Page to open if :open -t/-b/-w is used without URL. |<>|URL segments where `:navigate increment/decrement` will search for a number. |<>|Open base URL of the searchengine if a searchengine shortcut is invoked without parameters. +|<>|Which categories should be shown in the :open dialogue. |<>|Search engines which can be used via the address bar. |<>|Page(s) to open at the start. |<>|URL parameters to strip with `:yank url`. @@ -3227,6 +3228,18 @@ Type: <> Default: +pass:[false]+ +[[url.open_categories_shown]] +=== url.open_categories_shown +Which categories should be shown in the :open dialogue. + +Type: <> + +Default +- +pass:[bookmarks]+ +- +pass:[searchengines]+ +- +pass:[history]+ +- +pass:[quickmarks]+ + [[url.searchengines]] === url.searchengines Search engines which can be used via the address bar. diff --git a/qutebrowser/completion/models/urlmodel.py b/qutebrowser/completion/models/urlmodel.py index bebf6d829..5b544ea3d 100644 --- a/qutebrowser/completion/models/urlmodel.py +++ b/qutebrowser/completion/models/urlmodel.py @@ -22,6 +22,7 @@ from qutebrowser.completion.models import (completionmodel, listcategory, histcategory) from qutebrowser.utils import log, objreg +from qutebrowser.config import config _URLCOL = 0 @@ -50,7 +51,7 @@ def _delete_quickmark(data): def url(*, info): - """A model which combines bookmarks, quickmarks and web history URLs. + """A model which combines bookmarks, quickmarks, search engines and web history URLs. Used for the `open` command. """ @@ -59,16 +60,22 @@ def url(*, info): quickmarks = [(url, name) for (name, url) in objreg.get('quickmark-manager').marks.items()] bookmarks = objreg.get('bookmark-manager').marks.items() + searchengines = config.val.url.searchengines.items() + categories = config.val.url.open_categories_shown - if quickmarks: + if searchengines and "searchengines" in categories: + model.add_category(listcategory.ListCategory( + 'Search engines', searchengines, sort=False)) + + if quickmarks and "quickmarks" in categories: model.add_category(listcategory.ListCategory( 'Quickmarks', quickmarks, delete_func=_delete_quickmark, sort=False)) - if bookmarks: + if bookmarks and "bookmarks" in categories: model.add_category(listcategory.ListCategory( 'Bookmarks', bookmarks, delete_func=_delete_bookmark, sort=False)) - if info.config.get('completion.web_history_max_items') != 0: + if info.config.get('completion.web_history_max_items') != 0 and "history" in categories: hist_cat = histcategory.HistoryCategory(delete_func=_delete_history) model.add_category(hist_cat) return model diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml index 49e037538..a5a5a067e 100644 --- a/qutebrowser/config/configdata.yml +++ b/qutebrowser/config/configdata.yml @@ -1565,6 +1565,19 @@ url.open_base_url: default: false desc: Open base URL of the searchengine if a searchengine shortcut is invoked without parameters. +url.open_categories_shown: + type: + name: List + valtype: String + none_ok: true + default: + - searchengines + - quickmarks + - bookmarks + - history + desc: Which categories should be shown in the :open dialogue. + + url.searchengines: default: DEFAULT: https://duckduckgo.com/?q={} From a2b1e041d6ca9c41be34927cca7a1b96a59d06bf Mon Sep 17 00:00:00 2001 From: Philip Date: Sun, 17 Jun 2018 10:29:57 +0200 Subject: [PATCH 037/799] Implemented use of the order of url.open_categories_shown in the :open dialogue. Reworded the description of this option to match. --- qutebrowser/completion/models/urlmodel.py | 19 ++++++++++++------- qutebrowser/config/configdata.yml | 2 +- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/qutebrowser/completion/models/urlmodel.py b/qutebrowser/completion/models/urlmodel.py index 5b544ea3d..6cb3b1501 100644 --- a/qutebrowser/completion/models/urlmodel.py +++ b/qutebrowser/completion/models/urlmodel.py @@ -62,20 +62,25 @@ def url(*, info): bookmarks = objreg.get('bookmark-manager').marks.items() searchengines = config.val.url.searchengines.items() categories = config.val.url.open_categories_shown + models = {} if searchengines and "searchengines" in categories: - model.add_category(listcategory.ListCategory( - 'Search engines', searchengines, sort=False)) + models["searchengines"] = listcategory.ListCategory( + 'Search engines', searchengines, sort=False) if quickmarks and "quickmarks" in categories: - model.add_category(listcategory.ListCategory( + models["quickmarks"] = listcategory.ListCategory( 'Quickmarks', quickmarks, delete_func=_delete_quickmark, - sort=False)) + sort=False) if bookmarks and "bookmarks" in categories: - model.add_category(listcategory.ListCategory( - 'Bookmarks', bookmarks, delete_func=_delete_bookmark, sort=False)) + models["bookmarks"] = listcategory.ListCategory( + 'Bookmarks', bookmarks, delete_func=_delete_bookmark, sort=False) if info.config.get('completion.web_history_max_items') != 0 and "history" in categories: hist_cat = histcategory.HistoryCategory(delete_func=_delete_history) - model.add_category(hist_cat) + models["history"] = hist_cat + + for category in categories: + model.add_category(models[category]) + return model diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml index a5a5a067e..d2176916c 100644 --- a/qutebrowser/config/configdata.yml +++ b/qutebrowser/config/configdata.yml @@ -1575,7 +1575,7 @@ url.open_categories_shown: - quickmarks - bookmarks - history - desc: Which categories should be shown in the :open dialogue. + desc: Which categories to show in the :open dialogue. The order of this list is used for ordering the categories. url.searchengines: From 0a9806daf3fb93f1525539eb82455305fd79d099 Mon Sep 17 00:00:00 2001 From: Philip Date: Sun, 17 Jun 2018 10:42:45 +0200 Subject: [PATCH 038/799] Fixed crash which could occur if user adds nonexistent category to url.open_categories_shown --- qutebrowser/completion/models/urlmodel.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qutebrowser/completion/models/urlmodel.py b/qutebrowser/completion/models/urlmodel.py index 6cb3b1501..1f06fe861 100644 --- a/qutebrowser/completion/models/urlmodel.py +++ b/qutebrowser/completion/models/urlmodel.py @@ -81,6 +81,7 @@ def url(*, info): models["history"] = hist_cat for category in categories: - model.add_category(models[category]) + if category in models: + model.add_category(models[category]) return model From 663d1a4d2f2f9d1797a8bb3c745e954f9f3ab4ab Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 17 Jun 2018 19:56:26 +0200 Subject: [PATCH 039/799] Read dictionaries from /usr/share/qt on Qt >= 5.10 Fixes #3759 Supersedes #3762 See #2939, #4003 --- doc/changelog.asciidoc | 3 +++ qutebrowser/browser/webengine/spell.py | 23 ++++++++++++++++--- .../browser/webengine/webenginesettings.py | 2 ++ scripts/dictcli.py | 3 +++ 4 files changed, 28 insertions(+), 3 deletions(-) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index 35be61392..58cc880c4 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -87,6 +87,9 @@ Changed browsing. - When a prompt is opened in insert/passthrough mode, the mode is restored after closing the prompt. +- On Qt 5.10 or newer, dictionaries are now read from the qutebrowser data + directory (e.g. `~/.local/share/qutebrowser`) instead of `/usr/share/qt`. + Existing dictionaries are copied over. Fixed ~~~~~ diff --git a/qutebrowser/browser/webengine/spell.py b/qutebrowser/browser/webengine/spell.py index 5c2ed551b..5a013db74 100644 --- a/qutebrowser/browser/webengine/spell.py +++ b/qutebrowser/browser/webengine/spell.py @@ -22,9 +22,11 @@ import glob import os import re +import os.path +import shutil from PyQt5.QtCore import QLibraryInfo -from qutebrowser.utils import log, message +from qutebrowser.utils import log, message, standarddir, qtutils dict_version_re = re.compile(r".+-(?P[0-9]+-[0-9]+?)\.bdic") @@ -39,9 +41,12 @@ def version(filename): return tuple(int(n) for n in match.group('version').split('-')) -def dictionary_dir(): +def dictionary_dir(old=False): """Return the path (str) to the QtWebEngine's dictionaries directory.""" - datapath = QLibraryInfo.location(QLibraryInfo.DataPath) + if qtutils.version_check('5.10', compiled=False) and not old: + datapath = standarddir.data() + else: + datapath = QLibraryInfo.location(QLibraryInfo.DataPath) return os.path.join(datapath, 'qtwebengine_dictionaries') @@ -73,3 +78,15 @@ def local_filename(code): """ all_installed = local_files(code) return os.path.splitext(all_installed[0])[0] if all_installed else None + + +def init(): + if qtutils.version_check('5.10', compiled=False): + new_dir = dictionary_dir() + old_dir = dictionary_dir(old=True) + os.environ['QTWEBENGINE_DICTIONARIES_PATH'] = new_dir + try: + if os.path.exists(old_dir) and not os.path.exists(new_dir): + shutil.copytree(old_dir, new_dir) + except OSError: + log.misc.exception("Failed to copy old dictionaries") diff --git a/qutebrowser/browser/webengine/webenginesettings.py b/qutebrowser/browser/webengine/webenginesettings.py index bae8aaffb..da569eef6 100644 --- a/qutebrowser/browser/webengine/webenginesettings.py +++ b/qutebrowser/browser/webengine/webenginesettings.py @@ -298,6 +298,8 @@ def init(args): not hasattr(QWebEnginePage, 'setInspectedPage')): # only Qt < 5.11 os.environ['QTWEBENGINE_REMOTE_DEBUGGING'] = str(utils.random_port()) + spell.init() + _init_profiles() config.instance.changed.connect(_update_settings) diff --git a/scripts/dictcli.py b/scripts/dictcli.py index 12c80b72d..4017159b6 100755 --- a/scripts/dictcli.py +++ b/scripts/dictcli.py @@ -36,6 +36,7 @@ import attr sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir)) from qutebrowser.browser.webengine import spell from qutebrowser.config import configdata +from qutebrowser.utils import standarddir API_URL = 'https://chromium.googlesource.com/chromium/deps/hunspell_dictionaries.git/+/master/' @@ -257,6 +258,8 @@ def remove_old(languages): def main(): if configdata.DATA is None: configdata.init() + standarddir.init(None) + parser = get_argparser() argv = sys.argv[1:] args = parser.parse_args(argv) From 2029f52fdcc30810db88bf2c68dcbeeaa3cf7d52 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 17 Jun 2018 20:52:35 +0200 Subject: [PATCH 040/799] Show cause when ~/.netrc can't be read --- doc/changelog.asciidoc | 2 ++ qutebrowser/browser/shared.py | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index 58cc880c4..c919eac5c 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -90,6 +90,8 @@ Changed - On Qt 5.10 or newer, dictionaries are now read from the qutebrowser data directory (e.g. `~/.local/share/qutebrowser`) instead of `/usr/share/qt`. Existing dictionaries are copied over. +- If an error while parsing `~/.netrc` occurs, the cause of the error is now + logged. Fixed ~~~~~ diff --git a/qutebrowser/browser/shared.py b/qutebrowser/browser/shared.py index 8ebbe3926..31f00f52c 100644 --- a/qutebrowser/browser/shared.py +++ b/qutebrowser/browser/shared.py @@ -312,10 +312,10 @@ def netrc_authentication(url, authenticator): (user, _account, password) = authenticators except FileNotFoundError: log.misc.debug("No .netrc file found") - except OSError: - log.misc.exception("Unable to read the netrc file") - except netrc.NetrcParseError: - log.misc.exception("Error when parsing the netrc file") + except OSError as e: + log.misc.exception("Unable to read the netrc file: {}".format(e)) + except netrc.NetrcParseError as e: + log.misc.exception("Error when parsing the netrc file: {}".format(e)) if user is None: return False From 3399f2df9698a3c7bb7b9da3cfa34467f01efcb7 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 17 Jun 2018 21:03:44 +0200 Subject: [PATCH 041/799] Always clear searches between page loads Looks like this wasn't properly fixed in Qt for some reason. Fixes #3693 See #2728 and bef372e5f5dd811eacd959de28f17355407cb7e2 --- doc/changelog.asciidoc | 1 + qutebrowser/browser/webengine/webenginetab.py | 9 ++++----- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index c919eac5c..f1eee3983 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -97,6 +97,7 @@ Fixed ~~~~~ - Various subtle keyboard focus issues. +- Workaround for a Qt bug which preserves searches between page loads. Removed ~~~~~~~ diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index 86f5e08c8..6739c2f66 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -1137,11 +1137,10 @@ class WebEngineTab(browsertab.AbstractTab): @pyqtSlot() def _on_load_started(self): """Clear search when a new load is started if needed.""" - if (qtutils.version_check('5.9', compiled=False) and - not qtutils.version_check('5.9.2', compiled=False)): - # WORKAROUND for - # https://bugreports.qt.io/browse/QTBUG-61506 - self.search.clear() + # WORKAROUND for + # https://bugreports.qt.io/browse/QTBUG-61506 + # (seems to be back in later Qt versions as well) + self.search.clear() super()._on_load_started() self.data.netrc_used = False From 91c0aae05b1e81df4caabd4451bf79b97c854202 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 18 Jun 2018 19:11:13 +0200 Subject: [PATCH 042/799] Update requests from 2.18.4 to 2.19.1 --- misc/requirements/requirements-codecov.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-codecov.txt b/misc/requirements/requirements-codecov.txt index 8ce859da3..6920af52c 100644 --- a/misc/requirements/requirements-codecov.txt +++ b/misc/requirements/requirements-codecov.txt @@ -5,5 +5,5 @@ chardet==3.0.4 codecov==2.0.15 coverage==4.5.1 idna==2.7 -requests==2.18.4 +requests==2.19.1 urllib3==1.22 From da8f76d0821cc72182e415df2fbccfff9b978523 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 18 Jun 2018 19:11:14 +0200 Subject: [PATCH 043/799] Update requests from 2.18.4 to 2.19.1 --- misc/requirements/requirements-pylint-master.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-pylint-master.txt b/misc/requirements/requirements-pylint-master.txt index 15555b9d6..e31217497 100644 --- a/misc/requirements/requirements-pylint-master.txt +++ b/misc/requirements/requirements-pylint-master.txt @@ -11,7 +11,7 @@ mccabe==0.6.1 -e git+https://github.com/PyCQA/pylint.git#egg=pylint python-dateutil==2.7.3 ./scripts/dev/pylint_checkers -requests==2.18.4 +requests==2.19.1 six==1.11.0 uritemplate==3.0.0 urllib3==1.22 From fa0e8c1b51fd828978a7417c3ba9d298a960a534 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 18 Jun 2018 19:11:16 +0200 Subject: [PATCH 044/799] Update requests from 2.18.4 to 2.19.1 --- 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 c5081b1a6..018d5a427 100644 --- a/misc/requirements/requirements-pylint.txt +++ b/misc/requirements/requirements-pylint.txt @@ -11,7 +11,7 @@ mccabe==0.6.1 pylint==1.9.2 python-dateutil==2.7.3 ./scripts/dev/pylint_checkers -requests==2.18.4 +requests==2.19.1 six==1.11.0 uritemplate==3.0.0 urllib3==1.22 From c3b76d1d01f45339857f46d814b24492f81cbd16 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 18 Jun 2018 19:11:17 +0200 Subject: [PATCH 045/799] Update cheroot from 6.3.1 to 6.3.2 --- 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 67a81ad63..a36409725 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -2,7 +2,7 @@ attrs==18.1.0 beautifulsoup4==4.6.0 -cheroot==6.3.1 +cheroot==6.3.2 click==6.7 # colorama==0.3.9 coverage==4.5.1 From 7c4eaa80b0e9b07f0c08f7b0ed63f31a009abb99 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 18 Jun 2018 19:11:19 +0200 Subject: [PATCH 046/799] Update hypothesis from 3.57.0 to 3.59.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 a36409725..e3e1c6d68 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -11,7 +11,7 @@ fields==5.0.0 Flask==1.0.2 glob2==0.6 hunter==2.0.2 -hypothesis==3.57.0 +hypothesis==3.59.1 itsdangerous==0.24 # Jinja2==2.10 Mako==1.0.7 From a73a778b9d55d5e69dcb20bec055636924c15891 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 18 Jun 2018 19:11:20 +0200 Subject: [PATCH 047/799] Update pytest-qt from 2.4.0 to 2.4.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 e3e1c6d68..334e740c3 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -29,7 +29,7 @@ pytest-cov==2.5.1 pytest-faulthandler==1.5.0 pytest-instafail==0.4.0 pytest-mock==1.10.0 -pytest-qt==2.4.0 +pytest-qt==2.4.1 pytest-repeat==0.4.1 pytest-rerunfailures==4.1 pytest-travis-fold==1.3.0 From 1919029858ef25b832790f06b269d334f3d75d76 Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Mon, 18 Jun 2018 18:09:13 -0400 Subject: [PATCH 048/799] Add setting for controlling stacking of new tabs --- qutebrowser/config/configdata.yml | 19 ++++++++++++-- qutebrowser/mainwindow/tabbedbrowser.py | 5 +++- tests/end2end/features/tabs.feature | 33 +++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 3 deletions(-) diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml index 49e037538..e17f6afac 100644 --- a/qutebrowser/config/configdata.yml +++ b/qutebrowser/config/configdata.yml @@ -1348,12 +1348,27 @@ tabs.mousewheel_switching: tabs.new_position.related: default: next type: NewTabPosition - desc: Position of new tabs opened from another tab. + desc: >- + Position of new tabs opened from another tab. + + See `tabs.new_position.stacking` for controlling stacking behavior. tabs.new_position.unrelated: default: last type: NewTabPosition - desc: "Position of new tabs which aren't opened from another tab." + desc: >- + Position of new tabs which are not opened from another tab. + + See `tabs.new_position.stacking` for controlling stacking behavior. + +tabs.new_position.stacking: + default: true + type: Bool + desc: >- + Stack relative tabs on top of each other when opened consecutively. + + Only applies `next` and `prev` values of `tabs.new_position.related` and + `tabs.new_position.unrelated`. tabs.padding: default: diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py index 2b74df72b..cc1be1c00 100644 --- a/qutebrowser/mainwindow/tabbedbrowser.py +++ b/qutebrowser/mainwindow/tabbedbrowser.py @@ -533,7 +533,10 @@ class TabbedBrowser(QWidget): # *before* the currently focused tab, indices will shift by # 1 automatically. elif pos == 'next': - idx = self._tab_insert_idx_right + if config.val.tabs.new_position.stacking: + idx = self._tab_insert_idx_right + else: + idx = self.widget.currentIndex() + 1 self._tab_insert_idx_right += 1 elif pos == 'first': idx = 0 diff --git a/tests/end2end/features/tabs.feature b/tests/end2end/features/tabs.feature index 5f5ae5d29..7cc63ce52 100644 --- a/tests/end2end/features/tabs.feature +++ b/tests/end2end/features/tabs.feature @@ -894,6 +894,39 @@ Feature: Tab management - about:blank - data/hello.txt (active) + # stacking tabs + Scenario: stacking tabs opening tab with tabs.new_position.related next + When I set tabs.new_position.related to next + And I set tabs.new_position.stacking to true + And I set tabs.background to true + And I open about:blank + And I open data/navigate/index.html in a new tab + And I hint with args "all tab-bg" and follow a + And I hint with args "all tab-bg" and follow s + And I wait until data/navigate/prev.html is loaded + And I wait until data/navigate/next.html is loaded + Then the following tabs should be open: + - about:blank + - data/navigate/index.html (active) + - data/navigate/prev.html + - data/navigate/next.html + + Scenario: no stacking tabs opening tab with tabs.new_position.related next + When I set tabs.new_position.related to next + And I set tabs.new_position.stacking to false + And I set tabs.background to true + And I open about:blank + And I open data/navigate/index.html in a new tab + And I hint with args "all tab-bg" and follow a + And I hint with args "all tab-bg" and follow s + And I wait until data/navigate/prev.html is loaded + And I wait until data/navigate/next.html is loaded + Then the following tabs should be open: + - about:blank + - data/navigate/index.html (active) + - data/navigate/next.html + - data/navigate/prev.html + # :buffer Scenario: :buffer without args or count From 0e7bbccd71a2a9d0fb18fba32e8389cd19d51aef Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Tue, 19 Jun 2018 12:06:26 -0400 Subject: [PATCH 049/799] Fix stacking tabs setting with new_tab prev --- qutebrowser/mainwindow/tabbedbrowser.py | 17 +++++++------ tests/end2end/features/tabs.feature | 32 +++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 7 deletions(-) diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py index cc1be1c00..bc054900a 100644 --- a/qutebrowser/mainwindow/tabbedbrowser.py +++ b/qutebrowser/mainwindow/tabbedbrowser.py @@ -526,18 +526,21 @@ class TabbedBrowser(QWidget): else: pos = config.val.tabs.new_position.unrelated if pos == 'prev': - idx = self._tab_insert_idx_left - # On first sight, we'd think we have to decrement - # self._tab_insert_idx_left here, as we want the next tab to be - # *before* the one we just opened. However, since we opened a tab - # *before* the currently focused tab, indices will shift by - # 1 automatically. + if config.val.tabs.new_position.stacking: + idx = self._tab_insert_idx_left + # On first sight, we'd think we have to decrement + # self._tab_insert_idx_left here, as we want the next tab to be + # *before* the one we just opened. However, since we opened a tab + # *before* the currently focused tab, indices will shift by + # 1 automatically. + else: + idx = self.widget.currentIndex() elif pos == 'next': if config.val.tabs.new_position.stacking: idx = self._tab_insert_idx_right + self._tab_insert_idx_right += 1 else: idx = self.widget.currentIndex() + 1 - self._tab_insert_idx_right += 1 elif pos == 'first': idx = 0 elif pos == 'last': diff --git a/tests/end2end/features/tabs.feature b/tests/end2end/features/tabs.feature index 7cc63ce52..cb6fbd226 100644 --- a/tests/end2end/features/tabs.feature +++ b/tests/end2end/features/tabs.feature @@ -911,6 +911,22 @@ Feature: Tab management - data/navigate/prev.html - data/navigate/next.html + Scenario: stacking tabs opening tab with tabs.new_position.related prev + When I set tabs.new_position.related to prev + And I set tabs.new_position.stacking to true + And I set tabs.background to true + And I open about:blank + And I open data/navigate/index.html in a new tab + And I hint with args "all tab-bg" and follow a + And I hint with args "all tab-bg" and follow s + And I wait until data/navigate/prev.html is loaded + And I wait until data/navigate/next.html is loaded + Then the following tabs should be open: + - about:blank + - data/navigate/next.html + - data/navigate/prev.html + - data/navigate/index.html (active) + Scenario: no stacking tabs opening tab with tabs.new_position.related next When I set tabs.new_position.related to next And I set tabs.new_position.stacking to false @@ -927,6 +943,22 @@ Feature: Tab management - data/navigate/next.html - data/navigate/prev.html + Scenario: no stacking tabs opening tab with tabs.new_position.related prev + When I set tabs.new_position.related to prev + And I set tabs.new_position.stacking to false + And I set tabs.background to true + And I open about:blank + And I open data/navigate/index.html in a new tab + And I hint with args "all tab-bg" and follow a + And I hint with args "all tab-bg" and follow s + And I wait until data/navigate/prev.html is loaded + And I wait until data/navigate/next.html is loaded + Then the following tabs should be open: + - about:blank + - data/navigate/prev.html + - data/navigate/next.html + - data/navigate/index.html (active) + # :buffer Scenario: :buffer without args or count From e5405f0ae940a62d7be756acb1ae9549108742a3 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 21 Jun 2018 00:21:52 +0200 Subject: [PATCH 050/799] Properly add QtQuickWidgets dependency --- README.asciidoc | 1 + doc/changelog.asciidoc | 4 ++++ qutebrowser/misc/earlyinit.py | 1 + 3 files changed, 6 insertions(+) diff --git a/README.asciidoc b/README.asciidoc index db401d49d..89ee3a610 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -100,6 +100,7 @@ The following software and libraries are required to run qutebrowser: * http://qt.io/[Qt] 5.7.1 or newer (5.10 recommended) with the following modules: - QtCore / qtbase - QtQuick (part of qtbase in some distributions) + - QtQuickWidgets (part of qtbase/QtQuick in some distributions) - QtSQL (part of qtbase in some distributions) - QtOpenGL - QtWebEngine, or diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index f1eee3983..d796933ed 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -55,6 +55,10 @@ Added Changed ~~~~~~~ +- New dependency on the `PyQt5.QtQuickWidgets` module (which was already + accidentally introduced in v1.3.2). For most distributions, this is already + contained in the existing dependencies - only distributions which have + separate packages for PyQt submodules might be affected. - The Windows/macOS releases now bundle Qt 5.11.1 which is based on Chromium 65.0.3325.151 with security fixes up to Chromium 67.0.3396.87. - New short flags for commandline arguments: `-B` and `-T` for `--basedir` and diff --git a/qutebrowser/misc/earlyinit.py b/qutebrowser/misc/earlyinit.py index 9649d27cc..f30e17124 100644 --- a/qutebrowser/misc/earlyinit.py +++ b/qutebrowser/misc/earlyinit.py @@ -233,6 +233,7 @@ def check_libraries(): 'PyQt5.QtQml': _missing_str("PyQt5.QtQml"), 'PyQt5.QtSql': _missing_str("PyQt5.QtSql"), 'PyQt5.QtOpenGL': _missing_str("PyQt5.QtOpenGL"), + 'PyQt5.QtQuickWidgets': _missing_str("PyQt5.QtQuickWidgets"), } _check_modules(modules) From 4887385bdd47fffd1b0695cfb8e401fe8cb90dd8 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 21 Jun 2018 01:40:36 +0200 Subject: [PATCH 051/799] Fix test_dictionary_dir --- tests/unit/browser/webengine/test_spell.py | 23 +++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/tests/unit/browser/webengine/test_spell.py b/tests/unit/browser/webengine/test_spell.py index e8ce3cecc..1cc2c5d0f 100644 --- a/tests/unit/browser/webengine/test_spell.py +++ b/tests/unit/browser/webengine/test_spell.py @@ -17,14 +17,14 @@ # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see . -"""Tests for qutebrowser.browser.webengine.spell module.""" - import logging import os +import pytest from PyQt5.QtCore import QLibraryInfo + from qutebrowser.browser.webengine import spell -from qutebrowser.utils import usertypes +from qutebrowser.utils import usertypes, qtutils, standarddir def test_version(message_mock, caplog): @@ -38,10 +38,19 @@ def test_version(message_mock, caplog): assert msg.text == expected -def test_dictionary_dir(monkeypatch): - monkeypatch.setattr(QLibraryInfo, 'location', lambda _: 'datapath') - assert spell.dictionary_dir() == os.path.join('datapath', - 'qtwebengine_dictionaries') +@pytest.mark.parametrize('qt_version, old, subdir', [ + ('5.9', True, 'global_datapath'), + ('5.9', False, 'global_datapath'), + ('5.10', True, 'global_datapath'), + ('5.10', False, 'user_datapath'), +]) +def test_dictionary_dir(monkeypatch, qt_version, old, subdir): + monkeypatch.setattr(qtutils, 'qVersion', lambda: qt_version) + monkeypatch.setattr(QLibraryInfo, 'location', lambda _: 'global_datapath') + monkeypatch.setattr(standarddir, 'data', lambda: 'user_datapath') + + expected = os.path.join(subdir, 'qtwebengine_dictionaries') + assert spell.dictionary_dir(old=old) == expected def test_local_filename_dictionary_does_not_exist(monkeypatch): From e7a300865c60f4f306bb7a592e1a57ad01d094c7 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 21 Jun 2018 01:43:09 +0200 Subject: [PATCH 052/799] Fix lint --- qutebrowser/browser/webengine/spell.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qutebrowser/browser/webengine/spell.py b/qutebrowser/browser/webengine/spell.py index 5a013db74..55d8aea5b 100644 --- a/qutebrowser/browser/webengine/spell.py +++ b/qutebrowser/browser/webengine/spell.py @@ -21,8 +21,8 @@ import glob import os -import re import os.path +import re import shutil from PyQt5.QtCore import QLibraryInfo @@ -81,6 +81,7 @@ def local_filename(code): def init(): + """Initialize the dictionary path if supported.""" if qtutils.version_check('5.10', compiled=False): new_dir = dictionary_dir() old_dir = dictionary_dir(old=True) From 9f5ca475c9778041a0c26292f4da7bb3de822bda Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 21 Jun 2018 01:44:15 +0200 Subject: [PATCH 053/799] Don't try to set focus if prev_focus is None --- qutebrowser/mainwindow/tabbedbrowser.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py index 2b74df72b..9c4473874 100644 --- a/qutebrowser/mainwindow/tabbedbrowser.py +++ b/qutebrowser/mainwindow/tabbedbrowser.py @@ -506,7 +506,8 @@ class TabbedBrowser(QWidget): usertypes.KeyMode.yesno]: # If we were in a command prompt, restore old focus # The above commands need to be run to switch tabs - prev_focus.setFocus() + if prev_focus is not None: + prev_focus.setFocus() tab.show() self.new_tab.emit(tab, idx) From c87757a9134b9940409ef78f838c8832c076a0a4 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 21 Jun 2018 16:35:17 +0200 Subject: [PATCH 054/799] Revert "Properly add QtQuickWidgets dependency" Looks like FreeBSD doesn't have QtQuickWidgets packaged at all, so let's do the same without requiring it... This reverts commit e5405f0ae940a62d7be756acb1ae9549108742a3. --- README.asciidoc | 1 - doc/changelog.asciidoc | 4 ---- qutebrowser/misc/earlyinit.py | 1 - 3 files changed, 6 deletions(-) diff --git a/README.asciidoc b/README.asciidoc index 89ee3a610..db401d49d 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -100,7 +100,6 @@ The following software and libraries are required to run qutebrowser: * http://qt.io/[Qt] 5.7.1 or newer (5.10 recommended) with the following modules: - QtCore / qtbase - QtQuick (part of qtbase in some distributions) - - QtQuickWidgets (part of qtbase/QtQuick in some distributions) - QtSQL (part of qtbase in some distributions) - QtOpenGL - QtWebEngine, or diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index d796933ed..f1eee3983 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -55,10 +55,6 @@ Added Changed ~~~~~~~ -- New dependency on the `PyQt5.QtQuickWidgets` module (which was already - accidentally introduced in v1.3.2). For most distributions, this is already - contained in the existing dependencies - only distributions which have - separate packages for PyQt submodules might be affected. - The Windows/macOS releases now bundle Qt 5.11.1 which is based on Chromium 65.0.3325.151 with security fixes up to Chromium 67.0.3396.87. - New short flags for commandline arguments: `-B` and `-T` for `--basedir` and diff --git a/qutebrowser/misc/earlyinit.py b/qutebrowser/misc/earlyinit.py index f30e17124..9649d27cc 100644 --- a/qutebrowser/misc/earlyinit.py +++ b/qutebrowser/misc/earlyinit.py @@ -233,7 +233,6 @@ def check_libraries(): 'PyQt5.QtQml': _missing_str("PyQt5.QtQml"), 'PyQt5.QtSql': _missing_str("PyQt5.QtSql"), 'PyQt5.QtOpenGL': _missing_str("PyQt5.QtOpenGL"), - 'PyQt5.QtQuickWidgets': _missing_str("PyQt5.QtQuickWidgets"), } _check_modules(modules) From 62d8b5b57497da86ddbfda359ef248e2fe706695 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 21 Jun 2018 17:14:29 +0200 Subject: [PATCH 055/799] Don't depend on PyQt5.QtQuickWidgets to get RWHV Some distributions (at least FreeBSD) don't package that module, so let's not rely on it. --- doc/changelog.asciidoc | 3 +++ qutebrowser/browser/webengine/webview.py | 10 +++++----- tests/end2end/fixtures/quteprocess.py | 3 --- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index f1eee3983..972ad8c9f 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -55,6 +55,9 @@ Added Changed ~~~~~~~ +- In v1.3.2 a dependency on the `PyQt5.QtQuickWidgets` module was accidentally + introduced. Since that module isn't packaged everywhere, it's been removed + again. - The Windows/macOS releases now bundle Qt 5.11.1 which is based on Chromium 65.0.3325.151 with security fixes up to Chromium 67.0.3396.87. - New short flags for commandline arguments: `-B` and `-T` for `--basedir` and diff --git a/qutebrowser/browser/webengine/webview.py b/qutebrowser/browser/webengine/webview.py index 8fce9edb9..7436bc01a 100644 --- a/qutebrowser/browser/webengine/webview.py +++ b/qutebrowser/browser/webengine/webview.py @@ -22,7 +22,7 @@ import sip from PyQt5.QtCore import pyqtSignal, pyqtSlot, QUrl, PYQT_VERSION from PyQt5.QtGui import QPalette -from PyQt5.QtQuickWidgets import QQuickWidget +from PyQt5.QtWidgets import QWidget from PyQt5.QtWebEngineWidgets import (QWebEngineView, QWebEnginePage, QWebEngineScript) @@ -71,10 +71,10 @@ class WebEngineView(QWebEngineView): if proxy is not None: return proxy - # This should only find the RenderWidgetHostViewQtDelegateWidget, - # but not e.g. a QMenu - children = [c for c in self.findChildren(QQuickWidget) - if c.isVisible()] + # We don't want e.g. a QMenu. + rwhv_class = 'QtWebEngineCore::RenderWidgetHostViewQtDelegateWidget' + children = [c for c in self.findChildren(QWidget) + if c.isVisible() and c.inherits(rwhv_class)] log.webview.debug("Found possibly lost focusProxy: {}" .format(children)) diff --git a/tests/end2end/fixtures/quteprocess.py b/tests/end2end/fixtures/quteprocess.py index 4101e6142..7311c7b38 100644 --- a/tests/end2end/fixtures/quteprocess.py +++ b/tests/end2end/fixtures/quteprocess.py @@ -361,9 +361,6 @@ class QuteProc(testprocess.Process): "Focus object changed: " "", - # Qt >= 5.11 with workarounds - "Focus object changed: " - "", ] if (log_line.category == 'ipc' and From 5a7869f2feaa346853d2a85413d6527c87ef0d9f Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 21 Jun 2018 21:20:19 +0200 Subject: [PATCH 056/799] Fix XSS issue on qute://history Fixes #4011 --- qutebrowser/browser/qutescheme.py | 6 ++++-- tests/end2end/data/issue4011.html | 10 ++++++++++ tests/end2end/features/history.feature | 5 +++++ 3 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 tests/end2end/data/issue4011.html diff --git a/qutebrowser/browser/qutescheme.py b/qutebrowser/browser/qutescheme.py index 6fc2a8429..7e60b265f 100644 --- a/qutebrowser/browser/qutescheme.py +++ b/qutebrowser/browser/qutescheme.py @@ -24,6 +24,7 @@ Module attributes: _HANDLERS: The handlers registered via decorators. """ +import html import json import os import time @@ -241,8 +242,9 @@ def history_data(start_time, offset=None): end_time = start_time - 24*60*60 entries = hist.entries_between(end_time, start_time) - return [{"url": e.url, "title": e.title or e.url, "time": e.atime} - for e in entries] + return [{"url": html.escape(e.url), + "title": html.escape(e.title) or html.escape(e.url), + "time": e.atime} for e in entries] @add_handler('history') diff --git a/tests/end2end/data/issue4011.html b/tests/end2end/data/issue4011.html new file mode 100644 index 000000000..488193736 --- /dev/null +++ b/tests/end2end/data/issue4011.html @@ -0,0 +1,10 @@ + + + + + <img src="x" onerror="console.log('XSS')">foo + + + foo + + diff --git a/tests/end2end/features/history.feature b/tests/end2end/features/history.feature index 2e2e1712a..13a890c10 100644 --- a/tests/end2end/features/history.feature +++ b/tests/end2end/features/history.feature @@ -112,3 +112,8 @@ Feature: Page history And I wait until qute://history is loaded Then the page should contain the plaintext "3.txt" Then the page should contain the plaintext "4.txt" + + Scenario: XSS in :history + When I open data/issue4011.html + And I open qute://history + Then the javascript message "XSS" should not be logged From d961eab1d246c2fd9a209ba322514e1bc401c0f8 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 21 Jun 2018 21:42:08 +0200 Subject: [PATCH 057/799] Update changelog for v1.3.3 --- doc/changelog.asciidoc | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index 972ad8c9f..a0dab79a6 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -100,7 +100,6 @@ Fixed ~~~~~ - Various subtle keyboard focus issues. -- Workaround for a Qt bug which preserves searches between page loads. Removed ~~~~~~~ @@ -109,13 +108,22 @@ Removed - The `content.developer_extras` setting got removed. On QtWebKit, developer extras are now automatically enabled when opening the inspector. -v1.3.3 (unreleased) -------------------- +v1.3.3 +------ + +Security +~~~~~~~~ + +- An XSS vulnerability on the `qute://history` page allowed websites to inject + HTML into the page via a crafted title tag. This could allow them to steal + your browsing history. If you're currently unable to upgrade, avoid using + `:history`. Fixed ~~~~~ - Crash in a workaround for a Qt 5.11 bug in rare circumstances. +- Workaround for a Qt bug which preserves searches between page loads. v1.3.2 ------ From 7a7e04a054fb1f9874dc13586598a11bf0cde8a0 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 21 Jun 2018 21:42:44 +0200 Subject: [PATCH 058/799] Move fix to v1.3.3 in changelog --- doc/changelog.asciidoc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index a0dab79a6..0da1254c5 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -55,9 +55,6 @@ Added Changed ~~~~~~~ -- In v1.3.2 a dependency on the `PyQt5.QtQuickWidgets` module was accidentally - introduced. Since that module isn't packaged everywhere, it's been removed - again. - The Windows/macOS releases now bundle Qt 5.11.1 which is based on Chromium 65.0.3325.151 with security fixes up to Chromium 67.0.3396.87. - New short flags for commandline arguments: `-B` and `-T` for `--basedir` and @@ -124,6 +121,9 @@ Fixed - Crash in a workaround for a Qt 5.11 bug in rare circumstances. - Workaround for a Qt bug which preserves searches between page loads. +- In v1.3.2 a dependency on the `PyQt5.QtQuickWidgets` module was accidentally + introduced. Since that module isn't packaged everywhere, it's been removed + again. v1.3.2 ------ From 9a5439e5d09c0840918ad37c72e25f9edaef8b2d Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 21 Jun 2018 22:22:04 +0200 Subject: [PATCH 059/799] Re-add waiting for QQuickWidget Apparently this is still needed on some PyQt versions. --- tests/end2end/fixtures/quteprocess.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/end2end/fixtures/quteprocess.py b/tests/end2end/fixtures/quteprocess.py index 7311c7b38..4101e6142 100644 --- a/tests/end2end/fixtures/quteprocess.py +++ b/tests/end2end/fixtures/quteprocess.py @@ -361,6 +361,9 @@ class QuteProc(testprocess.Process): "Focus object changed: " "", + # Qt >= 5.11 with workarounds + "Focus object changed: " + "", ] if (log_line.category == 'ipc' and From 0864ad406927a0810aaca307909f8665778e4713 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 21 Jun 2018 22:28:27 +0200 Subject: [PATCH 060/799] Fix shadowing of 'html' name --- qutebrowser/browser/qutescheme.py | 84 +++++++++++++++---------------- 1 file changed, 42 insertions(+), 42 deletions(-) diff --git a/qutebrowser/browser/qutescheme.py b/qutebrowser/browser/qutescheme.py index 7e60b265f..7c9f9b1aa 100644 --- a/qutebrowser/browser/qutescheme.py +++ b/qutebrowser/browser/qutescheme.py @@ -124,12 +124,12 @@ class add_handler: # noqa: N801,N806 pylint: disable=invalid-name def wrong_backend_handler(self, url): """Show an error page about using the invalid backend.""" - html = jinja.render('error.html', - title="Error while opening qute://url", - url=url.toDisplayString(), - error='{} is not available with this ' - 'backend'.format(url.toDisplayString())) - return 'text/html', html + src = jinja.render('error.html', + title="Error while opening qute://url", + url=url.toDisplayString(), + error='{} is not available with this ' + 'backend'.format(url.toDisplayString())) + return 'text/html', src def data_for_url(url): @@ -197,11 +197,11 @@ def qute_bookmarks(_url): quickmarks = sorted(objreg.get('quickmark-manager').marks.items(), key=lambda x: x[0]) # Sort by name - html = jinja.render('bookmarks.html', - title='Bookmarks', - bookmarks=bookmarks, - quickmarks=quickmarks) - return 'text/html', html + src = jinja.render('bookmarks.html', + title='Bookmarks', + bookmarks=bookmarks, + quickmarks=quickmarks) + return 'text/html', src @add_handler('tabs') @@ -219,10 +219,10 @@ def qute_tabs(_url): urlstr = tab.url().toDisplayString() tabs[str(win_id)].append((tab.title(), urlstr)) - html = jinja.render('tabs.html', - title='Tabs', - tab_list_by_window=tabs) - return 'text/html', html + src = jinja.render('tabs.html', + title='Tabs', + tab_list_by_window=tabs) + return 'text/html', src def history_data(start_time, offset=None): @@ -289,25 +289,25 @@ def qute_javascript(url): @add_handler('pyeval') def qute_pyeval(_url): """Handler for qute://pyeval.""" - html = jinja.render('pre.html', title='pyeval', content=pyeval_output) - return 'text/html', html + src = jinja.render('pre.html', title='pyeval', content=pyeval_output) + return 'text/html', src @add_handler('spawn-output') def qute_spawn_output(_url): """Handler for qute://spawn-output.""" - html = jinja.render('pre.html', title='spawn output', content=spawn_output) - return 'text/html', html + src = jinja.render('pre.html', title='spawn output', content=spawn_output) + return 'text/html', src @add_handler('version') @add_handler('verizon') def qute_version(_url): """Handler for qute://version.""" - html = jinja.render('version.html', title='Version info', - version=version.version(), - copyright=qutebrowser.__copyright__) - return 'text/html', html + src = jinja.render('version.html', title='Version info', + version=version.version(), + copyright=qutebrowser.__copyright__) + return 'text/html', src @add_handler('plainlog') @@ -325,8 +325,8 @@ def qute_plainlog(url): if not level: level = 'vdebug' text = log.ram_handler.dump_log(html=False, level=level) - html = jinja.render('pre.html', title='log', content=text) - return 'text/html', html + src = jinja.render('pre.html', title='log', content=text) + return 'text/html', src @add_handler('log') @@ -345,8 +345,8 @@ def qute_log(url): level = 'vdebug' html_log = log.ram_handler.dump_log(html=True, level=level) - html = jinja.render('log.html', title='log', content=html_log) - return 'text/html', html + src = jinja.render('log.html', title='log', content=html_log) + return 'text/html', src @add_handler('gpl') @@ -417,12 +417,12 @@ def qute_help(url): @add_handler('backend-warning') def qute_backend_warning(_url): """Handler for qute://backend-warning.""" - html = jinja.render('backend-warning.html', - distribution=version.distribution(), - Distribution=version.Distribution, - version=pkg_resources.parse_version, - title="Legacy backend warning") - return 'text/html', html + src = jinja.render('backend-warning.html', + distribution=version.distribution(), + Distribution=version.Distribution, + version=pkg_resources.parse_version, + title="Legacy backend warning") + return 'text/html', src def _qute_settings_set(url): @@ -452,10 +452,10 @@ def qute_settings(url): if url.path() == '/set': return _qute_settings_set(url) - html = jinja.render('settings.html', title='settings', - configdata=configdata, - confget=config.instance.get_str) - return 'text/html', html + src = jinja.render('settings.html', title='settings', + configdata=configdata, + confget=config.instance.get_str) + return 'text/html', src @add_handler('bindings') @@ -469,9 +469,9 @@ def qute_bindings(_url): for mode in modes: bindings[mode] = config.key_instance.get_bindings_for(mode) - html = jinja.render('bindings.html', title='Bindings', - bindings=bindings) - return 'text/html', html + src = jinja.render('bindings.html', title='Bindings', + bindings=bindings) + return 'text/html', src @add_handler('back') @@ -480,10 +480,10 @@ def qute_back(url): Simple page to free ram / lazy load a site, goes back on focusing the tab. """ - html = jinja.render( + src = jinja.render( 'back.html', title='Suspended: ' + urllib.parse.unquote(url.fragment())) - return 'text/html', html + return 'text/html', src @add_handler('configdiff') From 66fc3a30dd710712fa5b6d9dc2f6fa92689be021 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 21 Jun 2018 23:30:27 +0200 Subject: [PATCH 061/799] Update changelog --- doc/changelog.asciidoc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index 0da1254c5..472d482bc 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -114,7 +114,8 @@ Security - An XSS vulnerability on the `qute://history` page allowed websites to inject HTML into the page via a crafted title tag. This could allow them to steal your browsing history. If you're currently unable to upgrade, avoid using - `:history`. + `:history`. A CVE request for this issue is pending, see + https://github.com/qutebrowser/qutebrowser/issues/4011[#4011] for updates. Fixed ~~~~~ From d2254ca48b1f79512727ff4fba38a2b4222c6850 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 21 Jun 2018 23:30:51 +0200 Subject: [PATCH 062/799] Release v1.3.3 (cherry picked from commit ad9b50601c82f66646088e9ebdd66613eb2e93e2) --- qutebrowser/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutebrowser/__init__.py b/qutebrowser/__init__.py index 770f014ea..da982a7de 100644 --- a/qutebrowser/__init__.py +++ b/qutebrowser/__init__.py @@ -26,7 +26,7 @@ __copyright__ = "Copyright 2014-2018 Florian Bruhin (The Compiler)" __license__ = "GPL" __maintainer__ = __author__ __email__ = "mail@qutebrowser.org" -__version_info__ = (1, 3, 2) +__version_info__ = (1, 3, 3) __version__ = '.'.join(str(e) for e in __version_info__) __description__ = "A keyboard-driven, vim-like browser based on PyQt5." From a02c25dfb1d0db1a850933250b270f1390b43e21 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 23 Jun 2018 14:27:07 +0200 Subject: [PATCH 063/799] Don't escape URLs for qute://history We only use the URL to set a 'href' attribute, which does not need escaping. See #4011 Fixes #4012 --- doc/changelog.asciidoc | 3 +++ qutebrowser/browser/qutescheme.py | 2 +- qutebrowser/javascript/history.js | 2 +- tests/end2end/features/history.feature | 7 +++++++ tests/end2end/features/test_history_bdd.py | 14 ++++++++++++++ tests/end2end/fixtures/webserver_sub.py | 5 +++++ 6 files changed, 31 insertions(+), 2 deletions(-) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index 472d482bc..0fcd56eb1 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -97,6 +97,9 @@ Fixed ~~~~~ - Various subtle keyboard focus issues. +- The security fix in v1.3.3 caused URLs with ampersands + (`www.example.com?one=1&two=2`) to send the wrong arguments when clicked on + the `qute://history` page. Removed ~~~~~~~ diff --git a/qutebrowser/browser/qutescheme.py b/qutebrowser/browser/qutescheme.py index 7c9f9b1aa..e3483bac0 100644 --- a/qutebrowser/browser/qutescheme.py +++ b/qutebrowser/browser/qutescheme.py @@ -242,7 +242,7 @@ def history_data(start_time, offset=None): end_time = start_time - 24*60*60 entries = hist.entries_between(end_time, start_time) - return [{"url": html.escape(e.url), + return [{"url": e.url, "title": html.escape(e.title) or html.escape(e.url), "time": e.atime} for e in entries] diff --git a/qutebrowser/javascript/history.js b/qutebrowser/javascript/history.js index 417441bd9..093b95b4e 100644 --- a/qutebrowser/javascript/history.js +++ b/qutebrowser/javascript/history.js @@ -114,7 +114,7 @@ window.loadHistory = (function() { title.className = "title"; const link = document.createElement("a"); link.href = itemUrl; - link.innerHTML = itemTitle; + link.innerHTML = itemTitle; // Properly escaped in qutescheme.py const host = document.createElement("span"); host.className = "hostname"; host.innerHTML = link.hostname; diff --git a/tests/end2end/features/history.feature b/tests/end2end/features/history.feature index 13a890c10..0432c0705 100644 --- a/tests/end2end/features/history.feature +++ b/tests/end2end/features/history.feature @@ -117,3 +117,10 @@ Feature: Page history When I open data/issue4011.html And I open qute://history Then the javascript message "XSS" should not be logged + + Scenario: Escaping of URLs in :history + When I open query?one=1&two=2 + And I open qute://history + And I hint with args "links normal" and follow a + And I wait until query?one=1&two=2 is loaded + Then the query parameter two should be set to 2 diff --git a/tests/end2end/features/test_history_bdd.py b/tests/end2end/features/test_history_bdd.py index 6efa08330..4d477d832 100644 --- a/tests/end2end/features/test_history_bdd.py +++ b/tests/end2end/features/test_history_bdd.py @@ -17,6 +17,7 @@ # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see . +import json import logging import re @@ -34,6 +35,19 @@ def turn_on_sql_history(quteproc): quteproc.wait_for_load_finished_url('qute://pyeval') +@bdd.then(bdd.parsers.parse("the query parameter {name} should be set to " + "{value}")) +def check_query(quteproc, name, value): + """Check if a given query is set correctly. + + This assumes we're on the server query page. + """ + content = quteproc.get_content() + data = json.loads(content) + print(data) + assert data[name] == value + + @bdd.then(bdd.parsers.parse("the history should contain:\n{expected}")) def check_history(quteproc, server, tmpdir, expected): path = tmpdir / 'history' diff --git a/tests/end2end/fixtures/webserver_sub.py b/tests/end2end/fixtures/webserver_sub.py index 7d9af2ee3..15cd0becc 100644 --- a/tests/end2end/fixtures/webserver_sub.py +++ b/tests/end2end/fixtures/webserver_sub.py @@ -261,6 +261,11 @@ def response_headers(): return response +@app.route('/query') +def query(): + return flask.jsonify(flask.request.args) + + @app.route('/user-agent') def view_user_agent(): """Return User-Agent.""" From 454c532668633d0e53ceab3ae3c4cc05654b3022 Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Thu, 21 Jun 2018 18:58:48 -0400 Subject: [PATCH 064/799] Fix behavior when toggling stacking behavior in a single tab --- qutebrowser/config/configdata.yml | 2 +- qutebrowser/mainwindow/tabbedbrowser.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml index e17f6afac..d4cfcd020 100644 --- a/qutebrowser/config/configdata.yml +++ b/qutebrowser/config/configdata.yml @@ -1365,7 +1365,7 @@ tabs.new_position.stacking: default: true type: Bool desc: >- - Stack relative tabs on top of each other when opened consecutively. + Stack related tabs on top of each other when opened consecutively. Only applies `next` and `prev` values of `tabs.new_position.related` and `tabs.new_position.unrelated`. diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py index bc054900a..c0c303414 100644 --- a/qutebrowser/mainwindow/tabbedbrowser.py +++ b/qutebrowser/mainwindow/tabbedbrowser.py @@ -530,17 +530,17 @@ class TabbedBrowser(QWidget): idx = self._tab_insert_idx_left # On first sight, we'd think we have to decrement # self._tab_insert_idx_left here, as we want the next tab to be - # *before* the one we just opened. However, since we opened a tab - # *before* the currently focused tab, indices will shift by + # *before* the one we just opened. However, since we opened a + # tab *before* the currently focused tab, indices will shift by # 1 automatically. else: idx = self.widget.currentIndex() elif pos == 'next': if config.val.tabs.new_position.stacking: idx = self._tab_insert_idx_right - self._tab_insert_idx_right += 1 else: idx = self.widget.currentIndex() + 1 + self._tab_insert_idx_right += 1 elif pos == 'first': idx = 0 elif pos == 'last': From e6e844b039130d365b72e1570dab09483cbf47bf Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 24 Jun 2018 19:54:24 +0200 Subject: [PATCH 065/799] Support URL patterns for content.headers settings See #3636 --- doc/changelog.asciidoc | 5 +++++ doc/help/settings.asciidoc | 10 ++++++++++ qutebrowser/browser/shared.py | 8 ++++---- qutebrowser/browser/webengine/interceptor.py | 10 ++++++---- .../browser/webkit/network/networkmanager.py | 2 +- qutebrowser/browser/webkit/webpage.py | 2 +- qutebrowser/config/configdata.yml | 14 ++++++++++++-- 7 files changed, 39 insertions(+), 12 deletions(-) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index 0fcd56eb1..dd0b03092 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -55,6 +55,11 @@ Added Changed ~~~~~~~ +- The following settings now support URL patterns: + - content.headers.do_not_track + - content.headers.custom + - content.headers.accept_language + - content.headers.user_agent - The Windows/macOS releases now bundle Qt 5.11.1 which is based on Chromium 65.0.3325.151 with security fixes up to Chromium 67.0.3396.87. - New short flags for commandline arguments: `-B` and `-T` for `--basedir` and diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc index 9143a4c41..13818716d 100644 --- a/doc/help/settings.asciidoc +++ b/doc/help/settings.asciidoc @@ -1622,6 +1622,9 @@ Default: +pass:[ask]+ [[content.headers.accept_language]] === content.headers.accept_language Value to send in the `Accept-Language` header. +Note that the value read from JavaScript is always the global value. + +This setting supports URL patterns. Type: <> @@ -1631,6 +1634,8 @@ Default: +pass:[en-US,en]+ === content.headers.custom Custom headers for qutebrowser HTTP requests. +This setting supports URL patterns. + Type: <> Default: empty @@ -1640,6 +1645,8 @@ Default: empty Value to send in the `DNT` header. When this is set to true, qutebrowser asks websites to not track your identity. If set to null, the DNT header is not sent at all. +This setting supports URL patterns. + Type: <> Default: +pass:[true]+ @@ -1664,6 +1671,9 @@ This setting is only available with the QtWebKit backend. [[content.headers.user_agent]] === content.headers.user_agent User agent to send. Unset to send the default. +Note that the value read from JavaScript is always the global value. + +This setting supports URL patterns. Type: <> diff --git a/qutebrowser/browser/shared.py b/qutebrowser/browser/shared.py index 31f00f52c..92dfdeb03 100644 --- a/qutebrowser/browser/shared.py +++ b/qutebrowser/browser/shared.py @@ -34,21 +34,21 @@ class CallSuper(Exception): """Raised when the caller should call the superclass instead.""" -def custom_headers(): +def custom_headers(url): """Get the combined custom headers.""" headers = {} - dnt_config = config.val.content.headers.do_not_track + dnt_config = config.instance.get('content.headers.do_not_track', url=url) if dnt_config is not None: dnt = b'1' if dnt_config else b'0' headers[b'DNT'] = dnt headers[b'X-Do-Not-Track'] = dnt - conf_headers = config.val.content.headers.custom + conf_headers = config.instance.get('content.headers.custom', url=url) for header, value in conf_headers.items(): headers[header.encode('ascii')] = value.encode('ascii') - accept_language = config.val.content.headers.accept_language + accept_language = config.instance.get('content.headers.accept_language', url=url) if accept_language is not None: headers[b'Accept-Language'] = accept_language.encode('ascii') diff --git a/qutebrowser/browser/webengine/interceptor.py b/qutebrowser/browser/webengine/interceptor.py index b04b7962e..18386eed8 100644 --- a/qutebrowser/browser/webengine/interceptor.py +++ b/qutebrowser/browser/webengine/interceptor.py @@ -68,15 +68,17 @@ class RequestInterceptor(QWebEngineUrlRequestInterceptor): info.firstPartyUrl().toDisplayString(), resource_type, navigation_type)) + url = info.requestUrl() + # FIXME:qtwebengine only block ads for NavigationTypeOther? - if self._host_blocker.is_blocked(info.requestUrl()): + if self._host_blocker.is_blocked(url): log.webview.info("Request to {} blocked by host blocker.".format( - info.requestUrl().host())) + url.host())) info.block(True) - for header, value in shared.custom_headers(): + for header, value in shared.custom_headers(url=url): info.setHttpHeader(header, value) - user_agent = config.val.content.headers.user_agent + user_agent = config.instance.get('content.headers.user_agent', url=url) if user_agent is not None: info.setHttpHeader(b'User-Agent', user_agent.encode('ascii')) diff --git a/qutebrowser/browser/webkit/network/networkmanager.py b/qutebrowser/browser/webkit/network/networkmanager.py index a66802375..ad58cc984 100644 --- a/qutebrowser/browser/webkit/network/networkmanager.py +++ b/qutebrowser/browser/webkit/network/networkmanager.py @@ -380,7 +380,7 @@ class NetworkManager(QNetworkAccessManager): result.setParent(self) return result - for header, value in shared.custom_headers(): + for header, value in shared.custom_headers(url=req.url()): req.setRawHeader(header, value) host_blocker = objreg.get('host-blocker') diff --git a/qutebrowser/browser/webkit/webpage.py b/qutebrowser/browser/webkit/webpage.py index 6bbc27109..853ff1b81 100644 --- a/qutebrowser/browser/webkit/webpage.py +++ b/qutebrowser/browser/webkit/webpage.py @@ -415,7 +415,7 @@ class BrowserPage(QWebPage): def userAgentForUrl(self, url): """Override QWebPage::userAgentForUrl to customize the user agent.""" - ua = config.val.content.headers.user_agent + ua = config.instance.get('content.headers.user_agent', url=url) if ua is None: return super().userAgentForUrl(url) else: diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml index 49e037538..cf8e60771 100644 --- a/qutebrowser/config/configdata.yml +++ b/qutebrowser/config/configdata.yml @@ -356,8 +356,12 @@ content.headers.accept_language: type: name: String none_ok: true + supports_pattern: true default: en-US,en - desc: Value to send in the `Accept-Language` header. + desc: >- + Value to send in the `Accept-Language` header. + + Note that the value read from JavaScript is always the global value. content.headers.custom: default: {} @@ -370,6 +374,7 @@ content.headers.custom: name: String encoding: ascii none_ok: true + supports_pattern: true desc: Custom headers for qutebrowser HTTP requests. content.headers.do_not_track: @@ -377,6 +382,7 @@ content.headers.do_not_track: name: Bool none_ok: true default: true + supports_pattern: true desc: >- Value to send in the `DNT` header. @@ -451,7 +457,11 @@ content.headers.user_agent: Gecko" - IE 11.0 for Desktop Win7 64-bit - desc: User agent to send. Unset to send the default. + supports_pattern: true + desc: >- + User agent to send. Unset to send the default. + + Note that the value read from JavaScript is always the global value. content.host_blocking.enabled: default: true From f5e69b2174794bf55fb5dab24f65054ddeeddc4f Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 24 Jun 2018 19:57:52 +0200 Subject: [PATCH 066/799] Show inspector after creating it --- qutebrowser/browser/commands.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index b73d86a87..0fddcd1e7 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -1455,6 +1455,7 @@ class CommandDispatcher: if tab.data.inspector is None: tab.data.inspector = inspector.create() tab.data.inspector.inspect(page) + tab.data.inspector.show() else: tab.data.inspector.toggle(page) except inspector.WebInspectorError as e: From f2f481d991ed73fd54abf79929841ad51a53962d Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 24 Jun 2018 21:38:37 +0200 Subject: [PATCH 067/799] Support URL patterns for permissions and ssl_strict See #3636 --- doc/changelog.asciidoc | 7 +++++++ doc/help/settings.asciidoc | 14 ++++++++++++++ qutebrowser/browser/shared.py | 4 ++-- qutebrowser/config/configdata.yml | 7 +++++++ 4 files changed, 30 insertions(+), 2 deletions(-) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index dd0b03092..efd451976 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -40,11 +40,14 @@ Added * Support for requesting persistent storage via `navigator.webkitPersistentStorage.requestQuota` with a new `content.persistent_storage` setting (requires Qt 5.11). + This setting also supports URL patterns. * Support for registering custom protocol handlers via `navigator.registerProtocolHandler` with a new `content.register_protocol_handler` setting (requires Qt 5.11). + This setting also supports URL patterns. * Support for WebRTC screen sharing with a new `content.desktop_capture` setting (requires Qt 5.10). + This setting also supports URL patterns. * New `content.autoplay` setting to enable/disable automatic video playback (requires Qt 5.10). * New `content.webrtc_public_interfaces_only` setting to only expose public @@ -60,6 +63,10 @@ Changed - content.headers.custom - content.headers.accept_language - content.headers.user_agent + - content.ssl_strict + - content.geolocation + - content.notifications + - content.media_capture - The Windows/macOS releases now bundle Qt 5.11.1 which is based on Chromium 65.0.3325.151 with security fixes up to Chromium 67.0.3396.87. - New short flags for commandline arguments: `-B` and `-T` for `--basedir` and diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc index 13818716d..75e684ffc 100644 --- a/doc/help/settings.asciidoc +++ b/doc/help/settings.asciidoc @@ -1570,6 +1570,8 @@ Default: +pass:[iso-8859-1]+ Allow websites to share screen content. On Qt < 5.10, a dialog box is always displayed, even if this is set to "true". +This setting supports URL patterns. + Type: <> Valid values: @@ -1609,6 +1611,8 @@ This setting is only available with the QtWebKit backend. === content.geolocation Allow websites to request geolocations. +This setting supports URL patterns. + Type: <> Valid values: @@ -1853,6 +1857,8 @@ Default: +pass:[true]+ === content.media_capture Allow websites to record audio/video. +This setting supports URL patterns. + Type: <> Valid values: @@ -1878,6 +1884,8 @@ Default: empty === content.notifications Allow websites to show notifications. +This setting supports URL patterns. + Type: <> Valid values: @@ -1905,6 +1913,8 @@ This setting is only available with the QtWebKit backend. === content.persistent_storage Allow websites to request persistent storage quota via `navigator.webkitPersistentStorage.requestQuota`. +This setting supports URL patterns. + Type: <> Valid values: @@ -1977,6 +1987,8 @@ This setting is only available with the QtWebKit backend. === content.register_protocol_handler Allow websites to register protocol handlers via `navigator.registerProtocolHandler`. +This setting supports URL patterns. + Type: <> Valid values: @@ -1995,6 +2007,8 @@ On QtWebKit, this setting is unavailable. === content.ssl_strict Validate SSL handshakes. +This setting supports URL patterns. + Type: <> Valid values: diff --git a/qutebrowser/browser/shared.py b/qutebrowser/browser/shared.py index 92dfdeb03..01ccab13b 100644 --- a/qutebrowser/browser/shared.py +++ b/qutebrowser/browser/shared.py @@ -156,7 +156,7 @@ def ignore_certificate_errors(url, errors, abort_on): Return: True if the error should be ignored, False otherwise. """ - ssl_strict = config.val.content.ssl_strict + ssl_strict = config.instance.get('content.ssl_strict', url=url) log.webview.debug("Certificate errors {!r}, strict {}".format( errors, ssl_strict)) @@ -213,7 +213,7 @@ def feature_permission(url, option, msg, yes_action, no_action, abort_on, The Question object if a question was asked (and blocking=False), None otherwise. """ - config_val = config.instance.get(option) + config_val = config.instance.get(option, url=url) if config_val == 'ask': if url.isValid(): urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded) diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml index cf8e60771..2698c34b1 100644 --- a/qutebrowser/config/configdata.yml +++ b/qutebrowser/config/configdata.yml @@ -321,6 +321,7 @@ content.windowed_fullscreen: content.desktop_capture: type: BoolAsk default: ask + supports_pattern: true desc: >- Allow websites to share screen content. @@ -350,6 +351,7 @@ content.frame_flattening: content.geolocation: default: ask type: BoolAsk + supports_pattern: true desc: Allow websites to request geolocations. content.headers.accept_language: @@ -604,6 +606,7 @@ content.local_storage: content.media_capture: default: ask type: BoolAsk + supports_pattern: true backend: QtWebEngine desc: Allow websites to record audio/video. @@ -620,6 +623,7 @@ content.netrc_file: content.notifications: default: ask type: BoolAsk + supports_pattern: true backend: QtWebKit desc: Allow websites to show notifications. @@ -636,6 +640,7 @@ content.pdfjs: content.persistent_storage: default: ask type: BoolAsk + supports_pattern: true backend: QtWebKit: false QtWebEngine: Qt 5.11 @@ -682,6 +687,7 @@ content.proxy_dns_requests: content.register_protocol_handler: default: ask type: BoolAsk + supports_pattern: true backend: QtWebKit: false QtWebEngine: Qt 5.11 @@ -691,6 +697,7 @@ content.register_protocol_handler: content.ssl_strict: default: ask type: BoolAsk + supports_pattern: true desc: Validate SSL handshakes. content.user_stylesheets: From fc19262eaa953c6240f0756930df88e85fe5700b Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 24 Jun 2018 22:31:27 +0200 Subject: [PATCH 068/799] Fix test_shared.py --- tests/unit/browser/test_shared.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/unit/browser/test_shared.py b/tests/unit/browser/test_shared.py index f24f7ad97..78302d8c1 100644 --- a/tests/unit/browser/test_shared.py +++ b/tests/unit/browser/test_shared.py @@ -19,6 +19,8 @@ import pytest +from PyQt5.QtCore import QUrl + from qutebrowser.browser import shared @@ -47,4 +49,4 @@ def test_custom_headers(config_stub, dnt, accept_language, custom_headers, headers.custom = custom_headers expected_items = sorted(expected.items()) - assert shared.custom_headers() == expected_items + assert shared.custom_headers(QUrl()) == expected_items From 13f765a00089ae6eb5c9dda66b6efc35da58efe2 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 24 Jun 2018 22:33:46 +0200 Subject: [PATCH 069/799] Fix changelog formatting --- doc/changelog.asciidoc | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index efd451976..786ec712e 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -59,14 +59,14 @@ Changed ~~~~~~~ - The following settings now support URL patterns: - - content.headers.do_not_track - - content.headers.custom - - content.headers.accept_language - - content.headers.user_agent - - content.ssl_strict - - content.geolocation - - content.notifications - - content.media_capture + * `content.headers.do_not_track` + * `content.headers.custom` + * `content.headers.accept_language` + * `content.headers.user_agent` + * `content.ssl_strict` + * `content.geolocation` + * `content.notifications` + * `content.media_capture` - The Windows/macOS releases now bundle Qt 5.11.1 which is based on Chromium 65.0.3325.151 with security fixes up to Chromium 67.0.3396.87. - New short flags for commandline arguments: `-B` and `-T` for `--basedir` and From 6c9e23af4a67df4cf3b14f4232e15ef84464cc4d Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 25 Jun 2018 08:14:02 +0200 Subject: [PATCH 070/799] eslint: Turn off max-lines-per-function --- qutebrowser/javascript/.eslintrc.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/qutebrowser/javascript/.eslintrc.yaml b/qutebrowser/javascript/.eslintrc.yaml index 02081f7a7..6a46f96e9 100644 --- a/qutebrowser/javascript/.eslintrc.yaml +++ b/qutebrowser/javascript/.eslintrc.yaml @@ -57,3 +57,4 @@ rules: no-ternary: "off" max-lines: "off" multiline-ternary: ["error", "always-multiline"] + max-lines-per-function: "off" From da0a6305df06f0f53d0460680f7418f80502c5ff Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Mon, 25 Jun 2018 12:45:17 -0400 Subject: [PATCH 071/799] Fix crash when tab is closed after a per-domain forced reload --- 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 6739c2f66..23c26ef95 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -979,6 +979,9 @@ class WebEngineTab(browsertab.AbstractTab): url: The QUrl to open. predict: If set to False, predicted_navigation is not emitted. """ + if sip.isdeleted(self._widget): + # https://github.com/qutebrowser/qutebrowser/issues/3896 + return self._saved_zoom = self.zoom.factor() self._openurl_prepare(url, predict=predict) self._widget.load(url) From e1bc5389a23900356d20bf5191722c4ccab0c709 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 25 Jun 2018 19:21:12 +0200 Subject: [PATCH 072/799] Update pyqt5 from 5.10 to 5.11.1 --- misc/requirements/requirements-pyqt-old.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-pyqt-old.txt b/misc/requirements/requirements-pyqt-old.txt index f0fa50ad5..f65c1ca9d 100644 --- a/misc/requirements/requirements-pyqt-old.txt +++ b/misc/requirements/requirements-pyqt-old.txt @@ -1,4 +1,4 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -PyQt5==5.10 # rq.filter: != 5.10.1 +PyQt5==5.11.1 # rq.filter: != 5.10.1 sip==4.19.8 From 1f19db07856f417d98df8d74d94ab12b04f7430f Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 25 Jun 2018 19:21:14 +0200 Subject: [PATCH 073/799] Update pyqt5 from 5.10.1 to 5.11.1 --- misc/requirements/requirements-pyqt.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-pyqt.txt b/misc/requirements/requirements-pyqt.txt index 059ff2df7..aa6c51286 100644 --- a/misc/requirements/requirements-pyqt.txt +++ b/misc/requirements/requirements-pyqt.txt @@ -1,4 +1,4 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -PyQt5==5.10.1 +PyQt5==5.11.1 sip==4.19.8 From d6554a131eec06eddbfdff64714530862cc49c2f Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 25 Jun 2018 19:21:15 +0200 Subject: [PATCH 074/799] Update hypothesis from 3.59.1 to 3.61.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 334e740c3..a032c2d2f 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -11,7 +11,7 @@ fields==5.0.0 Flask==1.0.2 glob2==0.6 hunter==2.0.2 -hypothesis==3.59.1 +hypothesis==3.61.0 itsdangerous==0.24 # Jinja2==2.10 Mako==1.0.7 From b3790f7a7e0eebd862875d8ba2abd8ec34607572 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 25 Jun 2018 19:21:17 +0200 Subject: [PATCH 075/799] Update pytest from 3.6.1 to 3.6.2 --- 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 a032c2d2f..d48f5b21a 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -22,7 +22,7 @@ parse-type==0.4.2 pluggy==0.6.0 py==1.5.3 py-cpuinfo==4.0.0 -pytest==3.6.1 +pytest==3.6.2 pytest-bdd==2.21.0 pytest-benchmark==3.1.1 pytest-cov==2.5.1 From 158ccd7d548931d2fdf5843a747b0891a4ad3a71 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 25 Jun 2018 20:08:42 +0200 Subject: [PATCH 076/799] Revert "Update pyqt5 from 5.10 to 5.11.1" This reverts commit e1bc5389a23900356d20bf5191722c4ccab0c709. --- misc/requirements/requirements-pyqt-old.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-pyqt-old.txt b/misc/requirements/requirements-pyqt-old.txt index f65c1ca9d..f0fa50ad5 100644 --- a/misc/requirements/requirements-pyqt-old.txt +++ b/misc/requirements/requirements-pyqt-old.txt @@ -1,4 +1,4 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -PyQt5==5.11.1 # rq.filter: != 5.10.1 +PyQt5==5.10 # rq.filter: != 5.10.1 sip==4.19.8 From 3f923b41e06e0568385cea9eedaf3cbd0dba5268 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 25 Jun 2018 20:08:50 +0200 Subject: [PATCH 077/799] Revert "Update pyqt5 from 5.10.1 to 5.11.1" This reverts commit 1f19db07856f417d98df8d74d94ab12b04f7430f. --- misc/requirements/requirements-pyqt.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-pyqt.txt b/misc/requirements/requirements-pyqt.txt index aa6c51286..059ff2df7 100644 --- a/misc/requirements/requirements-pyqt.txt +++ b/misc/requirements/requirements-pyqt.txt @@ -1,4 +1,4 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -PyQt5==5.11.1 +PyQt5==5.10.1 sip==4.19.8 From 8a4bba11ed846ffd96adf0089875229506290fc7 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 25 Jun 2018 20:35:48 +0200 Subject: [PATCH 078/799] Disable certificate workaround on Qt >= 5.9 Fixes #4020 --- doc/changelog.asciidoc | 2 ++ qutebrowser/browser/webengine/webview.py | 11 +++++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index 786ec712e..b8df97ea3 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -104,6 +104,8 @@ Changed Existing dictionaries are copied over. - If an error while parsing `~/.netrc` occurs, the cause of the error is now logged. +- On Qt 5.9 or newer, certificate errors now show Chromium's detailed error + page. Fixed ~~~~~ diff --git a/qutebrowser/browser/webengine/webview.py b/qutebrowser/browser/webengine/webview.py index 7436bc01a..11add368e 100644 --- a/qutebrowser/browser/webengine/webview.py +++ b/qutebrowser/browser/webengine/webview.py @@ -199,15 +199,18 @@ class WebEnginePage(QWebEnginePage): "{}".format(error)) ignore = False + log.webview.debug("ignore {}, URL {}, requested {}".format( + ignore, url, self.requestedUrl())) + + # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-56207 # We can't really know when to show an error page, as the error might # have happened when loading some resource. # However, self.url() is not available yet and self.requestedUrl() # might not match the URL we get from the error - so we just apply a # heuristic here. - # See https://bugreports.qt.io/browse/QTBUG-56207 - log.webview.debug("ignore {}, URL {}, requested {}".format( - ignore, url, self.requestedUrl())) - if not ignore and url.matches(self.requestedUrl(), QUrl.RemoveScheme): + if (not qtutils.version_check('5.9') and + not ignore and + url.matches(self.requestedUrl(), QUrl.RemoveScheme)): self.setHtml(error_page) return ignore From 81b3ef937ebf202670c1561429552874ed51d32a Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 25 Jun 2018 21:04:32 +0200 Subject: [PATCH 079/799] Move handling of certificate errors to webenginetab --- qutebrowser/browser/browsertab.py | 4 -- .../browser/webengine/certificateerror.py | 7 +++ qutebrowser/browser/webengine/webenginetab.py | 33 +++++++++++++- qutebrowser/browser/webengine/webview.py | 43 ++++--------------- qutebrowser/browser/webkit/webkittab.py | 4 ++ 5 files changed, 50 insertions(+), 41 deletions(-) diff --git a/qutebrowser/browser/browsertab.py b/qutebrowser/browser/browsertab.py index 7e78b2621..9f2e891c5 100644 --- a/qutebrowser/browser/browsertab.py +++ b/qutebrowser/browser/browsertab.py @@ -893,10 +893,6 @@ class AbstractTab(QWidget): self._progress = perc self.load_progress.emit(perc) - @pyqtSlot() - def _on_ssl_errors(self): - self._has_ssl_errors = True - def url(self, requested=False): raise NotImplementedError diff --git a/qutebrowser/browser/webengine/certificateerror.py b/qutebrowser/browser/webengine/certificateerror.py index 47953d4cc..768f54ec6 100644 --- a/qutebrowser/browser/webengine/certificateerror.py +++ b/qutebrowser/browser/webengine/certificateerror.py @@ -28,6 +28,10 @@ class CertificateErrorWrapper(usertypes.AbstractCertificateErrorWrapper): """A wrapper over a QWebEngineCertificateError.""" + def __init__(self, error): + super().__init__(error) + self.ignore = False + def __str__(self): return self._error.errorDescription() @@ -37,5 +41,8 @@ class CertificateErrorWrapper(usertypes.AbstractCertificateErrorWrapper): self._error.error()), string=str(self)) + def url(self): + return self._error.url() + def is_overridable(self): return self._error.isOverridable() diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index 6739c2f66..185021bfa 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -31,14 +31,15 @@ from PyQt5.QtCore import (pyqtSignal, pyqtSlot, Qt, QEvent, QPoint, QPointF, from PyQt5.QtGui import QKeyEvent, QIcon from PyQt5.QtNetwork import QAuthenticator from PyQt5.QtWidgets import QApplication -from PyQt5.QtWebEngineWidgets import QWebEnginePage, QWebEngineScript +from PyQt5.QtWebEngineWidgets import (QWebEnginePage, QWebEngineScript, + QWebEngineCertificateError) from qutebrowser.config import configdata, config from qutebrowser.browser import browsertab, mouse, shared from qutebrowser.browser.webengine import (webview, webengineelem, tabhistory, interceptor, webenginequtescheme, cookies, webenginedownloads, - webenginesettings) + webenginesettings, certificateerror) from qutebrowser.misc import miscwidgets from qutebrowser.utils import (usertypes, qtutils, log, javascript, utils, message, objreg, jinja, debug) @@ -1224,6 +1225,34 @@ class WebEngineTab(browsertab.AbstractTab): # the old icon is still displayed. self.icon_changed.emit(QIcon()) + @pyqtSlot(certificateerror.CertificateErrorWrapper) + def _on_ssl_errors(self, error): + self._has_ssl_errors = True + + url = error.url() + log.webview.debug("Certificate error: {}".format(error)) + + if error.is_overridable(): + error.ignore = shared.ignore_certificate_errors( + url, [error], abort_on=[self.shutting_down, self.load_started]) + else: + log.webview.error("Non-overridable certificate error: " + "{}".format(error)) + + log.webview.debug("ignore {}, URL {}, requested {}".format( + error.ignore, url, self.url(requested=True))) + + # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-56207 + # We can't really know when to show an error page, as the error might + # have happened when loading some resource. + # However, self.url() is not available yet and the requested URL + # might not match the URL we get from the error - so we just apply a + # heuristic here. + if (not qtutils.version_check('5.9') and + not error.ignore and + url.matches(self.url(requested=True), QUrl.RemoveScheme)): + self._show_error_page(url, str(error)) + @pyqtSlot(QUrl) def _on_predicted_navigation(self, url): """If we know we're going to visit an URL soon, change the settings. diff --git a/qutebrowser/browser/webengine/webview.py b/qutebrowser/browser/webengine/webview.py index 11add368e..76007b9d9 100644 --- a/qutebrowser/browser/webengine/webview.py +++ b/qutebrowser/browser/webengine/webview.py @@ -24,10 +24,11 @@ from PyQt5.QtCore import pyqtSignal, pyqtSlot, QUrl, PYQT_VERSION from PyQt5.QtGui import QPalette from PyQt5.QtWidgets import QWidget from PyQt5.QtWebEngineWidgets import (QWebEngineView, QWebEnginePage, - QWebEngineScript) + QWebEngineScript, + QWebEngineCertificateError) from qutebrowser.browser import shared -from qutebrowser.browser.webengine import certificateerror, webenginesettings +from qutebrowser.browser.webengine import webenginesettings, certificateerror from qutebrowser.config import config from qutebrowser.utils import log, debug, usertypes, jinja, objreg, qtutils from qutebrowser.misc import miscwidgets @@ -152,11 +153,13 @@ class WebEnginePage(QWebEnginePage): Signals: certificate_error: Emitted on certificate errors. + Needs to be directly connected to a slot setting the + 'ignore' attribute. shutting_down: Emitted when the page is shutting down. navigation_request: Emitted on acceptNavigationRequest. """ - certificate_error = pyqtSignal() + certificate_error = pyqtSignal(certificateerror.CertificateErrorWrapper) shutting_down = pyqtSignal() navigation_request = pyqtSignal(usertypes.NavigationRequest) @@ -181,39 +184,9 @@ class WebEnginePage(QWebEnginePage): def certificateError(self, error): """Handle certificate errors coming from Qt.""" - self.certificate_error.emit() - url = error.url() error = certificateerror.CertificateErrorWrapper(error) - log.webview.debug("Certificate error: {}".format(error)) - - url_string = url.toDisplayString() - error_page = jinja.render( - 'error.html', title="Error loading page: {}".format(url_string), - url=url_string, error=str(error)) - - if error.is_overridable(): - ignore = shared.ignore_certificate_errors( - url, [error], abort_on=[self.loadStarted, self.shutting_down]) - else: - log.webview.error("Non-overridable certificate error: " - "{}".format(error)) - ignore = False - - log.webview.debug("ignore {}, URL {}, requested {}".format( - ignore, url, self.requestedUrl())) - - # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-56207 - # We can't really know when to show an error page, as the error might - # have happened when loading some resource. - # However, self.url() is not available yet and self.requestedUrl() - # might not match the URL we get from the error - so we just apply a - # heuristic here. - if (not qtutils.version_check('5.9') and - not ignore and - url.matches(self.requestedUrl(), QUrl.RemoveScheme)): - self.setHtml(error_page) - - return ignore + self.certificate_error.emit(error) + return error.ignore def javaScriptConfirm(self, url, js_msg): """Override javaScriptConfirm to use qutebrowser prompts.""" diff --git a/qutebrowser/browser/webkit/webkittab.py b/qutebrowser/browser/webkit/webkittab.py index cf158ed2d..8039eb5eb 100644 --- a/qutebrowser/browser/webkit/webkittab.py +++ b/qutebrowser/browser/webkit/webkittab.py @@ -808,6 +808,10 @@ class WebKitTab(browsertab.AbstractTab): if navigation.is_main_frame: self.settings.update_for_url(navigation.url) + @pyqtSlot() + def _on_ssl_errors(self): + self._has_ssl_errors = True + def _connect_signals(self): view = self._widget page = view.page() From 87778277e012bf931ce15c6b89920c50ea30b589 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 25 Jun 2018 22:51:48 +0200 Subject: [PATCH 080/799] Fix SSL error page tests --- tests/end2end/features/test_prompts_bdd.py | 23 ++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/tests/end2end/features/test_prompts_bdd.py b/tests/end2end/features/test_prompts_bdd.py index 12d9cbeec..47b449ec1 100644 --- a/tests/end2end/features/test_prompts_bdd.py +++ b/tests/end2end/features/test_prompts_bdd.py @@ -20,6 +20,8 @@ import pytest_bdd as bdd bdd.scenarios('prompts.feature') +from qutebrowser.utils import qtutils + @bdd.when("I load an SSL page") def load_ssl_page(quteproc, ssl_server): @@ -46,14 +48,19 @@ def no_prompt_shown(quteproc): @bdd.then("a SSL error page should be shown") def ssl_error_page(request, quteproc): - if not request.config.webengine: - line = quteproc.wait_for(message='Error while loading *: SSL ' - 'handshake failed') - line.expected = True - quteproc.wait_for(message="Changing title for idx * to 'Error " - "loading page: *'") - content = quteproc.get_content().strip() - assert "Unable to load page" in content + if request.config.webengine and qtutils.version_check('5.9'): + quteproc.wait_for(message="Certificate error: *") + content = quteproc.get_content().strip() + assert "ERR_INSECURE_RESPONSE" in content + else: + if not request.config.webengine: + line = quteproc.wait_for(message='Error while loading *: SSL ' + 'handshake failed') + line.expected = True + quteproc.wait_for(message="Changing title for idx * to 'Error " + "loading page: *'") + content = quteproc.get_content().strip() + assert "Unable to load page" in content class AbstractCertificateErrorWrapper: From 876aa5a9b1b7d33c6e6dd662c73ab435fcf074b6 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 25 Jun 2018 22:51:55 +0200 Subject: [PATCH 081/799] Fix lint --- qutebrowser/browser/shared.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qutebrowser/browser/shared.py b/qutebrowser/browser/shared.py index 01ccab13b..2398ca2e4 100644 --- a/qutebrowser/browser/shared.py +++ b/qutebrowser/browser/shared.py @@ -48,7 +48,8 @@ def custom_headers(url): for header, value in conf_headers.items(): headers[header.encode('ascii')] = value.encode('ascii') - accept_language = config.instance.get('content.headers.accept_language', url=url) + accept_language = config.instance.get('content.headers.accept_language', + url=url) if accept_language is not None: headers[b'Accept-Language'] = accept_language.encode('ascii') From 1bc3d444b6c0e25cc754c34e2b40476cd5ba0464 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 25 Jun 2018 23:01:17 +0200 Subject: [PATCH 082/799] Stabilize escaping URLs test --- tests/end2end/features/history.feature | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/end2end/features/history.feature b/tests/end2end/features/history.feature index 0432c0705..e1a7d3326 100644 --- a/tests/end2end/features/history.feature +++ b/tests/end2end/features/history.feature @@ -121,6 +121,7 @@ Feature: Page history Scenario: Escaping of URLs in :history When I open query?one=1&two=2 And I open qute://history + And I wait 0.5s # JS loads the history async And I hint with args "links normal" and follow a And I wait until query?one=1&two=2 is loaded Then the query parameter two should be set to 2 From 54ca9b34e529e7d0fc84c77c16cff21ce302381e Mon Sep 17 00:00:00 2001 From: Jimmy Date: Mon, 25 Jun 2018 14:28:28 +1200 Subject: [PATCH 083/799] greasemonkey: enable running in isolated js worlds QtWebEngine (via chromium) has the ability to run injected scripts in isolated "worlds". What is isolated is just the javascript environment, so variables and functions defined by the page and the script won't clobber each other, or be able to interact (including variables saved to the global `window` object). The DOM is still accessible from "isolated" scripts. This is NOT a security measure. You cannot put untrusted scripts in one of these isolated worlds and expect it to not be able to do whatever page js can do, it is just for namespacing convenience. See https://stackoverflow.com/questions/9515704/insert-code-into-the-page-context-using-a-content-script for some examples of how to inject scripts into the page scope using DOM elements. Now you can specify the world ID in a `@qute-js-world` directive like: ``` // ==UserScript== // @name Do thing // @match *://some.site/* // @qute-js-world 1234 // ==/UserScript== document.body.innerHTML = "overwritten" ``` The QtWebEngine docs say worldid is a `quint32` so you can put whatever number (positive, whole, real) you want there. I have chosen to allow the `qutebrowser.utils.usertypes` enum as aliases for IDs that are predefined in `qutebrowser.browser.webengine.webenginetab._JS_WORLD_MAP`. So you can pass `main`, `application`, `user` or `jseval` in there too. `main` (0) is the default one and is the only one in which JS disabled when `content.javascript.enabled` is set to `false`. All others are still enabled. I'm not sure whether using any of those already-named worlds makes sense, apart from `main`. We could stop people from using them I suppose. Another option is to allow people to pass in `*` as a value to have scripts put into their own little worlds, probably backed by a counter in the GreaseMonkeyManager class. Chrome docs: https://developer.chrome.com/extensions/content_scripts#execution-environment Webengine docs: https://doc.qt.io/qt-5/qwebenginescript.html#details --- qutebrowser/browser/greasemonkey.py | 3 +++ qutebrowser/browser/webengine/webenginetab.py | 13 ++++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/qutebrowser/browser/greasemonkey.py b/qutebrowser/browser/greasemonkey.py index db8246bab..cff45f3ac 100644 --- a/qutebrowser/browser/greasemonkey.py +++ b/qutebrowser/browser/greasemonkey.py @@ -58,6 +58,7 @@ class GreasemonkeyScript: self.run_at = None self.script_meta = None self.runs_on_sub_frames = True + self.jsworld = "main" for name, value in properties: if name == 'name': self.name = value @@ -77,6 +78,8 @@ class GreasemonkeyScript: self.runs_on_sub_frames = False elif name == 'require': self.requires.append(value) + elif name == 'qute-js-world': + self.jsworld = value HEADER_REGEX = r'// ==UserScript==|\n+// ==/UserScript==\n' PROPS_REGEX = r'// @(?P[^\s]+)\s*(?P.*)' diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index 185021bfa..501735fa5 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -900,7 +900,18 @@ class _WebEngineScripts(QObject): # @run-at (and @include/@exclude/@match) is parsed by # QWebEngineScript. new_script = QWebEngineScript() - new_script.setWorldId(QWebEngineScript.MainWorld) + try: + world = int(script.jsworld) + except ValueError: + try: + world = _JS_WORLD_MAP[usertypes.JsWorld[ + script.jsworld.lower()]] + except KeyError: + log.greasemonkey.error( + "script {} has invalid value for '@qute-js-world'" + ": {}".format(script.name, script.jsworld)) + continue + new_script.setWorldId(world) new_script.setSourceCode(script.code()) new_script.setName("GM-{}".format(script.name)) new_script.setRunsOnSubFrames(script.runs_on_sub_frames) From 521268a1f719a89e38d779010d4353db612a803f Mon Sep 17 00:00:00 2001 From: Jimmy Date: Tue, 26 Jun 2018 12:33:40 +1200 Subject: [PATCH 084/799] Update comment. --- qutebrowser/browser/webengine/webenginetab.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index 501735fa5..a62524496 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -874,18 +874,18 @@ class _WebEngineScripts(QObject): self._inject_early_js('stylesheet', js_code, subframes=True) def _inject_userscripts(self): - """Register user JavaScript files with the global profiles.""" + """Register user JavaScript files with the current tab.""" # The Greasemonkey metadata block support in QtWebEngine only starts at # Qt 5.8. With 5.7.1, we need to inject the scripts ourselves in # response to urlChanged. if not qtutils.version_check('5.8'): return - # Since we are inserting scripts into profile.scripts they won't - # just get replaced by new gm scripts like if we were injecting them - # ourselves so we need to remove all gm scripts, while not removing - # any other stuff that might have been added. Like the one for - # stylesheets. + # Since we are inserting scripts into a per-tab collection, + # rather than just injecting scripts on page load, we need to + # make sure we replace existing scripts, not just add new ones. + # While, taking care not to remove any other scripts that might + # have been added elsewhere, like the one for stylesheets. greasemonkey = objreg.get('greasemonkey') scripts = self._widget.page().scripts() for script in scripts.toList(): From 324966cfe7316a395f3b043f734cd090b78d54f7 Mon Sep 17 00:00:00 2001 From: Jimmy Date: Tue, 26 Jun 2018 13:05:27 +1200 Subject: [PATCH 085/799] greasemonkey: also support qute-js-world on 5.7.1 A straight copy from webengintab. Yes I know I shouldn't be importing a private thing from webenginetab, I'm working on refactoring now. --- qutebrowser/browser/webengine/webview.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/qutebrowser/browser/webengine/webview.py b/qutebrowser/browser/webengine/webview.py index 76007b9d9..2eea5d837 100644 --- a/qutebrowser/browser/webengine/webview.py +++ b/qutebrowser/browser/webengine/webview.py @@ -288,7 +288,19 @@ class WebEnginePage(QWebEnginePage): def _add_script(script, injection_point): new_script = QWebEngineScript() new_script.setInjectionPoint(injection_point) - new_script.setWorldId(QWebEngineScript.MainWorld) + try: + world = int(script.jsworld) + except ValueError: + try: + from qutebrowser.browser.webengine.webenginetab import _JS_WORLD_MAP + world = _JS_WORLD_MAP[usertypes.JsWorld[ + script.jsworld.lower()]] + except KeyError: + log.greasemonkey.error( + "script {} has invalid value for '@qute-js-world'" + ": {}".format(script.name, script.jsworld)) + return + new_script.setWorldId(world) new_script.setSourceCode(script.code()) new_script.setName("GM-{}".format(script.name)) new_script.setRunsOnSubFrames(script.runs_on_sub_frames) From 5f4efced7b17d6538b02caf4e1f22d51e90b6f1d Mon Sep 17 00:00:00 2001 From: Jimmy Date: Sun, 27 May 2018 18:03:43 +1200 Subject: [PATCH 086/799] Sanitize generated filenames for downloads. Replace characters that Windows doesn't like from generated and suggested filenames for downloads. Does not apply to filenames that a user inters via the downloads location prompt. Fixes #3922 --- qutebrowser/browser/downloads.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/qutebrowser/browser/downloads.py b/qutebrowser/browser/downloads.py index dd112e00a..421b8c08b 100644 --- a/qutebrowser/browser/downloads.py +++ b/qutebrowser/browser/downloads.py @@ -134,6 +134,7 @@ def create_full_filename(basename, filename): Return: The full absolute path, or None if filename creation was not possible. """ + basename = utils.sanitize_filename(basename) # Remove chars which can't be encoded in the filename encoding. # See https://github.com/qutebrowser/qutebrowser/issues/427 encoding = sys.getfilesystemencoding() @@ -159,6 +160,7 @@ def get_filename_question(*, suggested_filename, url, parent=None): url: The URL the download originated from. parent: The parent of the question (a QObject). """ + suggested_filename = utils.sanitize_filename(suggested_filename) encoding = sys.getfilesystemencoding() suggested_filename = utils.force_encoding(suggested_filename, encoding) From 75c6e087c78bd344f416eb629f22df5b46e28279 Mon Sep 17 00:00:00 2001 From: Jimmy Date: Mon, 4 Jun 2018 10:54:02 +1200 Subject: [PATCH 087/799] Refactor individual tmpdir fixtures. I was adding a downloads one and saw the opportunity to refactor. Didn't end up saving that much because I couldn't figure out how to get the method that does the work to use the fixtures while also taking an argument, so they have to be passed through from the calling ones. Oh well. --- tests/helpers/fixtures.py | 42 ++++++++++++++++++++++++--------------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/tests/helpers/fixtures.py b/tests/helpers/fixtures.py index ec562c3b4..caf5a18e0 100644 --- a/tests/helpers/fixtures.py +++ b/tests/helpers/fixtures.py @@ -459,16 +459,35 @@ def mode_manager(win_registry, config_stub, qapp): objreg.delete('mode-manager', scope='window', window=0) +def standarddir_tmpdir(folder, monkeypatch, tmpdir): + """Set tmpdir/config as the configdir. + + Use this to avoid creating a 'real' config dir (~/.config/qute_test). + """ + confdir = tmpdir / folder + confdir.ensure(dir=True) + if hasattr(standarddir, folder): + monkeypatch.setattr(standarddir, folder, + lambda **_kwargs: str(confdir)) + return confdir + + +@pytest.fixture +def download_tmpdir(monkeypatch, tmpdir): + """Set tmpdir/download as the downloaddir. + + Use this to avoid creating a 'real' download dir (~/.config/qute_test). + """ + return standarddir_tmpdir('download', monkeypatch, tmpdir) + + @pytest.fixture def config_tmpdir(monkeypatch, tmpdir): """Set tmpdir/config as the configdir. Use this to avoid creating a 'real' config dir (~/.config/qute_test). """ - confdir = tmpdir / 'config' - confdir.ensure(dir=True) - monkeypatch.setattr(standarddir, 'config', lambda auto=False: str(confdir)) - return confdir + return standarddir_tmpdir('config', monkeypatch, tmpdir) @pytest.fixture @@ -477,10 +496,7 @@ def data_tmpdir(monkeypatch, tmpdir): Use this to avoid creating a 'real' data dir (~/.local/share/qute_test). """ - datadir = tmpdir / 'data' - datadir.ensure(dir=True) - monkeypatch.setattr(standarddir, 'data', lambda system=False: str(datadir)) - return datadir + return standarddir_tmpdir('data', monkeypatch, tmpdir) @pytest.fixture @@ -489,10 +505,7 @@ def runtime_tmpdir(monkeypatch, tmpdir): Use this to avoid creating a 'real' runtime dir. """ - runtimedir = tmpdir / 'runtime' - runtimedir.ensure(dir=True) - monkeypatch.setattr(standarddir, 'runtime', lambda: str(runtimedir)) - return runtimedir + return standarddir_tmpdir('runtime', monkeypatch, tmpdir) @pytest.fixture @@ -501,10 +514,7 @@ def cache_tmpdir(monkeypatch, tmpdir): Use this to avoid creating a 'real' cache dir (~/.cache/qute_test). """ - cachedir = tmpdir / 'cache' - cachedir.ensure(dir=True) - monkeypatch.setattr(standarddir, 'cache', lambda: str(cachedir)) - return cachedir + return standarddir_tmpdir('cache', monkeypatch, tmpdir) @pytest.fixture From 2510fde80fa051c7cbaf9dee9e6095a7ecc5eb54 Mon Sep 17 00:00:00 2001 From: Jimmy Date: Mon, 4 Jun 2018 10:57:16 +1200 Subject: [PATCH 088/799] Add unit test for sanitized download filenames. I think this covers the right path despite not using either of the backend specific download code. They both either call `set_target()` directly with a guessed filename or from the prompt question. When called directly though that basename set by `_init_item()` is used. It's a little high level but we already know that sanitize_filenames does, I wanted to test that it gets called. --- tests/unit/browser/webkit/test_downloads.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/unit/browser/webkit/test_downloads.py b/tests/unit/browser/webkit/test_downloads.py index 571e21704..f14c417f9 100644 --- a/tests/unit/browser/webkit/test_downloads.py +++ b/tests/unit/browser/webkit/test_downloads.py @@ -96,3 +96,24 @@ class TestDownloadTarget: ]) def test_class_hierarchy(self, obj): assert isinstance(obj, downloads._DownloadTarget) + + +@pytest.mark.parametrize('raw, expected', [ + ('http://foo/bar', 'bar'), + ('A *|<>\\: bear!', 'A ______ bear!') +]) +def test_sanitized_filenames(raw, expected, config_stub, download_tmpdir): + manager = downloads.AbstractDownloadManager() + target = downloads.FileDownloadTarget(download_tmpdir) + item = downloads.AbstractDownloadItem() + + # Don't try to start a timer outside of a QThread + manager._update_timer.isActive = lambda: True + + # Abstract methods + item._ensure_can_set_filename = lambda *args: True + item._after_set_filename = lambda *args: True + + manager._init_item(item, True, raw) + item.set_target(target) + assert item._filename.endswith(expected) From d31fa5229dd995041c4369cecf978eab3b0bc1b4 Mon Sep 17 00:00:00 2001 From: Jimmy Date: Tue, 5 Jun 2018 20:36:07 +1200 Subject: [PATCH 089/799] Stringify py.path object for py3.5 https://www.python.org/dev/peps/pep-0519/ Huh, I didn't realise cpython and the stdlib look up "special" methods on the class only. --- tests/unit/browser/webkit/test_downloads.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/browser/webkit/test_downloads.py b/tests/unit/browser/webkit/test_downloads.py index f14c417f9..68573002c 100644 --- a/tests/unit/browser/webkit/test_downloads.py +++ b/tests/unit/browser/webkit/test_downloads.py @@ -104,7 +104,7 @@ class TestDownloadTarget: ]) def test_sanitized_filenames(raw, expected, config_stub, download_tmpdir): manager = downloads.AbstractDownloadManager() - target = downloads.FileDownloadTarget(download_tmpdir) + target = downloads.FileDownloadTarget(str(download_tmpdir)) item = downloads.AbstractDownloadItem() # Don't try to start a timer outside of a QThread From ffd6ffef45d04ee0cea99365ce5a5f908451cab2 Mon Sep 17 00:00:00 2001 From: Jimmy Date: Tue, 26 Jun 2018 15:38:01 +1200 Subject: [PATCH 090/799] Add force_encoding call to sanitize_filename. So that callers don't have to call both. --- qutebrowser/browser/downloads.py | 4 +--- qutebrowser/utils/utils.py | 6 ++++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/qutebrowser/browser/downloads.py b/qutebrowser/browser/downloads.py index 421b8c08b..27408ffd6 100644 --- a/qutebrowser/browser/downloads.py +++ b/qutebrowser/browser/downloads.py @@ -135,11 +135,11 @@ def create_full_filename(basename, filename): The full absolute path, or None if filename creation was not possible. """ basename = utils.sanitize_filename(basename) + # Filename can be a full path so don't use sanitize_filename on it. # Remove chars which can't be encoded in the filename encoding. # See https://github.com/qutebrowser/qutebrowser/issues/427 encoding = sys.getfilesystemencoding() filename = utils.force_encoding(filename, encoding) - basename = utils.force_encoding(basename, encoding) if os.path.isabs(filename) and (os.path.isdir(filename) or filename.endswith(os.sep)): # We got an absolute directory from the user, so we save it under @@ -161,8 +161,6 @@ def get_filename_question(*, suggested_filename, url, parent=None): parent: The parent of the question (a QObject). """ suggested_filename = utils.sanitize_filename(suggested_filename) - encoding = sys.getfilesystemencoding() - suggested_filename = utils.force_encoding(suggested_filename, encoding) q = usertypes.Question(parent) q.title = "Save file to:" diff --git a/qutebrowser/utils/utils.py b/qutebrowser/utils/utils.py index 4554aef2e..8beb21279 100644 --- a/qutebrowser/utils/utils.py +++ b/qutebrowser/utils/utils.py @@ -499,6 +499,12 @@ def sanitize_filename(name, replacement='_'): """ if replacement is None: replacement = '' + + # Remove chars which can't be encoded in the filename encoding. + # See https://github.com/qutebrowser/qutebrowser/issues/427 + encoding = sys.getfilesystemencoding() + name = force_encoding(name, encoding) + # Bad characters taken from Windows, there are even fewer on Linux # See also # https://en.wikipedia.org/wiki/Filename#Reserved_characters_and_words From 1febcb9fce933a4045395b9b7164c3c0051511a3 Mon Sep 17 00:00:00 2001 From: Jimmy Date: Tue, 26 Jun 2018 15:39:16 +1200 Subject: [PATCH 091/799] Also call sanitize_filename in TempDownloadManager Just in case the suggested name used for the temp file suffix somehow has an illegal character in it. --- qutebrowser/browser/downloads.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/qutebrowser/browser/downloads.py b/qutebrowser/browser/downloads.py index 27408ffd6..517d93638 100644 --- a/qutebrowser/browser/downloads.py +++ b/qutebrowser/browser/downloads.py @@ -1224,8 +1224,7 @@ class TempDownloadManager: A tempfile.NamedTemporaryFile that should be used to save the file. """ tmpdir = self._get_tmpdir() - encoding = sys.getfilesystemencoding() - suggested_name = utils.force_encoding(suggested_name, encoding) + suggested_name = utils.sanitize_filename(suggested_name) # Make sure that the filename is not too long suggested_name = utils.elide_filename(suggested_name, 50) fobj = tempfile.NamedTemporaryFile(dir=tmpdir.name, delete=False, From 747acfe7fad3a0fa70d88b6fa8d2891fd0eecb15 Mon Sep 17 00:00:00 2001 From: Jimmy Date: Tue, 26 Jun 2018 16:24:37 +1200 Subject: [PATCH 092/799] Makes sanitize_filenames platform dependant. Hopefully having the posix bad_chars list so minimal won't ruin anyones day. --- qutebrowser/utils/utils.py | 10 ++++++++-- tests/unit/browser/webkit/test_downloads.py | 19 +++++++++++++++---- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/qutebrowser/utils/utils.py b/qutebrowser/utils/utils.py index 8beb21279..f3ab6b5ee 100644 --- a/qutebrowser/utils/utils.py +++ b/qutebrowser/utils/utils.py @@ -505,10 +505,16 @@ def sanitize_filename(name, replacement='_'): encoding = sys.getfilesystemencoding() name = force_encoding(name, encoding) - # Bad characters taken from Windows, there are even fewer on Linux # See also # https://en.wikipedia.org/wiki/Filename#Reserved_characters_and_words - bad_chars = '\\/:*?"<>|' + if is_windows: + bad_chars = '\\/:*?"<>|' + elif is_mac: + # Colons can be confusing in finder https://superuser.com/a/326627 + bad_chars = '/:' + else: + bad_chars = '/' + for bad_char in bad_chars: name = name.replace(bad_char, replacement) return name diff --git a/tests/unit/browser/webkit/test_downloads.py b/tests/unit/browser/webkit/test_downloads.py index 68573002c..8dc80f92c 100644 --- a/tests/unit/browser/webkit/test_downloads.py +++ b/tests/unit/browser/webkit/test_downloads.py @@ -22,6 +22,14 @@ import pytest from qutebrowser.browser import downloads, qtnetworkdownloads +@pytest.fixture(autouse=True) +def is_platform(monkeypatch, platform="windows"): + for p in ["mac", "linux", "posix", "windows"]: + monkeypatch.setattr( + 'qutebrowser.utils.utils.is_{}'.format(p), + p == platform) + + def test_download_model(qapp, qtmodeltester, config_stub, cookiejar_and_cache, fake_args): """Simple check for download model internals.""" @@ -98,11 +106,14 @@ class TestDownloadTarget: assert isinstance(obj, downloads._DownloadTarget) -@pytest.mark.parametrize('raw, expected', [ - ('http://foo/bar', 'bar'), - ('A *|<>\\: bear!', 'A ______ bear!') +@pytest.mark.parametrize('raw, platform, expected', [ + ('http://foo/bar', 'windows', 'bar'), + ('A *|<>\\: bear!', 'windows', 'A ______ bear!'), + ('A *|<>\\: bear!', 'posix', 'A *|<>\\: bear!') ]) -def test_sanitized_filenames(raw, expected, config_stub, download_tmpdir): +def test_sanitized_filenames(raw, platform, expected, config_stub, + download_tmpdir, monkeypatch): + is_platform(monkeypatch, platform) manager = downloads.AbstractDownloadManager() target = downloads.FileDownloadTarget(str(download_tmpdir)) item = downloads.AbstractDownloadItem() From 6f1232e621355f8940993dc5c1bed4252b113aef Mon Sep 17 00:00:00 2001 From: Jimmy Date: Tue, 26 Jun 2018 14:25:38 +1200 Subject: [PATCH 093/799] greasemonkey: move 5.7.1 injection method into _WebEngineScripts Moves the 5.8 check to `_WebEngineScripts.init()`. Changes `_inject_userscripts` to allow for the two code paths. With 5.7.1 we need to specify the injection point and not clear all scripts for each call, since we have to call it three times. Change the 5.8+ hook to call a new method which passes all the scripts into `_inject_userscripts` so that doesn't have to have a fallback conditional inside it because thats an inversion of responsibility! Pulling the remove scripts part into a seperate function and making it the callers responsibilty to call that first would tidy it up a little more but meh. I was worried about just doing `_widget.page().urlChanged.connect()` once at tab init, where before it was connected at page init, because I was under the impression that the child page can be replaced at any time, eg when navigating to a new origin. But under manual testing I didn't see that at all. Maybe I was mistaken or maybe that only started in a later Qt version. --- qutebrowser/browser/webengine/webenginetab.py | 76 +++++++++++++------ qutebrowser/browser/webengine/webview.py | 56 +------------- 2 files changed, 54 insertions(+), 78 deletions(-) diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index a62524496..d3d497ec7 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -854,9 +854,16 @@ class _WebEngineScripts(QObject): self._inject_early_js('js', js_code, subframes=True) self._init_stylesheet() - greasemonkey = objreg.get('greasemonkey') - greasemonkey.scripts_reloaded.connect(self._inject_userscripts) - self._inject_userscripts() + # The Greasemonkey metadata block support in QtWebEngine only starts at + # Qt 5.8. With 5.7.1, we need to inject the scripts ourselves in + # response to urlChanged. + if not qtutils.version_check('5.8'): + self._widget.page().urlChanged.connect( + self._inject_userscripts_for_url) + else: + greasemonkey = objreg.get('greasemonkey') + greasemonkey.scripts_reloaded.connect(self._inject_all_userscripts) + self._inject_all_userscripts() def _init_stylesheet(self): """Initialize custom stylesheets. @@ -873,32 +880,52 @@ class _WebEngineScripts(QObject): ) self._inject_early_js('stylesheet', js_code, subframes=True) - def _inject_userscripts(self): - """Register user JavaScript files with the current tab.""" - # The Greasemonkey metadata block support in QtWebEngine only starts at - # Qt 5.8. With 5.7.1, we need to inject the scripts ourselves in - # response to urlChanged. - if not qtutils.version_check('5.8'): - return + def _inject_userscripts_for_url(self, url): + greasemonkey = objreg.get('greasemonkey') + matching_scripts = greasemonkey.scripts_for(url) + self._inject_userscripts( + matching_scripts.start, QWebEngineScript.DocumentCreation, True) + self._inject_userscripts( + matching_scripts.end, QWebEngineScript.DocumentReady, False) + self._inject_userscripts( + matching_scripts.idle, QWebEngineScript.Deferred, False) + def _inject_all_userscripts(self): + greasemonkey = objreg.get('greasemonkey') + scripts = greasemonkey.all_scripts() + self._inject_userscripts(scripts) + + def _inject_userscripts(self, scripts=None, injection_point=None, + remove_first=True): + """Register user JavaScript files with the current tab. + + Args: + scripts: A list of GreasemonkeyScripts, or None to add all + known by the Greasemonkey subsystem. + injection_point: The QWebEngineScript::InjectionPoint stage + to inject the script into, None to use + auto-detection. + remove_first: Whether to remove all previously injected + scripts before adding these ones. + """ # Since we are inserting scripts into a per-tab collection, # rather than just injecting scripts on page load, we need to # make sure we replace existing scripts, not just add new ones. # While, taking care not to remove any other scripts that might # have been added elsewhere, like the one for stylesheets. - greasemonkey = objreg.get('greasemonkey') - scripts = self._widget.page().scripts() - for script in scripts.toList(): - if script.name().startswith("GM-"): - log.greasemonkey.debug('Removing script: {}' - .format(script.name())) - removed = scripts.remove(script) - assert removed, script.name() + page_scripts = self._widget.page().scripts() + if remove_first: + for script in page_scripts.toList(): + if script.name().startswith("GM-"): + log.greasemonkey.debug('Removing script: {}' + .format(script.name())) + removed = page_scripts.remove(script) + assert removed, script.name() - # Then add the new scripts. - for script in greasemonkey.all_scripts(): - # @run-at (and @include/@exclude/@match) is parsed by - # QWebEngineScript. + if not scripts: + return + + for script in scripts: new_script = QWebEngineScript() try: world = int(script.jsworld) @@ -915,9 +942,12 @@ class _WebEngineScripts(QObject): new_script.setSourceCode(script.code()) new_script.setName("GM-{}".format(script.name)) new_script.setRunsOnSubFrames(script.runs_on_sub_frames) + # Override the @run-at value parsed by QWebEngineScript if desired. + if injection_point: + new_script.setInjectionPoint(injection_point) log.greasemonkey.debug('adding script: {}' .format(new_script.name())) - scripts.insert(new_script) + page_scripts.insert(new_script) class WebEngineTab(browsertab.AbstractTab): diff --git a/qutebrowser/browser/webengine/webview.py b/qutebrowser/browser/webengine/webview.py index 2eea5d837..75b5df354 100644 --- a/qutebrowser/browser/webengine/webview.py +++ b/qutebrowser/browser/webengine/webview.py @@ -20,11 +20,10 @@ """The main browser widget for QtWebEngine.""" import sip -from PyQt5.QtCore import pyqtSignal, pyqtSlot, QUrl, PYQT_VERSION +from PyQt5.QtCore import pyqtSignal, QUrl, PYQT_VERSION from PyQt5.QtGui import QPalette from PyQt5.QtWidgets import QWidget from PyQt5.QtWebEngineWidgets import (QWebEngineView, QWebEnginePage, - QWebEngineScript, QWebEngineCertificateError) from qutebrowser.browser import shared @@ -169,7 +168,6 @@ class WebEnginePage(QWebEnginePage): self._theme_color = theme_color self._set_bg_color() config.instance.changed.connect(self._set_bg_color) - self.urlChanged.connect(self._inject_userjs) @config.change_filter('colors.webpage.bg') def _set_bg_color(self): @@ -264,55 +262,3 @@ class WebEnginePage(QWebEnginePage): is_main_frame=is_main_frame) self.navigation_request.emit(navigation) return navigation.accepted - - @pyqtSlot('QUrl') - def _inject_userjs(self, url): - """Inject userscripts registered for `url` into the current page.""" - if qtutils.version_check('5.8'): - # Handled in webenginetab with the builtin Greasemonkey - # support. - return - - # Using QWebEnginePage.scripts() to hold the user scripts means - # we don't have to worry ourselves about where to inject the - # page but also means scripts hang around for the tab lifecycle. - # So clear them here. - scripts = self.scripts() - for script in scripts.toList(): - if script.name().startswith("GM-"): - log.greasemonkey.debug("Removing script: {}" - .format(script.name())) - removed = scripts.remove(script) - assert removed, script.name() - - def _add_script(script, injection_point): - new_script = QWebEngineScript() - new_script.setInjectionPoint(injection_point) - try: - world = int(script.jsworld) - except ValueError: - try: - from qutebrowser.browser.webengine.webenginetab import _JS_WORLD_MAP - world = _JS_WORLD_MAP[usertypes.JsWorld[ - script.jsworld.lower()]] - except KeyError: - log.greasemonkey.error( - "script {} has invalid value for '@qute-js-world'" - ": {}".format(script.name, script.jsworld)) - return - new_script.setWorldId(world) - new_script.setSourceCode(script.code()) - new_script.setName("GM-{}".format(script.name)) - new_script.setRunsOnSubFrames(script.runs_on_sub_frames) - log.greasemonkey.debug("Adding script: {}" - .format(new_script.name())) - scripts.insert(new_script) - - greasemonkey = objreg.get('greasemonkey') - matching_scripts = greasemonkey.scripts_for(url) - for script in matching_scripts.start: - _add_script(script, QWebEngineScript.DocumentCreation) - for script in matching_scripts.end: - _add_script(script, QWebEngineScript.DocumentReady) - for script in matching_scripts.idle: - _add_script(script, QWebEngineScript.Deferred) From ee2c7658592d65030c76178267d04ac63213b2d9 Mon Sep 17 00:00:00 2001 From: Jimmy Date: Tue, 26 Jun 2018 14:45:38 +1200 Subject: [PATCH 094/799] greasemonkey: check _widget is not deleted Just for good luck. No crash has been reported here but it is a common pattern for functions called from signals. --- 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 d3d497ec7..8734a4b04 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -908,6 +908,9 @@ class _WebEngineScripts(QObject): remove_first: Whether to remove all previously injected scripts before adding these ones. """ + if sip.isdeleted(self._widget): + return + # Since we are inserting scripts into a per-tab collection, # rather than just injecting scripts on page load, we need to # make sure we replace existing scripts, not just add new ones. From c43d173197b02bd765a47335643d3643b35f687f Mon Sep 17 00:00:00 2001 From: Jimmy Date: Tue, 26 Jun 2018 14:54:32 +1200 Subject: [PATCH 095/799] greasemonkey: s/userscripts/greasemonkey_scripts/ No need to confuse developers as well as users. --- qutebrowser/browser/webengine/webenginetab.py | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index 8734a4b04..43fecf9f1 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -859,11 +859,12 @@ class _WebEngineScripts(QObject): # response to urlChanged. if not qtutils.version_check('5.8'): self._widget.page().urlChanged.connect( - self._inject_userscripts_for_url) + self._inject_greasemonkey_scripts_for_url) else: greasemonkey = objreg.get('greasemonkey') - greasemonkey.scripts_reloaded.connect(self._inject_all_userscripts) - self._inject_all_userscripts() + greasemonkey.scripts_reloaded.connect( + self._inject_all_greasemonkey_scripts) + self._inject_all_greasemonkey_scripts() def _init_stylesheet(self): """Initialize custom stylesheets. @@ -880,23 +881,23 @@ class _WebEngineScripts(QObject): ) self._inject_early_js('stylesheet', js_code, subframes=True) - def _inject_userscripts_for_url(self, url): + def _inject_greasemonkey_scripts_for_url(self, url): greasemonkey = objreg.get('greasemonkey') matching_scripts = greasemonkey.scripts_for(url) - self._inject_userscripts( + self._inject_greasemonkey_scripts( matching_scripts.start, QWebEngineScript.DocumentCreation, True) - self._inject_userscripts( + self._inject_greasemonkey_scripts( matching_scripts.end, QWebEngineScript.DocumentReady, False) - self._inject_userscripts( + self._inject_greasemonkey_scripts( matching_scripts.idle, QWebEngineScript.Deferred, False) - def _inject_all_userscripts(self): + def _inject_all_greasemonkey_scripts(self): greasemonkey = objreg.get('greasemonkey') scripts = greasemonkey.all_scripts() - self._inject_userscripts(scripts) + self._inject_greasemonkey_scripts(scripts) - def _inject_userscripts(self, scripts=None, injection_point=None, - remove_first=True): + def _inject_greasemonkey_scripts(self, scripts=None, injection_point=None, + remove_first=True): """Register user JavaScript files with the current tab. Args: From 680ae89ffd82795c52c7b2322f19bdd5819de613 Mon Sep 17 00:00:00 2001 From: Jimmy Date: Tue, 26 Jun 2018 17:47:10 +1200 Subject: [PATCH 096/799] Also mock platform to windows for utils tests. This is the same fixture I added in tests/unit/browser/webkit/test_downloads.py --- tests/unit/utils/test_utils.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/tests/unit/utils/test_utils.py b/tests/unit/utils/test_utils.py index ef2b6c8d4..b8f9b932e 100644 --- a/tests/unit/utils/test_utils.py +++ b/tests/unit/utils/test_utils.py @@ -113,6 +113,14 @@ class TestElidingFilenames: assert utils.elide_filename(filename, length) == expected +@pytest.fixture(autouse=True) +def is_platform(monkeypatch, platform="windows"): + for p in ["mac", "linux", "posix", "windows"]: + monkeypatch.setattr( + 'qutebrowser.utils.utils.is_{}'.format(p), + p == platform) + + @pytest.fixture(params=[True, False]) def freezer(request, monkeypatch): if request.param and not getattr(sys, 'frozen', False): @@ -629,12 +637,15 @@ def test_force_encoding(inp, enc, expected): assert utils.force_encoding(inp, enc) == expected -@pytest.mark.parametrize('inp, expected', [ - ('normal.txt', 'normal.txt'), - ('user/repo issues.mht', 'user_repo issues.mht'), - (' - "*?:|', '_Test_File_ - _____'), +@pytest.mark.parametrize('inp, platform, expected', [ + ('normal.txt', 'windows', 'normal.txt'), + ('user/repo issues.mht', 'windows', 'user_repo issues.mht'), + (' - "*?:|', 'windows', '_Test_File_ - _____'), + (' - "*?:|', 'mac', ' - "*?_|'), + (' - "*?:|', 'posix', ' - "*?:|'), ]) -def test_sanitize_filename(inp, expected): +def test_sanitize_filename(inp, platform, expected, monkeypatch): + is_platform(monkeypatch, platform) assert utils.sanitize_filename(inp) == expected From 85a9f6a08abfdeff9d29c1cb8cd3d45ec8c5b0a5 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 26 Jun 2018 10:23:48 +0200 Subject: [PATCH 097/799] Fix lint --- qutebrowser/browser/webengine/webenginetab.py | 3 +-- qutebrowser/browser/webengine/webview.py | 5 ++--- tests/end2end/features/test_prompts_bdd.py | 4 ++-- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index 185021bfa..8e88a6efb 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -31,8 +31,7 @@ from PyQt5.QtCore import (pyqtSignal, pyqtSlot, Qt, QEvent, QPoint, QPointF, from PyQt5.QtGui import QKeyEvent, QIcon from PyQt5.QtNetwork import QAuthenticator from PyQt5.QtWidgets import QApplication -from PyQt5.QtWebEngineWidgets import (QWebEnginePage, QWebEngineScript, - QWebEngineCertificateError) +from PyQt5.QtWebEngineWidgets import QWebEnginePage, QWebEngineScript from qutebrowser.config import configdata, config from qutebrowser.browser import browsertab, mouse, shared diff --git a/qutebrowser/browser/webengine/webview.py b/qutebrowser/browser/webengine/webview.py index 76007b9d9..6a854d513 100644 --- a/qutebrowser/browser/webengine/webview.py +++ b/qutebrowser/browser/webengine/webview.py @@ -24,13 +24,12 @@ from PyQt5.QtCore import pyqtSignal, pyqtSlot, QUrl, PYQT_VERSION from PyQt5.QtGui import QPalette from PyQt5.QtWidgets import QWidget from PyQt5.QtWebEngineWidgets import (QWebEngineView, QWebEnginePage, - QWebEngineScript, - QWebEngineCertificateError) + QWebEngineScript) from qutebrowser.browser import shared from qutebrowser.browser.webengine import webenginesettings, certificateerror from qutebrowser.config import config -from qutebrowser.utils import log, debug, usertypes, jinja, objreg, qtutils +from qutebrowser.utils import log, debug, usertypes, objreg, qtutils from qutebrowser.misc import miscwidgets diff --git a/tests/end2end/features/test_prompts_bdd.py b/tests/end2end/features/test_prompts_bdd.py index 47b449ec1..ef55ae662 100644 --- a/tests/end2end/features/test_prompts_bdd.py +++ b/tests/end2end/features/test_prompts_bdd.py @@ -55,10 +55,10 @@ def ssl_error_page(request, quteproc): else: if not request.config.webengine: line = quteproc.wait_for(message='Error while loading *: SSL ' - 'handshake failed') + 'handshake failed') line.expected = True quteproc.wait_for(message="Changing title for idx * to 'Error " - "loading page: *'") + "loading page: *'") content = quteproc.get_content().strip() assert "Unable to load page" in content From 3312c221c4691357a8564448f4d2c0f686a4944e Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 26 Jun 2018 10:26:17 +0200 Subject: [PATCH 098/799] Stabilize ssl_strict test --- tests/end2end/features/test_prompts_bdd.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/end2end/features/test_prompts_bdd.py b/tests/end2end/features/test_prompts_bdd.py index ef55ae662..a21c943f1 100644 --- a/tests/end2end/features/test_prompts_bdd.py +++ b/tests/end2end/features/test_prompts_bdd.py @@ -17,6 +17,8 @@ # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see . +import time + import pytest_bdd as bdd bdd.scenarios('prompts.feature') @@ -50,6 +52,7 @@ def no_prompt_shown(quteproc): def ssl_error_page(request, quteproc): if request.config.webengine and qtutils.version_check('5.9'): quteproc.wait_for(message="Certificate error: *") + time.sleep(0.5) # Wait for error page to appear content = quteproc.get_content().strip() assert "ERR_INSECURE_RESPONSE" in content else: From 1536843f33d3113c1355ad7cd600dc4e7dd458c9 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 26 Jun 2018 10:39:04 +0200 Subject: [PATCH 099/799] Only get greasemonkey object once --- qutebrowser/browser/webengine/webenginetab.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index aa1c014ef..3fc4806cd 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -788,6 +788,7 @@ class _WebEngineScripts(QObject): super().__init__(parent) self._tab = tab self._widget = None + self._greasemonkey = objreg.get('greasemonkey') def connect_signals(self): config.instance.changed.connect(self._on_config_changed) @@ -860,8 +861,7 @@ class _WebEngineScripts(QObject): self._widget.page().urlChanged.connect( self._inject_greasemonkey_scripts_for_url) else: - greasemonkey = objreg.get('greasemonkey') - greasemonkey.scripts_reloaded.connect( + self._greasemonkey.scripts_reloaded.connect( self._inject_all_greasemonkey_scripts) self._inject_all_greasemonkey_scripts() @@ -881,8 +881,7 @@ class _WebEngineScripts(QObject): self._inject_early_js('stylesheet', js_code, subframes=True) def _inject_greasemonkey_scripts_for_url(self, url): - greasemonkey = objreg.get('greasemonkey') - matching_scripts = greasemonkey.scripts_for(url) + matching_scripts = self._greasemonkey.scripts_for(url) self._inject_greasemonkey_scripts( matching_scripts.start, QWebEngineScript.DocumentCreation, True) self._inject_greasemonkey_scripts( @@ -891,8 +890,7 @@ class _WebEngineScripts(QObject): matching_scripts.idle, QWebEngineScript.Deferred, False) def _inject_all_greasemonkey_scripts(self): - greasemonkey = objreg.get('greasemonkey') - scripts = greasemonkey.all_scripts() + scripts = self._greasemonkey.all_scripts() self._inject_greasemonkey_scripts(scripts) def _inject_greasemonkey_scripts(self, scripts=None, injection_point=None, From ea4ee6f00b35d83339becc08b4596c5e53f880a4 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 26 Jun 2018 10:39:33 +0200 Subject: [PATCH 100/799] Use the url_changed signal in the tab API --- 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 3fc4806cd..799eb7c24 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -858,7 +858,7 @@ class _WebEngineScripts(QObject): # Qt 5.8. With 5.7.1, we need to inject the scripts ourselves in # response to urlChanged. if not qtutils.version_check('5.8'): - self._widget.page().urlChanged.connect( + self._tab.url_changed.connect( self._inject_greasemonkey_scripts_for_url) else: self._greasemonkey.scripts_reloaded.connect( From 8519aa940fc51eaa666e20c8021f4c2fae13c933 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 26 Jun 2018 10:40:13 +0200 Subject: [PATCH 101/799] Decorate slots properly --- qutebrowser/browser/webengine/webenginetab.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index 799eb7c24..aec3783aa 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -880,6 +880,7 @@ class _WebEngineScripts(QObject): ) self._inject_early_js('stylesheet', js_code, subframes=True) + @pyqtSlot(QUrl) def _inject_greasemonkey_scripts_for_url(self, url): matching_scripts = self._greasemonkey.scripts_for(url) self._inject_greasemonkey_scripts( @@ -889,6 +890,7 @@ class _WebEngineScripts(QObject): self._inject_greasemonkey_scripts( matching_scripts.idle, QWebEngineScript.Deferred, False) + @pyqtSlot() def _inject_all_greasemonkey_scripts(self): scripts = self._greasemonkey.all_scripts() self._inject_greasemonkey_scripts(scripts) From aed964d9f567fe9ad72b595aa8c15c9a3c654c26 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 26 Jun 2018 10:41:21 +0200 Subject: [PATCH 102/799] Update changelog --- doc/changelog.asciidoc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index b8df97ea3..f4c62a713 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -106,6 +106,8 @@ Changed logged. - On Qt 5.9 or newer, certificate errors now show Chromium's detailed error page. +- Greasemonkey scripts now support a "@qute-js-world" tag to run them in a + different JavaScript context. Fixed ~~~~~ From 2f612aa6dffb4e60d7cc62881ccfd41a2eb411d2 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 26 Jun 2018 15:54:56 +0200 Subject: [PATCH 103/799] Update comment --- 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 dc5168a8d..570d1f887 100644 --- a/qutebrowser/utils/version.py +++ b/qutebrowser/utils/version.py @@ -318,7 +318,7 @@ def _chromium_version(): Qt 5.9: Chromium 56 Qt 5.10: Chromium 61 Qt 5.11: Chromium 65 - Qt 5.12: Chromium 69 (?) + Qt 5.12: Chromium 69 (? - current dev branch: 67) Also see https://www.chromium.org/developers/calendar """ From a804300dc022afc8870a0ee117cba2c32ee79890 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 26 Jun 2018 16:56:22 +0200 Subject: [PATCH 104/799] Add missing tests for spell.init() --- tests/unit/browser/webengine/test_spell.py | 57 ++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/tests/unit/browser/webengine/test_spell.py b/tests/unit/browser/webengine/test_spell.py index 1cc2c5d0f..2532eb907 100644 --- a/tests/unit/browser/webengine/test_spell.py +++ b/tests/unit/browser/webengine/test_spell.py @@ -92,3 +92,60 @@ def test_local_filename_installed_malformed(tmpdir, monkeypatch, caplog): (tmpdir / lang_file).ensure() with caplog.at_level(logging.WARNING): assert spell.local_filename('en-US') == 'en-US-11-0' + + +class TestInit: + + ENV = 'QTWEBENGINE_DICTIONARIES_PATH' + + @pytest.fixture(autouse=True) + def remove_envvar(self, monkeypatch): + monkeypatch.delenv(self.ENV, raising=False) + + @pytest.fixture + def patch_new_qt(self, monkeypatch): + monkeypatch.setattr(spell.qtutils, 'version_check', + lambda _ver, compiled: True) + + @pytest.fixture + def dict_dir(self, data_tmpdir): + return data_tmpdir / 'qtwebengine_dictionaries' + + @pytest.fixture + def old_dict_dir(self, monkeypatch, tmpdir): + data_dir = tmpdir / 'old' + dict_dir = data_dir / 'qtwebengine_dictionaries' + (dict_dir / 'somedict').ensure() + monkeypatch.setattr(spell.QLibraryInfo, 'location', + lambda _arg: data_dir) + return dict_dir + + def test_old_qt(self, monkeypatch): + monkeypatch.setattr(spell.qtutils, 'version_check', + lambda _ver, compiled: False) + spell.init() + assert self.ENV not in os.environ + + def test_new_qt(self, dict_dir, patch_new_qt): + spell.init() + assert os.environ[self.ENV] == str(dict_dir) + + def test_moving(self, old_dict_dir, dict_dir, patch_new_qt): + spell.init() + assert (dict_dir / 'somedict').exists() + + def test_moving_oserror(self, mocker, caplog, + old_dict_dir, dict_dir, patch_new_qt): + mocker.patch('shutil.copytree', side_effect=OSError) + + with caplog.at_level(logging.ERROR): + spell.init() + + record = caplog.records[0] + assert record.message == 'Failed to copy old dictionaries' + + def test_moving_existing_destdir(self, old_dict_dir, dict_dir, + patch_new_qt): + dict_dir.ensure(dir=True) + spell.init() + assert not (dict_dir / 'somedict').exists() From 96defc5dc275418c7753f93344066c68e13f69a3 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 27 Jun 2018 15:53:58 +0200 Subject: [PATCH 105/799] Update PyYAML to 4.1 --- misc/requirements/requirements-tests-git.txt | 6 +----- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/misc/requirements/requirements-tests-git.txt b/misc/requirements/requirements-tests-git.txt index 6681dd15e..ce00cd31c 100644 --- a/misc/requirements/requirements-tests-git.txt +++ b/misc/requirements/requirements-tests-git.txt @@ -35,8 +35,4 @@ git+https://github.com/pallets/markupsafe.git hg+http://bitbucket.org/birkenfeld/pygments-main hg+https://bitbucket.org/fdik/pypeg git+https://github.com/python-attrs/attrs.git - -# Fails to build: -# gcc: error: ext/_yaml.c: No such file or directory -# hg+https://bitbucket.org/xi/pyyaml -PyYAML==3.12 +git+https://github.com/yaml/pyyaml.git diff --git a/requirements.txt b/requirements.txt index 2695ba55f..4d4e1972e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,4 +7,4 @@ Jinja2==2.10 MarkupSafe==1.0 Pygments==2.2.0 pyPEG2==2.15.2 -PyYAML==3.12 +PyYAML==4.1 From e9c78b29edf312faeb9d82bd6c0bd576df632cd5 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 27 Jun 2018 15:56:31 +0200 Subject: [PATCH 106/799] Ignore Python 3.7 collections.abc warning Related issues/PRs: https://github.com/yaml/pyyaml/pull/181 https://github.com/pypa/setuptools/issues/1401 https://github.com/pallets/markupsafe/pull/98 https://github.com/yaml/pyyaml/pull/181 https://github.com/pallets/jinja/pull/867 --- pytest.ini | 3 +++ qutebrowser/utils/log.py | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/pytest.ini b/pytest.ini index c897f0be7..452841a6e 100644 --- a/pytest.ini +++ b/pytest.ini @@ -64,3 +64,6 @@ qt_log_ignore = ^QSettings::value: Empty key passed ^Icon theme ".*" not found xfail_strict = true +filterwarnings = + # This happens in many qutebrowser dependencies... + ignore:Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated, and in 3.8 it will stop working:DeprecationWarning diff --git a/qutebrowser/utils/log.py b/qutebrowser/utils/log.py index 30e570e16..48711614d 100644 --- a/qutebrowser/utils/log.py +++ b/qutebrowser/utils/log.py @@ -209,6 +209,11 @@ def _init_py_warnings(): """Initialize Python warning handling.""" warnings.simplefilter('default') warnings.filterwarnings('ignore', module='pdb', category=ResourceWarning) + # This happens in many qutebrowser dependencies... + warnings.filterwarnings('ignore', category=DeprecationWarning, + message="Using or importing the ABCs from " + "'collections' instead of from 'collections.abc' " + "is deprecated, and in 3.8 it will stop working") @contextlib.contextmanager From a7af5195d14a0604e6809f58e97b168b78493dda Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 28 Jun 2018 11:22:44 +0200 Subject: [PATCH 107/799] Set title when showing PDF.js error page Fixes #3894 --- doc/changelog.asciidoc | 2 ++ qutebrowser/browser/webkit/webpage.py | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index f4c62a713..64a051ad0 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -116,6 +116,8 @@ Fixed - The security fix in v1.3.3 caused URLs with ampersands (`www.example.com?one=1&two=2`) to send the wrong arguments when clicked on the `qute://history` page. +- Crash when opening a PDF page with PDF.js enabled (on QtWebKit), but no + PDF.js installed. Removed ~~~~~~~ diff --git a/qutebrowser/browser/webkit/webpage.py b/qutebrowser/browser/webkit/webpage.py index 853ff1b81..a6c26c8ee 100644 --- a/qutebrowser/browser/webkit/webpage.py +++ b/qutebrowser/browser/webkit/webpage.py @@ -212,7 +212,8 @@ class BrowserPage(QWebPage): page = pdfjs.generate_pdfjs_page(reply.url()) except pdfjs.PDFJSNotFound: page = jinja.render('no_pdfjs.html', - url=reply.url().toDisplayString()) + url=reply.url().toDisplayString(), + title="PDF.js not found") self.mainFrame().setContent(page.encode('utf-8'), 'text/html', reply.url()) reply.deleteLater() From dbd4ce48e656308aacef313c172b421159f21c8b Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 28 Jun 2018 11:43:32 +0200 Subject: [PATCH 108/799] Downgrade and filter PyYAML 4.1 This reverts commit 96defc5dc275418c7753f93344066c68e13f69a3. With PyYAML 4.1 we don't have C extensions on Travis CI: https://github.com/yaml/pyyaml/issues/179 https://github.com/yaml/pyyaml/issues/182 Unfortunately, cython isn't in the APT whitelist either: https://github.com/travis-ci/apt-source-whitelist/issues/37 --- misc/requirements/requirements-qutebrowser.txt-raw | 4 +++- misc/requirements/requirements-tests-git.txt | 6 +++++- requirements.txt | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/misc/requirements/requirements-qutebrowser.txt-raw b/misc/requirements/requirements-qutebrowser.txt-raw index c66c65beb..506af17c0 100644 --- a/misc/requirements/requirements-qutebrowser.txt-raw +++ b/misc/requirements/requirements-qutebrowser.txt-raw @@ -1,7 +1,9 @@ Jinja2 Pygments pyPEG2 -PyYAML +PyYAML!=4.1 colorama cssutils attrs + +#@ filter: PyYAML != 4.1 diff --git a/misc/requirements/requirements-tests-git.txt b/misc/requirements/requirements-tests-git.txt index ce00cd31c..6681dd15e 100644 --- a/misc/requirements/requirements-tests-git.txt +++ b/misc/requirements/requirements-tests-git.txt @@ -35,4 +35,8 @@ git+https://github.com/pallets/markupsafe.git hg+http://bitbucket.org/birkenfeld/pygments-main hg+https://bitbucket.org/fdik/pypeg git+https://github.com/python-attrs/attrs.git -git+https://github.com/yaml/pyyaml.git + +# Fails to build: +# gcc: error: ext/_yaml.c: No such file or directory +# hg+https://bitbucket.org/xi/pyyaml +PyYAML==3.12 diff --git a/requirements.txt b/requirements.txt index 4d4e1972e..842b0c42d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,4 +7,4 @@ Jinja2==2.10 MarkupSafe==1.0 Pygments==2.2.0 pyPEG2==2.15.2 -PyYAML==4.1 +PyYAML==3.12 # rq.filter: != 4.1 From e857400c2cff543a67714afe163970f5b7b7fe48 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 28 Jun 2018 13:26:45 +0200 Subject: [PATCH 109/799] Add missing str() --- tests/unit/browser/webengine/test_spell.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/browser/webengine/test_spell.py b/tests/unit/browser/webengine/test_spell.py index 2532eb907..14b343df5 100644 --- a/tests/unit/browser/webengine/test_spell.py +++ b/tests/unit/browser/webengine/test_spell.py @@ -117,7 +117,7 @@ class TestInit: dict_dir = data_dir / 'qtwebengine_dictionaries' (dict_dir / 'somedict').ensure() monkeypatch.setattr(spell.QLibraryInfo, 'location', - lambda _arg: data_dir) + lambda _arg: str(data_dir)) return dict_dir def test_old_qt(self, monkeypatch): From f7ae7e7d40849ea033050e43d4d8515587c3abf2 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 23 Jun 2018 14:32:57 +0200 Subject: [PATCH 110/799] Update for PyQt 5.11 --- .appveyor.yml | 2 +- .travis.yml | 13 +++---------- README.asciidoc | 4 ++-- doc/contributing.asciidoc | 2 -- misc/requirements/requirements-pyqt-old.txt | 4 ---- misc/requirements/requirements-pyqt-old.txt-raw | 2 -- misc/requirements/requirements-pyqt.txt | 4 ++-- tox.ini | 7 ++++--- 8 files changed, 12 insertions(+), 26 deletions(-) delete mode 100644 misc/requirements/requirements-pyqt-old.txt delete mode 100644 misc/requirements/requirements-pyqt-old.txt-raw diff --git a/.appveyor.yml b/.appveyor.yml index f2424fc94..d336f20cc 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -7,7 +7,7 @@ environment: PYTHONUNBUFFERED: 1 PYTHON: C:\Python36\python.exe matrix: - - TESTENV: py36-pyqt510 + - TESTENV: py36-pyqt511 - TESTENV: pylint install: diff --git a/.travis.yml b/.travis.yml index 143938aef..c8732fe29 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,16 +20,9 @@ matrix: - os: linux env: TESTENV=py36-pyqt59 - os: linux - env: TESTENV=py36-pyqt510-cov - # We need a newer Xvfb as a WORKAROUND for: - # https://bugreports.qt.io/browse/QTBUG-64928 - sudo: required - addons: - apt: - sources: - - sourceline: "deb http://us.archive.ubuntu.com/ubuntu/ xenial main universe" - packages: - - xvfb + env: TESTENV=py36-pyqt510 + - os: linux + env: TESTENV=py36-pyqt511-cov - os: osx env: TESTENV=py36 OSX=sierra osx_image: xcode9.2 diff --git a/README.asciidoc b/README.asciidoc index db401d49d..2237955dd 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -97,7 +97,7 @@ Requirements The following software and libraries are required to run qutebrowser: * http://www.python.org/[Python] 3.5 or newer (3.6 recommended) -* http://qt.io/[Qt] 5.7.1 or newer (5.10 recommended) with the following modules: +* http://qt.io/[Qt] 5.7.1 or newer (5.11.1 recommended) with the following modules: - QtCore / qtbase - QtQuick (part of qtbase in some distributions) - QtSQL (part of qtbase in some distributions) @@ -107,7 +107,7 @@ The following software and libraries are required to run qutebrowser: link:https://github.com/annulen/webkit/wiki[updated fork] (5.212) is supported * http://www.riverbankcomputing.com/software/pyqt/intro[PyQt] 5.7.0 or newer - (5.10 recommended) for Python 3 + (5.11.2 recommended) for Python 3 * https://pypi.python.org/pypi/setuptools/[pkg_resources/setuptools] * http://fdik.org/pyPEG/[pyPEG2] * http://jinja.pocoo.org/[jinja2] diff --git a/doc/contributing.asciidoc b/doc/contributing.asciidoc index 07c2dfe25..b130825ec 100644 --- a/doc/contributing.asciidoc +++ b/doc/contributing.asciidoc @@ -689,8 +689,6 @@ 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 diff --git a/misc/requirements/requirements-pyqt-old.txt b/misc/requirements/requirements-pyqt-old.txt deleted file mode 100644 index f0fa50ad5..000000000 --- a/misc/requirements/requirements-pyqt-old.txt +++ /dev/null @@ -1,4 +0,0 @@ -# This file is automatically generated by scripts/dev/recompile_requirements.py - -PyQt5==5.10 # rq.filter: != 5.10.1 -sip==4.19.8 diff --git a/misc/requirements/requirements-pyqt-old.txt-raw b/misc/requirements/requirements-pyqt-old.txt-raw deleted file mode 100644 index 16b82e0d5..000000000 --- a/misc/requirements/requirements-pyqt-old.txt-raw +++ /dev/null @@ -1,2 +0,0 @@ -PyQt5==5.10.0 -#@ filter: PyQt5 != 5.10.1 \ No newline at end of file diff --git a/misc/requirements/requirements-pyqt.txt b/misc/requirements/requirements-pyqt.txt index 059ff2df7..063f85122 100644 --- a/misc/requirements/requirements-pyqt.txt +++ b/misc/requirements/requirements-pyqt.txt @@ -1,4 +1,4 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -PyQt5==5.10.1 -sip==4.19.8 +PyQt5==5.11.2 +PyQt5-sip==4.19.11 diff --git a/tox.ini b/tox.ini index 4e155df57..ab7be4d83 100644 --- a/tox.ini +++ b/tox.ini @@ -13,8 +13,8 @@ skipsdist = true setenv = QT_QPA_PLATFORM_PLUGIN_PATH={envdir}/Lib/site-packages/PyQt5/plugins/platforms PYTEST_QT_API=pyqt5 - pyqt{,56,571,59,510}: LINK_PYQT_SKIP=true - pyqt{,56,571,59,510}: QUTE_BDD_WEBENGINE=true + pyqt{,56,571,59,510,511}: LINK_PYQT_SKIP=true + pyqt{,56,571,59,510,511}: QUTE_BDD_WEBENGINE=true cov: PYTEST_ADDOPTS=--cov --cov-report xml --cov-report=html --cov-report= passenv = PYTHON DISPLAY XAUTHORITY HOME USERNAME USER CI TRAVIS XDG_* QUTE_* DOCKER basepython = @@ -28,6 +28,7 @@ deps = pyqt571: PyQt5==5.7.1 pyqt59: PyQt5==5.9.2 pyqt510: PyQt5==5.10.1 + pyqt511: PyQt5==5.11.2 commands = {envpython} scripts/link_pyqt.py --tox {envdir} {envpython} -bb -m pytest {posargs:tests} @@ -59,7 +60,7 @@ commands = {envpython} -c "" usedevelop = true deps = -r{toxinidir}/requirements.txt - -r{toxinidir}/misc/requirements/requirements-pyqt-old.txt + -r{toxinidir}/misc/requirements/requirements-pyqt.txt [testenv:misc] ignore_errors = true From c3455d9082bf7c32ed3932c9d4ad5edef1c51f0f Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 23 Jun 2018 15:57:30 +0200 Subject: [PATCH 111/799] Add a wrapper around sip Starting with PyQt 5.11, the sip module now is bundled with PyQt as PyQt.sip. Having a qutebrowser.qt also helps with #3625, see #995 --- doc/changelog.asciidoc | 1 + qutebrowser/browser/browsertab.py | 2 +- qutebrowser/browser/downloads.py | 2 +- qutebrowser/browser/downloadview.py | 2 +- qutebrowser/browser/qutescheme.py | 2 +- qutebrowser/browser/webengine/webenginetab.py | 2 +- qutebrowser/browser/webengine/webview.py | 2 +- qutebrowser/browser/webkit/webkittab.py | 2 +- qutebrowser/browser/webkit/webpage.py | 2 +- qutebrowser/mainwindow/prompt.py | 2 +- qutebrowser/misc/earlyinit.py | 2 +- qutebrowser/misc/sessions.py | 2 +- qutebrowser/misc/utilcmds.py | 2 +- qutebrowser/qt.py | 28 +++++++++++++++++++ scripts/dev/ci/travis_install.sh | 5 +++- tests/conftest.py | 2 +- tests/unit/utils/test_version.py | 2 +- 17 files changed, 47 insertions(+), 15 deletions(-) create mode 100644 qutebrowser/qt.py diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index 64a051ad0..07b11207a 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -21,6 +21,7 @@ v1.4.0 (unreleased) Added ~~~~~ +- Support for the bundled `sip` module in PyQt 5.11. - New `--debug-flag log-requests` to log requests to the debug log for debugging. - New `--first` flag for `:hint` (bound to `gi` for inputs) which automatically diff --git a/qutebrowser/browser/browsertab.py b/qutebrowser/browser/browsertab.py index 9f2e891c5..0170b4627 100644 --- a/qutebrowser/browser/browsertab.py +++ b/qutebrowser/browser/browsertab.py @@ -22,7 +22,6 @@ import enum import itertools -import sip import attr from PyQt5.QtCore import pyqtSignal, pyqtSlot, QUrl, QObject, QSizeF, Qt from PyQt5.QtGui import QIcon @@ -38,6 +37,7 @@ from qutebrowser.utils import (utils, objreg, usertypes, log, qtutils, urlutils, message) from qutebrowser.misc import miscwidgets, objects from qutebrowser.browser import mouse, hints +from qutebrowser.qt import sip tab_id_gen = itertools.count(0) diff --git a/qutebrowser/browser/downloads.py b/qutebrowser/browser/downloads.py index dd112e00a..2e30c26c2 100644 --- a/qutebrowser/browser/downloads.py +++ b/qutebrowser/browser/downloads.py @@ -29,7 +29,6 @@ import pathlib import tempfile import enum -import sip from PyQt5.QtCore import (pyqtSlot, pyqtSignal, Qt, QObject, QModelIndex, QTimer, QAbstractListModel, QUrl) @@ -37,6 +36,7 @@ from qutebrowser.commands import cmdexc, cmdutils from qutebrowser.config import config from qutebrowser.utils import (usertypes, standarddir, utils, message, log, qtutils) +from qutebrowser.qt import sip ModelRole = enum.IntEnum('ModelRole', ['item'], start=Qt.UserRole) diff --git a/qutebrowser/browser/downloadview.py b/qutebrowser/browser/downloadview.py index 80da117d2..e90e37509 100644 --- a/qutebrowser/browser/downloadview.py +++ b/qutebrowser/browser/downloadview.py @@ -21,13 +21,13 @@ import functools -import sip from PyQt5.QtCore import pyqtSlot, QSize, Qt, QTimer from PyQt5.QtWidgets import QListView, QSizePolicy, QMenu, QStyleFactory from qutebrowser.browser import downloads from qutebrowser.config import config from qutebrowser.utils import qtutils, utils, objreg +from qutebrowser.qt import sip def update_geometry(obj): diff --git a/qutebrowser/browser/qutescheme.py b/qutebrowser/browser/qutescheme.py index e3483bac0..b3787e5d0 100644 --- a/qutebrowser/browser/qutescheme.py +++ b/qutebrowser/browser/qutescheme.py @@ -34,7 +34,6 @@ import urllib import collections import pkg_resources -import sip from PyQt5.QtCore import QUrlQuery, QUrl import qutebrowser @@ -42,6 +41,7 @@ from qutebrowser.config import config, configdata, configexc, configdiff from qutebrowser.utils import (version, utils, jinja, log, message, docutils, objreg, urlutils) from qutebrowser.misc import objects +from qutebrowser.qt import sip pyeval_output = ":pyeval was never called" diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index aec3783aa..6254dd191 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -25,7 +25,6 @@ import sys import re import html as html_utils -import sip from PyQt5.QtCore import (pyqtSignal, pyqtSlot, Qt, QEvent, QPoint, QPointF, QUrl, QTimer, QObject, qVersion) from PyQt5.QtGui import QKeyEvent, QIcon @@ -42,6 +41,7 @@ from qutebrowser.browser.webengine import (webview, webengineelem, tabhistory, from qutebrowser.misc import miscwidgets from qutebrowser.utils import (usertypes, qtutils, log, javascript, utils, message, objreg, jinja, debug) +from qutebrowser.qt import sip _qute_scheme_handler = None diff --git a/qutebrowser/browser/webengine/webview.py b/qutebrowser/browser/webengine/webview.py index ca4126833..b10cc5f9a 100644 --- a/qutebrowser/browser/webengine/webview.py +++ b/qutebrowser/browser/webengine/webview.py @@ -19,7 +19,6 @@ """The main browser widget for QtWebEngine.""" -import sip from PyQt5.QtCore import pyqtSignal, QUrl, PYQT_VERSION from PyQt5.QtGui import QPalette from PyQt5.QtWidgets import QWidget @@ -30,6 +29,7 @@ from qutebrowser.browser.webengine import webenginesettings, certificateerror from qutebrowser.config import config from qutebrowser.utils import log, debug, usertypes, objreg, qtutils from qutebrowser.misc import miscwidgets +from qutebrowser.qt import sip class WebEngineView(QWebEngineView): diff --git a/qutebrowser/browser/webkit/webkittab.py b/qutebrowser/browser/webkit/webkittab.py index 8039eb5eb..31cb82f29 100644 --- a/qutebrowser/browser/webkit/webkittab.py +++ b/qutebrowser/browser/webkit/webkittab.py @@ -23,7 +23,6 @@ import re import functools import xml.etree.ElementTree -import sip from PyQt5.QtCore import (pyqtSlot, Qt, QEvent, QUrl, QPoint, QTimer, QSizeF, QSize) from PyQt5.QtGui import QKeyEvent, QIcon @@ -35,6 +34,7 @@ from qutebrowser.browser import browsertab, shared from qutebrowser.browser.webkit import (webview, tabhistory, webkitelem, webkitsettings) from qutebrowser.utils import qtutils, usertypes, utils, log, debug +from qutebrowser.qt import sip class WebKitAction(browsertab.AbstractAction): diff --git a/qutebrowser/browser/webkit/webpage.py b/qutebrowser/browser/webkit/webpage.py index a6c26c8ee..4e0701329 100644 --- a/qutebrowser/browser/webkit/webpage.py +++ b/qutebrowser/browser/webkit/webpage.py @@ -22,7 +22,6 @@ import html import functools -import sip from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt, QUrl, QPoint from PyQt5.QtGui import QDesktopServices from PyQt5.QtNetwork import QNetworkReply, QNetworkRequest @@ -35,6 +34,7 @@ from qutebrowser.browser import pdfjs, shared from qutebrowser.browser.webkit import http from qutebrowser.browser.webkit.network import networkmanager from qutebrowser.utils import message, usertypes, log, jinja, objreg +from qutebrowser.qt import sip class BrowserPage(QWebPage): diff --git a/qutebrowser/mainwindow/prompt.py b/qutebrowser/mainwindow/prompt.py index f7af28440..357f63dd7 100644 --- a/qutebrowser/mainwindow/prompt.py +++ b/qutebrowser/mainwindow/prompt.py @@ -24,7 +24,6 @@ import html import collections import attr -import sip from PyQt5.QtCore import (pyqtSlot, pyqtSignal, Qt, QTimer, QDir, QModelIndex, QItemSelectionModel, QObject, QEventLoop) from PyQt5.QtWidgets import (QWidget, QGridLayout, QVBoxLayout, QLineEdit, @@ -36,6 +35,7 @@ from qutebrowser.config import config from qutebrowser.utils import usertypes, log, utils, qtutils, objreg, message from qutebrowser.keyinput import modeman from qutebrowser.commands import cmdutils, cmdexc +from qutebrowser.qt import sip prompt_queue = None diff --git a/qutebrowser/misc/earlyinit.py b/qutebrowser/misc/earlyinit.py index 9649d27cc..92e3a9731 100644 --- a/qutebrowser/misc/earlyinit.py +++ b/qutebrowser/misc/earlyinit.py @@ -246,7 +246,7 @@ def configure_pyqt(): from PyQt5.QtCore import pyqtRemoveInputHook pyqtRemoveInputHook() - import sip + from qutebrowser.qt import sip try: # Added in sip 4.19.4 sip.enableoverflowchecking(True) diff --git a/qutebrowser/misc/sessions.py b/qutebrowser/misc/sessions.py index dddf48b05..358ab0c53 100644 --- a/qutebrowser/misc/sessions.py +++ b/qutebrowser/misc/sessions.py @@ -24,7 +24,6 @@ import os.path import itertools import urllib -import sip from PyQt5.QtCore import QUrl, QObject, QPoint, QTimer from PyQt5.QtWidgets import QApplication import yaml @@ -35,6 +34,7 @@ from qutebrowser.commands import cmdexc, cmdutils from qutebrowser.config import config, configfiles from qutebrowser.completion.models import miscmodels from qutebrowser.mainwindow import mainwindow +from qutebrowser.qt import sip default = object() # Sentinel value diff --git a/qutebrowser/misc/utilcmds.py b/qutebrowser/misc/utilcmds.py index 5d987afa3..66483e09a 100644 --- a/qutebrowser/misc/utilcmds.py +++ b/qutebrowser/misc/utilcmds.py @@ -29,7 +29,6 @@ try: except ImportError: hunter = None -import sip from PyQt5.QtCore import QUrl # so it's available for :debug-pyeval from PyQt5.QtWidgets import QApplication # pylint: disable=unused-import @@ -40,6 +39,7 @@ from qutebrowser.commands import cmdutils, runners, cmdexc from qutebrowser.config import config, configdata from qutebrowser.misc import consolewidget from qutebrowser.utils.version import pastebin_version +from qutebrowser.qt import sip @cmdutils.register(maxsplit=1, no_cmd_split=True, no_replace_variables=True) diff --git a/qutebrowser/qt.py b/qutebrowser/qt.py new file mode 100644 index 000000000..2878bbe98 --- /dev/null +++ b/qutebrowser/qt.py @@ -0,0 +1,28 @@ +# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: + +# Copyright 2018 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 . + +"""Wrappers around Qt/PyQt code.""" + +# pylint: disable=unused-import +# PyQt 5.11 comes with a bundled sip, +# for older PyQt versions it's a separate module. +try: + from PyQt5 import sip +except ImportError: + import sip diff --git a/scripts/dev/ci/travis_install.sh b/scripts/dev/ci/travis_install.sh index 20aa1c12d..29711a11b 100644 --- a/scripts/dev/ci/travis_install.sh +++ b/scripts/dev/ci/travis_install.sh @@ -62,7 +62,10 @@ check_pyqt() { python3 < Date: Sat, 23 Jun 2018 17:21:55 +0200 Subject: [PATCH 112/799] Add workaround for PyQt 5.11 headerDataChanged bug https://www.riverbankcomputing.com/pipermail/pyqt/2018-June/040445.html --- doc/changelog.asciidoc | 3 ++- qutebrowser/browser/downloads.py | 7 ++++++- qutebrowser/completion/models/completionmodel.py | 8 +++++++- qutebrowser/completion/models/histcategory.py | 6 ++++++ qutebrowser/completion/models/listcategory.py | 8 +++++++- 5 files changed, 28 insertions(+), 4 deletions(-) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index 07b11207a..1b97a906b 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -21,7 +21,8 @@ v1.4.0 (unreleased) Added ~~~~~ -- Support for the bundled `sip` module in PyQt 5.11. +- Support for the bundled `sip` module in PyQt 5.11 and workarounds for + PyQt 5.11 bugs. - New `--debug-flag log-requests` to log requests to the debug log for debugging. - New `--first` flag for `:hint` (bound to `gi` for inputs) which automatically diff --git a/qutebrowser/browser/downloads.py b/qutebrowser/browser/downloads.py index 2e30c26c2..b06cedbfd 100644 --- a/qutebrowser/browser/downloads.py +++ b/qutebrowser/browser/downloads.py @@ -30,7 +30,7 @@ import tempfile import enum from PyQt5.QtCore import (pyqtSlot, pyqtSignal, Qt, QObject, QModelIndex, - QTimer, QAbstractListModel, QUrl) + QTimer, QAbstractListModel, QUrl, PYQT_VERSION) from qutebrowser.commands import cmdexc, cmdutils from qutebrowser.config import config @@ -878,6 +878,11 @@ class DownloadModel(QAbstractListModel): """A list model showing downloads.""" + if PYQT_VERSION == 0x050b00: + # WORKAROUND for PyQt 5.11 bug: + # https://www.riverbankcomputing.com/pipermail/pyqt/2018-June/040445.html + headerDataChanged = pyqtSignal(Qt.Orientation, int, int) + def __init__(self, qtnetwork_manager, webengine_manager=None, parent=None): super().__init__(parent) self._qtnetwork_manager = qtnetwork_manager diff --git a/qutebrowser/completion/models/completionmodel.py b/qutebrowser/completion/models/completionmodel.py index 1c77e1d31..633d425d9 100644 --- a/qutebrowser/completion/models/completionmodel.py +++ b/qutebrowser/completion/models/completionmodel.py @@ -19,7 +19,8 @@ """A model that proxies access to one or more completion categories.""" -from PyQt5.QtCore import Qt, QModelIndex, QAbstractItemModel +from PyQt5.QtCore import (Qt, QModelIndex, QAbstractItemModel, pyqtSignal, + PYQT_VERSION) from qutebrowser.utils import log, qtutils from qutebrowser.commands import cmdexc @@ -38,6 +39,11 @@ class CompletionModel(QAbstractItemModel): _categories: The sub-categories. """ + if PYQT_VERSION == 0x050b00: + # WORKAROUND for PyQt 5.11 bug: + # https://www.riverbankcomputing.com/pipermail/pyqt/2018-June/040445.html + headerDataChanged = pyqtSignal(Qt.Orientation, int, int) + def __init__(self, *, column_widths=(30, 70, 0), parent=None): super().__init__(parent) self.column_widths = column_widths diff --git a/qutebrowser/completion/models/histcategory.py b/qutebrowser/completion/models/histcategory.py index 60f801492..06fa487ed 100644 --- a/qutebrowser/completion/models/histcategory.py +++ b/qutebrowser/completion/models/histcategory.py @@ -19,6 +19,7 @@ """A completion category that queries the SQL History store.""" +from PyQt5.QtCore import pyqtSignal, Qt, PYQT_VERSION from PyQt5.QtSql import QSqlQueryModel from qutebrowser.misc import sql @@ -30,6 +31,11 @@ class HistoryCategory(QSqlQueryModel): """A completion category that queries the SQL History store.""" + if PYQT_VERSION == 0x050b00: + # WORKAROUND for PyQt 5.11 bug: + # https://www.riverbankcomputing.com/pipermail/pyqt/2018-June/040445.html + headerDataChanged = pyqtSignal(Qt.Orientation, int, int) + def __init__(self, *, delete_func=None, parent=None): """Create a new History completion category.""" super().__init__(parent=parent) diff --git a/qutebrowser/completion/models/listcategory.py b/qutebrowser/completion/models/listcategory.py index 13bc1e6b2..1d0c41e99 100644 --- a/qutebrowser/completion/models/listcategory.py +++ b/qutebrowser/completion/models/listcategory.py @@ -21,7 +21,8 @@ import re -from PyQt5.QtCore import Qt, QSortFilterProxyModel, QRegExp +from PyQt5.QtCore import (Qt, QSortFilterProxyModel, QRegExp, PYQT_VERSION, + pyqtSignal) from PyQt5.QtGui import QStandardItem, QStandardItemModel from qutebrowser.utils import qtutils @@ -31,6 +32,11 @@ class ListCategory(QSortFilterProxyModel): """Expose a list of items as a category for the CompletionModel.""" + if PYQT_VERSION == 0x050b00: + # WORKAROUND for PyQt 5.11 bug: + # https://www.riverbankcomputing.com/pipermail/pyqt/2018-June/040445.html + headerDataChanged = pyqtSignal(Qt.Orientation, int, int) + def __init__(self, name, items, sort=True, delete_func=None, parent=None): super().__init__(parent) self.name = name From ad19833e34796fe72a2d3e5c948b623c1dd1e92a Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 24 Jun 2018 08:49:51 +0200 Subject: [PATCH 113/799] Revert "Add workaround for PyQt 5.11 headerDataChanged bug" PyQt 5.11.1 has already been released, with the bug fixed. This reverts commit 291763a55643342a6f977ce2a12dcc6f4badbe8a. --- doc/changelog.asciidoc | 3 +-- qutebrowser/browser/downloads.py | 7 +------ qutebrowser/completion/models/completionmodel.py | 8 +------- qutebrowser/completion/models/histcategory.py | 6 ------ qutebrowser/completion/models/listcategory.py | 8 +------- 5 files changed, 4 insertions(+), 28 deletions(-) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index 1b97a906b..07b11207a 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -21,8 +21,7 @@ v1.4.0 (unreleased) Added ~~~~~ -- Support for the bundled `sip` module in PyQt 5.11 and workarounds for - PyQt 5.11 bugs. +- Support for the bundled `sip` module in PyQt 5.11. - New `--debug-flag log-requests` to log requests to the debug log for debugging. - New `--first` flag for `:hint` (bound to `gi` for inputs) which automatically diff --git a/qutebrowser/browser/downloads.py b/qutebrowser/browser/downloads.py index b06cedbfd..2e30c26c2 100644 --- a/qutebrowser/browser/downloads.py +++ b/qutebrowser/browser/downloads.py @@ -30,7 +30,7 @@ import tempfile import enum from PyQt5.QtCore import (pyqtSlot, pyqtSignal, Qt, QObject, QModelIndex, - QTimer, QAbstractListModel, QUrl, PYQT_VERSION) + QTimer, QAbstractListModel, QUrl) from qutebrowser.commands import cmdexc, cmdutils from qutebrowser.config import config @@ -878,11 +878,6 @@ class DownloadModel(QAbstractListModel): """A list model showing downloads.""" - if PYQT_VERSION == 0x050b00: - # WORKAROUND for PyQt 5.11 bug: - # https://www.riverbankcomputing.com/pipermail/pyqt/2018-June/040445.html - headerDataChanged = pyqtSignal(Qt.Orientation, int, int) - def __init__(self, qtnetwork_manager, webengine_manager=None, parent=None): super().__init__(parent) self._qtnetwork_manager = qtnetwork_manager diff --git a/qutebrowser/completion/models/completionmodel.py b/qutebrowser/completion/models/completionmodel.py index 633d425d9..1c77e1d31 100644 --- a/qutebrowser/completion/models/completionmodel.py +++ b/qutebrowser/completion/models/completionmodel.py @@ -19,8 +19,7 @@ """A model that proxies access to one or more completion categories.""" -from PyQt5.QtCore import (Qt, QModelIndex, QAbstractItemModel, pyqtSignal, - PYQT_VERSION) +from PyQt5.QtCore import Qt, QModelIndex, QAbstractItemModel from qutebrowser.utils import log, qtutils from qutebrowser.commands import cmdexc @@ -39,11 +38,6 @@ class CompletionModel(QAbstractItemModel): _categories: The sub-categories. """ - if PYQT_VERSION == 0x050b00: - # WORKAROUND for PyQt 5.11 bug: - # https://www.riverbankcomputing.com/pipermail/pyqt/2018-June/040445.html - headerDataChanged = pyqtSignal(Qt.Orientation, int, int) - def __init__(self, *, column_widths=(30, 70, 0), parent=None): super().__init__(parent) self.column_widths = column_widths diff --git a/qutebrowser/completion/models/histcategory.py b/qutebrowser/completion/models/histcategory.py index 06fa487ed..60f801492 100644 --- a/qutebrowser/completion/models/histcategory.py +++ b/qutebrowser/completion/models/histcategory.py @@ -19,7 +19,6 @@ """A completion category that queries the SQL History store.""" -from PyQt5.QtCore import pyqtSignal, Qt, PYQT_VERSION from PyQt5.QtSql import QSqlQueryModel from qutebrowser.misc import sql @@ -31,11 +30,6 @@ class HistoryCategory(QSqlQueryModel): """A completion category that queries the SQL History store.""" - if PYQT_VERSION == 0x050b00: - # WORKAROUND for PyQt 5.11 bug: - # https://www.riverbankcomputing.com/pipermail/pyqt/2018-June/040445.html - headerDataChanged = pyqtSignal(Qt.Orientation, int, int) - def __init__(self, *, delete_func=None, parent=None): """Create a new History completion category.""" super().__init__(parent=parent) diff --git a/qutebrowser/completion/models/listcategory.py b/qutebrowser/completion/models/listcategory.py index 1d0c41e99..13bc1e6b2 100644 --- a/qutebrowser/completion/models/listcategory.py +++ b/qutebrowser/completion/models/listcategory.py @@ -21,8 +21,7 @@ import re -from PyQt5.QtCore import (Qt, QSortFilterProxyModel, QRegExp, PYQT_VERSION, - pyqtSignal) +from PyQt5.QtCore import Qt, QSortFilterProxyModel, QRegExp from PyQt5.QtGui import QStandardItem, QStandardItemModel from qutebrowser.utils import qtutils @@ -32,11 +31,6 @@ class ListCategory(QSortFilterProxyModel): """Expose a list of items as a category for the CompletionModel.""" - if PYQT_VERSION == 0x050b00: - # WORKAROUND for PyQt 5.11 bug: - # https://www.riverbankcomputing.com/pipermail/pyqt/2018-June/040445.html - headerDataChanged = pyqtSignal(Qt.Orientation, int, int) - def __init__(self, name, items, sort=True, delete_func=None, parent=None): super().__init__(parent) self.name = name From 77fe2e1c859c3b74905aa05746f48d36e7d6312a Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 23 Jun 2018 17:25:30 +0200 Subject: [PATCH 114/799] Fix test_set_wrong_backend --- tests/unit/config/test_config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/config/test_config.py b/tests/unit/config/test_config.py index e1ef7ef94..bf4f7c02d 100644 --- a/tests/unit/config/test_config.py +++ b/tests/unit/config/test_config.py @@ -642,8 +642,8 @@ class TestConfig: meth = getattr(conf, method) with pytest.raises(configexc.BackendError): with qtbot.assert_not_emitted(conf.changed): - meth('content.cookies.accept', 'all') - assert not conf._values['content.cookies.accept'] + meth('hints.find_implementation', 'javascript') + assert not conf._values['hints.find_implementation'] @pytest.mark.parametrize('method, value', [ ('set_obj', {}), From d861c097b157a88d69dd54b0ab7e428c91977270 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 23 Jun 2018 18:10:00 +0200 Subject: [PATCH 115/799] Support new dead keys added in Qt 5.11 properly See https://codereview.qt-project.org/#/c/207231/ --- qutebrowser/keyinput/keyutils.py | 31 ++++++++++++++++ tests/unit/keyinput/key_data.py | 61 ++++++++++++++++---------------- 2 files changed, 61 insertions(+), 31 deletions(-) diff --git a/qutebrowser/keyinput/keyutils.py b/qutebrowser/keyinput/keyutils.py index f0cf9c22a..e22e24f77 100644 --- a/qutebrowser/keyinput/keyutils.py +++ b/qutebrowser/keyinput/keyutils.py @@ -138,6 +138,37 @@ def _key_to_string(key): 'Dead_Hook': 'Hook', 'Dead_Horn': 'Horn', + 'Dead_Stroke': '̵', + 'Dead_Abovecomma': '̓', + 'Dead_Abovereversedcomma': '̔', + 'Dead_Doublegrave': '̏', + 'Dead_Belowring': '̥', + 'Dead_Belowmacron': '̱', + 'Dead_Belowcircumflex': '̭', + 'Dead_Belowtilde': '̰', + 'Dead_Belowbreve': '̮', + 'Dead_Belowdiaeresis': '̤', + 'Dead_Invertedbreve': '̑', + 'Dead_Belowcomma': '̦', + 'Dead_Currency': '¤', + 'Dead_a': 'a', + 'Dead_A': 'A', + 'Dead_e': 'e', + 'Dead_E': 'E', + 'Dead_i': 'i', + 'Dead_I': 'I', + 'Dead_o': 'o', + 'Dead_O': 'O', + 'Dead_u': 'u', + 'Dead_U': 'U', + 'Dead_Small_Schwa': 'ə', + 'Dead_Capital_Schwa': 'Ə', + 'Dead_Greek': 'Greek', + 'Dead_Lowline': '̲', + 'Dead_Aboveverticalline': '̍', + 'Dead_Belowverticalline': '\u0329', + 'Dead_Longsolidusoverlay': '̸', + 'Memo': 'Memo', 'ToDoList': 'To Do List', 'Calendar': 'Calendar', diff --git a/tests/unit/keyinput/key_data.py b/tests/unit/keyinput/key_data.py index bf1ccdede..48b5c8c56 100644 --- a/tests/unit/keyinput/key_data.py +++ b/tests/unit/keyinput/key_data.py @@ -374,37 +374,36 @@ KEYS = [ Key('Dead_Hook', 'Hook', qtest=False), Key('Dead_Horn', 'Horn', qtest=False), - # Not in Qt 5.10, so data may be wrong! - Key('Dead_Stroke', qtest=False), - Key('Dead_Abovecomma', qtest=False), - Key('Dead_Abovereversedcomma', qtest=False), - Key('Dead_Doublegrave', qtest=False), - Key('Dead_Belowring', qtest=False), - Key('Dead_Belowmacron', qtest=False), - Key('Dead_Belowcircumflex', qtest=False), - Key('Dead_Belowtilde', qtest=False), - Key('Dead_Belowbreve', qtest=False), - Key('Dead_Belowdiaeresis', qtest=False), - Key('Dead_Invertedbreve', qtest=False), - Key('Dead_Belowcomma', qtest=False), - Key('Dead_Currency', qtest=False), - Key('Dead_a', qtest=False), - Key('Dead_A', qtest=False), - Key('Dead_e', qtest=False), - Key('Dead_E', qtest=False), - Key('Dead_i', qtest=False), - Key('Dead_I', qtest=False), - Key('Dead_o', qtest=False), - Key('Dead_O', qtest=False), - Key('Dead_u', qtest=False), - Key('Dead_U', qtest=False), - Key('Dead_Small_Schwa', qtest=False), - Key('Dead_Capital_Schwa', qtest=False), - Key('Dead_Greek', qtest=False), - Key('Dead_Lowline', qtest=False), - Key('Dead_Aboveverticalline', qtest=False), - Key('Dead_Belowverticalline', qtest=False), - Key('Dead_Longsolidusoverlay', qtest=False), + Key('Dead_Stroke', '̵', qtest=False), + Key('Dead_Abovecomma', '̓', qtest=False), + Key('Dead_Abovereversedcomma', '̔', qtest=False), + Key('Dead_Doublegrave', '̏', qtest=False), + Key('Dead_Belowring', '̥', qtest=False), + Key('Dead_Belowmacron', '̱', qtest=False), + Key('Dead_Belowcircumflex', '̭', qtest=False), + Key('Dead_Belowtilde', '̰', qtest=False), + Key('Dead_Belowbreve', '̮', qtest=False), + Key('Dead_Belowdiaeresis', '̤', qtest=False), + Key('Dead_Invertedbreve', '̑', qtest=False), + Key('Dead_Belowcomma', '̦', qtest=False), + Key('Dead_Currency', '¤', qtest=False), + Key('Dead_a', 'a', qtest=False), + Key('Dead_A', 'A', qtest=False), + Key('Dead_e', 'e', qtest=False), + Key('Dead_E', 'E', qtest=False), + Key('Dead_i', 'i', qtest=False), + Key('Dead_I', 'I', qtest=False), + Key('Dead_o', 'o', qtest=False), + Key('Dead_O', 'O', qtest=False), + Key('Dead_u', 'u', qtest=False), + Key('Dead_U', 'U', qtest=False), + Key('Dead_Small_Schwa', 'ə', qtest=False), + Key('Dead_Capital_Schwa', 'Ə', qtest=False), + Key('Dead_Greek', 'Greek', qtest=False), + Key('Dead_Lowline', '̲', qtest=False), + Key('Dead_Aboveverticalline', '̍', qtest=False), + Key('Dead_Belowverticalline', '\u0329', qtest=False), + Key('Dead_Longsolidusoverlay', '̸', qtest=False), ### multimedia/internet keys - ignored by default - see QKeyEvent c'tor Key('Back'), From 9a14574c9f87d3e8937b513746bd222b5e5e319c Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 23 Jun 2018 18:31:01 +0200 Subject: [PATCH 116/799] Skip invalid links on any Qt 5.11 version See #3661 --- tests/end2end/features/hints.feature | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/end2end/features/hints.feature b/tests/end2end/features/hints.feature index e33b16f68..3a0cb1da0 100644 --- a/tests/end2end/features/hints.feature +++ b/tests/end2end/features/hints.feature @@ -165,13 +165,13 @@ Feature: Using hints And I hint with args "all run message-info {hint-url}" and follow a Then the message "http://localhost:(port)/data/hello.txt" should be shown - @qt!=5.11.0 + @qt<5.11 Scenario: Clicking an invalid link When I open data/invalid_link.html And I hint with args "all" and follow a Then the error "Invalid link clicked - *" should be shown - @qt!=5.11.0 + @qt<5.11 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 From 41303ecfcfad4d8c054cc113c0065ff016033d24 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 27 Jun 2018 12:53:29 +0200 Subject: [PATCH 117/799] Make sure temporary dir exists This seems to be enforced with Qt 5.12 See #4025 --- tests/unit/utils/test_standarddir.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/unit/utils/test_standarddir.py b/tests/unit/utils/test_standarddir.py index a483ede64..b9777f670 100644 --- a/tests/unit/utils/test_standarddir.py +++ b/tests/unit/utils/test_standarddir.py @@ -183,10 +183,13 @@ class TestStandardDir: @pytest.mark.qt_log_ignore(r'^QStandardPaths: ') def test_linux_invalid_runtimedir(self, monkeypatch, tmpdir): """With invalid XDG_RUNTIME_DIR, fall back to TempLocation.""" + tmpdir_env = tmpdir / 'temp' + tmpdir_env.ensure(dir=True) monkeypatch.setenv('XDG_RUNTIME_DIR', str(tmpdir / 'does-not-exist')) - monkeypatch.setenv('TMPDIR', str(tmpdir / 'temp')) + monkeypatch.setenv('TMPDIR', tmpdir_env) + standarddir._init_dirs() - assert standarddir.runtime() == str(tmpdir / 'temp' / APPNAME) + assert standarddir.runtime() == str(tmpdir_env / APPNAME) @pytest.mark.fake_os('windows') def test_runtimedir_empty_tempdir(self, monkeypatch, tmpdir): From 6ca11ed95bd68b0b296bcdb7dbe0c750f8baf9d5 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 2 Jul 2018 23:15:04 +0200 Subject: [PATCH 118/799] Adjust SSL test for Qt 5.11 --- tests/end2end/features/test_prompts_bdd.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/end2end/features/test_prompts_bdd.py b/tests/end2end/features/test_prompts_bdd.py index a21c943f1..0d74700b4 100644 --- a/tests/end2end/features/test_prompts_bdd.py +++ b/tests/end2end/features/test_prompts_bdd.py @@ -54,7 +54,8 @@ def ssl_error_page(request, quteproc): quteproc.wait_for(message="Certificate error: *") time.sleep(0.5) # Wait for error page to appear content = quteproc.get_content().strip() - assert "ERR_INSECURE_RESPONSE" in content + assert ("ERR_INSECURE_RESPONSE" in content or # Qt <= 5.10 + "ERR_CERT_AUTHORITY_INVALID" in content) # Qt 5.11 else: if not request.config.webengine: line = quteproc.wait_for(message='Error while loading *: SSL ' From f6f713bbfe58f428432a3f10d4d00f5b1cedcf1b Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 2 Jul 2018 23:19:57 +0200 Subject: [PATCH 119/799] Skip key forwarding tests on Qt 5.11.1 See #4036 --- tests/end2end/features/keyinput.feature | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/end2end/features/keyinput.feature b/tests/end2end/features/keyinput.feature index f681012f1..5456b6739 100644 --- a/tests/end2end/features/keyinput.feature +++ b/tests/end2end/features/keyinput.feature @@ -18,6 +18,7 @@ Feature: Keyboard input # input.forward_unbound_keys + @qt<5.11.1 Scenario: Forwarding all keys When I open data/keyinput/log.html And I set input.forward_unbound_keys to all @@ -30,6 +31,7 @@ Feature: Keyboard input And the javascript message "key press: 112" should be logged And the javascript message "key release: 112" should be logged + @qt<5.11.1 Scenario: Forwarding special keys When I open data/keyinput/log.html And I set input.forward_unbound_keys to auto From 1d91a3ac66953b1cd1a2214a71be71dc0575052f Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 2 Jul 2018 19:21:17 +0200 Subject: [PATCH 120/799] Update hypothesis from 3.61.0 to 3.65.0 (cherry picked from commit 98ecc7e77b7dd21037224a4f3b51d0cb654610e6) --- 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 d48f5b21a..2fa63dced 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -11,7 +11,7 @@ fields==5.0.0 Flask==1.0.2 glob2==0.6 hunter==2.0.2 -hypothesis==3.61.0 +hypothesis==3.65.0 itsdangerous==0.24 # Jinja2==2.10 Mako==1.0.7 From 564cd3732bdd8cefa2bdf6d6fb5ed9a8f98fdffe Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 2 Jul 2018 19:21:19 +0200 Subject: [PATCH 121/799] Update py from 1.5.3 to 1.5.4 (cherry picked from commit 2006ad70e88cf5fffec75aa465f968ebba722758) --- 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 2fa63dced..4475def05 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -20,7 +20,7 @@ more-itertools==4.2.0 parse==1.8.4 parse-type==0.4.2 pluggy==0.6.0 -py==1.5.3 +py==1.5.4 py-cpuinfo==4.0.0 pytest==3.6.2 pytest-bdd==2.21.0 From c33c01907503f73c0c8838ebeabbf77997c5b01e Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 2 Jul 2018 19:21:20 +0200 Subject: [PATCH 122/799] Update py from 1.5.3 to 1.5.4 (cherry picked from commit fa9698564af184e6e7cdddc96c9906442031197e) --- misc/requirements/requirements-tox.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-tox.txt b/misc/requirements/requirements-tox.txt index e8b3de2b1..64ab8bf68 100644 --- a/misc/requirements/requirements-tox.txt +++ b/misc/requirements/requirements-tox.txt @@ -1,7 +1,7 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py pluggy==0.6.0 -py==1.5.3 +py==1.5.4 six==1.11.0 tox==3.0.0 virtualenv==16.0.0 From 1c8917b10e7561d286bfce51bad3d2c76b3f2e00 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 3 Jul 2018 10:27:10 +0200 Subject: [PATCH 123/799] Remove old brew_install line --- scripts/dev/ci/travis_install.sh | 5 ----- 1 file changed, 5 deletions(-) diff --git a/scripts/dev/ci/travis_install.sh b/scripts/dev/ci/travis_install.sh index 29711a11b..20dc2da8d 100644 --- a/scripts/dev/ci/travis_install.sh +++ b/scripts/dev/ci/travis_install.sh @@ -43,11 +43,6 @@ travis_retry() { return $result } -brew_install() { - brew update - brew install "$@" -} - pip_install() { travis_retry python3 -m pip install "$@" } From 05531ddcf097cdbe1e4a6dc2a154fecdf0930e12 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 3 Jul 2018 10:27:38 +0200 Subject: [PATCH 124/799] brew: Update instead of install libyaml --- scripts/dev/ci/travis_install.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/dev/ci/travis_install.sh b/scripts/dev/ci/travis_install.sh index 20dc2da8d..18f5aa9ec 100644 --- a/scripts/dev/ci/travis_install.sh +++ b/scripts/dev/ci/travis_install.sh @@ -82,8 +82,8 @@ elif [[ $TRAVIS_OS_NAME == osx ]]; then brew --version brew update - brew upgrade python - brew install qt5 pyqt5 libyaml + brew upgrade python libyaml + brew install qt5 pyqt5 pip_install -r misc/requirements/requirements-tox.txt python3 -m pip --version From 26d6cf8ef60dd21e63f44ce281830ee08d8d5c7b Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 3 Jul 2018 10:29:28 +0200 Subject: [PATCH 125/799] Stabilize URL escaping test --- tests/end2end/features/history.feature | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/end2end/features/history.feature b/tests/end2end/features/history.feature index e1a7d3326..c3791945e 100644 --- a/tests/end2end/features/history.feature +++ b/tests/end2end/features/history.feature @@ -118,10 +118,11 @@ Feature: Page history And I open qute://history Then the javascript message "XSS" should not be logged + @flaky Scenario: Escaping of URLs in :history When I open query?one=1&two=2 And I open qute://history - And I wait 0.5s # JS loads the history async + And I wait 1s # JS loads the history async And I hint with args "links normal" and follow a And I wait until query?one=1&two=2 is loaded Then the query parameter two should be set to 2 From efc4eb906954ff6eb40cd675060fc9ed3f632401 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 3 Jul 2018 10:30:30 +0200 Subject: [PATCH 126/799] Use PyYAML from git for requirements-tests-git --- misc/requirements/requirements-tests-git.txt | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/misc/requirements/requirements-tests-git.txt b/misc/requirements/requirements-tests-git.txt index 6681dd15e..ce00cd31c 100644 --- a/misc/requirements/requirements-tests-git.txt +++ b/misc/requirements/requirements-tests-git.txt @@ -35,8 +35,4 @@ git+https://github.com/pallets/markupsafe.git hg+http://bitbucket.org/birkenfeld/pygments-main hg+https://bitbucket.org/fdik/pypeg git+https://github.com/python-attrs/attrs.git - -# Fails to build: -# gcc: error: ext/_yaml.c: No such file or directory -# hg+https://bitbucket.org/xi/pyyaml -PyYAML==3.12 +git+https://github.com/yaml/pyyaml.git From e6e28c846f97535f489ad55b29516e1b5c8bef73 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 3 Jul 2018 10:30:59 +0200 Subject: [PATCH 127/799] Use PyYAML 3.13b1 It seems to be finished except for a missing wheel we don't need, and it makes things work on Python 3.7 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 842b0c42d..4516340d7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,4 +7,4 @@ Jinja2==2.10 MarkupSafe==1.0 Pygments==2.2.0 pyPEG2==2.15.2 -PyYAML==3.12 # rq.filter: != 4.1 +PyYAML==3.13b1 # rq.filter: != 4.1 From ec0bbe67f8b67c8419a0e6b815bcf979a38310af Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 3 Jul 2018 11:00:33 +0200 Subject: [PATCH 128/799] travis: Test with Python 3.7 See https://github.com/travis-ci/travis-ci/issues/9069 --- .travis.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.travis.yml b/.travis.yml index c8732fe29..87605f873 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,6 +23,12 @@ matrix: env: TESTENV=py36-pyqt510 - os: linux env: TESTENV=py36-pyqt511-cov + # https://github.com/travis-ci/travis-ci/issues/9069 + - os: linux + python: 3.7 + sudo: required + dist: xenial + env: TESTENV=py37-pyqt511 - os: osx env: TESTENV=py36 OSX=sierra osx_image: xcode9.2 From 49be92e047a96a7fc2c7c23a269292e560701299 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 3 Jul 2018 11:06:31 +0200 Subject: [PATCH 129/799] Use 64-bit Python on AppVeyor QtWebEngine isn't available in the 32-bit build. --- .appveyor.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index d336f20cc..70e51fa42 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -5,7 +5,7 @@ cache: build: off environment: PYTHONUNBUFFERED: 1 - PYTHON: C:\Python36\python.exe + PYTHON: C:\Python36-x64\python.exe matrix: - TESTENV: py36-pyqt511 - TESTENV: pylint @@ -13,7 +13,7 @@ environment: install: - '%PYTHON% -m pip install -U pip' - '%PYTHON% -m pip install -r misc\requirements\requirements-tox.txt' - - 'set PATH=%PATH%;C:\Python36' + - 'set PATH=%PATH%;C:\Python36-x64' test_script: - '%PYTHON% -m tox -e %TESTENV%' From 0af8eec73a8a4290b0a0348f3ad8d8295e17deaa Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 3 Jul 2018 11:08:56 +0200 Subject: [PATCH 130/799] build_release: Remove 32-bit support for Windows QtWebEngine isn't available for 32-bit anymore: https://blog.qt.io/blog/2018/05/22/qt-5-11-released/ (comments) --- scripts/dev/build_release.py | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/scripts/dev/build_release.py b/scripts/dev/build_release.py index 146b7a462..366c6f393 100755 --- a/scripts/dev/build_release.py +++ b/scripts/dev/build_release.py @@ -239,17 +239,7 @@ def build_windows(): except FileNotFoundError: python_x64 = r'C:\Python{}\python.exe'.format(ver) - try: - reg32_key = winreg.OpenKeyEx(winreg.HKEY_LOCAL_MACHINE, - r'SOFTWARE\WOW6432Node\Python\PythonCore' - r'\{}-32\InstallPath'.format(dot_ver)) - python_x86 = winreg.QueryValueEx(reg32_key, 'ExecutablePath')[0] - except FileNotFoundError: - python_x86 = r'C:\Python{}-32\python.exe'.format(ver) - out_pyinstaller = os.path.join('dist', 'qutebrowser') - out_32 = os.path.join('dist', - 'qutebrowser-{}-x86'.format(qutebrowser.__version__)) out_64 = os.path.join('dist', 'qutebrowser-{}-x64'.format(qutebrowser.__version__)) @@ -258,12 +248,6 @@ def build_windows(): utils.print_title("Updating VersionInfo file") gen_versioninfo.main() - utils.print_title("Running pyinstaller 32bit") - _maybe_remove(out_32) - call_tox('pyinstaller', '-r', python=python_x86) - shutil.move(out_pyinstaller, out_32) - patch_windows(out_32) - utils.print_title("Running pyinstaller 64bit") _maybe_remove(out_64) call_tox('pyinstaller', '-r', python=python_x64) @@ -279,31 +263,17 @@ def build_windows(): '/DVERSION={}'.format(qutebrowser.__version__), 'misc/qutebrowser.nsi'], check=True) - name_32 = 'qutebrowser-{}-win32.exe'.format(qutebrowser.__version__) name_64 = 'qutebrowser-{}-amd64.exe'.format(qutebrowser.__version__) artifacts += [ - (os.path.join('dist', name_32), - 'application/vnd.microsoft.portable-executable', - 'Windows 32bit installer'), (os.path.join('dist', name_64), 'application/vnd.microsoft.portable-executable', 'Windows 64bit installer'), ] - utils.print_title("Running 32bit smoke test") - smoke_test(os.path.join(out_32, 'qutebrowser.exe')) utils.print_title("Running 64bit smoke test") smoke_test(os.path.join(out_64, 'qutebrowser.exe')) - utils.print_title("Zipping 32bit standalone...") - name = 'qutebrowser-{}-windows-standalone-win32'.format( - qutebrowser.__version__) - shutil.make_archive(name, 'zip', 'dist', os.path.basename(out_32)) - artifacts.append(('{}.zip'.format(name), - 'application/zip', - 'Windows 32bit standalone')) - utils.print_title("Zipping 64bit standalone...") name = 'qutebrowser-{}-windows-standalone-amd64'.format( qutebrowser.__version__) From 5add2cd9302bf4c116f2fc4443d5627b192f045b Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 3 Jul 2018 11:15:59 +0200 Subject: [PATCH 131/799] Update changelog --- doc/changelog.asciidoc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index 07b11207a..79a75f49f 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -123,6 +123,10 @@ Fixed Removed ~~~~~~~ +- No prebuilt binaries for 32-bit Windows are supplied anymore. This is due to + Qt removing QtWebEngine support for those upstream. It might be possible to + distribute 32-bit binaries again with Qt 5.12 in December, but that will only + happen if it turns out enough people actually need 32-bit support. - `:tab-detach` which has been deprecated in v1.1.0 has been removed. - The `content.developer_extras` setting got removed. On QtWebKit, developer extras are now automatically enabled when opening the inspector. From f65f3db747b862d0acb6838dbd915febac99f155 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 3 Jul 2018 11:17:27 +0200 Subject: [PATCH 132/799] appveyor: Make sure we get the Python we want --- .appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.appveyor.yml b/.appveyor.yml index 70e51fa42..92a20c0bd 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -13,7 +13,7 @@ environment: install: - '%PYTHON% -m pip install -U pip' - '%PYTHON% -m pip install -r misc\requirements\requirements-tox.txt' - - 'set PATH=%PATH%;C:\Python36-x64' + - 'set PATH=C:\Python36-x64;%PATH' test_script: - '%PYTHON% -m tox -e %TESTENV%' From b8fb5d45909780a8c51c0706673eeea6fe186470 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 3 Jul 2018 11:37:26 +0200 Subject: [PATCH 133/799] Make sure we're using Python 3.6 in release instructions --- doc/contributing.asciidoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/contributing.asciidoc b/doc/contributing.asciidoc index b130825ec..26ff12a15 100644 --- a/doc/contributing.asciidoc +++ b/doc/contributing.asciidoc @@ -710,8 +710,8 @@ qutebrowser release as closed. * Linux: Run `git checkout v1.$x.$y && ./.venv/bin/python3 scripts/dev/build_release.py --upload v1.$x.$y`. -* Windows: Run `git checkout v1.X.Y; C:\Python36-32\python scripts\dev\build_release.py --asciidoc C:\Python27\python C:\asciidoc-8.6.9\asciidoc.py --upload v1.X.Y` (replace X/Y by hand). -* macOS: Run `git checkout v1.X.Y && python3 scripts/dev/build_release.py --upload v1.X.Y` (replace X/Y by hand). +* Windows: Run `git checkout v1.X.Y; py -3.6 scripts\dev\build_release.py --asciidoc C:\Python27\python C:\asciidoc-8.6.9\asciidoc.py --upload v1.X.Y` (replace X/Y by hand). +* macOS: Run `pyenv shell 3.6.6 && git checkout v1.X.Y && python3 scripts/dev/build_release.py --upload v1.X.Y` (replace X/Y by hand). * On server: - Run `python3 scripts/dev/download_release.py v1.X.Y` (replace X/Y by hand). - Run `git pull github master && sudo python3 scripts/asciidoc2html.py --website /srv/http/qutebrowser` From 38791a2386e273193b33c2a6d1abf82709242382 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 3 Jul 2018 11:38:48 +0200 Subject: [PATCH 134/799] Only import gen_versioninfo on Windows --- scripts/dev/build_release.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/dev/build_release.py b/scripts/dev/build_release.py index 366c6f393..689bd49fc 100755 --- a/scripts/dev/build_release.py +++ b/scripts/dev/build_release.py @@ -45,7 +45,6 @@ sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir, import qutebrowser from scripts import utils # from scripts.dev import update_3rdparty -from scripts.dev import gen_versioninfo def call_script(name, *args, python=sys.executable): @@ -245,6 +244,7 @@ def build_windows(): artifacts = [] + from scripts.dev import gen_versioninfo utils.print_title("Updating VersionInfo file") gen_versioninfo.main() From 641f7eb3c56a4c5a089700a91b413d462dfb1855 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 3 Jul 2018 13:08:54 +0200 Subject: [PATCH 135/799] Don't import test_file on Windows See https://github.com/pytest-dev/pytest/issues/3650 --- tests/unit/utils/test_qtutils.py | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/tests/unit/utils/test_qtutils.py b/tests/unit/utils/test_qtutils.py index 6de085b25..3a848474f 100644 --- a/tests/unit/utils/test_qtutils.py +++ b/tests/unit/utils/test_qtutils.py @@ -25,13 +25,6 @@ import os import os.path import unittest import unittest.mock -try: - # pylint: disable=no-name-in-module,useless-suppression - from test import test_file - # pylint: enable=no-name-in-module,useless-suppression -except ImportError: - # Debian patches Python to remove the tests... - test_file = None import pytest from PyQt5.QtCore import (QDataStream, QPoint, QUrl, QByteArray, QIODevice, @@ -40,6 +33,20 @@ from PyQt5.QtCore import (QDataStream, QPoint, QUrl, QByteArray, QIODevice, from qutebrowser.utils import qtutils, utils import overflow_test_cases +if utils.is_linux: + # Those are not run on macOS because that seems to cause a hang sometimes. + # On Windows, we don't run them either because of + # https://github.com/pytest-dev/pytest/issues/3650 + try: + # pylint: disable=no-name-in-module,useless-suppression + from test import test_file + # pylint: enable=no-name-in-module,useless-suppression + except ImportError: + # Debian patches Python to remove the tests... + test_file = None +else: + test_file = None + # pylint: disable=bad-continuation @pytest.mark.parametrize(['qversion', 'compiled', 'pyqt', 'version', 'exact', @@ -476,13 +483,11 @@ class TestSavefileOpen: assert data == b'foo\nbar\nbaz' -if test_file is not None and not utils.is_mac: +if test_file is not None: # If we were able to import Python's test_file module, we run some code # here which defines unittest TestCases to run the python tests over # PyQIODevice. - # Those are not run on macOS because that seems to cause a hang sometimes. - @pytest.fixture(scope='session', autouse=True) def clean_up_python_testfile(): """Clean up the python testfile after tests if tests didn't.""" From 7e8c741937fc9e34946bf3d6c54436899bf391e9 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 3 Jul 2018 13:10:15 +0200 Subject: [PATCH 136/799] Set compiled=False for BDD Qt comparisons We often check for bugs and not APIs there. --- tests/end2end/conftest.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/end2end/conftest.py b/tests/end2end/conftest.py index 136d6eb67..3240cc7b8 100644 --- a/tests/end2end/conftest.py +++ b/tests/end2end/conftest.py @@ -85,8 +85,8 @@ def _get_version_tag(tag): do_skip = { '==': not qtutils.version_check(version, exact=True, compiled=False), - '>=': not qtutils.version_check(version), - '<': qtutils.version_check(version), + '>=': not qtutils.version_check(version, compiled=False), + '<': qtutils.version_check(version, compiled=False), '!=': qtutils.version_check(version, exact=True, compiled=False), } return pytest.mark.skipif(do_skip[op], reason='Needs ' + tag) From d03b03f7cb46a9833477e3bd50f5a582ca9ef20a Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 3 Jul 2018 13:13:58 +0200 Subject: [PATCH 137/799] Use Python 3.7 on macOS --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 87605f873..81a5d6fbb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,7 +30,7 @@ matrix: dist: xenial env: TESTENV=py37-pyqt511 - os: osx - env: TESTENV=py36 OSX=sierra + env: TESTENV=py37 OSX=sierra osx_image: xcode9.2 language: generic # https://github.com/qutebrowser/qutebrowser/issues/2013 From ba362de2c0209a0f860b3c7cf64fe35cbd56f7f0 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 3 Jul 2018 13:15:07 +0200 Subject: [PATCH 138/799] Turn on AppVeyor debugging --- .appveyor.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.appveyor.yml b/.appveyor.yml index 92a20c0bd..bab5a5e73 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -17,3 +17,6 @@ install: test_script: - '%PYTHON% -m tox -e %TESTENV%' + +on_finish: + - ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1')) From 7a9183d0b718c158dd0bbe456a72623f1e69d5b8 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 3 Jul 2018 13:17:15 +0200 Subject: [PATCH 139/799] Remove 32-bit makensis call I forgot to remove this in 0af8eec73a8a4290b0a0348f3ad8d8295e17deaa --- scripts/dev/build_release.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/scripts/dev/build_release.py b/scripts/dev/build_release.py index 689bd49fc..254132b3c 100755 --- a/scripts/dev/build_release.py +++ b/scripts/dev/build_release.py @@ -255,9 +255,6 @@ def build_windows(): patch_windows(out_64) utils.print_title("Building installers") - subprocess.run(['makensis.exe', - '/DVERSION={}'.format(qutebrowser.__version__), - 'misc/qutebrowser.nsi'], check=True) subprocess.run(['makensis.exe', '/DX64', '/DVERSION={}'.format(qutebrowser.__version__), From 6abe8f2c978f6ef774a7970fbd32ee8882aa785f Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 3 Jul 2018 13:22:05 +0200 Subject: [PATCH 140/799] Ignore QtNetwork warning on macOS --- pytest.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/pytest.ini b/pytest.ini index 452841a6e..1b5016321 100644 --- a/pytest.ini +++ b/pytest.ini @@ -63,6 +63,7 @@ qt_log_ignore = ^inotify_add_watch\(".*"\) failed: "No space left on device" ^QSettings::value: Empty key passed ^Icon theme ".*" not found + ^Error receiving trust for a CA certificate xfail_strict = true filterwarnings = # This happens in many qutebrowser dependencies... From 8115e109db295e4e1d342312ac1766f988c14265 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 3 Jul 2018 13:27:18 +0200 Subject: [PATCH 141/799] Update default env in tox.ini --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index ab7be4d83..c3b8a258b 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,7 @@ # and then run "tox" from this directory. [tox] -envlist = py36-pyqt59-cov,misc,vulture,flake8,pylint,pyroma,check-manifest,eslint +envlist = py36-pyqt511-cov,misc,vulture,flake8,pylint,pyroma,check-manifest,eslint distshare = {toxworkdir} skipsdist = true From 24e93fe02371ee9d64b673a0e7ff0f535a1a7a24 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 3 Jul 2018 13:27:31 +0200 Subject: [PATCH 142/799] Remove AppVeyor debugging --- .appveyor.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index bab5a5e73..92a20c0bd 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -17,6 +17,3 @@ install: test_script: - '%PYTHON% -m tox -e %TESTENV%' - -on_finish: - - ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1')) From 857288b283075940b25c29f6beb01ccd5d548139 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 3 Jul 2018 13:28:18 +0200 Subject: [PATCH 143/799] Try importing QtWebEngine from AppVeyor --- .appveyor.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.appveyor.yml b/.appveyor.yml index 92a20c0bd..f51b5e727 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -16,4 +16,5 @@ install: - 'set PATH=C:\Python36-x64;%PATH' test_script: + - '%PYTHON% -c "from PyQt5 import QtWebEngineWidgets"' - '%PYTHON% -m tox -e %TESTENV%' From 8c11c516b48412787bec4dd0c639bed72bc8d33c Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 3 Jul 2018 13:32:53 +0200 Subject: [PATCH 144/799] tox: Always allow setting python via envvar --- .appveyor.yml | 1 - tox.ini | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index f51b5e727..92a20c0bd 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -16,5 +16,4 @@ install: - 'set PATH=C:\Python36-x64;%PATH' test_script: - - '%PYTHON% -c "from PyQt5 import QtWebEngineWidgets"' - '%PYTHON% -m tox -e %TESTENV%' diff --git a/tox.ini b/tox.ini index c3b8a258b..21baa7be5 100644 --- a/tox.ini +++ b/tox.ini @@ -18,9 +18,9 @@ setenv = cov: PYTEST_ADDOPTS=--cov --cov-report xml --cov-report=html --cov-report= passenv = PYTHON DISPLAY XAUTHORITY HOME USERNAME USER CI TRAVIS XDG_* QUTE_* DOCKER basepython = - py35: python3.5 - py36: python3.6 - py37: python3.7 + py35: {env:PYTHON:python3.5} + py36: {env:PYTHON:python3.6} + py37: {env:PYTHON:python3.7} deps = -r{toxinidir}/requirements.txt -r{toxinidir}/misc/requirements/requirements-tests.txt From 4d1e56a8c621126c7fd5375e4d7a635c7293aeba Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 3 Jul 2018 13:39:34 +0200 Subject: [PATCH 145/799] Ignore "Lost UI shared context" error happening on AppVeyor --- tests/end2end/fixtures/quteprocess.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/end2end/fixtures/quteprocess.py b/tests/end2end/fixtures/quteprocess.py index 4101e6142..01cca1ee1 100644 --- a/tests/end2end/fixtures/quteprocess.py +++ b/tests/end2end/fixtures/quteprocess.py @@ -204,6 +204,10 @@ def is_ignored_chromium_message(line): # [30412:30412:0323/074933.387250:ERROR:node_channel.cc(899)] Dropping # message on closed channel. 'Dropping message on closed channel.', + # [2204:1408:0703/113804.788:ERROR: + # gpu_process_transport_factory.cc(1019)] Lost UI shared context. + 'Lost UI shared context.', + ] return any(testutils.pattern_match(pattern=pattern, value=message) for pattern in ignored_messages) From dfafab4cff5fe564297fdcef907b4dac03c0b116 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 3 Jul 2018 13:52:50 +0200 Subject: [PATCH 146/799] Update changelog --- doc/changelog.asciidoc | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index 79a75f49f..68f2684f3 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -119,6 +119,7 @@ Fixed the `qute://history` page. - Crash when opening a PDF page with PDF.js enabled (on QtWebKit), but no PDF.js installed. +- Crash when closing a tab shortly after opening it. Removed ~~~~~~~ From a0f36c5cbf6b9557313f082a837ac599ba88d4fc Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 3 Jul 2018 14:15:08 +0200 Subject: [PATCH 147/799] Skip JS test which is too flaky --- tests/end2end/features/javascript.feature | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/end2end/features/javascript.feature b/tests/end2end/features/javascript.feature index 8c4348e5f..0239f7bb5 100644 --- a/tests/end2end/features/javascript.feature +++ b/tests/end2end/features/javascript.feature @@ -8,7 +8,7 @@ Feature: Javascript stuff When I open data/javascript/consolelog.html Then the javascript message "console.log works!" should be logged - @flaky + @skip # Too flaky Scenario: Opening/Closing a window via JS When I open data/javascript/window_open.html And I run :tab-only From 42a3622906117b4dbb38ab843cf71309f6f5f688 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 3 Jul 2018 15:38:29 +0200 Subject: [PATCH 148/799] Ignore a new Qt 5.11 lowlevel message --- tests/end2end/fixtures/quteprocess.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/end2end/fixtures/quteprocess.py b/tests/end2end/fixtures/quteprocess.py index 01cca1ee1..e0ba4e00d 100644 --- a/tests/end2end/fixtures/quteprocess.py +++ b/tests/end2end/fixtures/quteprocess.py @@ -104,6 +104,15 @@ def is_ignored_lowlevel_message(message): # Qt 5.11 # DevTools listening on ws://127.0.0.1:37945/devtools/browser/... 'DevTools listening on *', + # /home/travis/build/qutebrowser/qutebrowser/.tox/py36-pyqt511-cov/lib/ + # python3.6/site-packages/PyQt5/Qt/libexec/QtWebEngineProcess: + # /lib/x86_64-linux-gnu/libdbus-1.so.3: no version information + # available (required by /home/travis/build/qutebrowser/qutebrowser/ + # .tox/py36-pyqt511-cov/lib/python3.6/site-packages/PyQt5/Qt/libexec/ + # ../lib/libQt5WebEngineCore.so.5) + '*/QtWebEngineProcess: /lib/x86_64-linux-gnu/libdbus-1.so.3: no ' + 'version information available (required by ' + '*/libQt5WebEngineCore.so.5)', ] return any(testutils.pattern_match(pattern=pattern, value=message) for pattern in ignored_messages) From 85cc1e4f849d60aa5cb1d4ebb037637c517aae28 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 3 Jul 2018 15:44:25 +0200 Subject: [PATCH 149/799] Update changelog for v1.4.0 --- doc/changelog.asciidoc | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index 68f2684f3..f550f2ef1 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -15,13 +15,14 @@ breaking changes (such as renamed commands) can happen in minor releases. // `Fixed` for any bug fixes. // `Security` to invite users to upgrade in case of vulnerabilities. -v1.4.0 (unreleased) -------------------- +v1.4.0 +------ Added ~~~~~ -- Support for the bundled `sip` module in PyQt 5.11. +- Support for the bundled `sip` module in PyQt 5.11 and other changes in + Qt/PyQt 5.11.x. - New `--debug-flag log-requests` to log requests to the debug log for debugging. - New `--first` flag for `:hint` (bound to `gi` for inputs) which automatically From 0f037fb415581283074dff8b2d090cefcd337602 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 3 Jul 2018 15:44:44 +0200 Subject: [PATCH 150/799] Release v1.4.0 --- qutebrowser/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutebrowser/__init__.py b/qutebrowser/__init__.py index da982a7de..9061290ae 100644 --- a/qutebrowser/__init__.py +++ b/qutebrowser/__init__.py @@ -26,7 +26,7 @@ __copyright__ = "Copyright 2014-2018 Florian Bruhin (The Compiler)" __license__ = "GPL" __maintainer__ = __author__ __email__ = "mail@qutebrowser.org" -__version_info__ = (1, 3, 3) +__version_info__ = (1, 4, 0) __version__ = '.'.join(str(e) for e in __version_info__) __description__ = "A keyboard-driven, vim-like browser based on PyQt5." From 0a31e19eda91fbb4d5e6a333591cd0b028e7c25a Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 3 Jul 2018 17:14:34 +0200 Subject: [PATCH 151/799] Handle download errors when the reply is already gone Fixes #1270 --- doc/changelog.asciidoc | 8 ++++++++ qutebrowser/browser/qtnetworkdownloads.py | 11 +++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index f550f2ef1..28a7d89ea 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -15,6 +15,14 @@ breaking changes (such as renamed commands) can happen in minor releases. // `Fixed` for any bug fixes. // `Security` to invite users to upgrade in case of vulnerabilities. +v1.5.0 (unreleased) +------------------- + +Fixed +~~~~~ + +- Rare crash when an error occurs in downloads. + v1.4.0 ------ diff --git a/qutebrowser/browser/qtnetworkdownloads.py b/qutebrowser/browser/qtnetworkdownloads.py index 4e992b172..bdd2b280a 100644 --- a/qutebrowser/browser/qtnetworkdownloads.py +++ b/qutebrowser/browser/qtnetworkdownloads.py @@ -29,7 +29,7 @@ from PyQt5.QtCore import pyqtSlot, pyqtSignal, QTimer from PyQt5.QtNetwork import QNetworkRequest, QNetworkReply from qutebrowser.config import config -from qutebrowser.utils import message, usertypes, log, urlutils, utils +from qutebrowser.utils import message, usertypes, log, urlutils, utils, debug from qutebrowser.browser import downloads from qutebrowser.browser.webkit import http from qutebrowser.browser.webkit.network import networkmanager @@ -307,7 +307,14 @@ class DownloadItem(downloads.AbstractDownloadItem): """Handle QNetworkReply errors.""" if code == QNetworkReply.OperationCanceledError: return - self._die(self._reply.errorString()) + + if self._reply is None: + error = "Unknown error: {}".format( + debug.qenum_key(QNetworkReply, code)) + else: + error = self._reply.errorString() + + self._die(error) @pyqtSlot() def _on_read_timer_timeout(self): From e80e695a56516de6bea435345f2ae3589c42d127 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 4 Jul 2018 14:08:04 +0200 Subject: [PATCH 152/799] Add a mkvenv-pypi-old environment Fixes #4038 See #3662 --- doc/install.asciidoc | 4 ++++ tox.ini | 13 +++++++++++++ 2 files changed, 17 insertions(+) diff --git a/doc/install.asciidoc b/doc/install.asciidoc index ddf1cd120..f5fccb32e 100644 --- a/doc/install.asciidoc +++ b/doc/install.asciidoc @@ -392,6 +392,10 @@ https://docs.python.org/3/library/venv.html[virtual environment]: $ tox -e mkvenv-pypi ---- +If your system comes with Python 3.5.3 or older (such as Ubuntu 16.04 LTS), use +`tox -e mkvenv-pypi-old` instead. This installs an older Qt version (5.10) due +to bugs in newer versions. + This installs all needed Python dependencies in a `.venv` subfolder. This comes with an up-to-date Qt/PyQt including QtWebEngine, but has a few diff --git a/tox.ini b/tox.ini index 21baa7be5..02f3ae729 100644 --- a/tox.ini +++ b/tox.ini @@ -62,6 +62,19 @@ deps = -r{toxinidir}/requirements.txt -r{toxinidir}/misc/requirements/requirements-pyqt.txt +# Older PyQt for Python 3.5 +# 5.11.2: https://www.riverbankcomputing.com/pipermail/pyqt/2018-July/040511.html +# 5.10.1: https://github.com/qutebrowser/qutebrowser/issues/3662 +[testenv:mkvenv-pypi-old] +basepython = {env:PYTHON:python3.5} +envdir = {toxinidir}/.venv +commands = {envpython} -c "" +usedevelop = true +deps = + -r{toxinidir}/requirements.txt + PyQt5==5.10 + sip==4.19.8 + [testenv:misc] ignore_errors = true basepython = {env:PYTHON:python3} From ae32b79d54fa70dff5f948d49371bc1648ba2fe8 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 4 Jul 2018 15:37:50 +0200 Subject: [PATCH 153/799] Add exam comments to contributing docs --- .github/CONTRIBUTING.asciidoc | 5 +++++ doc/contributing.asciidoc | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/.github/CONTRIBUTING.asciidoc b/.github/CONTRIBUTING.asciidoc index 6449c6323..4421f071a 100644 --- a/.github/CONTRIBUTING.asciidoc +++ b/.github/CONTRIBUTING.asciidoc @@ -1,3 +1,8 @@ +IMPORTANT: I'm currently (July 2018) more busy than usual until September, +because of exams coming up. Review of non-trivial pull requests will thus be +delayed until then. If you're reading this note after mid-September, please +open an issue. + - Before you start to work on something, please leave a comment on the relevant issue (or open one). This makes sure there is no duplicate work done. diff --git a/doc/contributing.asciidoc b/doc/contributing.asciidoc index 26ff12a15..47ecfd9ff 100644 --- a/doc/contributing.asciidoc +++ b/doc/contributing.asciidoc @@ -5,6 +5,11 @@ The Compiler :data-uri: :toc: +IMPORTANT: I'm currently (July 2018) more busy than usual until September, +because of exams coming up. Review of non-trivial pull requests will thus be +delayed until then. If you're reading this note after mid-September, please +open an issue. + I `<3` footnote:[Of course, that says `<3` in HTML.] contributors! This document contains guidelines for contributing to qutebrowser, as well as From 0f8296d384bf8cf0bf6674483c30ed3f3513c0a2 Mon Sep 17 00:00:00 2001 From: Olmo Kramer Date: Thu, 22 Mar 2018 23:45:16 +0100 Subject: [PATCH 154/799] Add hints.selectors setting --- doc/help/settings.asciidoc | 68 ++++++++++++++++++++ qutebrowser/browser/hints.py | 22 +++++-- qutebrowser/browser/navigate.py | 5 +- qutebrowser/browser/webelem.py | 27 +------- qutebrowser/config/configdata.yml | 54 ++++++++++++++++ tests/unit/browser/webkit/test_webkitelem.py | 68 +++++++++----------- 6 files changed, 174 insertions(+), 70 deletions(-) diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc index 75e684ffc..b545282ea 100644 --- a/doc/help/settings.asciidoc +++ b/doc/help/settings.asciidoc @@ -203,6 +203,7 @@ |<>|Comma-separated list of regular expressions to use for 'next' links. |<>|Comma-separated list of regular expressions to use for 'prev' links. |<>|Scatter hint key chains (like Vimium) or not (like dwb). +|<>|CSS selectors used to determine which elements on a page should have hints. |<>|Make characters in hint strings uppercase. |<>|Maximum time (in minutes) between two history items for them to be considered being from the same browsing session. |<>|Allow Escape to quit the crash reporter. @@ -2479,6 +2480,73 @@ Type: <> Default: +pass:[true]+ +[[hints.selectors]] +=== hints.selectors +CSS selectors used to determine which elements on a page should have hints. + +This setting supports URL patterns. + +Type: <> + +Default: + +- +pass:[all]+: + +* +pass:[a]+ +* +pass:[area]+ +* +pass:[textarea]+ +* +pass:[select]+ +* +pass:[input:not([type="hidden"])]+ +* +pass:[button]+ +* +pass:[frame]+ +* +pass:[iframe]+ +* +pass:[img]+ +* +pass:[link]+ +* +pass:[summary]+ +* +pass:[[onclick]]+ +* +pass:[[onmousedown]]+ +* +pass:[[role="link"]]+ +* +pass:[[role="option"]]+ +* +pass:[[role="button"]]+ +* +pass:[[ng-click]]+ +* +pass:[[ngClick]]+ +* +pass:[[data-ng-click]]+ +* +pass:[[x-ng-click]]+ +- +pass:[images]+: + +* +pass:[img]+ +- +pass:[inputs]+: + +* +pass:[input[type="text"]]+ +* +pass:[input[type="date"]]+ +* +pass:[input[type="datetime-local"]]+ +* +pass:[input[type="email"]]+ +* +pass:[input[type="month"]]+ +* +pass:[input[type="number"]]+ +* +pass:[input[type="password"]]+ +* +pass:[input[type="search"]]+ +* +pass:[input[type="tel"]]+ +* +pass:[input[type="time"]]+ +* +pass:[input[type="url"]]+ +* +pass:[input[type="week"]]+ +* +pass:[input:not([type])]+ +* +pass:[textarea]+ +- +pass:[links]+: + +* +pass:[a[href]]+ +* +pass:[area[href]]+ +* +pass:[link[href]]+ +* +pass:[[role="link"][href]]+ +- +pass:[media]+: + +* +pass:[audio]+ +* +pass:[img]+ +* +pass:[video]+ +- +pass:[urls]+: + +* +pass:[[src]]+ +* +pass:[[href]]+ + [[hints.uppercase]] === hints.uppercase Make characters in hint strings uppercase. diff --git a/qutebrowser/browser/hints.py b/qutebrowser/browser/hints.py index 6e5f4d7f4..27d869023 100644 --- a/qutebrowser/browser/hints.py +++ b/qutebrowser/browser/hints.py @@ -637,9 +637,8 @@ class HintManager(QObject): star_args_optional=True, maxsplit=2) @cmdutils.argument('win_id', win_id=True) def start(self, # pylint: disable=keyword-arg-before-vararg - group=webelem.Group.all, target=Target.normal, - *args, win_id, mode=None, add_history=False, rapid=False, - first=False): + group='all', target=Target.normal, *args, win_id, mode=None, + add_history=False, rapid=False, first=False): """Start hinting. Args: @@ -741,10 +740,25 @@ class HintManager(QObject): raise cmdexc.CommandError("No URL set for this page yet!") self._context.args = args self._context.group = group - selector = webelem.SELECTORS[self._context.group] + selector = self._get_selector() self._context.tab.elements.find_css(selector, self._start_cb, only_visible=True) + def _get_selector(self): + """Get the CSS selectors for this url and hinting group.""" + url = self._context.baseurl + group = self._context.group + + selectors = config.instance.get('hints.selectors', url) + if group not in selectors: + selectors = config.val.hints.selectors + + if group not in selectors: + raise cmdexc.CommandError("Undefined hinting group " + "'{}'!".format(group)) + + return ','.join(selectors[group]) + def current_mode(self): """Return the currently active hinting mode (or None otherwise).""" if self._context is None: diff --git a/qutebrowser/browser/navigate.py b/qutebrowser/browser/navigate.py index 257ce6fe0..dbe2b5ed9 100644 --- a/qutebrowser/browser/navigate.py +++ b/qutebrowser/browser/navigate.py @@ -21,7 +21,6 @@ import posixpath -from qutebrowser.browser import webelem from qutebrowser.config import config from qutebrowser.utils import objreg, urlutils, log, message, qtutils from qutebrowser.mainwindow import mainwindow @@ -147,5 +146,5 @@ def prevnext(*, browsertab, win_id, baseurl, prev=False, else: browsertab.openurl(url) - browsertab.elements.find_css(webelem.SELECTORS[webelem.Group.links], - _prevnext_cb) + link_selector = 'a[href], area[href], link[href], [role=link][href]' + browsertab.elements.find_css(link_selector, _prevnext_cb) diff --git a/qutebrowser/browser/webelem.py b/qutebrowser/browser/webelem.py index 1d719738b..91bfd4d6e 100644 --- a/qutebrowser/browser/webelem.py +++ b/qutebrowser/browser/webelem.py @@ -17,14 +17,8 @@ # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see . -"""Generic web element related code. +"""Generic web element related code.""" -Module attributes: - Group: Enum for different kinds of groups. - SELECTORS: CSS selectors for different groups of elements. -""" - -import enum import collections.abc from PyQt5.QtCore import QUrl, Qt, QEvent, QTimer @@ -36,25 +30,6 @@ from qutebrowser.mainwindow import mainwindow from qutebrowser.utils import log, usertypes, utils, qtutils, objreg -Group = enum.Enum('Group', ['all', 'links', 'images', 'url', 'inputs']) - - -SELECTORS = { - Group.all: ('a, area, textarea, select, input:not([type=hidden]), button, ' - 'frame, iframe, link, summary, [onclick], [onmousedown], ' - '[role=link], [role=option], [role=button], img, ' - # Angular 1 selectors - '[ng-click], [ngClick], [data-ng-click], [x-ng-click]'), - Group.links: 'a[href], area[href], link[href], [role=link][href]', - Group.images: 'img', - Group.url: '[src], [href]', - Group.inputs: ('input[type=text], input[type=email], input[type=url], ' - 'input[type=tel], input[type=number], ' - 'input[type=password], input[type=search], ' - 'input:not([type]), textarea'), -} - - class Error(Exception): """Base class for WebElement errors.""" diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml index 2698c34b1..73184be82 100644 --- a/qutebrowser/config/configdata.yml +++ b/qutebrowser/config/configdata.yml @@ -1037,6 +1037,60 @@ hints.scatter: Ignored for number hints. +hints.selectors: + default: + all: + - 'a' + - 'area' + - 'textarea' + - 'select' + - 'input:not([type="hidden"])' + - 'button' + - 'frame' + - 'iframe' + - 'img' + - 'link' + - 'summary' + - '[onclick]' + - '[onmousedown]' + - '[role="link"]' + - '[role="option"]' + - '[role="button"]' + - '[ng-click]' + - '[ngClick]' + - '[data-ng-click]' + - '[x-ng-click]' + links: + - 'a[href]' + - 'area[href]' + - 'link[href]' + - '[role="link"][href]' + images: + - 'img' + urls: + - '[src]' + - '[href]' + inputs: + - 'input[type="text"]' + - 'input[type="email"]' + - 'input[type="url"]' + - 'input[type="tel"]' + - 'input[type="number"]' + - 'input[type="password"]' + - 'input[type="search"]' + - 'input:not([type])' + - 'textarea' + type: + name: Dict + keytype: String + valtype: + name: List + none_ok: true + valtype: String + supports_pattern: true + desc: CSS selectors used to determine which elements on a page should have + hints. + hints.uppercase: default: false type: Bool diff --git a/tests/unit/browser/webkit/test_webkitelem.py b/tests/unit/browser/webkit/test_webkitelem.py index df3de6310..eb06a3bca 100644 --- a/tests/unit/browser/webkit/test_webkitelem.py +++ b/tests/unit/browser/webkit/test_webkitelem.py @@ -29,7 +29,7 @@ import pytest from PyQt5.QtCore import QRect, QPoint, QUrl QWebElement = pytest.importorskip('PyQt5.QtWebKit').QWebElement -from qutebrowser.browser import webelem, browsertab +from qutebrowser.browser import browsertab from qutebrowser.browser.webkit import webkitelem from qutebrowser.misc import objects from qutebrowser.utils import usertypes @@ -146,53 +146,46 @@ class SelectionAndFilterTests: TESTS = [ ('', []), ('', []), - ('', [webelem.Group.url]), - ('', [webelem.Group.url]), + ('', ['urls']), + ('', ['urls']), - ('', [webelem.Group.all]), - ('', [webelem.Group.all, webelem.Group.links, - webelem.Group.url]), - ('', [webelem.Group.all, - webelem.Group.links, - webelem.Group.url]), + ('', ['all']), + ('', ['all', 'links', 'urls']), + ('', ['all', 'links', 'urls']), - ('', [webelem.Group.all]), - ('', [webelem.Group.all, webelem.Group.links, - webelem.Group.url]), + ('', ['all']), + ('', ['all', 'links', 'urls']), - ('', [webelem.Group.all]), - ('', [webelem.Group.all, webelem.Group.links, - webelem.Group.url]), + ('', ['all']), + ('', ['all', 'links', 'urls']), - ('