From 42ac3dcda010f0f2191f5dcc15d3da644d4a478e Mon Sep 17 00:00:00 2001 From: gammelon Date: Sat, 17 Feb 2018 11:13:54 +0100 Subject: [PATCH 001/223] Add Option url.open_base_url when set to true, invoking a searchengine shortcut without argument opens the baseurl of that searchengine instead of DEFAULT searchengine --- qutebrowser/config/configdata.yml | 6 ++++++ qutebrowser/utils/urlutils.py | 7 +++++++ 2 files changed, 13 insertions(+) diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml index 72450978b..23ff271f0 100644 --- a/qutebrowser/config/configdata.yml +++ b/qutebrowser/config/configdata.yml @@ -1445,6 +1445,12 @@ url.incdec_segments: desc: URL segments where `:navigate increment/decrement` will search for a number. +url.open_base_url: + type: Bool + default: false + desc: Invoking `:open {shortcut}` (without argument), where {shortcut} is a search engine shortcut + will open the base url of the shortcut instead of using the default search engine. + url.searchengines: default: DEFAULT: https://duckduckgo.com/?q={} diff --git a/qutebrowser/utils/urlutils.py b/qutebrowser/utils/urlutils.py index 2ed466dd1..1bb062f3f 100644 --- a/qutebrowser/utils/urlutils.py +++ b/qutebrowser/utils/urlutils.py @@ -102,6 +102,13 @@ def _get_search_url(txt): engine = 'DEFAULT' template = config.val.url.searchengines[engine] url = qurl_from_user_input(template.format(urllib.parse.quote(term))) + + if config.val.url.open_base_url: + try: + search_url = urllib.parse.urlparse(config.val.url.searchengines[term]) + url = QUrl('{}://{}'.format(search_url.scheme, search_url.netloc)) + except KeyError: + pass qtutils.ensure_valid(url) return url From e169e2165da7892c0b9731a94481aebbc867df26 Mon Sep 17 00:00:00 2001 From: bttner <35602050+bttner@users.noreply.github.com> Date: Thu, 15 Feb 2018 12:02:42 +0100 Subject: [PATCH 002/223] Refactor TabbedBrowser from inheritance to composition --- qutebrowser/app.py | 2 +- qutebrowser/browser/commands.py | 51 ++++---- qutebrowser/browser/hints.py | 2 +- qutebrowser/browser/signalfilter.py | 4 +- qutebrowser/completion/models/miscmodels.py | 6 +- qutebrowser/mainwindow/mainwindow.py | 12 +- .../mainwindow/statusbar/backforward.py | 2 +- qutebrowser/mainwindow/statusbar/bar.py | 2 +- qutebrowser/mainwindow/tabbedbrowser.py | 122 +++++++++--------- qutebrowser/mainwindow/tabwidget.py | 16 +-- qutebrowser/misc/sessions.py | 9 +- qutebrowser/misc/utilcmds.py | 2 +- qutebrowser/utils/objreg.py | 2 +- tests/helpers/stubs.py | 38 +++--- tests/unit/browser/test_signalfilter.py | 16 +-- tests/unit/completion/test_models.py | 18 +-- .../mainwindow/statusbar/test_backforward.py | 12 +- tests/unit/mainwindow/test_tabwidget.py | 4 +- 18 files changed, 165 insertions(+), 155 deletions(-) diff --git a/qutebrowser/app.py b/qutebrowser/app.py index ec477ce8f..546c1e17a 100644 --- a/qutebrowser/app.py +++ b/qutebrowser/app.py @@ -339,7 +339,7 @@ def _open_startpage(win_id=None): for cur_win_id in list(window_ids): # Copying as the dict could change tabbed_browser = objreg.get('tabbed-browser', scope='window', window=cur_win_id) - if tabbed_browser.count() == 0: + if tabbed_browser.widget.count() == 0: log.init.debug("Opening start pages") for url in config.val.url.start_pages: tabbed_browser.tabopen(url) diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 69cb3142f..1eef43148 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -74,16 +74,16 @@ class CommandDispatcher: def _count(self): """Convenience method to get the widget count.""" - return self._tabbed_browser.count() + return self._tabbed_browser.widget.count() def _set_current_index(self, idx): """Convenience method to set the current widget index.""" cmdutils.check_overflow(idx, 'int') - self._tabbed_browser.setCurrentIndex(idx) + self._tabbed_browser.widget.setCurrentIndex(idx) def _current_index(self): """Convenience method to get the current widget index.""" - return self._tabbed_browser.currentIndex() + return self._tabbed_browser.widget.currentIndex() def _current_url(self): """Convenience method to get the current url.""" @@ -102,7 +102,7 @@ class CommandDispatcher: def _current_widget(self): """Get the currently active widget from a command.""" - widget = self._tabbed_browser.currentWidget() + widget = self._tabbed_browser.widget.currentWidget() if widget is None: raise cmdexc.CommandError("No WebView available yet!") return widget @@ -148,10 +148,10 @@ class CommandDispatcher: None if no widget was found. """ if count is None: - return self._tabbed_browser.currentWidget() + return self._tabbed_browser.widget.currentWidget() elif 1 <= count <= self._count(): cmdutils.check_overflow(count + 1, 'int') - return self._tabbed_browser.widget(count - 1) + return self._tabbed_browser.widget.widget(count - 1) else: return None @@ -164,7 +164,7 @@ class CommandDispatcher: if not show_error: return raise cmdexc.CommandError("No last focused tab!") - idx = self._tabbed_browser.indexOf(tab) + idx = self._tabbed_browser.widget.indexOf(tab) if idx == -1: raise cmdexc.CommandError("Last focused tab vanished!") self._set_current_index(idx) @@ -213,7 +213,7 @@ class CommandDispatcher: what's configured in 'tabs.select_on_remove'. count: The tab index to close, or None """ - tabbar = self._tabbed_browser.tabBar() + tabbar = self._tabbed_browser.widget.tabBar() selection_override = self._get_selection_override(prev, next_, opposite) @@ -265,7 +265,7 @@ class CommandDispatcher: return to_pin = not tab.data.pinned - self._tabbed_browser.set_tab_pinned(tab, to_pin) + self._tabbed_browser.widget.set_tab_pinned(tab, to_pin) @cmdutils.register(instance='command-dispatcher', name='open', maxsplit=0, scope='window') @@ -484,7 +484,8 @@ class CommandDispatcher: """ cmdutils.check_exclusive((bg, window), 'bw') curtab = self._current_widget() - cur_title = self._tabbed_browser.page_title(self._current_index()) + cur_title = self._tabbed_browser.widget.page_title( + self._current_index()) try: history = curtab.history.serialize() except browsertab.WebTabError as e: @@ -500,18 +501,18 @@ class CommandDispatcher: newtab = new_tabbed_browser.tabopen(background=bg) new_tabbed_browser = objreg.get('tabbed-browser', scope='window', window=newtab.win_id) - idx = new_tabbed_browser.indexOf(newtab) + idx = new_tabbed_browser.widget.indexOf(newtab) - new_tabbed_browser.set_page_title(idx, cur_title) + new_tabbed_browser.widget.set_page_title(idx, cur_title) if config.val.tabs.favicons.show: - new_tabbed_browser.setTabIcon(idx, curtab.icon()) + new_tabbed_browser.widget.setTabIcon(idx, curtab.icon()) if config.val.tabs.tabs_are_windows: - new_tabbed_browser.window().setWindowIcon(curtab.icon()) + new_tabbed_browser.widget.window().setWindowIcon(curtab.icon()) newtab.data.keep_icon = True newtab.history.deserialize(history) newtab.zoom.set_factor(curtab.zoom.factor()) - new_tabbed_browser.set_tab_pinned(newtab, curtab.data.pinned) + new_tabbed_browser.widget.set_tab_pinned(newtab, curtab.data.pinned) return newtab @cmdutils.register(instance='command-dispatcher', scope='window') @@ -847,7 +848,7 @@ class CommandDispatcher: keep: Stay in visual mode after yanking the selection. """ if what == 'title': - s = self._tabbed_browser.page_title(self._current_index()) + s = self._tabbed_browser.widget.page_title(self._current_index()) elif what == 'domain': port = self._current_url().port() s = '{}://{}{}'.format(self._current_url().scheme(), @@ -959,7 +960,7 @@ class CommandDispatcher: force: Avoid confirmation for pinned tabs. """ cmdutils.check_exclusive((prev, next_), 'pn') - cur_idx = self._tabbed_browser.currentIndex() + cur_idx = self._tabbed_browser.widget.currentIndex() assert cur_idx != -1 def _to_close(i): @@ -1076,11 +1077,11 @@ class CommandDispatcher: tabbed_browser = objreg.get('tabbed-browser', scope='window', window=win_id) - if not 0 < idx <= tabbed_browser.count(): + if not 0 < idx <= tabbed_browser.widget.count(): raise cmdexc.CommandError( "There's no tab with index {}!".format(idx)) - return (tabbed_browser, tabbed_browser.widget(idx-1)) + return (tabbed_browser, tabbed_browser.widget.widget(idx-1)) @cmdutils.register(instance='command-dispatcher', scope='window', maxsplit=0) @@ -1108,10 +1109,10 @@ class CommandDispatcher: tabbed_browser, tab = self._resolve_buffer_index(index) - window = tabbed_browser.window() + window = tabbed_browser.widget.window() window.activateWindow() window.raise_() - tabbed_browser.setCurrentWidget(tab) + tabbed_browser.widget.setCurrentWidget(tab) @cmdutils.register(instance='command-dispatcher', scope='window') @cmdutils.argument('index', choices=['last']) @@ -1195,7 +1196,7 @@ class CommandDispatcher: cur_idx = self._current_index() cmdutils.check_overflow(cur_idx, 'int') cmdutils.check_overflow(new_idx, 'int') - self._tabbed_browser.tabBar().moveTab(cur_idx, new_idx) + self._tabbed_browser.widget.tabBar().moveTab(cur_idx, new_idx) @cmdutils.register(instance='command-dispatcher', scope='window', maxsplit=0, no_replace_variables=True) @@ -1279,10 +1280,10 @@ class CommandDispatcher: idx = self._current_index() if idx != -1: - env['QUTE_TITLE'] = self._tabbed_browser.page_title(idx) + env['QUTE_TITLE'] = self._tabbed_browser.widget.page_title(idx) # FIXME:qtwebengine: If tab is None, run_async will fail! - tab = self._tabbed_browser.currentWidget() + tab = self._tabbed_browser.widget.currentWidget() try: url = self._tabbed_browser.current_url() @@ -2220,5 +2221,5 @@ class CommandDispatcher: pass return - window = self._tabbed_browser.window() + window = self._tabbed_browser.widget.window() window.setWindowState(window.windowState() ^ Qt.WindowFullScreen) diff --git a/qutebrowser/browser/hints.py b/qutebrowser/browser/hints.py index 0390d5d1f..e22beeb1a 100644 --- a/qutebrowser/browser/hints.py +++ b/qutebrowser/browser/hints.py @@ -682,7 +682,7 @@ class HintManager(QObject): """ tabbed_browser = objreg.get('tabbed-browser', scope='window', window=self._win_id) - tab = tabbed_browser.currentWidget() + tab = tabbed_browser.widget.currentWidget() if tab is None: raise cmdexc.CommandError("No WebView available yet!") diff --git a/qutebrowser/browser/signalfilter.py b/qutebrowser/browser/signalfilter.py index 663aa67e7..7cc46abdb 100644 --- a/qutebrowser/browser/signalfilter.py +++ b/qutebrowser/browser/signalfilter.py @@ -76,11 +76,11 @@ class SignalFilter(QObject): tabbed_browser = objreg.get('tabbed-browser', scope='window', window=self._win_id) try: - tabidx = tabbed_browser.indexOf(tab) + tabidx = tabbed_browser.widget.indexOf(tab) except RuntimeError: # The tab has been deleted already return - if tabidx == tabbed_browser.currentIndex(): + if tabidx == tabbed_browser.widget.currentIndex(): if log_signal: log.signals.debug("emitting: {} (tab {})".format( debug.dbg_signal(signal, args), tabidx)) diff --git a/qutebrowser/completion/models/miscmodels.py b/qutebrowser/completion/models/miscmodels.py index 049d89295..8606bbf75 100644 --- a/qutebrowser/completion/models/miscmodels.py +++ b/qutebrowser/completion/models/miscmodels.py @@ -117,11 +117,11 @@ def _buffer(skip_win_id=None): if tabbed_browser.shutting_down: continue tabs = [] - for idx in range(tabbed_browser.count()): - tab = tabbed_browser.widget(idx) + for idx in range(tabbed_browser.widget.count()): + tab = tabbed_browser.widget.widget(idx) tabs.append(("{}/{}".format(win_id, idx + 1), tab.url().toDisplayString(), - tabbed_browser.page_title(idx))) + tabbed_browser.widget.page_title(idx))) cat = listcategory.ListCategory("{}".format(win_id), tabs, delete_func=delete_buffer) model.add_category(cat) diff --git a/qutebrowser/mainwindow/mainwindow.py b/qutebrowser/mainwindow/mainwindow.py index 05482a1d5..b15e98e22 100644 --- a/qutebrowser/mainwindow/mainwindow.py +++ b/qutebrowser/mainwindow/mainwindow.py @@ -327,7 +327,7 @@ class MainWindow(QWidget): self.tabbed_browser) objreg.register('command-dispatcher', dispatcher, scope='window', window=self.win_id) - self.tabbed_browser.destroyed.connect( + self.tabbed_browser.widget.destroyed.connect( functools.partial(objreg.delete, 'command-dispatcher', scope='window', window=self.win_id)) @@ -347,10 +347,10 @@ class MainWindow(QWidget): def _add_widgets(self): """Add or readd all widgets to the VBox.""" - self._vbox.removeWidget(self.tabbed_browser) + self._vbox.removeWidget(self.tabbed_browser.widget) self._vbox.removeWidget(self._downloadview) self._vbox.removeWidget(self.status) - widgets = [self.tabbed_browser] + widgets = [self.tabbed_browser.widget] downloads_position = config.val.downloads.position if downloads_position == 'top': @@ -469,7 +469,7 @@ class MainWindow(QWidget): self.tabbed_browser.cur_scroll_perc_changed.connect( status.percentage.set_perc) - self.tabbed_browser.tab_index_changed.connect( + self.tabbed_browser.widget.tab_index_changed.connect( status.tabindex.on_tab_index_changed) self.tabbed_browser.cur_url_changed.connect(status.url.set_url) @@ -517,7 +517,7 @@ class MainWindow(QWidget): super().resizeEvent(e) self._update_overlay_geometries() self._downloadview.updateGeometry() - self.tabbed_browser.tabBar().refresh() + self.tabbed_browser.widget.tabBar().refresh() def showEvent(self, e): """Extend showEvent to register us as the last-visible-main-window. @@ -546,7 +546,7 @@ class MainWindow(QWidget): if crashsignal.is_crashing: e.accept() return - tab_count = self.tabbed_browser.count() + tab_count = self.tabbed_browser.widget.count() download_model = objreg.get('download-model', scope='window', window=self.win_id) download_count = download_model.running_downloads() diff --git a/qutebrowser/mainwindow/statusbar/backforward.py b/qutebrowser/mainwindow/statusbar/backforward.py index 8ea60ee75..5e244cf8c 100644 --- a/qutebrowser/mainwindow/statusbar/backforward.py +++ b/qutebrowser/mainwindow/statusbar/backforward.py @@ -32,7 +32,7 @@ class Backforward(textbase.TextBase): def on_tab_cur_url_changed(self, tabs): """Called on URL changes.""" - tab = tabs.currentWidget() + tab = tabs.widget.currentWidget() if tab is None: # pragma: no cover self.setText('') self.hide() diff --git a/qutebrowser/mainwindow/statusbar/bar.py b/qutebrowser/mainwindow/statusbar/bar.py index 8057bfdb8..9efc26858 100644 --- a/qutebrowser/mainwindow/statusbar/bar.py +++ b/qutebrowser/mainwindow/statusbar/bar.py @@ -268,7 +268,7 @@ class StatusBar(QWidget): """Get the currently displayed tab.""" window = objreg.get('tabbed-browser', scope='window', window=self._win_id) - return window.currentWidget() + return window.widget.currentWidget() def set_mode_active(self, mode, val): """Setter for self.{insert,command,caret}_active. diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py index 8cee35524..8d1498acb 100644 --- a/qutebrowser/mainwindow/tabbedbrowser.py +++ b/qutebrowser/mainwindow/tabbedbrowser.py @@ -22,7 +22,7 @@ import functools import attr -from PyQt5.QtWidgets import QSizePolicy +from PyQt5.QtWidgets import QSizePolicy, QWidget from PyQt5.QtCore import pyqtSignal, pyqtSlot, QTimer, QUrl from PyQt5.QtGui import QIcon @@ -50,7 +50,7 @@ class TabDeletedError(Exception): """Exception raised when _tab_index is called for a deleted tab.""" -class TabbedBrowser(tabwidget.TabWidget): +class TabbedBrowser(QWidget): """A TabWidget with QWebViews inside. @@ -110,17 +110,18 @@ class TabbedBrowser(tabwidget.TabWidget): new_tab = pyqtSignal(browsertab.AbstractTab, int) def __init__(self, *, win_id, private, parent=None): - super().__init__(win_id, parent) + super().__init__(parent) + self.widget = tabwidget.TabWidget(win_id, parent) self._win_id = win_id self._tab_insert_idx_left = 0 self._tab_insert_idx_right = -1 self.shutting_down = False - self.tabCloseRequested.connect(self.on_tab_close_requested) - self.new_tab_requested.connect(self.tabopen) - self.currentChanged.connect(self.on_current_changed) + self.widget.tabCloseRequested.connect(self.on_tab_close_requested) + self.widget.new_tab_requested.connect(self.tabopen) + self.widget.currentChanged.connect(self.on_current_changed) self.cur_load_started.connect(self.on_cur_load_started) - self.cur_fullscreen_requested.connect(self.tabBar().maybe_hide) - self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) + self.cur_fullscreen_requested.connect(self.widget.tabBar().maybe_hide) + self.widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self._undo_stack = [] self._filter = signalfilter.SignalFilter(win_id, self) self._now_focused = None @@ -128,12 +129,12 @@ class TabbedBrowser(tabwidget.TabWidget): self.search_options = {} self._local_marks = {} self._global_marks = {} - self.default_window_icon = self.window().windowIcon() + self.default_window_icon = self.widget.window().windowIcon() self.private = private config.instance.changed.connect(self._on_config_changed) def __repr__(self): - return utils.get_repr(self, count=self.count()) + return utils.get_repr(self, count=self.widget.count()) @pyqtSlot(str) def _on_config_changed(self, option): @@ -142,7 +143,7 @@ class TabbedBrowser(tabwidget.TabWidget): elif option == 'window.title_format': self._update_window_title() elif option in ['tabs.title.format', 'tabs.title.format_pinned']: - self._update_tab_titles() + self.widget.update_tab_titles() def _tab_index(self, tab): """Get the index of a given tab. @@ -150,7 +151,7 @@ class TabbedBrowser(tabwidget.TabWidget): Raises TabDeletedError if the tab doesn't exist anymore. """ try: - idx = self.indexOf(tab) + idx = self.widget.indexOf(tab) except RuntimeError as e: log.webview.debug("Got invalid tab ({})!".format(e)) raise TabDeletedError(e) @@ -166,8 +167,8 @@ class TabbedBrowser(tabwidget.TabWidget): iterating over the list. """ widgets = [] - for i in range(self.count()): - widget = self.widget(i) + for i in range(self.widget.count()): + widget = self.widget.widget(i) if widget is None: log.webview.debug("Got None-widget in tabbedbrowser!") else: @@ -186,12 +187,12 @@ class TabbedBrowser(tabwidget.TabWidget): if field is not None and ('{' + field + '}') not in title_format: return - idx = self.currentIndex() + idx = self.widget.currentIndex() if idx == -1: # (e.g. last tab removed) log.webview.debug("Not updating window title because index is -1") return - fields = self.get_tab_fields(idx) + fields = self.widget.get_tab_fields(idx) fields['id'] = self._win_id title = title_format.format(**fields) @@ -247,8 +248,8 @@ class TabbedBrowser(tabwidget.TabWidget): Return: The current URL as QUrl. """ - idx = self.currentIndex() - return super().tab_url(idx) + idx = self.widget.currentIndex() + return self.widget.tab_url(idx) def shutdown(self): """Try to shut down all tabs cleanly.""" @@ -284,7 +285,7 @@ class TabbedBrowser(tabwidget.TabWidget): new_undo: Whether the undo entry should be a new item in the stack. """ last_close = config.val.tabs.last_close - count = self.count() + count = self.widget.count() if last_close == 'ignore' and count == 1: return @@ -311,7 +312,7 @@ class TabbedBrowser(tabwidget.TabWidget): new_undo: Whether the undo entry should be a new item in the stack. crashed: Whether we're closing a tab with crashed renderer process. """ - idx = self.indexOf(tab) + idx = self.widget.indexOf(tab) if idx == -1: if crashed: return @@ -349,7 +350,7 @@ class TabbedBrowser(tabwidget.TabWidget): self._undo_stack[-1].append(entry) tab.shutdown() - self.removeTab(idx) + self.widget.removeTab(idx) if not crashed: # WORKAROUND for a segfault when we delete the crashed tab. # see https://bugreports.qt.io/browse/QTBUG-58698 @@ -362,14 +363,14 @@ class TabbedBrowser(tabwidget.TabWidget): last_close = config.val.tabs.last_close use_current_tab = False if last_close in ['blank', 'startpage', 'default-page']: - only_one_tab_open = self.count() == 1 - no_history = len(self.widget(0).history) == 1 + only_one_tab_open = self.widget.count() == 1 + no_history = len(self.widget.widget(0).history) == 1 urls = { 'blank': QUrl('about:blank'), 'startpage': config.val.url.start_pages[0], 'default-page': config.val.url.default_page, } - first_tab_url = self.widget(0).url() + first_tab_url = self.widget.widget(0).url() last_close_urlstr = urls[last_close].toString().rstrip('/') first_tab_urlstr = first_tab_url.toString().rstrip('/') last_close_url_used = first_tab_urlstr == last_close_urlstr @@ -379,14 +380,14 @@ class TabbedBrowser(tabwidget.TabWidget): for entry in reversed(self._undo_stack.pop()): if use_current_tab: self.openurl(entry.url, newtab=False) - newtab = self.widget(0) + newtab = self.widget.widget(0) use_current_tab = False else: newtab = self.tabopen(entry.url, background=False, idx=entry.index) newtab.history.deserialize(entry.history) - self.set_tab_pinned(newtab, entry.pinned) + self.widget.set_tab_pinned(newtab, entry.pinned) @pyqtSlot('QUrl', bool) def openurl(self, url, newtab): @@ -397,15 +398,15 @@ class TabbedBrowser(tabwidget.TabWidget): newtab: True to open URL in a new tab, False otherwise. """ qtutils.ensure_valid(url) - if newtab or self.currentWidget() is None: + if newtab or self.widget.currentWidget() is None: self.tabopen(url, background=False) else: - self.currentWidget().openurl(url) + self.widget.currentWidget().openurl(url) @pyqtSlot(int) def on_tab_close_requested(self, idx): """Close a tab via an index.""" - tab = self.widget(idx) + tab = self.widget.widget(idx) if tab is None: log.webview.debug("Got invalid tab {} for index {}!".format( tab, idx)) @@ -456,7 +457,7 @@ class TabbedBrowser(tabwidget.TabWidget): "related {}, idx {}".format( url, background, related, idx)) - if (config.val.tabs.tabs_are_windows and self.count() > 0 and + if (config.val.tabs.tabs_are_windows and self.widget.count() > 0 and not ignore_tabs_are_windows): window = mainwindow.MainWindow(private=self.private) window.show() @@ -466,12 +467,12 @@ class TabbedBrowser(tabwidget.TabWidget): related=related) tab = browsertab.create(win_id=self._win_id, private=self.private, - parent=self) + parent=self.widget) self._connect_tab_signals(tab) if idx is None: idx = self._get_new_tab_idx(related) - self.insertTab(idx, tab, "") + self.widget.insertTab(idx, tab, "") if url is not None: tab.openurl(url) @@ -482,10 +483,11 @@ class TabbedBrowser(tabwidget.TabWidget): # Make sure the background tab has the correct initial size. # With a foreground tab, it's going to be resized correctly by the # layout anyways. - tab.resize(self.currentWidget().size()) - self.tab_index_changed.emit(self.currentIndex(), self.count()) + tab.resize(self.widget.currentWidget().size()) + self.widget.tab_index_changed.emit(self.widget.currentIndex(), + self.widget.count()) else: - self.setCurrentWidget(tab) + self.widget.setCurrentWidget(tab) tab.show() self.new_tab.emit(tab, idx) @@ -530,11 +532,11 @@ class TabbedBrowser(tabwidget.TabWidget): """Update favicons when config was changed.""" for i, tab in enumerate(self.widgets()): if config.val.tabs.favicons.show: - self.setTabIcon(i, tab.icon()) + self.widget.setTabIcon(i, tab.icon()) if config.val.tabs.tabs_are_windows: self.window().setWindowIcon(tab.icon()) else: - self.setTabIcon(i, QIcon()) + self.widget.setTabIcon(i, QIcon()) if config.val.tabs.tabs_are_windows: self.window().setWindowIcon(self.default_window_icon) @@ -550,15 +552,15 @@ class TabbedBrowser(tabwidget.TabWidget): except TabDeletedError: # We can get signals for tabs we already deleted... return - self._update_tab_title(idx) + self.widget.update_tab_title(idx) if tab.data.keep_icon: tab.data.keep_icon = False else: - self.setTabIcon(idx, QIcon()) + self.widget.setTabIcon(idx, QIcon()) if (config.val.tabs.tabs_are_windows and config.val.tabs.favicons.show): self.window().setWindowIcon(self.default_window_icon) - if idx == self.currentIndex(): + if idx == self.widget.currentIndex(): self._update_window_title() @pyqtSlot() @@ -589,8 +591,8 @@ class TabbedBrowser(tabwidget.TabWidget): return log.webview.debug("Changing title for idx {} to '{}'".format( idx, text)) - self.set_page_title(idx, text) - if idx == self.currentIndex(): + self.widget.set_page_title(idx, text) + if idx == self.widget.currentIndex(): self._update_window_title() @pyqtSlot(browsertab.AbstractTab, QUrl) @@ -607,8 +609,8 @@ class TabbedBrowser(tabwidget.TabWidget): # We can get signals for tabs we already deleted... return - if not self.page_title(idx): - self.set_page_title(idx, url.toDisplayString()) + if not self.widget.page_title(idx): + self.widget.set_page_title(idx, url.toDisplayString()) @pyqtSlot(browsertab.AbstractTab, QIcon) def on_icon_changed(self, tab, icon): @@ -627,7 +629,7 @@ class TabbedBrowser(tabwidget.TabWidget): except TabDeletedError: # We can get signals for tabs we already deleted... return - self.setTabIcon(idx, icon) + self.widget.setTabIcon(idx, icon) if config.val.tabs.tabs_are_windows: self.window().setWindowIcon(icon) @@ -636,7 +638,7 @@ class TabbedBrowser(tabwidget.TabWidget): """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.widget.currentWidget() log.modes.debug("Left status-input mode, focusing {!r}".format( widget)) if widget is None: @@ -652,7 +654,7 @@ class TabbedBrowser(tabwidget.TabWidget): if idx == -1 or self.shutting_down: # closing the last tab (before quitting) or shutting down return - tab = self.widget(idx) + tab = self.widget.widget(idx) if tab is None: log.webview.debug("on_current_changed got called with invalid " "index {}".format(idx)) @@ -680,8 +682,8 @@ class TabbedBrowser(tabwidget.TabWidget): self._now_focused = tab self.current_tab_changed.emit(tab) QTimer.singleShot(0, self._update_window_title) - self._tab_insert_idx_left = self.currentIndex() - self._tab_insert_idx_right = self.currentIndex() + 1 + self._tab_insert_idx_left = self.widget.currentIndex() + self._tab_insert_idx_right = self.widget.currentIndex() + 1 @pyqtSlot() def on_cmd_return_pressed(self): @@ -699,9 +701,9 @@ class TabbedBrowser(tabwidget.TabWidget): stop = config.val.colors.tabs.indicator.stop system = config.val.colors.tabs.indicator.system color = utils.interpolate_color(start, stop, perc, system) - self.set_tab_indicator_color(idx, color) - self._update_tab_title(idx) - if idx == self.currentIndex(): + self.widget.set_tab_indicator_color(idx, color) + self.widget.update_tab_title(idx) + if idx == self.widget.currentIndex(): self._update_window_title() def on_load_finished(self, tab, ok): @@ -718,23 +720,23 @@ class TabbedBrowser(tabwidget.TabWidget): color = utils.interpolate_color(start, stop, 100, system) else: color = config.val.colors.tabs.indicator.error - self.set_tab_indicator_color(idx, color) - self._update_tab_title(idx) - if idx == self.currentIndex(): + self.widget.set_tab_indicator_color(idx, color) + self.widget.update_tab_title(idx) + if idx == self.widget.currentIndex(): self._update_window_title() tab.handle_auto_insert_mode(ok) @pyqtSlot() def on_scroll_pos_changed(self): """Update tab and window title when scroll position changed.""" - idx = self.currentIndex() + idx = self.widget.currentIndex() if idx == -1: # (e.g. last tab removed) log.webview.debug("Not updating scroll position because index is " "-1") return self._update_window_title('scroll_pos') - self._update_tab_title(idx, 'scroll_pos') + self.widget.update_tab_title(idx, 'scroll_pos') def _on_renderer_process_terminated(self, tab, status, code): """Show an error when a renderer process terminated.""" @@ -767,7 +769,7 @@ class TabbedBrowser(tabwidget.TabWidget): # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-58698 message.error(msg) self._remove_tab(tab, crashed=True) - if self.count() == 0: + if self.widget.count() == 0: self.tabopen(QUrl('about:blank')) def resizeEvent(self, e): @@ -804,7 +806,7 @@ class TabbedBrowser(tabwidget.TabWidget): if key != "'": message.error("Failed to set mark: url invalid") return - point = self.currentWidget().scroller.pos_px() + point = self.widget.currentWidget().scroller.pos_px() if key.isupper(): self._global_marks[key] = point, url @@ -825,7 +827,7 @@ class TabbedBrowser(tabwidget.TabWidget): except qtutils.QtValueError: urlkey = None - tab = self.currentWidget() + tab = self.widget.currentWidget() if key.isupper(): if key in self._global_marks: diff --git a/qutebrowser/mainwindow/tabwidget.py b/qutebrowser/mainwindow/tabwidget.py index 965e5b219..abc6cedae 100644 --- a/qutebrowser/mainwindow/tabwidget.py +++ b/qutebrowser/mainwindow/tabwidget.py @@ -60,7 +60,7 @@ class TabWidget(QTabWidget): self.setTabBar(bar) bar.tabCloseRequested.connect(self.tabCloseRequested) bar.tabMoved.connect(functools.partial( - QTimer.singleShot, 0, self._update_tab_titles)) + QTimer.singleShot, 0, self.update_tab_titles)) bar.currentChanged.connect(self._on_current_changed) bar.new_tab_requested.connect(self._on_new_tab_requested) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) @@ -108,7 +108,7 @@ class TabWidget(QTabWidget): bar.set_tab_data(idx, 'pinned', pinned) tab.data.pinned = pinned - self._update_tab_title(idx) + self.update_tab_title(idx) def tab_indicator_color(self, idx): """Get the tab indicator color for the given index.""" @@ -117,13 +117,13 @@ class TabWidget(QTabWidget): def set_page_title(self, idx, title): """Set the tab title user data.""" self.tabBar().set_tab_data(idx, 'page-title', title) - self._update_tab_title(idx) + self.update_tab_title(idx) def page_title(self, idx): """Get the tab title user data.""" return self.tabBar().page_title(idx) - def _update_tab_title(self, idx, field=None): + def update_tab_title(self, idx, field=None): """Update the tab text for the given tab. Args: @@ -197,20 +197,20 @@ class TabWidget(QTabWidget): fields['scroll_pos'] = scroll_pos return fields - def _update_tab_titles(self): + def update_tab_titles(self): """Update all texts.""" for idx in range(self.count()): - self._update_tab_title(idx) + self.update_tab_title(idx) def tabInserted(self, idx): """Update titles when a tab was inserted.""" super().tabInserted(idx) - self._update_tab_titles() + self.update_tab_titles() def tabRemoved(self, idx): """Update titles when a tab was removed.""" super().tabRemoved(idx) - self._update_tab_titles() + self.update_tab_titles() def addTab(self, page, icon_or_text, text_or_empty=None): """Override addTab to use our own text setting logic. diff --git a/qutebrowser/misc/sessions.py b/qutebrowser/misc/sessions.py index a8a652dbb..dddf48b05 100644 --- a/qutebrowser/misc/sessions.py +++ b/qutebrowser/misc/sessions.py @@ -246,7 +246,7 @@ class SessionManager(QObject): if tabbed_browser.private: win_data['private'] = True for i, tab in enumerate(tabbed_browser.widgets()): - active = i == tabbed_browser.currentIndex() + active = i == tabbed_browser.widget.currentIndex() win_data['tabs'].append(self._save_tab(tab, active)) data['windows'].append(win_data) return data @@ -427,11 +427,12 @@ class SessionManager(QObject): if tab.get('active', False): tab_to_focus = i if new_tab.data.pinned: - tabbed_browser.set_tab_pinned(new_tab, new_tab.data.pinned) + tabbed_browser.widget.set_tab_pinned(new_tab, + new_tab.data.pinned) if tab_to_focus is not None: - tabbed_browser.setCurrentIndex(tab_to_focus) + tabbed_browser.widget.setCurrentIndex(tab_to_focus) if win.get('active', False): - QTimer.singleShot(0, tabbed_browser.activateWindow) + QTimer.singleShot(0, tabbed_browser.widget.activateWindow) if data['windows']: self.did_load = True diff --git a/qutebrowser/misc/utilcmds.py b/qutebrowser/misc/utilcmds.py index d2743d56e..4b55eb04e 100644 --- a/qutebrowser/misc/utilcmds.py +++ b/qutebrowser/misc/utilcmds.py @@ -185,7 +185,7 @@ def debug_cache_stats(): tabbed_browser = objreg.get('tabbed-browser', scope='window', window='last-focused') # pylint: disable=protected-access - tab_bar = tabbed_browser.tabBar() + tab_bar = tabbed_browser.widget.tabBar() tabbed_browser_info = tab_bar._minimum_tab_size_hint_helper.cache_info() # pylint: enable=protected-access diff --git a/qutebrowser/utils/objreg.py b/qutebrowser/utils/objreg.py index 8d44a9eb5..17fc34b92 100644 --- a/qutebrowser/utils/objreg.py +++ b/qutebrowser/utils/objreg.py @@ -171,7 +171,7 @@ def _get_tab_registry(win_id, tab_id): if tab_id == 'current': tabbed_browser = get('tabbed-browser', scope='window', window=win_id) - tab = tabbed_browser.currentWidget() + tab = tabbed_browser.widget.currentWidget() if tab is None: raise RegistryUnavailableError('window') tab_id = tab.tab_id diff --git a/tests/helpers/stubs.py b/tests/helpers/stubs.py index 64bc793cb..56c0a808e 100644 --- a/tests/helpers/stubs.py +++ b/tests/helpers/stubs.py @@ -497,37 +497,50 @@ class SessionManagerStub: def list_sessions(self): return self.sessions - class TabbedBrowserStub(QObject): """Stub for the tabbed-browser object.""" + def __init__(self, parent=None): + super().__init__(parent) + self.widget = TabWidgetStub() + self.shutting_down = False + self.opened_url = None + + def on_tab_close_requested(self, idx): + del self.widget.tabs[idx] + + def widgets(self): + return self.widget.tabs + + def tabopen(self, url): + self.opened_url = url + + def openurl(self, url, *, newtab): + self.opened_url = url + +class TabWidgetStub(QObject): + + """Stub for the tab-widget object.""" + new_tab = pyqtSignal(browsertab.AbstractTab, int) def __init__(self, parent=None): super().__init__(parent) self.tabs = [] - self.shutting_down = False self._qtabbar = QTabBar() self.index_of = None self.current_index = None - self.opened_url = None def count(self): return len(self.tabs) - def widgets(self): - return self.tabs - def widget(self, i): return self.tabs[i] def page_title(self, i): return self.tabs[i].title() - def on_tab_close_requested(self, idx): - del self.tabs[idx] - def tabBar(self): return self._qtabbar @@ -551,13 +564,6 @@ class TabbedBrowserStub(QObject): return None return self.tabs[idx - 1] - def tabopen(self, url): - self.opened_url = url - - def openurl(self, url, *, newtab): - self.opened_url = url - - class ApplicationStub(QObject): """Stub to insert as the app object in objreg.""" diff --git a/tests/unit/browser/test_signalfilter.py b/tests/unit/browser/test_signalfilter.py index 18f52a32a..957b85943 100644 --- a/tests/unit/browser/test_signalfilter.py +++ b/tests/unit/browser/test_signalfilter.py @@ -68,8 +68,8 @@ def objects(): @pytest.mark.parametrize('index_of, emitted', [(0, True), (1, False)]) def test_filtering(objects, tabbed_browser_stubs, index_of, emitted): browser = tabbed_browser_stubs[0] - browser.current_index = 0 - browser.index_of = index_of + browser.widget.current_index = 0 + browser.widget.index_of = index_of objects.signaller.signal.emit('foo') if emitted: assert objects.signaller.filtered_signal_arg == 'foo' @@ -80,8 +80,8 @@ def test_filtering(objects, tabbed_browser_stubs, index_of, emitted): @pytest.mark.parametrize('index_of, verb', [(0, 'emitting'), (1, 'ignoring')]) def test_logging(caplog, objects, tabbed_browser_stubs, index_of, verb): browser = tabbed_browser_stubs[0] - browser.current_index = 0 - browser.index_of = index_of + browser.widget.current_index = 0 + browser.widget.index_of = index_of with caplog.at_level(logging.DEBUG, logger='signals'): objects.signaller.signal.emit('foo') @@ -94,8 +94,8 @@ def test_logging(caplog, objects, tabbed_browser_stubs, index_of, verb): @pytest.mark.parametrize('index_of', [0, 1]) def test_no_logging(caplog, objects, tabbed_browser_stubs, index_of): browser = tabbed_browser_stubs[0] - browser.current_index = 0 - browser.index_of = index_of + browser.widget.current_index = 0 + browser.widget.index_of = index_of with caplog.at_level(logging.DEBUG, logger='signals'): objects.signaller.link_hovered.emit('foo') @@ -106,7 +106,7 @@ def test_no_logging(caplog, objects, tabbed_browser_stubs, index_of): def test_runtime_error(objects, tabbed_browser_stubs): """Test that there's no crash if indexOf() raises RuntimeError.""" browser = tabbed_browser_stubs[0] - browser.current_index = 0 - browser.index_of = RuntimeError + browser.widget.current_index = 0 + browser.widget.index_of = RuntimeError objects.signaller.signal.emit('foo') assert objects.signaller.filtered_signal_arg is None diff --git a/tests/unit/completion/test_models.py b/tests/unit/completion/test_models.py index ff9a24112..b3865950c 100644 --- a/tests/unit/completion/test_models.py +++ b/tests/unit/completion/test_models.py @@ -528,12 +528,12 @@ def test_session_completion(qtmodeltester, session_manager_stub): def test_tab_completion(qtmodeltester, fake_web_tab, app_stub, win_registry, tabbed_browser_stubs): - tabbed_browser_stubs[0].tabs = [ + tabbed_browser_stubs[0].widget.tabs = [ fake_web_tab(QUrl('https://github.com'), 'GitHub', 0), fake_web_tab(QUrl('https://wikipedia.org'), 'Wikipedia', 1), fake_web_tab(QUrl('https://duckduckgo.com'), 'DuckDuckGo', 2), ] - tabbed_browser_stubs[1].tabs = [ + tabbed_browser_stubs[1].widget.tabs = [ fake_web_tab(QUrl('https://wiki.archlinux.org'), 'ArchWiki', 0), ] model = miscmodels.buffer() @@ -556,12 +556,12 @@ def test_tab_completion(qtmodeltester, fake_web_tab, app_stub, win_registry, def test_tab_completion_delete(qtmodeltester, fake_web_tab, app_stub, win_registry, tabbed_browser_stubs): """Verify closing a tab by deleting it from the completion widget.""" - tabbed_browser_stubs[0].tabs = [ + tabbed_browser_stubs[0].widget.tabs = [ fake_web_tab(QUrl('https://github.com'), 'GitHub', 0), fake_web_tab(QUrl('https://wikipedia.org'), 'Wikipedia', 1), fake_web_tab(QUrl('https://duckduckgo.com'), 'DuckDuckGo', 2) ] - tabbed_browser_stubs[1].tabs = [ + tabbed_browser_stubs[1].widget.tabs = [ fake_web_tab(QUrl('https://wiki.archlinux.org'), 'ArchWiki', 0), ] model = miscmodels.buffer() @@ -577,19 +577,19 @@ def test_tab_completion_delete(qtmodeltester, fake_web_tab, app_stub, assert model.data(idx) == '0/2' model.delete_cur_item(idx) - actual = [tab.url() for tab in tabbed_browser_stubs[0].tabs] + actual = [tab.url() for tab in tabbed_browser_stubs[0].widget.tabs] assert actual == [QUrl('https://github.com'), QUrl('https://duckduckgo.com')] def test_other_buffer_completion(qtmodeltester, fake_web_tab, app_stub, win_registry, tabbed_browser_stubs, info): - tabbed_browser_stubs[0].tabs = [ + tabbed_browser_stubs[0].widget.tabs = [ fake_web_tab(QUrl('https://github.com'), 'GitHub', 0), fake_web_tab(QUrl('https://wikipedia.org'), 'Wikipedia', 1), fake_web_tab(QUrl('https://duckduckgo.com'), 'DuckDuckGo', 2), ] - tabbed_browser_stubs[1].tabs = [ + tabbed_browser_stubs[1].widget.tabs = [ fake_web_tab(QUrl('https://wiki.archlinux.org'), 'ArchWiki', 0), ] info.win_id = 1 @@ -609,12 +609,12 @@ def test_other_buffer_completion(qtmodeltester, fake_web_tab, app_stub, def test_window_completion(qtmodeltester, fake_web_tab, tabbed_browser_stubs, info): - tabbed_browser_stubs[0].tabs = [ + tabbed_browser_stubs[0].widget.tabs = [ fake_web_tab(QUrl('https://github.com'), 'GitHub', 0), fake_web_tab(QUrl('https://wikipedia.org'), 'Wikipedia', 1), fake_web_tab(QUrl('https://duckduckgo.com'), 'DuckDuckGo', 2) ] - tabbed_browser_stubs[1].tabs = [ + tabbed_browser_stubs[1].widget.tabs = [ fake_web_tab(QUrl('https://wiki.archlinux.org'), 'ArchWiki', 0) ] diff --git a/tests/unit/mainwindow/statusbar/test_backforward.py b/tests/unit/mainwindow/statusbar/test_backforward.py index 6e594c0d2..11e3da616 100644 --- a/tests/unit/mainwindow/statusbar/test_backforward.py +++ b/tests/unit/mainwindow/statusbar/test_backforward.py @@ -43,8 +43,8 @@ def test_backforward_widget(backforward_widget, tabbed_browser_stubs, """Ensure the Backforward widget shows the correct text.""" tab = fake_web_tab(can_go_back=can_go_back, can_go_forward=can_go_forward) tabbed_browser = tabbed_browser_stubs[0] - tabbed_browser.current_index = 1 - tabbed_browser.tabs = [tab] + tabbed_browser.widget.current_index = 1 + tabbed_browser.widget.tabs = [tab] backforward_widget.enabled = True backforward_widget.on_tab_cur_url_changed(tabbed_browser) assert backforward_widget.text() == expected_text @@ -59,7 +59,7 @@ def test_backforward_widget(backforward_widget, tabbed_browser_stubs, # Check that the widget gets reset if empty. if can_go_back and can_go_forward: tab = fake_web_tab(can_go_back=False, can_go_forward=False) - tabbed_browser.tabs = [tab] + tabbed_browser.widget.tabs = [tab] backforward_widget.enabled = True backforward_widget.on_tab_cur_url_changed(tabbed_browser) assert backforward_widget.text() == '' @@ -70,15 +70,15 @@ def test_none_tab(backforward_widget, tabbed_browser_stubs, fake_web_tab): """Make sure nothing crashes when passing None as tab.""" tab = fake_web_tab(can_go_back=True, can_go_forward=True) tabbed_browser = tabbed_browser_stubs[0] - tabbed_browser.current_index = 1 - tabbed_browser.tabs = [tab] + tabbed_browser.widget.current_index = 1 + tabbed_browser.widget.tabs = [tab] backforward_widget.enabled = True backforward_widget.on_tab_cur_url_changed(tabbed_browser) assert backforward_widget.text() == '[<>]' assert backforward_widget.isVisible() - tabbed_browser.current_index = -1 + tabbed_browser.widget.current_index = -1 backforward_widget.on_tab_cur_url_changed(tabbed_browser) assert backforward_widget.text() == '' diff --git a/tests/unit/mainwindow/test_tabwidget.py b/tests/unit/mainwindow/test_tabwidget.py index 7ad22fcc3..36e6a0c48 100644 --- a/tests/unit/mainwindow/test_tabwidget.py +++ b/tests/unit/mainwindow/test_tabwidget.py @@ -71,7 +71,7 @@ class TestTabWidget: with qtbot.waitExposed(widget): widget.show() - benchmark(widget._update_tab_titles) + benchmark(widget.update_tab_titles) @pytest.mark.parametrize("num_tabs", [4, 10]) def test_add_remove_tab_benchmark(self, benchmark, browser, @@ -79,7 +79,7 @@ class TestTabWidget: """Benchmark for addTab and removeTab.""" def _run_bench(): for i in range(num_tabs): - browser.addTab(fake_web_tab(), 'foobar' + str(i)) + browser.widget.addTab(fake_web_tab(), 'foobar' + str(i)) with qtbot.waitExposed(browser): browser.show() From 16218a990006652b7b9aec3c3f29b67b16d97c8a Mon Sep 17 00:00:00 2001 From: gammelon Date: Tue, 20 Feb 2018 18:11:50 +0100 Subject: [PATCH 003/223] Remove unnecessary try, rephrase to imperative mood --- qutebrowser/config/configdata.yml | 3 +-- qutebrowser/utils/urlutils.py | 10 ++++------ 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml index 23ff271f0..2cb36abcc 100644 --- a/qutebrowser/config/configdata.yml +++ b/qutebrowser/config/configdata.yml @@ -1448,8 +1448,7 @@ url.incdec_segments: url.open_base_url: type: Bool default: false - desc: Invoking `:open {shortcut}` (without argument), where {shortcut} is a search engine shortcut - will open the base url of the shortcut instead of using the default search engine. + desc: Open base URL of the searchengine if a searchengine shortcut is invoked without parameters. url.searchengines: default: diff --git a/qutebrowser/utils/urlutils.py b/qutebrowser/utils/urlutils.py index 1bb062f3f..5f502e6fd 100644 --- a/qutebrowser/utils/urlutils.py +++ b/qutebrowser/utils/urlutils.py @@ -103,12 +103,10 @@ def _get_search_url(txt): template = config.val.url.searchengines[engine] url = qurl_from_user_input(template.format(urllib.parse.quote(term))) - if config.val.url.open_base_url: - try: - search_url = urllib.parse.urlparse(config.val.url.searchengines[term]) - url = QUrl('{}://{}'.format(search_url.scheme, search_url.netloc)) - except KeyError: - pass + if config.val.url.open_base_url and \ + term in config.val.url.searchengines.keys(): + search_url = urllib.parse.urlparse(config.val.url.searchengines[term]) + url = QUrl('{}://{}'.format(search_url.scheme, search_url.netloc)) qtutils.ensure_valid(url) return url From a730290d409b7e53770addcf0a1159a8ea736869 Mon Sep 17 00:00:00 2001 From: gammelon Date: Mon, 5 Mar 2018 16:32:41 +0100 Subject: [PATCH 004/223] Use QUrl for parsing, add tests --- qutebrowser/utils/urlutils.py | 6 ++++-- tests/unit/utils/test_urlutils.py | 36 +++++++++++++++++++++---------- 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/qutebrowser/utils/urlutils.py b/qutebrowser/utils/urlutils.py index 5f502e6fd..8bbf5b362 100644 --- a/qutebrowser/utils/urlutils.py +++ b/qutebrowser/utils/urlutils.py @@ -105,8 +105,10 @@ def _get_search_url(txt): if config.val.url.open_base_url and \ term in config.val.url.searchengines.keys(): - search_url = urllib.parse.urlparse(config.val.url.searchengines[term]) - url = QUrl('{}://{}'.format(search_url.scheme, search_url.netloc)) + url = qurl_from_user_input(config.val.url.searchengines[term]) + url.setPath(None) + url.setFragment(None) + url.setQuery(None) qtutils.ensure_valid(url) return url diff --git a/tests/unit/utils/test_urlutils.py b/tests/unit/utils/test_urlutils.py index 6621edfed..a96236f6e 100644 --- a/tests/unit/utils/test_urlutils.py +++ b/tests/unit/utils/test_urlutils.py @@ -277,18 +277,27 @@ class TestFuzzyUrl: def test_special_urls(url, special): assert urlutils.is_special_url(QUrl(url)) == special - -@pytest.mark.parametrize('url, host, query', [ - ('testfoo', 'www.example.com', 'q=testfoo'), - ('test testfoo', 'www.qutebrowser.org', 'q=testfoo'), - ('test testfoo bar foo', 'www.qutebrowser.org', 'q=testfoo bar foo'), - ('test testfoo ', 'www.qutebrowser.org', 'q=testfoo'), - ('!python testfoo', 'www.example.com', 'q=%21python testfoo'), - ('blub testfoo', 'www.example.com', 'q=blub testfoo'), - ('stripped ', 'www.example.com', 'q=stripped'), - ('test-with-dash testfoo', 'www.example.org', 'q=testfoo'), +@pytest.mark.parametrize('url, host, query, open_base_url', [ + ('testfoo', 'www.example.com', 'q=testfoo', False), + ('test testfoo', 'www.qutebrowser.org', 'q=testfoo', False), + ('test testfoo bar foo', 'www.qutebrowser.org', 'q=testfoo bar foo', False), + ('test testfoo ', 'www.qutebrowser.org', 'q=testfoo', False), + ('!python testfoo', 'www.example.com', 'q=%21python testfoo', False), + ('blub testfoo', 'www.example.com', 'q=blub testfoo', False), + ('stripped ', 'www.example.com', 'q=stripped', False), + ('test-with-dash testfoo', 'www.example.org', 'q=testfoo', False), + ('testfoo', 'www.example.com', 'q=testfoo', True), + ('test testfoo', 'www.qutebrowser.org', 'q=testfoo', True), + ('test testfoo bar foo', 'www.qutebrowser.org', 'q=testfoo bar foo', True), + ('test testfoo ', 'www.qutebrowser.org', 'q=testfoo', True), + ('!python testfoo', 'www.example.com', 'q=%21python testfoo', True), + ('blub testfoo', 'www.example.com', 'q=blub testfoo', True), + ('stripped ', 'www.example.com', 'q=stripped', True), + ('test-with-dash testfoo', 'www.example.org', 'q=testfoo', True), + ('test', 'www.qutebrowser.org', '', True), + ('test-with-dash', 'www.example.org', '', True), ]) -def test_get_search_url(url, host, query): +def test_get_search_url(config_stub, url, host, query, open_base_url): """Test _get_search_url(). Args: @@ -296,7 +305,12 @@ def test_get_search_url(url, host, query): host: The expected search machine host. query: The expected search query. """ + config_stub.val.url.open_base_url = open_base_url url = urlutils._get_search_url(url) + if open_base_url and query == '': + assert url.path() == '' + assert url.fragment() == '' + assert url.host() == host assert url.query() == query From 46533c336736bf57045c0e2d281fd75ea7c55279 Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Fri, 9 Mar 2018 02:02:31 -0500 Subject: [PATCH 005/223] Fix pinned tabs being too small in extreme situations --- qutebrowser/mainwindow/tabwidget.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/qutebrowser/mainwindow/tabwidget.py b/qutebrowser/mainwindow/tabwidget.py index 965e5b219..8261492a9 100644 --- a/qutebrowser/mainwindow/tabwidget.py +++ b/qutebrowser/mainwindow/tabwidget.py @@ -477,7 +477,7 @@ class TabBar(QTabBar): Args: index: The index of the tab to get a size hint for. ellipsis: Whether to use ellipsis to calculate width - instead of the tab's text. + instead of the tab's text. Forced to true for pinned tabs. Return: A QSize of the smallest tab size we can make. """ @@ -489,6 +489,11 @@ class TabBar(QTabBar): else: icon_width = min(icon.actualSize(self.iconSize()).width(), self.iconSize().width()) + icon_padding + + pinned = self._tab_pinned(index) + if pinned: + # Never consider ellipsis an option for pinned tabs + ellipsis = False return self._minimum_tab_size_hint_helper(self.tabText(index), icon_width, ellipsis) From 4a78b0519dc4c48bb62a670de2806aba0f664dd1 Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Fri, 9 Mar 2018 02:02:56 -0500 Subject: [PATCH 006/223] Add tabs.min_width setting Controls min width in pixels of non pinned tabs Closes #3690 --- qutebrowser/config/configdata.yml | 10 ++++++++++ qutebrowser/mainwindow/tabwidget.py | 12 ++++++++---- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml index a6a2d5317..f2fe79a1d 100644 --- a/qutebrowser/config/configdata.yml +++ b/qutebrowser/config/configdata.yml @@ -1406,6 +1406,16 @@ tabs.width: desc: "Width (in pixels or as percentage of the window) of the tab bar if it's vertical." +tabs.min_width: + default: -1 + type: + name: Int + minval: -1 + desc: >- + Minimum width (in pixels) of tabs (-1 to use text contents for min width). + + This setting only applies when tabs are horizontal. + tabs.width.indicator: renamed: tabs.indicator.width diff --git a/qutebrowser/mainwindow/tabwidget.py b/qutebrowser/mainwindow/tabwidget.py index 8261492a9..0fc7fed40 100644 --- a/qutebrowser/mainwindow/tabwidget.py +++ b/qutebrowser/mainwindow/tabwidget.py @@ -358,7 +358,8 @@ class TabBar(QTabBar): # Clear _minimum_tab_size_hint_helper cache when appropriate if option in ["tabs.indicator.padding", "tabs.padding", - "tabs.indicator.width"]: + "tabs.indicator.width", + "tabs.min_width"]: self._minimum_tab_size_hint_helper.cache_clear() def _on_show_switching_delay_changed(self): @@ -495,13 +496,13 @@ class TabBar(QTabBar): # Never consider ellipsis an option for pinned tabs ellipsis = False return self._minimum_tab_size_hint_helper(self.tabText(index), - icon_width, - ellipsis) + icon_width, ellipsis, + pinned) @functools.lru_cache(maxsize=2**9) def _minimum_tab_size_hint_helper(self, tab_text: str, icon_width: int, - ellipsis: bool) -> QSize: + ellipsis: bool, pinned: bool) -> QSize: """Helper function to cache tab results. Config values accessed in here should be added to _on_config_changed to @@ -526,6 +527,9 @@ class TabBar(QTabBar): height = self.fontMetrics().height() + padding_v width = (text_width + icon_width + padding_h + indicator_width) + min_width = config.val.tabs.min_width + if not pinned and not self.vertical and min_width > 0: + width = max(min_width, width) return QSize(width, height) def _pinned_statistics(self) -> (int, int): From 16729956398125dc2f8cfd6c5cb8dc740a373d55 Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Fri, 9 Mar 2018 02:19:49 -0500 Subject: [PATCH 007/223] Clean up style issues --- qutebrowser/mainwindow/tabwidget.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qutebrowser/mainwindow/tabwidget.py b/qutebrowser/mainwindow/tabwidget.py index 0fc7fed40..6605380be 100644 --- a/qutebrowser/mainwindow/tabwidget.py +++ b/qutebrowser/mainwindow/tabwidget.py @@ -478,7 +478,8 @@ class TabBar(QTabBar): Args: index: The index of the tab to get a size hint for. ellipsis: Whether to use ellipsis to calculate width - instead of the tab's text. Forced to true for pinned tabs. + instead of the tab's text. + Forced to false for pinned tabs. Return: A QSize of the smallest tab size we can make. """ From 7e3c966afeca64d99f9623a12a4286197fbd29d5 Mon Sep 17 00:00:00 2001 From: gammelon Date: Fri, 9 Mar 2018 15:52:03 +0100 Subject: [PATCH 008/223] rewrite tests --- qutebrowser/utils/urlutils.py | 3 +- tests/unit/utils/test_urlutils.py | 49 ++++++++++++++++++------------- 2 files changed, 29 insertions(+), 23 deletions(-) diff --git a/qutebrowser/utils/urlutils.py b/qutebrowser/utils/urlutils.py index 8bbf5b362..0c77a5d0f 100644 --- a/qutebrowser/utils/urlutils.py +++ b/qutebrowser/utils/urlutils.py @@ -103,8 +103,7 @@ def _get_search_url(txt): template = config.val.url.searchengines[engine] url = qurl_from_user_input(template.format(urllib.parse.quote(term))) - if config.val.url.open_base_url and \ - term in config.val.url.searchengines.keys(): + if config.val.url.open_base_url and term in config.val.url.searchengines: url = qurl_from_user_input(config.val.url.searchengines[term]) url.setPath(None) url.setFragment(None) diff --git a/tests/unit/utils/test_urlutils.py b/tests/unit/utils/test_urlutils.py index a96236f6e..d2da02d0f 100644 --- a/tests/unit/utils/test_urlutils.py +++ b/tests/unit/utils/test_urlutils.py @@ -277,25 +277,17 @@ class TestFuzzyUrl: def test_special_urls(url, special): assert urlutils.is_special_url(QUrl(url)) == special -@pytest.mark.parametrize('url, host, query, open_base_url', [ - ('testfoo', 'www.example.com', 'q=testfoo', False), - ('test testfoo', 'www.qutebrowser.org', 'q=testfoo', False), - ('test testfoo bar foo', 'www.qutebrowser.org', 'q=testfoo bar foo', False), - ('test testfoo ', 'www.qutebrowser.org', 'q=testfoo', False), - ('!python testfoo', 'www.example.com', 'q=%21python testfoo', False), - ('blub testfoo', 'www.example.com', 'q=blub testfoo', False), - ('stripped ', 'www.example.com', 'q=stripped', False), - ('test-with-dash testfoo', 'www.example.org', 'q=testfoo', False), - ('testfoo', 'www.example.com', 'q=testfoo', True), - ('test testfoo', 'www.qutebrowser.org', 'q=testfoo', True), - ('test testfoo bar foo', 'www.qutebrowser.org', 'q=testfoo bar foo', True), - ('test testfoo ', 'www.qutebrowser.org', 'q=testfoo', True), - ('!python testfoo', 'www.example.com', 'q=%21python testfoo', True), - ('blub testfoo', 'www.example.com', 'q=blub testfoo', True), - ('stripped ', 'www.example.com', 'q=stripped', True), - ('test-with-dash testfoo', 'www.example.org', 'q=testfoo', True), - ('test', 'www.qutebrowser.org', '', True), - ('test-with-dash', 'www.example.org', '', True), +@pytest.mark.parametrize('open_base_url', [True, False]) + +@pytest.mark.parametrize('url, host, query', [ + ('testfoo', 'www.example.com', 'q=testfoo'), + ('test testfoo', 'www.qutebrowser.org', 'q=testfoo'), + ('test testfoo bar foo', 'www.qutebrowser.org', 'q=testfoo bar foo'), + ('test testfoo ', 'www.qutebrowser.org', 'q=testfoo'), + ('!python testfoo', 'www.example.com', 'q=%21python testfoo'), + ('blub testfoo', 'www.example.com', 'q=blub testfoo'), + ('stripped ', 'www.example.com', 'q=stripped'), + ('test-with-dash testfoo', 'www.example.org', 'q=testfoo'), ]) def test_get_search_url(config_stub, url, host, query, open_base_url): """Test _get_search_url(). @@ -308,13 +300,28 @@ def test_get_search_url(config_stub, url, host, query, open_base_url): config_stub.val.url.open_base_url = open_base_url url = urlutils._get_search_url(url) if open_base_url and query == '': - assert url.path() == '' - assert url.fragment() == '' + assert not url.path() + assert not url.fragment() assert url.host() == host assert url.query() == query +@pytest.mark.parametrize('url, host, query', [ + ('test', 'www.qutebrowser.org', ''), + ('test-with-dash', 'www.example.org', ''), +]) + +def test_get_search_url_open_base_url(config_stub, url, host, query): + """Test _get_search_url() with url.open_base_url_enabled. + + Args: + url: The "URL" to enter. + host: The expected search machine host. + query: The expected search query. + """ + test_get_search_url(config_stub, url, host, query, True) + @pytest.mark.parametrize('url', ['\n', ' ', '\n ']) def test_get_search_url_invalid(url): with pytest.raises(ValueError): From 0ce94dae1cde3b5ac3dfca79385e8fad9e592677 Mon Sep 17 00:00:00 2001 From: gammelon Date: Fri, 9 Mar 2018 15:55:40 +0100 Subject: [PATCH 009/223] forgot one bit --- tests/unit/utils/test_urlutils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/utils/test_urlutils.py b/tests/unit/utils/test_urlutils.py index d2da02d0f..5fc897497 100644 --- a/tests/unit/utils/test_urlutils.py +++ b/tests/unit/utils/test_urlutils.py @@ -299,7 +299,7 @@ def test_get_search_url(config_stub, url, host, query, open_base_url): """ config_stub.val.url.open_base_url = open_base_url url = urlutils._get_search_url(url) - if open_base_url and query == '': + if open_base_url and not query: assert not url.path() assert not url.fragment() From cf4e47246187e37ffc8ae3fce4d7ad4a58489676 Mon Sep 17 00:00:00 2001 From: Johannes Wegener Date: Sat, 17 Feb 2018 20:58:33 +0100 Subject: [PATCH 010/223] add basic completion to file dialog --- qutebrowser/mainwindow/prompt.py | 27 ++++++++++++++++++++++++++- tests/unit/mainwindow/test_prompt.py | 8 ++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/qutebrowser/mainwindow/prompt.py b/qutebrowser/mainwindow/prompt.py index 90415b261..f7af28440 100644 --- a/qutebrowser/mainwindow/prompt.py +++ b/qutebrowser/mainwindow/prompt.py @@ -596,6 +596,8 @@ class FilenamePrompt(_BasePrompt): if config.val.prompt.filebrowser: self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) + self._to_complete = '' + @pyqtSlot(str) def _set_fileview_root(self, path, *, tabbed=False): """Set the root path for the file display.""" @@ -604,6 +606,9 @@ class FilenamePrompt(_BasePrompt): separators += os.altsep dirname = os.path.dirname(path) + basename = os.path.basename(path) + if not tabbed: + self._to_complete = '' try: if not path: @@ -617,6 +622,7 @@ class FilenamePrompt(_BasePrompt): elif os.path.isdir(dirname) and not tabbed: # Input like /foo/ba -> show /foo contents path = dirname + self._to_complete = basename else: return except OSError: @@ -634,7 +640,11 @@ class FilenamePrompt(_BasePrompt): index: The QModelIndex of the selected element. clicked: Whether the element was clicked. """ - path = os.path.normpath(self._file_model.filePath(index)) + if index == QModelIndex(): + path = os.path.join(self._file_model.rootPath(), self._to_complete) + else: + path = os.path.normpath(self._file_model.filePath(index)) + if clicked: path += os.sep else: @@ -696,6 +706,7 @@ class FilenamePrompt(_BasePrompt): assert last_index.isValid() idx = selmodel.currentIndex() + if not idx.isValid(): # No item selected yet idx = last_index if which == 'prev' else first_index @@ -709,10 +720,24 @@ class FilenamePrompt(_BasePrompt): if not idx.isValid(): idx = last_index if which == 'prev' else first_index + idx = self._do_completion(idx, which) + selmodel.setCurrentIndex( idx, QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Rows) self._insert_path(idx, clicked=False) + def _do_completion(self, idx, which): + filename = self._file_model.fileName(idx) + while not filename.startswith(self._to_complete) and idx.isValid(): + if which == 'prev': + idx = self._file_view.indexAbove(idx) + else: + assert which == 'next', which + idx = self._file_view.indexBelow(idx) + filename = self._file_model.fileName(idx) + + return idx + def _allowed_commands(self): return [('prompt-accept', 'Accept'), ('leave-mode', 'Abort')] diff --git a/tests/unit/mainwindow/test_prompt.py b/tests/unit/mainwindow/test_prompt.py index e467b0316..7c8d2b0ad 100644 --- a/tests/unit/mainwindow/test_prompt.py +++ b/tests/unit/mainwindow/test_prompt.py @@ -81,6 +81,14 @@ class TestFileCompletion: for _ in range(3): qtbot.keyPress(prompt._lineedit, Qt.Key_Backspace) + # foo should get completed from f + prompt.item_focus('next') + assert prompt._lineedit.text() == str(testdir / 'foo') + + # Deleting /[foo] + for _ in range(3): + qtbot.keyPress(prompt._lineedit, Qt.Key_Backspace) + # We should now show / again, so tabbing twice gives us .. -> bar prompt.item_focus('next') prompt.item_focus('next') From 996561b50e8cf9caa8c3c4cda7c29c13aec9850a Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Fri, 9 Mar 2018 12:57:07 -0500 Subject: [PATCH 011/223] Apply tabs.min_width to all tabs when tabs are unshrunk --- qutebrowser/config/configdata.yml | 5 ++++- qutebrowser/mainwindow/tabwidget.py | 6 ++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml index f2fe79a1d..adbcdfa90 100644 --- a/qutebrowser/config/configdata.yml +++ b/qutebrowser/config/configdata.yml @@ -1411,11 +1411,14 @@ tabs.min_width: type: name: Int minval: -1 + maxval: maxint desc: >- - Minimum width (in pixels) of tabs (-1 to use text contents for min width). + Minimum width (in pixels) of tabs (-1 to use min text contents size for min width). This setting only applies when tabs are horizontal. + This setting does not apply to pinned tabs, unless `tabs.pinned.shrink` is False. + tabs.width.indicator: renamed: tabs.indicator.width diff --git a/qutebrowser/mainwindow/tabwidget.py b/qutebrowser/mainwindow/tabwidget.py index 6605380be..5274ee768 100644 --- a/qutebrowser/mainwindow/tabwidget.py +++ b/qutebrowser/mainwindow/tabwidget.py @@ -359,7 +359,8 @@ class TabBar(QTabBar): if option in ["tabs.indicator.padding", "tabs.padding", "tabs.indicator.width", - "tabs.min_width"]: + "tabs.min_width", + "tabs.pinned.shrink"]: self._minimum_tab_size_hint_helper.cache_clear() def _on_show_switching_delay_changed(self): @@ -529,7 +530,8 @@ class TabBar(QTabBar): width = (text_width + icon_width + padding_h + indicator_width) min_width = config.val.tabs.min_width - if not pinned and not self.vertical and min_width > 0: + if (not self.vertical and min_width > 0 and + not pinned or not config.val.tabs.pinned.shrink): width = max(min_width, width) return QSize(width, height) From d72691ee49184905f1e31530868bbe9c5efebcd7 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 12 Mar 2018 07:38:56 +0100 Subject: [PATCH 012/223] Simplify ListOrValue configtype --- qutebrowser/config/configtypes.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/qutebrowser/config/configtypes.py b/qutebrowser/config/configtypes.py index 14855bf03..86b72a9ff 100644 --- a/qutebrowser/config/configtypes.py +++ b/qutebrowser/config/configtypes.py @@ -506,6 +506,16 @@ class ListOrValue(BaseType): self.listtype = List(valtype, none_ok=none_ok, *args, **kwargs) self.valtype = valtype + def _val_and_type(self, value): + """Get the value and type to use for to_str/to_doc/from_str.""" + if isinstance(value, list): + if len(value) == 1: + return value[0], self.valtype + else: + return value, self.listtype + else: + return value, self.valtype + def get_name(self): return self.listtype.get_name() + ', or ' + self.valtype.get_name() @@ -533,25 +543,15 @@ class ListOrValue(BaseType): if value is None: return '' - if isinstance(value, list): - if len(value) == 1: - return self.valtype.to_str(value[0]) - else: - return self.listtype.to_str(value) - else: - return self.valtype.to_str(value) + val, typ = self._val_and_type(value) + return typ.to_str(val) def to_doc(self, value, indent=0): if value is None: return 'empty' - if isinstance(value, list): - if len(value) == 1: - return self.valtype.to_doc(value[0], indent) - else: - return self.listtype.to_doc(value, indent) - else: - return self.valtype.to_doc(value, indent) + val, typ = self._val_and_type(value) + return typ.to_doc(val) class FlagList(List): From c03ef10d54e2129d309ea5d6c40471efa105764e Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 12 Mar 2018 07:39:20 +0100 Subject: [PATCH 013/223] tests: Add a yaml_config_stub fixture --- tests/helpers/fixtures.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/tests/helpers/fixtures.py b/tests/helpers/fixtures.py index f9f02ba8b..f8729d3f8 100644 --- a/tests/helpers/fixtures.py +++ b/tests/helpers/fixtures.py @@ -193,11 +193,15 @@ def configdata_init(): @pytest.fixture -def config_stub(stubs, monkeypatch, configdata_init, config_tmpdir): - """Fixture which provides a fake config object.""" - yaml_config = configfiles.YamlConfig() +def yaml_config_stub(config_tmpdir): + """Fixture which provides a YamlConfig object.""" + return configfiles.YamlConfig() - conf = config.Config(yaml_config=yaml_config) + +@pytest.fixture +def config_stub(stubs, monkeypatch, configdata_init, yaml_config_stub): + """Fixture which provides a fake config object.""" + conf = config.Config(yaml_config=yaml_config_stub) monkeypatch.setattr(config, 'instance', conf) container = config.ConfigContainer(conf) From 990c0707f4533bab35be1c24dd7dca759fc8fdcd Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 12 Mar 2018 08:00:18 +0100 Subject: [PATCH 014/223] Make from_obj() work for List/Dict configtypes We can't easily make it work for ListOrValue as we don't know which of both we get at this point. --- qutebrowser/config/configtypes.py | 6 ++++-- tests/unit/config/test_configtypes.py | 21 +++++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/qutebrowser/config/configtypes.py b/qutebrowser/config/configtypes.py index 86b72a9ff..e84d55075 100644 --- a/qutebrowser/config/configtypes.py +++ b/qutebrowser/config/configtypes.py @@ -451,7 +451,7 @@ class List(BaseType): def from_obj(self, value): if value is None: return [] - return value + return [self.valtype.from_obj(v) for v in value] def to_py(self, value): self._basic_py_validation(value, list) @@ -1199,7 +1199,9 @@ class Dict(BaseType): def from_obj(self, value): if value is None: return {} - return value + + return {self.keytype.from_obj(key): self.valtype.from_obj(val) + for key, val in value.items()} def _fill_fixed_keys(self, value): """Fill missing fixed keys with a None-value.""" diff --git a/tests/unit/config/test_configtypes.py b/tests/unit/config/test_configtypes.py index 4dd7837fb..930234425 100644 --- a/tests/unit/config/test_configtypes.py +++ b/tests/unit/config/test_configtypes.py @@ -533,6 +533,14 @@ class FlagListSubclass(configtypes.FlagList): 'foo', 'bar', 'baz') +class FromObjType(configtypes.BaseType): + + """Config type to test from_obj for List/Dict.""" + + def from_obj(self, obj): + return int(obj) + + class TestList: """Test List and FlagList.""" @@ -647,6 +655,12 @@ class TestList: with pytest.raises(AssertionError): typ.to_doc([['foo']]) + def test_from_obj_sub(self): + """Make sure the list calls from_obj() on sub-types.""" + typ = configtypes.List(valtype=FromObjType()) + value = typ.from_obj(['1', '2']) + assert value == [1, 2] + class TestFlagList: @@ -1665,6 +1679,13 @@ class TestDict: print(doc) assert doc == expected + def test_from_obj_sub(self): + """Make sure the dict calls from_obj() on sub-types.""" + typ = configtypes.Dict(keytype=configtypes.String(), + valtype=FromObjType()) + value = typ.from_obj({'1': '2'}) + assert value == {'1': 2} + def unrequired_class(**kwargs): return configtypes.File(required=False, **kwargs) From 994181212734cacdfa6e4d7cb35402881282bf4f Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 12 Mar 2018 08:00:56 +0100 Subject: [PATCH 015/223] Normalize keys read from the config This makes sure the internal bindings.commands object only contains normalized key sequences. Fixes #3699 --- qutebrowser/config/configtypes.py | 3 +++ tests/unit/config/test_config.py | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/qutebrowser/config/configtypes.py b/qutebrowser/config/configtypes.py index e84d55075..e85d153b9 100644 --- a/qutebrowser/config/configtypes.py +++ b/qutebrowser/config/configtypes.py @@ -1650,6 +1650,9 @@ class Key(BaseType): """A name of a key.""" + def from_obj(self, value): + return str(keyutils.KeySequence.parse(value)) + def to_py(self, value): self._basic_py_validation(value, str) if not value: diff --git a/tests/unit/config/test_config.py b/tests/unit/config/test_config.py index 9104c0f53..e1ef7ef94 100644 --- a/tests/unit/config/test_config.py +++ b/tests/unit/config/test_config.py @@ -339,6 +339,24 @@ class TestKeyConfig: key_config_stub.unbind(seq) assert key_config_stub.get_command(seq, mode='normal') is None + def test_unbind_old_syntax(self, yaml_config_stub, key_config_stub, + config_stub): + """Test unbinding bindings added before the keybinding refactoring. + + We used to normalize keys differently, so we can have in the + config. + + See https://github.com/qutebrowser/qutebrowser/issues/3699 + """ + bindings = {'normal': {'': 'nop'}} + yaml_config_stub.set_obj('bindings.commands', bindings) + config_stub.read_yaml() + + key_config_stub.unbind(keyutils.KeySequence.parse(''), + save_yaml=True) + + assert config.instance.get_obj('bindings.commands') == {'normal': {}} + def test_empty_command(self, key_config_stub): """Try binding a key to an empty command.""" message = "Can't add binding 'x' with empty command in normal mode" From a6885a0d41eade54d9db3fc8e0789e6db7224c53 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 12 Mar 2018 08:03:20 +0100 Subject: [PATCH 016/223] Update changelog --- doc/changelog.asciidoc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index f8daed78d..3d10e6ea2 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -36,6 +36,8 @@ Fixed - With "tox -e mkvenv-pypi", PyQt 5.10.0 is used again instead of Qt 5.10.1, because of an issue with Qt 5.10.1 which causes qutebrowser to fail to start ("Could not find QtWebEngineProcess"). +- Unbinding keys which were bound in older qutebrowser versions now doesn't + crash anymore. v1.2.0 ------ From 8c5b7bcd0395f113383a730752b766636c50f776 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 12 Mar 2018 08:51:08 +0100 Subject: [PATCH 017/223] Fix lint --- qutebrowser/config/configtypes.py | 1 + tests/unit/config/test_configtypes.py | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/qutebrowser/config/configtypes.py b/qutebrowser/config/configtypes.py index e85d153b9..20e240690 100644 --- a/qutebrowser/config/configtypes.py +++ b/qutebrowser/config/configtypes.py @@ -1651,6 +1651,7 @@ class Key(BaseType): """A name of a key.""" def from_obj(self, value): + """Make sure key sequences are always normalized.""" return str(keyutils.KeySequence.parse(value)) def to_py(self, value): diff --git a/tests/unit/config/test_configtypes.py b/tests/unit/config/test_configtypes.py index 930234425..533932981 100644 --- a/tests/unit/config/test_configtypes.py +++ b/tests/unit/config/test_configtypes.py @@ -537,8 +537,11 @@ class FromObjType(configtypes.BaseType): """Config type to test from_obj for List/Dict.""" - def from_obj(self, obj): - return int(obj) + def from_obj(self, value): + return int(value) + + def to_py(self, value): + return value class TestList: From 455f6b8a702a0d770fccf38cf387bb5b2d468573 Mon Sep 17 00:00:00 2001 From: gammelon Date: Mon, 12 Mar 2018 12:37:52 +0100 Subject: [PATCH 018/223] Fix blank lines --- tests/unit/utils/test_urlutils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/utils/test_urlutils.py b/tests/unit/utils/test_urlutils.py index 5fc897497..03c5239fa 100644 --- a/tests/unit/utils/test_urlutils.py +++ b/tests/unit/utils/test_urlutils.py @@ -277,8 +277,8 @@ class TestFuzzyUrl: def test_special_urls(url, special): assert urlutils.is_special_url(QUrl(url)) == special -@pytest.mark.parametrize('open_base_url', [True, False]) +@pytest.mark.parametrize('open_base_url', [True, False]) @pytest.mark.parametrize('url, host, query', [ ('testfoo', 'www.example.com', 'q=testfoo'), ('test testfoo', 'www.qutebrowser.org', 'q=testfoo'), @@ -311,7 +311,6 @@ def test_get_search_url(config_stub, url, host, query, open_base_url): ('test', 'www.qutebrowser.org', ''), ('test-with-dash', 'www.example.org', ''), ]) - def test_get_search_url_open_base_url(config_stub, url, host, query): """Test _get_search_url() with url.open_base_url_enabled. @@ -322,6 +321,7 @@ def test_get_search_url_open_base_url(config_stub, url, host, query): """ test_get_search_url(config_stub, url, host, query, True) + @pytest.mark.parametrize('url', ['\n', ' ', '\n ']) def test_get_search_url_invalid(url): with pytest.raises(ValueError): From 38bb3673db2215e0d88f56673b2c25893e286132 Mon Sep 17 00:00:00 2001 From: Ryan Roden-Corrent Date: Mon, 12 Mar 2018 08:34:50 -0400 Subject: [PATCH 019/223] Preserve a backup if editor callback fails. Currently the editor deletes its temp file whenever editing is finished. With this patch, the file will not be deleted if the editor callback encounters an exception. One example is if the tab containing the edited element is closed. The editor errors with "Edited element vanished", but with this patch it will also print "Backup at ..." so the user does not lose their work. Resolves #1596. Supersedes #3641, using the cleaner approach started in #1677. --- qutebrowser/browser/commands.py | 7 +++-- qutebrowser/misc/editor.py | 40 ++++++++++++++++++--------- tests/end2end/features/editor.feature | 1 + tests/unit/misc/test_editor.py | 19 +++++++++++++ 4 files changed, 51 insertions(+), 16 deletions(-) diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index b84bde7a8..fb447d6a9 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -53,7 +53,6 @@ class CommandDispatcher: cmdutils.register() decorators are run, currentWidget() will return None. Attributes: - _editor: The ExternalEditor object. _win_id: The window ID the CommandDispatcher is associated with. _tabbed_browser: The TabbedBrowser used. """ @@ -1639,7 +1638,7 @@ class CommandDispatcher: ed = editor.ExternalEditor(watch=True, parent=self._tabbed_browser) ed.file_updated.connect(functools.partial( - self.on_file_updated, elem)) + self.on_file_updated, ed, elem)) ed.editing_finished.connect(lambda: mainwindow.raise_window( objreg.last_focused_window(), alert=False)) ed.edit(text, caret_position) @@ -1654,7 +1653,7 @@ class CommandDispatcher: tab = self._current_widget() tab.elements.find_focused(self._open_editor_cb) - def on_file_updated(self, elem, text): + def on_file_updated(self, ed, elem, text): """Write the editor text into the form field and clean up tempfile. Callback for GUIProcess when the edited text was updated. @@ -1667,7 +1666,9 @@ class CommandDispatcher: elem.set_value(text) except webelem.OrphanedError as e: message.error('Edited element vanished') + ed.backup() except webelem.Error as e: + ed.backup() raise cmdexc.CommandError(str(e)) @cmdutils.register(instance='command-dispatcher', maxsplit=0, diff --git a/qutebrowser/misc/editor.py b/qutebrowser/misc/editor.py index 154660001..d9024000b 100644 --- a/qutebrowser/misc/editor.py +++ b/qutebrowser/misc/editor.py @@ -42,6 +42,7 @@ class ExternalEditor(QObject): _proc: The GUIProcess of the editor. _watcher: A QFileSystemWatcher to watch the edited file for changes. Only set if watch=True. + _content: The last-saved text of the editor. Signals: file_updated: The text in the edited file was updated. @@ -112,19 +113,7 @@ class ExternalEditor(QObject): if self._filename is not None: raise ValueError("Already editing a file!") try: - # Close while the external process is running, as otherwise systems - # with exclusive write access (e.g. Windows) may fail to update - # the file from the external editor, see - # https://github.com/qutebrowser/qutebrowser/issues/1767 - with tempfile.NamedTemporaryFile( - # pylint: disable=bad-continuation - mode='w', prefix='qutebrowser-editor-', - encoding=config.val.editor.encoding, - delete=False) as fobj: - # pylint: enable=bad-continuation - if text: - fobj.write(text) - self._filename = fobj.name + self._filename = self._create_tempfile(text) except OSError as e: message.error("Failed to create initial file: {}".format(e)) return @@ -134,6 +123,31 @@ class ExternalEditor(QObject): line, column = self._calc_line_and_column(text, caret_position) self._start_editor(line=line, column=column) + def backup(self): + """Create a backup if the content has changed from the original.""" + if not self._content: + return + try: + fname = self._create_tempfile(self._content) + message.info('Editor backup at {}'.format(fname)) + except OSError as e: + message.error('Failed to create editor backup: {}'.format(e)) + + def _create_tempfile(self, text): + # Close while the external process is running, as otherwise systems + # with exclusive write access (e.g. Windows) may fail to update + # the file from the external editor, see + # https://github.com/qutebrowser/qutebrowser/issues/1767 + with tempfile.NamedTemporaryFile( + # pylint: disable=bad-continuation + mode='w', prefix='qutebrowser-editor-', + encoding=config.val.editor.encoding, + delete=False) as fobj: + # pylint: enable=bad-continuation + if text: + fobj.write(text) + return fobj.name + @pyqtSlot(str) def _on_file_changed(self, path): try: diff --git a/tests/end2end/features/editor.feature b/tests/end2end/features/editor.feature index 391e88749..33535856c 100644 --- a/tests/end2end/features/editor.feature +++ b/tests/end2end/features/editor.feature @@ -128,6 +128,7 @@ Feature: Opening external editors And I run :tab-close And I kill the waiting editor Then the error "Edited element vanished" should be shown + And the message "Editor backup at *" should be shown # Could not get signals working on Windows @posix diff --git a/tests/unit/misc/test_editor.py b/tests/unit/misc/test_editor.py index 8cf778d89..caa0b42cf 100644 --- a/tests/unit/misc/test_editor.py +++ b/tests/unit/misc/test_editor.py @@ -157,6 +157,25 @@ class TestFileHandling: with pytest.raises(ValueError): editor.edit("") + def test_backup(self, qtbot, message_mock): + editor = editormod.ExternalEditor(watch=True) + editor.edit('foo') + with qtbot.wait_signal(editor.file_updated) as blocker: + _update_file(editor._filename, 'bar') + + editor.backup() + + msg = message_mock.getmsg(usertypes.MessageLevel.info) + prefix = 'Editor backup at ' + assert msg.text.startswith(prefix) + fname = msg.text[len(prefix):] + + with qtbot.wait_signal(editor.editing_finished) as blocker: + editor._proc.finished.emit(0, QProcess.NormalExit) + + with open(fname, 'r') as f: + assert f.read() == 'bar' + @pytest.mark.parametrize('initial_text, edited_text', [ ('', 'Hello'), From 55c24cad9a96079a140c3268cce8677c5836b977 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 12 Mar 2018 17:10:16 +0100 Subject: [PATCH 020/223] Update setuptools from 38.5.1 to 38.5.2 --- misc/requirements/requirements-pip.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-pip.txt b/misc/requirements/requirements-pip.txt index fb4a1a933..4784f27df 100644 --- a/misc/requirements/requirements-pip.txt +++ b/misc/requirements/requirements-pip.txt @@ -3,6 +3,6 @@ appdirs==1.4.3 packaging==17.1 pyparsing==2.2.0 -setuptools==38.5.1 +setuptools==38.5.2 six==1.11.0 wheel==0.30.0 From 3855d49821b261011f5d0499f854dacbc3d0dd92 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 12 Mar 2018 17:10:18 +0100 Subject: [PATCH 021/223] Update hypothesis from 3.48.0 to 3.49.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 53d33c04f..f467c8c9e 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -11,7 +11,7 @@ fields==5.0.0 Flask==0.12.2 glob2==0.6 hunter==2.0.2 -hypothesis==3.48.0 +hypothesis==3.49.0 itsdangerous==0.24 # Jinja2==2.10 Mako==1.0.7 From 01aa1f755de9804f551f54bc1ce2cde7803c62b4 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 12 Mar 2018 17:10:19 +0100 Subject: [PATCH 022/223] Update pytest from 3.4.1 to 3.4.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 f467c8c9e..409ad4e33 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.2 py-cpuinfo==3.3.0 -pytest==3.4.1 +pytest==3.4.2 pytest-bdd==2.20.0 pytest-benchmark==3.1.1 pytest-cov==2.5.1 From 80843c0b538a9597389e0db4b14d5548b0e57b90 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 13 Mar 2018 07:39:04 +0100 Subject: [PATCH 023/223] Update changelog --- doc/changelog.asciidoc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index 3d10e6ea2..7e93255ef 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -21,6 +21,8 @@ v1.3.0 (unreleased) Changed ~~~~~~~ +- The file dialog for downloads now has basic tab completion based on the + entered text. - `:version` now shows OS information for POSIX OS other than Linux/macOS. v1.2.1 (unreleased) From dcd6bcd2f494b32bfb2d8d7af918f43e4edc8081 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 13 Mar 2018 08:47:41 +0100 Subject: [PATCH 024/223] Apply changes from PR review --- qutebrowser/mainwindow/tabbedbrowser.py | 2 +- tests/helpers/stubs.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py index 7ffc7eb8e..9ee87a874 100644 --- a/qutebrowser/mainwindow/tabbedbrowser.py +++ b/qutebrowser/mainwindow/tabbedbrowser.py @@ -111,7 +111,7 @@ class TabbedBrowser(QWidget): def __init__(self, *, win_id, private, parent=None): super().__init__(parent) - self.widget = tabwidget.TabWidget(win_id, parent) + self.widget = tabwidget.TabWidget(win_id, parent=self) self._win_id = win_id self._tab_insert_idx_left = 0 self._tab_insert_idx_right = -1 diff --git a/tests/helpers/stubs.py b/tests/helpers/stubs.py index 6fa42cfef..b5e30bb0b 100644 --- a/tests/helpers/stubs.py +++ b/tests/helpers/stubs.py @@ -472,6 +472,7 @@ class SessionManagerStub: def list_sessions(self): return self.sessions + class TabbedBrowserStub(QObject): """Stub for the tabbed-browser object.""" @@ -494,6 +495,7 @@ class TabbedBrowserStub(QObject): def openurl(self, url, *, newtab): self.opened_url = url + class TabWidgetStub(QObject): """Stub for the tab-widget object.""" @@ -539,6 +541,7 @@ class TabWidgetStub(QObject): return None return self.tabs[idx - 1] + class ApplicationStub(QObject): """Stub to insert as the app object in objreg.""" From 1c9598d2c00257ea82fda211f3e734bcc3e76524 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 13 Mar 2018 09:46:09 +0100 Subject: [PATCH 025/223] Don't emit predicted_navigation with invalid URLs Fixes #3706 --- qutebrowser/browser/webengine/webenginetab.py | 5 ++++- qutebrowser/browser/webkit/webkittab.py | 4 +++- qutebrowser/config/websettings.py | 3 ++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index f1857327b..6206a7774 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -706,7 +706,9 @@ class WebEngineTab(browsertab.AbstractTab): self._widget.shutdown() def reload(self, *, force=False): - self.predicted_navigation.emit(self.url()) + if self.url().isValid(): + self.predicted_navigation.emit(self.url()) + if force: action = QWebEnginePage.ReloadAndBypassCache else: @@ -931,6 +933,7 @@ class WebEngineTab(browsertab.AbstractTab): @pyqtSlot(QUrl) def _on_predicted_navigation(self, url): """If we know we're going to visit an URL soon, change the settings.""" + qtutils.ensure_valid(url) self.settings.update_for_url(url) @pyqtSlot(usertypes.NavigationRequest) diff --git a/qutebrowser/browser/webkit/webkittab.py b/qutebrowser/browser/webkit/webkittab.py index 422645e61..934fdb3c3 100644 --- a/qutebrowser/browser/webkit/webkittab.py +++ b/qutebrowser/browser/webkit/webkittab.py @@ -701,7 +701,9 @@ class WebKitTab(browsertab.AbstractTab): self._widget.shutdown() def reload(self, *, force=False): - self.predicted_navigation.emit(self.url()) + if self.url().isValid(): + self.predicted_navigation.emit(self.url()) + if force: action = QWebPage.ReloadAndBypassCache else: diff --git a/qutebrowser/config/websettings.py b/qutebrowser/config/websettings.py index c069f7d56..cfb53e658 100644 --- a/qutebrowser/config/websettings.py +++ b/qutebrowser/config/websettings.py @@ -22,7 +22,7 @@ from PyQt5.QtGui import QFont from qutebrowser.config import config, configutils -from qutebrowser.utils import log, usertypes, urlmatch +from qutebrowser.utils import log, usertypes, urlmatch, qtutils from qutebrowser.misc import objects UNSET = object() @@ -141,6 +141,7 @@ class AbstractSettings: Return: A set of settings which actually changed. """ + qtutils.ensure_valid(url) changed_settings = set() for values in config.instance: if not values.opt.supports_pattern: From a7b6d179d40b9eeb854df6773de9f4a637d2b5f4 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 13 Mar 2018 09:51:03 +0100 Subject: [PATCH 026/223] Update changelog --- doc/changelog.asciidoc | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index 7e93255ef..3d52a648d 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -40,6 +40,7 @@ Fixed ("Could not find QtWebEngineProcess"). - Unbinding keys which were bound in older qutebrowser versions now doesn't crash anymore. +- Fixed a crash when reloading a page which wasn't fully loaded with v1.2.0 v1.2.0 ------ From 27966c94a60b744592a6038351778589d9cc640d Mon Sep 17 00:00:00 2001 From: Ryan Roden-Corrent Date: Tue, 13 Mar 2018 07:31:48 -0400 Subject: [PATCH 027/223] Fix up editor backup patch. - Use qutebrowser-editor-backup as the backup file prefix - Consistently use message.error instead of cmdexc - Improve test coverage for the backup function - Fix lint errors in the unit test code --- qutebrowser/browser/commands.py | 2 +- qutebrowser/misc/editor.py | 9 +++++---- tests/unit/misc/test_editor.py | 25 ++++++++++++++++++++++--- 3 files changed, 28 insertions(+), 8 deletions(-) diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index fb447d6a9..0a30c7135 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -1668,8 +1668,8 @@ class CommandDispatcher: message.error('Edited element vanished') ed.backup() except webelem.Error as e: + message.error(str(e)) ed.backup() - raise cmdexc.CommandError(str(e)) @cmdutils.register(instance='command-dispatcher', maxsplit=0, scope='window') diff --git a/qutebrowser/misc/editor.py b/qutebrowser/misc/editor.py index d9024000b..473f67c3e 100644 --- a/qutebrowser/misc/editor.py +++ b/qutebrowser/misc/editor.py @@ -113,7 +113,7 @@ class ExternalEditor(QObject): if self._filename is not None: raise ValueError("Already editing a file!") try: - self._filename = self._create_tempfile(text) + self._filename = self._create_tempfile(text, 'qutebrowser-editor-') except OSError as e: message.error("Failed to create initial file: {}".format(e)) return @@ -128,19 +128,20 @@ class ExternalEditor(QObject): if not self._content: return try: - fname = self._create_tempfile(self._content) + fname = self._create_tempfile(self._content, + 'qutebrowser-editor-backup-') message.info('Editor backup at {}'.format(fname)) except OSError as e: message.error('Failed to create editor backup: {}'.format(e)) - def _create_tempfile(self, text): + def _create_tempfile(self, text, prefix): # Close while the external process is running, as otherwise systems # with exclusive write access (e.g. Windows) may fail to update # the file from the external editor, see # https://github.com/qutebrowser/qutebrowser/issues/1767 with tempfile.NamedTemporaryFile( # pylint: disable=bad-continuation - mode='w', prefix='qutebrowser-editor-', + mode='w', prefix=prefix, encoding=config.val.editor.encoding, delete=False) as fobj: # pylint: enable=bad-continuation diff --git a/tests/unit/misc/test_editor.py b/tests/unit/misc/test_editor.py index caa0b42cf..095f00ef2 100644 --- a/tests/unit/misc/test_editor.py +++ b/tests/unit/misc/test_editor.py @@ -160,7 +160,7 @@ class TestFileHandling: def test_backup(self, qtbot, message_mock): editor = editormod.ExternalEditor(watch=True) editor.edit('foo') - with qtbot.wait_signal(editor.file_updated) as blocker: + with qtbot.wait_signal(editor.file_updated): _update_file(editor._filename, 'bar') editor.backup() @@ -170,12 +170,31 @@ class TestFileHandling: assert msg.text.startswith(prefix) fname = msg.text[len(prefix):] - with qtbot.wait_signal(editor.editing_finished) as blocker: + with qtbot.wait_signal(editor.editing_finished): editor._proc.finished.emit(0, QProcess.NormalExit) - with open(fname, 'r') as f: + with open(fname, 'r', encoding='utf-8') as f: assert f.read() == 'bar' + def test_backup_no_content(self, qtbot, message_mock): + editor = editormod.ExternalEditor(watch=True) + editor.edit('foo') + editor.backup() + # content has not changed, so no backup should be created + assert not message_mock.messages + + def test_backup(self, qtbot, message_mock, mocker): + editor = editormod.ExternalEditor(watch=True) + editor.edit('foo') + with qtbot.wait_signal(editor.file_updated): + _update_file(editor._filename, 'bar') + + mocker.patch('tempfile.NamedTemporaryFile', side_effect=OSError) + editor.backup() + + msg = message_mock.getmsg(usertypes.MessageLevel.error) + assert msg.text.startswith('Failed to create editor backup:') + @pytest.mark.parametrize('initial_text, edited_text', [ ('', 'Hello'), From 73517f0a51f6f64bc9c2cf7c8508b1e7f12d7185 Mon Sep 17 00:00:00 2001 From: Ryan Roden-Corrent Date: Tue, 13 Mar 2018 08:50:34 -0400 Subject: [PATCH 028/223] Fix test_backup_error. - Need caplog at level error - Rename test to be unique --- tests/unit/misc/test_editor.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/unit/misc/test_editor.py b/tests/unit/misc/test_editor.py index 095f00ef2..94021484a 100644 --- a/tests/unit/misc/test_editor.py +++ b/tests/unit/misc/test_editor.py @@ -183,14 +183,15 @@ class TestFileHandling: # content has not changed, so no backup should be created assert not message_mock.messages - def test_backup(self, qtbot, message_mock, mocker): + def test_backup_error(self, qtbot, message_mock, mocker, caplog): editor = editormod.ExternalEditor(watch=True) editor.edit('foo') with qtbot.wait_signal(editor.file_updated): _update_file(editor._filename, 'bar') mocker.patch('tempfile.NamedTemporaryFile', side_effect=OSError) - editor.backup() + with caplog.at_level(logging.ERROR): + editor.backup() msg = message_mock.getmsg(usertypes.MessageLevel.error) assert msg.text.startswith('Failed to create editor backup:') From b88ac51d25da043ca431b2cc12a353f34bce06f7 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 13 Mar 2018 14:10:22 +0100 Subject: [PATCH 029/223] Fall back to non-keypad keys without any keypad bindings Fixes #3701 --- qutebrowser/keyinput/basekeyparser.py | 10 +++++ qutebrowser/keyinput/keyutils.py | 6 +++ tests/unit/keyinput/test_basekeyparser.py | 45 +++++++++++++++++++++-- tests/unit/keyinput/test_keyutils.py | 9 +++++ 4 files changed, 67 insertions(+), 3 deletions(-) diff --git a/qutebrowser/keyinput/basekeyparser.py b/qutebrowser/keyinput/basekeyparser.py index 1582a5485..014b16f80 100644 --- a/qutebrowser/keyinput/basekeyparser.py +++ b/qutebrowser/keyinput/basekeyparser.py @@ -147,10 +147,18 @@ class BaseKeyParser(QObject): return QKeySequence.NoMatch # First, try a straightforward match + self._debug_log("Trying simple match") match, binding = self._match_key(sequence) + # Then try without optional modifiers + if match == QKeySequence.NoMatch: + self._debug_log("Trying match without modifiers") + sequence = sequence.strip_modifiers() + match, binding = self._match_key(sequence) + # If that doesn't match, try a key_mapping if match == QKeySequence.NoMatch: + self._debug_log("Trying match with key_mappings") mapped = sequence.with_mappings(config.val.bindings.key_mappings) if sequence != mapped: self._debug_log("Mapped {} -> {}".format( @@ -159,10 +167,12 @@ class BaseKeyParser(QObject): sequence = mapped # If that doesn't match either, try treating it as count. + txt = str(sequence[-1]) # To account for sequences changed above. if (match == QKeySequence.NoMatch and txt.isdigit() and self._supports_count and not (not self._count and txt == '0')): + self._debug_log("Trying match as count") assert len(txt) == 1, txt if not dry_run: self._count += txt diff --git a/qutebrowser/keyinput/keyutils.py b/qutebrowser/keyinput/keyutils.py index a56efeab8..d0a17eca8 100644 --- a/qutebrowser/keyinput/keyutils.py +++ b/qutebrowser/keyinput/keyutils.py @@ -510,6 +510,12 @@ class KeySequence: return self.__class__(*keys) + def strip_modifiers(self): + """Strip optional modifiers from keys.""" + modifiers = Qt.KeypadModifier + keys = [key & ~modifiers for key in self._iter_keys()] + return self.__class__(*keys) + def with_mappings(self, mappings): """Get a new KeySequence with the given mappings applied.""" keys = [] diff --git a/tests/unit/keyinput/test_basekeyparser.py b/tests/unit/keyinput/test_basekeyparser.py index 6465db875..e4837783a 100644 --- a/tests/unit/keyinput/test_basekeyparser.py +++ b/tests/unit/keyinput/test_basekeyparser.py @@ -198,13 +198,34 @@ class TestHandle: keyparser.execute.assert_called_with('message-info ba', None) assert not keyparser._sequence - @pytest.mark.parametrize('key, number', [(Qt.Key_0, 0), (Qt.Key_1, 1)]) - def test_number_press(self, handle_text, keyparser, key, number): - handle_text(key) + @pytest.mark.parametrize('key, modifiers, number', [ + (Qt.Key_0, Qt.NoModifier, 0), + (Qt.Key_1, Qt.NoModifier, 1), + (Qt.Key_1, Qt.KeypadModifier, 1), + ]) + def test_number_press(self, fake_keyevent, keyparser, + key, modifiers, number): + keyparser.handle(fake_keyevent(key, modifiers)) command = 'message-info {}'.format(number) keyparser.execute.assert_called_once_with(command, None) assert not keyparser._sequence + @pytest.mark.parametrize('modifiers, text', [ + (Qt.NoModifier, '2'), + (Qt.KeypadModifier, 'num-2'), + ]) + def test_number_press_keypad(self, fake_keyevent, keyparser, config_stub, + modifiers, text): + """Make sure a binding overrides the 2 binding.""" + config_stub.val.bindings.commands = {'normal': { + '2': 'message-info 2', + '': 'message-info num-2'}} + keyparser._read_config('normal') + keyparser.handle(fake_keyevent(Qt.Key_2, modifiers)) + command = 'message-info {}'.format(text) + keyparser.execute.assert_called_once_with(command, None) + assert not keyparser._sequence + def test_umlauts(self, handle_text, keyparser, config_stub): config_stub.val.bindings.commands = {'normal': {'ü': 'message-info ü'}} keyparser._read_config('normal') @@ -215,6 +236,15 @@ class TestHandle: handle_text(Qt.Key_X) keyparser.execute.assert_called_once_with('message-info a', None) + def test_mapping_keypad(self, config_stub, fake_keyevent, keyparser): + """Make sure falling back to non-numpad keys works with mappings.""" + config_stub.val.bindings.commands = {'normal': {'a': 'nop'}} + config_stub.val.bindings.key_mappings = {'1': 'a'} + keyparser._read_config('normal') + + keyparser.handle(fake_keyevent(Qt.Key_1, Qt.KeypadModifier)) + keyparser.execute.assert_called_once_with('nop', None) + def test_binding_and_mapping(self, config_stub, handle_text, keyparser): """with a conflicting binding/mapping, the binding should win.""" handle_text(Qt.Key_B) @@ -296,6 +326,15 @@ class TestCount: assert sig1.args == ('4',) assert sig2.args == ('42',) + def test_numpad(self, fake_keyevent, keyparser): + """Make sure we can enter a count via numpad.""" + for key, modifiers in [(Qt.Key_4, Qt.KeypadModifier), + (Qt.Key_2, Qt.KeypadModifier), + (Qt.Key_B, Qt.NoModifier), + (Qt.Key_A, Qt.NoModifier)]: + keyparser.handle(fake_keyevent(key, modifiers)) + keyparser.execute.assert_called_once_with('message-info ba', 42) + def test_clear_keystring(qtbot, keyparser): """Test that the keystring is cleared and the signal is emitted.""" diff --git a/tests/unit/keyinput/test_keyutils.py b/tests/unit/keyinput/test_keyutils.py index 0bc78ca12..92e9292ef 100644 --- a/tests/unit/keyinput/test_keyutils.py +++ b/tests/unit/keyinput/test_keyutils.py @@ -377,6 +377,15 @@ class TestKeySequence: with pytest.raises(keyutils.KeyParseError): seq.append_event(event) + def test_strip_modifiers(self): + seq = keyutils.KeySequence(Qt.Key_0, + Qt.Key_1 | Qt.KeypadModifier, + Qt.Key_A | Qt.ControlModifier) + expected = keyutils.KeySequence(Qt.Key_0, + Qt.Key_1, + Qt.Key_A | Qt.ControlModifier) + assert seq.strip_modifiers() == expected + def test_with_mappings(self): seq = keyutils.KeySequence.parse('foobar') mappings = {keyutils.KeySequence('b'): keyutils.KeySequence('t')} From 8b9c6ccee2399ce41f013f35c64ab75cf296ea07 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 13 Mar 2018 14:37:29 +0100 Subject: [PATCH 030/223] Split up BaseKeyParser.handle into functions --- qutebrowser/keyinput/basekeyparser.py | 71 +++++++++++++++------------ 1 file changed, 40 insertions(+), 31 deletions(-) diff --git a/qutebrowser/keyinput/basekeyparser.py b/qutebrowser/keyinput/basekeyparser.py index 014b16f80..f0f2c6f28 100644 --- a/qutebrowser/keyinput/basekeyparser.py +++ b/qutebrowser/keyinput/basekeyparser.py @@ -108,11 +108,43 @@ class BaseKeyParser(QObject): assert not isinstance(seq, str), seq match = sequence.matches(seq) if match == QKeySequence.ExactMatch: - return (match, cmd) + return match, cmd elif match == QKeySequence.PartialMatch: result = QKeySequence.PartialMatch - return (result, None) + return result, None + + def _match_without_modifiers(self, sequence): + """Try to match a key with optional modifiers stripped.""" + self._debug_log("Trying match without modifiers") + sequence = sequence.strip_modifiers() + match, binding = self._match_key(sequence) + return match, binding, sequence + + def _match_key_mapping(self, sequence): + """Try to match a key in bindings.key_mappings.""" + self._debug_log("Trying match with key_mappings") + mapped = sequence.with_mappings(config.val.bindings.key_mappings) + if sequence != mapped: + self._debug_log("Mapped {} -> {}".format( + sequence, mapped)) + match, binding = self._match_key(mapped) + sequence = mapped + return match, binding, sequence + return QKeySequence.NoMatch, None, sequence + + def _match_count(self, sequence, dry_run): + """Try to match a key as count.""" + txt = str(sequence[-1]) # To account for sequences changed above. + if (txt.isdigit() and self._supports_count and + not (not self._count and txt == '0')): + self._debug_log("Trying match as count") + assert len(txt) == 1, txt + if not dry_run: + self._count += txt + self.keystring_updated.emit(self._count + str(self._sequence)) + return True + return False def handle(self, e, *, dry_run=False): """Handle a new keypress. @@ -146,38 +178,15 @@ class BaseKeyParser(QObject): self.clear_keystring() return QKeySequence.NoMatch - # First, try a straightforward match - self._debug_log("Trying simple match") match, binding = self._match_key(sequence) - - # Then try without optional modifiers if match == QKeySequence.NoMatch: - self._debug_log("Trying match without modifiers") - sequence = sequence.strip_modifiers() - match, binding = self._match_key(sequence) - - # If that doesn't match, try a key_mapping + match, binding, sequence = self._match_without_modifiers(sequence) if match == QKeySequence.NoMatch: - self._debug_log("Trying match with key_mappings") - mapped = sequence.with_mappings(config.val.bindings.key_mappings) - if sequence != mapped: - self._debug_log("Mapped {} -> {}".format( - sequence, mapped)) - match, binding = self._match_key(mapped) - sequence = mapped - - # If that doesn't match either, try treating it as count. - txt = str(sequence[-1]) # To account for sequences changed above. - if (match == QKeySequence.NoMatch and - txt.isdigit() and - self._supports_count and - not (not self._count and txt == '0')): - self._debug_log("Trying match as count") - assert len(txt) == 1, txt - if not dry_run: - self._count += txt - self.keystring_updated.emit(self._count + str(self._sequence)) - return QKeySequence.ExactMatch + match, binding, sequence = self._match_key_mapping(sequence) + if match == QKeySequence.NoMatch: + was_count = self._match_count(sequence, dry_run) + if was_count: + return QKeySequence.ExactMatch if dry_run: return match From 8e01353a944395ee74bb216347e967a8d32b0843 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 13 Mar 2018 14:41:40 +0100 Subject: [PATCH 031/223] Update changelog --- doc/changelog.asciidoc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index 3d52a648d..da0671ed2 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -41,6 +41,8 @@ Fixed - Unbinding keys which were bound in older qutebrowser versions now doesn't crash anymore. - Fixed a crash when reloading a page which wasn't fully loaded with v1.2.0 +- Keys on the numeric keypad now fall back to the same bindings without `Num+` + if no `Num+` binding was found. v1.2.0 ------ From a6e94cf30cdca42ab93ac7801d2f044248880d01 Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Tue, 13 Mar 2018 18:54:08 -0400 Subject: [PATCH 032/223] Fix hinting in frames on qt5.9 with input ranges --- qutebrowser/javascript/webelem.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/qutebrowser/javascript/webelem.js b/qutebrowser/javascript/webelem.js index eb6ce2790..f7ab0f636 100644 --- a/qutebrowser/javascript/webelem.js +++ b/qutebrowser/javascript/webelem.js @@ -74,9 +74,8 @@ window._qutebrowser.webelem = (function() { try { return elem.selectionStart; } catch (err) { - if (err instanceof (frame - ? frame.DOMException - : DOMException) && + if ((err instanceof DOMException || + (frame && err instanceof frame.DOMException)) && err.name === "InvalidStateError") { // nothing to do, caret_position is already null } else { From 35beff98a94213f725a0a568e4d2a81d2b43c926 Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Tue, 13 Mar 2018 19:11:36 -0400 Subject: [PATCH 033/223] Add test for #3711 --- tests/end2end/data/hints/issue3711.html | 13 +++++++++++++ tests/end2end/data/hints/issue3711_frame.html | 11 +++++++++++ tests/end2end/features/hints.feature | 5 +++++ 3 files changed, 29 insertions(+) create mode 100644 tests/end2end/data/hints/issue3711.html create mode 100644 tests/end2end/data/hints/issue3711_frame.html diff --git a/tests/end2end/data/hints/issue3711.html b/tests/end2end/data/hints/issue3711.html new file mode 100644 index 000000000..6abceccc2 --- /dev/null +++ b/tests/end2end/data/hints/issue3711.html @@ -0,0 +1,13 @@ + + + + Issue 3711 + + + + + + diff --git a/tests/end2end/data/hints/issue3711_frame.html b/tests/end2end/data/hints/issue3711_frame.html new file mode 100644 index 000000000..37c5e5b71 --- /dev/null +++ b/tests/end2end/data/hints/issue3711_frame.html @@ -0,0 +1,11 @@ + + + + + + Issue 3771 Parent Frame + + + + + diff --git a/tests/end2end/features/hints.feature b/tests/end2end/features/hints.feature index a2ac468d0..a1c4d0bde 100644 --- a/tests/end2end/features/hints.feature +++ b/tests/end2end/features/hints.feature @@ -249,6 +249,11 @@ Feature: Using hints And I hint with args "all current" and follow a Then no crash should happen + Scenario: No error when hinting ranged input in frames + When I open data/hints/issue3711_frame.html + And I hint with args "all current" and follow a + Then no crash should happen + ### hints.auto_follow.timeout @not_mac @flaky From 7278b7c2e5e9c343da9b5ce6ac8458daef95e166 Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Tue, 13 Mar 2018 22:25:15 -0400 Subject: [PATCH 034/223] Improve wording of documentation --- qutebrowser/config/configdata.yml | 2 +- qutebrowser/mainwindow/tabwidget.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml index adbcdfa90..a561c6283 100644 --- a/qutebrowser/config/configdata.yml +++ b/qutebrowser/config/configdata.yml @@ -1413,7 +1413,7 @@ tabs.min_width: minval: -1 maxval: maxint desc: >- - Minimum width (in pixels) of tabs (-1 to use min text contents size for min width). + Minimum width (in pixels) of tabs (-1 for the default minimum size behavior). This setting only applies when tabs are horizontal. diff --git a/qutebrowser/mainwindow/tabwidget.py b/qutebrowser/mainwindow/tabwidget.py index 5274ee768..7faf99df6 100644 --- a/qutebrowser/mainwindow/tabwidget.py +++ b/qutebrowser/mainwindow/tabwidget.py @@ -479,8 +479,8 @@ class TabBar(QTabBar): Args: index: The index of the tab to get a size hint for. ellipsis: Whether to use ellipsis to calculate width - instead of the tab's text. - Forced to false for pinned tabs. + instead of the tab's text. + Forced to False for pinned tabs. Return: A QSize of the smallest tab size we can make. """ From 64530375abadacfd4fad6704cebf95b8b0cce280 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 14 Mar 2018 07:31:50 +0100 Subject: [PATCH 035/223] Update changelog --- doc/changelog.asciidoc | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index da0671ed2..78d507848 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -43,6 +43,7 @@ Fixed - Fixed a crash when reloading a page which wasn't fully loaded with v1.2.0 - Keys on the numeric keypad now fall back to the same bindings without `Num+` if no `Num+` binding was found. +- Fixed hinting on some pages with Qt < 5.10. v1.2.0 ------ From 2563ecf6d8518f632b370228c98742c6b71e1174 Mon Sep 17 00:00:00 2001 From: Jimmy Date: Wed, 14 Mar 2018 19:48:46 +1300 Subject: [PATCH 036/223] Greasemonkey: add FAQ entry. The most common questsions regarding greasemonkey support on IRC are "how do I use it" and "why doesn't my script work", hopefully this can be a starting point for most people experiencing issues. --- doc/faq.asciidoc | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/doc/faq.asciidoc b/doc/faq.asciidoc index 9b3f210ea..1c06d690a 100644 --- a/doc/faq.asciidoc +++ b/doc/faq.asciidoc @@ -212,6 +212,35 @@ Why takes it longer to open an URL in qutebrowser than in chromium?:: qutebrowser if it is not running already. Also check if you want to use webengine as backend in line 17 and change it to your needs. + +How do I make qutebrowser use greasemonkey scripts?:: + There is currently no UI elements to handle managing greasemonkey scripts. + All management of what scripts are installed or disabled is done in the + filesystem by you. qutebrowser reads all files that have an extension of + `.js` from the `/greasemonkey/` folder and attempts to load them. + Where `` is the qutebrowser data directory shown in the `Paths` + section of the page displayed by `:version`. If you want to disable a + script just rename it, for example, to have `.disabled` on the end, after + the `.js` extension. To reload scripts from that directory run the command + `:greasemonkey-reload`. ++ +Troubleshooting: to check that your script is being loaded when +`:greasemonkey-reload` runs you can start qutebrowser with the `--debug` +argument and check the messages on the program's standard output with the +`greasemonkey` facility. If there are javascript errors with your script you +may also see messages with the `js` facility. ++ +Note that there are some missing features which you may run into: + +. Some scripts expect `GM_xmlhttpRequest` to ignore Cross Origin Resource + Sharing restrictions, this is currently not supported so scripts making + requests to third party sites will often fail to function correctly. +. If your backend is a QtWebEngine version 5.8, 5.9 or 5.10 regular expressions + are not supported in `@include` or `@exclude` rules. If your script uses them + you can re-write them to use glob expressions or convert them to `@match` + rules. See https://wiki.greasespot.net/Metadata_Block[the wiki] for more info. +. Any greasemonkey API function to do with adding UI elements is not currently + supported. That means context menu extentensions and background pages. == Troubleshooting From fac0f66e52c189344c8d5c27e0018c5909cda7f9 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 14 Mar 2018 07:50:41 +0100 Subject: [PATCH 037/223] Insert qutebrowser scripts on DocumentCreation and DocumentReady In #3521, the injection point was changed to DocumentReady as a fix for https://bugreports.qt.io/browse/QTBUG-66011 / #3490. However, that prevents e.g. using hints before a page is fully loaded, which can be annoying on a mobile connection. Instead, just run the scripts twice, which won't hurt and makes sure they're available. --- qutebrowser/browser/webengine/webenginetab.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index 6206a7774..c21d29fef 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -640,17 +640,21 @@ class WebEngineTab(browsertab.AbstractTab): utils.read_file('javascript/webelem.js'), utils.read_file('javascript/caret.js'), ]) + scripts = self._widget.page().scripts() + script = QWebEngineScript() - # We can't use DocumentCreation here as WORKAROUND for - # https://bugreports.qt.io/browse/QTBUG-66011 - script.setInjectionPoint(QWebEngineScript.DocumentReady) + script.setInjectionPoint(QWebEngineScript.DocumentCreation) script.setSourceCode(js_code) - - page = self._widget.page() script.setWorldId(QWebEngineScript.ApplicationWorld) - # FIXME:qtwebengine what about runsOnSubFrames? - page.scripts().insert(script) + scripts.insert(script) + + # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-66011 + script2 = QWebEngineScript() + script2.setInjectionPoint(QWebEngineScript.DocumentReady) + script2.setSourceCode(js_code) + script2.setWorldId(QWebEngineScript.ApplicationWorld) + scripts.insert(script2) def _install_event_filter(self): self._widget.focusProxy().installEventFilter(self._mouse_event_filter) From 102b2be361d1f7040a49a2f56bcf914ff9c5253d Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 14 Mar 2018 07:56:00 +0100 Subject: [PATCH 038/223] Update changelog --- doc/changelog.asciidoc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index 78d507848..45b2c1b31 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -25,6 +25,11 @@ Changed entered text. - `:version` now shows OS information for POSIX OS other than Linux/macOS. +Fixed +~~~~~ + +- Using hints before a page is fully loaded is now possible again. + v1.2.1 (unreleased) ------------------- From 0d212650057960c5397a368bd8455d2cf5fd2a0d Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 14 Mar 2018 08:07:38 +0100 Subject: [PATCH 039/223] Update changelog --- doc/changelog.asciidoc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index 45b2c1b31..605d2af6f 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -24,6 +24,8 @@ Changed - The file dialog for downloads now has basic tab completion based on the entered text. - `:version` now shows OS information for POSIX OS other than Linux/macOS. +- When there's an error inserting the text from an external editor, a backup + file is now saved. Fixed ~~~~~ From e5edc0f940985797c33eba288021b582f2db97c7 Mon Sep 17 00:00:00 2001 From: Jimmy Date: Wed, 14 Mar 2018 20:19:51 +1300 Subject: [PATCH 040/223] fixup! Greasemonkey: add FAQ entry. Change the wording and the `--debug` suggestion. Add comma after "this is currently not supported", I was tempted to add a semicolon before it to but I resisted. Added a "then" after QtWebEngine version list, not a comma. --- doc/faq.asciidoc | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/doc/faq.asciidoc b/doc/faq.asciidoc index 1c06d690a..5fd36d67b 100644 --- a/doc/faq.asciidoc +++ b/doc/faq.asciidoc @@ -225,20 +225,22 @@ How do I make qutebrowser use greasemonkey scripts?:: `:greasemonkey-reload`. + Troubleshooting: to check that your script is being loaded when -`:greasemonkey-reload` runs you can start qutebrowser with the `--debug` -argument and check the messages on the program's standard output with the -`greasemonkey` facility. If there are javascript errors with your script you -may also see messages with the `js` facility. +`:greasemonkey-reload` runs you can start qutebrowser with the arguments +`--debug --logfilter greasemonkey,js` and check the messages on the +program's standard output for errors parsing or loading your script. +You may also see javascript errors if your script is expecting an environment +that we fail to provide. + Note that there are some missing features which you may run into: . Some scripts expect `GM_xmlhttpRequest` to ignore Cross Origin Resource - Sharing restrictions, this is currently not supported so scripts making + Sharing restrictions, this is currently not supported, so scripts making requests to third party sites will often fail to function correctly. -. If your backend is a QtWebEngine version 5.8, 5.9 or 5.10 regular expressions - are not supported in `@include` or `@exclude` rules. If your script uses them - you can re-write them to use glob expressions or convert them to `@match` - rules. See https://wiki.greasespot.net/Metadata_Block[the wiki] for more info. +. If your backend is a QtWebEngine version 5.8, 5.9 or 5.10 then regular + expressions are not supported in `@include` or `@exclude` rules. If your + script uses them you can re-write them to use glob expressions or convert + them to `@match` rules. + See https://wiki.greasespot.net/Metadata_Block[the wiki] for more info. . Any greasemonkey API function to do with adding UI elements is not currently supported. That means context menu extentensions and background pages. From 84bae210abaa4e7219dc050fd9843c496ff0320a Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 14 Mar 2018 09:06:04 +0100 Subject: [PATCH 041/223] Fix wrong number in cheatsheet --- doc/img/cheatsheet-big.png | Bin 1100960 -> 1100823 bytes doc/img/cheatsheet-small.png | Bin 47524 -> 47520 bytes misc/cheatsheet.svg | 4 ++-- 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/img/cheatsheet-big.png b/doc/img/cheatsheet-big.png index bc4b3e0534ee484535759c6d21ddec9cbf155bd7..57a0c444802a398020b0ebdf6e900d5307e72826 100644 GIT binary patch delta 167349 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 ^o_vlYZgl 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±=1&Z~<9VzFN*F)L6h?4vPE90v6C8lW-ZOcFmX8ie#t zjW2j;o^`t)Qi}^oi~cYj?0fK?wq5}}0r~mp!?%`&goF^D=VKTNks1tgj5~E>sI|z7 zSFdYVZ=g5~vT|*COJUg!{{PFP9|W<4L3=Vf{{Kct6OzB)+bhE};x(Bw?TF z0Zd4@O@S|AikD`MQpjG4+zQ}0I#5oC`&2uEcc+J`?Ym;I4m$9ZH<|~-k0X9@9;IO3 zEK=EHfN#;K3=C8UJD_M?&(8i5WQCb-_&05xas`P(y%|o3C7t%DJzLShba-H|Lk;?; za=ccSkNf{%T}>X|TE3WN_3+#-9v+IP1I%2$cfe%g&H>IpF!J{}ZHvy$?sBjxtw9Ya3d-2MRod6tno-TZ7!GW!M zuQAFHiEe{K`-Sg6h^tFrL|uKoHM%nk3|XWj9Vk0H)5X8ti;mxHrEpi1Uk<2YZfvlC zlm8ICy>ti>UFTv78#@3zt*7QSc7}q|(26LU?XWPGQ?}2!69o!u*FIX3?Jx!*HH%>9 zyH##92R`x;Fn3suUZ&0s9$C*%3HBK}LWS6v*kQ{?(*t$)3dUKK&(jtSG%CF0O^ff@ zbKB@DIGXLK7oSAnD<4#!8cB;EHEKh2boukIW30msb%_BbE^JR_I|@d9v>Q*VQ=T*K zK9%RYcmdbpShik1U>94XJ8PN!TK^!^0ydd)G%mw|tOBUybdfA?cm4xUlU%B}_#inP zh`4;wL+A|N<0fyL3VV@r)B{YAoU3JOvp}I;HsSJo#fLZ#sFpZk+ww5`UGtb7h!hqV zZ%!x%KKmBgb=&Dd1o>Q8V-DBtfSboJ>?z~m;u`z3jQC=rlW~8Jp~3Zwk#_+Wo#hGf z`%INl-933KJjtB+1rO2jN#|sfHLo@G$O%MTde8$wmcu+4AX=av>Vl?nC?q zvn5qL9!(7O!;bc zG{*UoJj+6JPc(OQMB_c?1F8!ewL-<(j!tiO5j;~7S7Q)Sc57$oB>jxJD%gAA6ln97 zHD&^4pZjp=ex`0{t#5f`bXx3Y?!sG;mT-N=VB}B;qlw>>18Q;9H++SM02vQL!M)23 zsmk_(I(mxXj?RxCW5NZsuM>WqUxR|C0=Y2NEj~#PtHDRWbQPt<6oz6w&y-;G1 zZ3VAN3k*{FMlcIW4p#@Dvsj9J9X9p{j2IWrx;-)w{k|(e)i7uSIn4Rti%ryOUf%kt zAK$-=o{dKPI7p&uUCbL|8=XC+mZJ3-bwbc+_8(X}-Rs85bC#~V{B3099Yl`1Rj1*H zpy>hUo-Jqjx_&jVJ8ZZV% z#ynt1`FL*P=q%%oAYN=88;}5lw62-_LC_?LybZ0kXd(Xne~-FmcUi#b6Og!x^P|Po zhYwd#TXoDgJ)n-Ri8Dc|?iA6;M%1{oH#hFNMt%bDf_*p_g`kl_>MSxiHdX^PZ&y*- zQU->1gQp+~irWXMP3$ z%G>F>{qmJ7Rv1?p#N%U}3*RCtN@?iS_v3jIwsj$H%@rUxg+NvsrJyOvbh^YIBTJR0 zpCD7L^LU8DqN1T7vIc?s6F@$tpTbK*&xt$nsP3bNe{q|@ga5GwR`ISsw12}JU`#W4 zsEWgEkbsmr$w(>ICGNo06L%MoF7j+BfFO0vC@9@R(V_#AJ#N z#0r|$Czks=o#E^D@0LC%sX$V&q2qZ_O;N}#l^;G%lOx??V!ST01D~=1u7ci4D63)FIn~G z_3O8TM;|Qjl-VGxuhH~I@CnXg`E)ZH2Mp*^x(IiE!5(@6GJ~tQYDLu@VExYs>VdOH zPiW5;7H%!XlFBo6Rd8hr-)&-?mH)tyK3;S)KVChyZt7o>B1l@QURHOjh*b5WsdBT~ zo5Eassu^N0;w4rslp^4*xKr<8!>Gjt&`CMYHo=3X?c1{iy{uCtcZ5t-l-)2iy=8U4 zk9B`ch*rj3;YIw3f=RZx9RL@@Sw|a|E{gs>=dFmhzMjVob#DtZ8)!mz&p&XDE$1^E zGBn$k^P%)2)_Nw&Cchn9y7I#3B1(6sN)24BuAW`(37YKo(N9`Yx%8E&XDoaQ`lX{) z1mBk@BEpWb6gHlJ@!_)2Pee^Z_QnhBY(!#qT>8OtMIPI;7hIo$SWlM|hKBLP^SMD-=rZFZ3WLcH5n54uHjq2%kJq?d2 zqZdCJtt8I3ljWU{QN;#GZ+Yv1YqaIXZmd_o$#WoLvHSIF-|$_h^>)go0h|AdmPVQi zLUumN$gFy3Ln~bKKpUNrv4Mljki4C(aq6#tUoH$BBpt3*$e1y~0}& z$*}3qyhJ>X^^XDL{dSbVp+m7{nASwSy^_GLdhA#~4=1EIr?j-f&KsEZ9RtDe=iqTB zK3*YaZXr%Z?3__lRGx0e8>vY}4=AW^jkDsSz832-8FH9JQFwr4<0#SI@ z;|U~|2gfn5E@)ytaM1I2hjrMPctPnA{Udwua;OeRzxb<|ZzPGE^d2Zc4=2#`T-5A4 zv&KPhMexPH%%Ye&cIi(N`31353gZ*AhY4%4a!;ssFEkz5D7kd)2adO_cIXV1>5GVQ zMA(QgkBpI_p~X|b$JgeQIT~I8LCCw4ZRK_|IC$%UWK5H<(=j-$#H+TV>HnfeTeh|B za9>Hqp17W~TJnKb1`B8KPOPAPUQ{$Wz3RFADqn9!IeD*$Py0&TXSF{3@B}dHT0=}?R!e<(TmLTbd$po@|1Fl%Iyj{evO;hvVm1gOP%ioI;;1a|ypfY=br7ZndiGO2#8X%&)m+3&V%^-YA z`g?n=;u3#frw?GR=e-0Ll9A+HOhE5)+)m)>*G5Rk$cm?l#{e_8-}eu#^69v#O)=NIYTiqPDh$8CNH)0l@Sv>u`b z`1jxVd-Bhvm&Mn-ecMwug~(>nY{OXB{Gdg52RkaR=6QS%gYLyyYAAlr?RZ%ru*Khi z@$&Jx#E{AKAk{(fzyDt;kg#3L7`&<4Xz}xy8$X2cNA0UuJ}v2*K2KoQSa)Aad&1+S z0B~s_>^Jeg1HlZqubXWUPW}TBCIl3?5R^|^6Frn42{R;>qHE4WUZ=?a$wLauNAK~& z^~RTf`DbpLO-9`adUe1`w=SK8(^5M~K$e}O4}eEK2E>yKNFX1e)6{%3knn${7P>|L zvWcq7$~=6)$MDVjZhIWPf>PBVe1zG349k{{)oG(ML*OKvl^z3#6jfKJ)X8&)p6MNU z3w%;VO4DyYuR)AlCo zADt&zq3&=WjtbzRwGuLE{ee4)nuBanbv{kGf3zw;O7n!=mEe+K}UIZ z?VjhjHp(Rc`R<8ZuMC1D0Zw*Q{MGXtZf#mD@>ax68z2&_c5p}9@j6_5w4pjjt4_U0 z?|p^OuuoQz>A>s(DXGU8jGSe-1Kc>DR6A(kf|gwI1uQJWy!LT^zWIvv!ltWuW%@}y z*N!T(9kh<97cX9PmGd3nW)HBj1JIy8kYo%etoC30#OAbG=v)t3KoJG1D^`h}!3iG7 zH^^u?cLkgEC{2xc*zyiPwSJf|DT=3E7kB7h8VcF4{vYwUSdl2>dPcCK19*N-f%F-W zv2%f-=V2yxuI{>NI;cve3mCioKvP|KJj=bJG81rS(EAje-h2S!n~FDX*iZ%Tni8uz zNJO`H`G0qAn2W%Ru!dgh48&}YNyCP-raY8x=7%+;Q+c+B^+E0n`!o$aI+Sy~$S zEMg!cSecu@A|MITlSIVF*JJeFjt8WipZ#xm#eF~0wAoG;R7EZnoQun3UmcX4&iLv#-O){W!8 z+R2nWb5KkogrADqsZ9OFJBi{s=o~uH3?jom5J21;JtJdc6&>c5A8{%G>$C-c&v zh*Sk$s0#`~XW_PoQ7Q_0WT{)bjEkcVdCo=Q zFrmVzz*4+joPLwoU({D-1eISqw9fiC=je{{vkV;%qP~C2_=btgQlefK!_D&$vm7rr zC&aV^m~&+68JQdv9=cTe4ZKKKT`KY*Io zcU39qip9;*0Gb14htuQ@Ou6&%I0L1xmR{^(GY>u8 zR~WWF6aGN9hL8P4!~MM=rlZ9~7wpn96KN@_cda(a+8|X~j)`-u>Fxw+$R;jy zLeUn^pBF%Gc3rkf?5-%S90$;-%+qM!I6ksVtPgsP3#)|B^@QHLcQ3uPnBZ&Q3vfEZ zj!g-9D*`>=WnzV0Ag;|TeBP%lS?T)=x^32`=t$8}#c!vc38<>|XxQ854WD9Y;GLTh zS-KJ_^G@30aB_%aA+-VEs=Q9yqO^FW&FGc7+FFZckDGQNnaaKbL=f91RJjoyLf?=J zYO`%Q5`UKXe!cPb8i`u2bZZAw;krUzXi_I9-mUF3LU-&-EUy{0?eJ4#HZkzmv-~3l) z8{p_w)=GO`p^jRj11DE@raTepGvK2RGxh1yr*;6zL2D%oU>u`q!kZBSw^7jh{re}O zebG(V@Rl{V`KZ0(1y)Q8s2k0@ksU&D7ZKJBy1@;Or4~WlDMbHo{LcI9US1R(9rC+2 z?lkDpfQD1xt>Gw)30?!X+xlYnUcf&&S2ruxx|TM0XX z-MiKAQn{L#X+1e~F>r zmEMjOXwQRjG~HBTKCYtEIe2IEf`A{ zT&HhgPo2aHUmZp2JhscH`;;T`keL;_jmj`ym>X_W1Hd;(ify|@rW(<4Ih$|H;pX4B?h(1ml0&I?EEd%d+5qs z3^Go*k(jvmui`iEIh3X@(ndUyzI11=*Uo9Kt&m$)H?RIE-(U1C<~f65HYk6T#k1DN z?K-Z~u8E#@GCFxLWS*Bq3-a=sPIuHs&HZ|m&YG>vcmC|YewWZal{p?@=rE>W1EY7k z`SnMd@6b5BzJLGzj1zTyV?hev`QoO#ir?l|GtQp%41xEi$|mAVoivnztTS4-Mu2J4 zZCkcmAt4(izPtzdMJzT~9Bu?o76%-_#60d846yKM>YA=;uWKi={yYi^-qXRp(GRk= znW@N60^{VHd0k_4etvtmU*HzIVZ6vWNhh>09G{XYG>$j*t{+rq=8DKMe4e!b5TsNA z0(R+VSv+#Gz|AXL#)D18mr5&cabc}k1t}?~VDmQVgKX~0CySEVY zDqWKE&rN>#>BWOY^!rCyr8hgm&au(K-VB4hx##r|_$J5;`qrq_*_QwxEPY=Q+Xn-B zrFx92d*>GY=yQGPx4dHwG)>>nBf0hdQR9nOHkEIZg=d;b5`BXb%_7;Hy1PMI+bMG--i^JcXB~} zwIlkl{z)+isY+a!$4vT1_dobYNv!CsfQpV3hQ=3?F{h)kMDr-O%kXVr(?mDw;k}KJ zU|jbJ;#y$Z*;jAG>{pTCYG!uGqc=Jk6k{c)Gg?&sO{SEzv>?W{u5C+5?s$z@Q!fj& z-V%+Yfw&+muZ-cP(9RcZl3UM@N%V7+;M^W8;q&1gKAoeG*dUc(BvK(aiBTPAL2&Mz z*3k3Pa=zX@fZPxGvFYl?{GfoC#O7!X z1>}0RRaG6a_Jn;?|HLZDlqU?!f1sgIzEA>>DT2^{r56A4-os)QTk!MfnMb$ydRG)J z@kigR>;qfY&V14nff0rXbNxrhV0FZ<8gSsNn?25YE28?W#SqOgga^$0domuMP8r^36e)Jfxns)9x(V%A|$mq887YYjOV+vi0xg6V2TZ@Z1ToR~_X zG_oSq5^%8rI%Pbjq$?v|R&vMSD^csep-;YtWuK&dNUzhGa!<-CIqq=YKwlr`xiOE~ zw;av|YM+nZArI|Yz#ouQsOZNSXpagH;ZiDlbYlG30Y?o|>9%7=H{*OGzd$2aNU z61Afb1!}m77tf+0+645!$90lhS=NhNm@so?m(6@xQ8F?>7A6dV?K`FFLTrG9#+?|_ zhqoM5$8p=j@l~T8RoXiz#s;uy8L5~1#l`O&oDRY&_xtllOqSjLLY+C=gY7d_&e9;H zW=DrIuerh$wqi3o<4PBZkJUEcZ2-bM!qsw0QYb;EFUBByy>2d76T8y0Ze#0x&(X;L za2JB&uHU9iqU&%^1B%>VgQq3!R8HD4m@|koojd%e5bXyfMbgb+56T>X!v?z>!mu7E zf&@lR)Y-wL3h-8x#UH$W_w$=OfJ+!q<>fyY)e|)jN>ykA*Gp_1A|q!{aO<09KY_cV z6yEN~C2$A4t*gkap1*EdT-s-+(r#1P(3lg*DY+LqsM3`lH|>J=K(bZt3(jMo~6}=8f zzl*|u9)YFLBCxUDYtN7T2JjD^iy)#m-aj}xnt3%A_|#rS^}&y1@5xi zWADTfn#EY9Z!EndtLw`s=lIhg(0-6Vw(;RFfB-;?l7<95w!9G2`&rJ1f~&;kI_2m0 zgfE|CzpwW!|1e#9ee z&{A#i*fHk;FSd8^E#N&=WS4fu>~j@LvY!{TL~^(!9l^ILbpCPXHw688IOVw*mj}M* zG27wvJo>DkQ||TBJ__As#BOty9rAU*=tYGk!$Ol_`?=al4@+RXVVJS)gtHN%Ld3Bx z55+GvBzV7c!OSfmoUbbjb1oR=3H)Ii+toiR=ZCjoRb?Lh7$fVmA0}*fpNME#O2)(rU7!lMT9mpL>)3HtLhQ zy|*HI&!kd;uC6WvdTH>{Zam^cWEI}JRcU7qk&Nv{{)#Y+AN;gNMpliX0$iU>_pLyb zlM09xwS&WOFf<=W8HEW_{_|KKT;AU0as0!w^?_QrNP};A1@yISN6=R{WNV*fI}2rW zJHaGNC<4TOc#X+>g zB@l9X2-cX1PB-SFJMUdWr`>@!WHFW#C*vU$V@8%+7MKu$1R4-=_Naxy#Ygm7#o zC*ZyRa`AH+MS;IMQah2ALzPy5ko7Y{^O>g+cpbz{fnmk^;bYbTq>ZL1z#y*8;CFMO zfF&Lj=vC<}H{yvb@VX80tbfz8Fu$9JVMWqr{(VdV4BI%rHtFy;Za()~gmdTi-|d}! z>Nh1qwrS>?EAI*1nzWbU@q@AHZWRqQaV1SncGB{H@Pz!Jp@nMERkv*Mg2QQL z2yeZO`K3$hPm%L8&_B<67>D7>SGs)y+}3>MggcuodRWz#q*c)s=3-vF{^ zj?swFcrz@@y9!yDnHBI$Xr^$%B0`F^lGN1g;FsP2aOTB!jZjnJ!KHO>5sH~|3U72| z+SS$dY;!6rNO((V&fst0bx4kB+(9XDeQF^KY*V6w!=y2>xR@Ix7$5K@YOc?+L_ws3 zH79o?(lRn`fStvG=`B5tG%-Z%G2hkDzy>$LP?fEa9D%JZJ#p!$pflVzKVL*R`&bjH zvQ?70{ADRk4tqGjtR4DXRa3J9C63~$QyfrXl-p#%$rJXeDc!a#05PWM5a4ZjOYy&!wQee)Rx0(^oB>apSQVs!%^UEsJ6YFn_B~v+*Y900 zd!LB4nfwqiA!TWu#j5K8DzN(I|9AuZ&N6S{414j2l9DO-00pk`(bN!G31>U-Vu;JGy4~loR*Thc71 z8EI#2ZC^DuYNohm1VK@D?A|_BIC`%xR)-uT=QwZ&hH&alF)uiuuWWPRq5L$utt=p* z2ymhta*C#b0W%FO_XP{5=h)0WB(A$~Kf{K|w*kIv*}Z!e)Fm?$)Z%HIUD!k#d2#CI zJvCL;o}nRsp!a>KXPDzZuXDlHn4FuTQW942PL<&0|_y_i>ub92+wP|P!>8R zVm->x;dzmm9JEZ=*3=|V`FougkvVYSueQ$d@q@reFQU41c>kk@tT+C=Ud%r$7`|OS z*c}?Gn)1w<`oWLuy9~y#M8bn<&oxkbF2#qtWXEIGAqY)3h=o-+D< z>n$+}5I8i5ndk6t7#k$pdz|AOlxTuH#n_F9hlddSu{t&FuR~q@3Xj9powMYN+giu3 zK^j$tH5M(pyAHQv?fc6toYY<>*n+Va82^PsMB^{$U5bdcTX<^|5zyfq@KMb&XjpOZ{g0Ty7O-;Brqe7gczpXLpx#JQ_Wc1BzaGQ&h)|oo9`5D@@g4ap?>v^WlGV3Jce$+z zEjN_-y|KInD)Ds-+tD7kKe65LE8ZaAiWLJEeHmH%nZZo}SYdx3fH_qkU^Zxim7ut> ztt`#3lfgm#IyPB+8J#`fHRhF}#+QwUcMBtD9RN8W5t+hdZ%GhL6ZyaEzut z!f2)k0vVd`uFdYtA+&0|ATq>6b|{f0?HN<&4Jkuz+lDJBR*OCpcK)RF)o&~?yf2c| zgK?8>la@-f!uc6zct8+su3VvLEtlid?_lQj3Y+bk zD}mx(1nFA$Pn^r=oiQIYmpYhDAi`gRlQOajpcmCey`e*C7s1hgYky@mrGQD1Gf!M* z=Wl$tzJhgsZbeLGbT=xD?KDw&sBdyZse;*yZIUJQfZ~#J+#~WHN6RyEazfCCd-`YT6UjpvP~^^9&n!i5UbU2(j3}#WV-d0GsRAA+ihU_OtP8TIMedc*JIs zTnMm+*kZuuXc^y9d}GLb%2DkmBq#eIK9JSAFp(~?kO!Nj~GjB`pA4RtuY?xk_z)$cWA#xxNyI5>=7}7)xBu>34p6gJWwS%~fLTZHq z6Y&zkP$J@4FpNX6V~6;eqx{sRR?7y%s@VJ)?$8!!!w9pjV)GWhsRPF_Pg}>B{+0tS zldRmU`3=*8kuDm?K}@rMlne+Z0aOeqw(TWy2uIa{>d*jm!4;~p>X_BUugfz4aAAj z)+N%66$a_L{z;h!rk2(`NFxIkh*S;C~?9 zi9vD9uM(zh)5+En0r9@uZ@vdfu4>CRxnCKbMyOXzNPLPI^A_q z^#PdbzpTG9`xl#f(5CODK)Xzg&8**3I_5hvEZ83dOzq1Py7q~tz==Ra#eQ$aQTJ(b z7txU72(9UFU#4SxR!>Pc&Zk8giHg{RAsh3Bt6~Ym#fL^OA}sSUcqk3xAigyvB;wA0K|OUpX*mHZer0E_$ftx0>RQ4% zXraHXQCbhT@7l2=t&-ccOecz;tapbPJi6Laz`h5={uB>v+zViugA9VGcXV5=jPq;9 z0RfRM$hAuA$`+%LIW=~ufogTWyVPjpz70BjRc}Rw9Ea2%uh1Xc9c~RfI2x>DpH0J7 z5FZ>1?H%t>5Hvga;EZYQXhqewb6o9iWF*-`u=ng+=;vV2Loynw88ZtD>NRr`XfE49 zj?#N&oyv{DI||jv-{QFaXzeRUG=!2kYh9iI!_gIdwZUON@OkOZy>E}kzSL-W7g*(f zDRcG1dbk%ZL&hL}!DLGkBz$ZN-vRa|N^QG@%#8rW&cSg5;SGAK^svoarVOq+I_kKv2s(O3FLwwY>gh?C{G;&Zp^@$^ ztIYP=Kg-u8SbV%Z>p$0gI|tTnSPQziE8=arPu+$ry{NPc#(2hb#;EAYh7s7GBDkTW$t;i= zLd=I=tD%SAO(Mt-ljwvNtS)vBz3gSOP31bw9^$UN|GNTuK~eL3PJoQR0GC1_3tzqBfW z@wi+#9K2DUAqvhSncR{~zdx-<^ETps7{#wkelI$mBvjJRKoQishjzbc)6D0q-OCM3 zAhMB#{B1uiZzyA<)5%tf`~|(9N+@&dF;U;B2cKPD^aAGLGTQ! z@EK&XVoT53ul#WCxvs?DTQIBdt$6I$W@SOcbuSHdGSauc)bZXBR>9L_{80C*i-MW- zW2{=sYf4?A+_KzFijk3#>_;R_KyGQrfD=EhEG_>lm;)xm_>~dc{{W*#a!ah@-VWFa z45unk$cLB05r9WYY8W#+TT>_pr3B!mR8Cuy&{4Y`KMJnuD zB`k?QjdVm4+re(t6ME;)nk?DRY3jU)D(hF@SPBZU5>7+t-MdR^fKzTZ$qk`mY`%cD zDKd56D$%{*H=f`L$6L}cHh%PhBNHiv*zaw^x-q<$+IU$L%Qu!zQy{pGZiQ~jfg4<< zNap>kHd*1(IFzXJC|poLx$KRSA1{mSJ;VY8Nyz^J%b<%(yaH{mt^thB+2HoNLM|d& z6hbHmnJ?NwcG!Bu4;>=ef`B;?MiIn{0PeVPZF@A8HrCFDL6Gz~^49)UeCsjz#U6^) z#Fh^*66rhe&`(Cb*}rNt^~gvO{5C9vJX@{^_A%j+LLwac%bkp{*6oIMprw>z+uGW{ zw#4EbJiI5>=EjlneNmtS2ehgUdt`t6`%SGMaY)C#5ZtwEN}s=uk4u%QtEeP&VywK{ zal+}_r&*H?lCRIhV&H&0$1>FUwH%VKh4Vq4qppoM;tIwbokk@bUOE9c?scIkQ#Ce9 zen78;&3>U0jJHs>wrO9!yobkaA9fUp+ur0ZP1u1wYkRS&1B__Ym&Mu=cEdms9NkGJ zPXxW+n(=dsJYb5cvURneZ75oONkDiwVV7Y$M!H^5h*eLviLEQ?glEjL@m<2gOK(zT zvxE0IS8%Tui)_L^grd6QO*^%K7@#>Ooheo$9sv}E+Wc;ygwQ$O+53Dn`7)l(Zl0GJ zR$^^j<2XnjJ?IW2rKP}!Lxv#5r5)gX2u^!Y+zTJ^1>w>hKwi}TOPgRzKC|4mrV@|R zfp5@Udau~B!ehFdMFN9@My|Xu-IQ8=PvgxID?1J44@PksQFpe3Di(F%>Z58)w1GOa zGsKM!-R8baUBT!Jh^7@B`tV(_pJ&H8tun-mC#7NQLT$I-teeLpoq>;8nVbg7=;4Xe z8xaYED?=U&P=eR?0%cf~bvR_xO@v0cz-10RbH_d%?PacsvRN(yK%$EP{oDDB?Qm!XtoCS=S2!5g;BDSYt{KuiI^PT`2ICx*bLhk43(6e~LB8 z>_7yyp9j~U(lt7py6e%{1EKkf80TqWXbu@!pV%}rQgc6+5fl3cso!{~d+k)1;)R|y zW3LbTbV=Zgz?Xbrj?&}5lGhVZ8hV%$Kd+87DMwC7ui8$*dJ^cG)U+964q8s*Y>vA^ z(l_C5;QM>$PN7;@^z)Fm2&Ex>v0ql7f4feHuihXbG4W-V!C%l1U*5W|VDstOo|wHh z*I+BYcJ108l*+}?Oi{O(MicOz3V1+!CiYxUA{J9#IevW(t#^W#G#{Q-GQu)N9tEl= zvhyqU88BvT!DK5mQDoum>({Tz?$pdRTpKs05A4^DxGc{xY0DTY*gYkcX;k8>oAL*? zJt$cNt_s^jUQ|@q)=i}Mu`iyY#k5UG4tP5hz}8z5@(uwz!zVo_XE(m;Wz2Fp1=`W> zzrVVmp`GsA+aZP}XhMcgAUSP*euT&OA_FzGwU(XwWfD>m5uk+*2@Nn<{27k?|JL50s ztd{95Y%#dWP}ewB=OjM#26VHfEsD_^l*)1B_5idJb{~;@lT~eX6*6vIIrFGnmpeqr z$0kicKttpY|Ay+&oS?y>p=PfwvxdQjKequE14mXT;P|H3%*6HT+Uvi*GmN6$^t$gj zE6?x7R9gNF8K0+gB9uV&pS9N68Ic55`UYkZ+?E29Pcy3}_&=PWbRC_X7?$_%$+!PzV|5A-I zPY_qBv-LAR2Om!L+#fMw6GfmKx;jP|H zJwgFd0>G7fp_AGt-HUxm{z&SRsXz3VL0*?oABfT^84s`cujoVyW!_3R=wWMS_M`FJ z>DjIO`Ihp^xXS?u57&Jabth`_ysKgQ77Z;P(Re9jkT1v0MJjFrm?%lbKVr8}56{Z6}!mN)~bNVRC zd+-`*7Vp(3%h7epo=a9ONE>EIoASP)cF# zv1_Abvs~dU9DT@Q*OWr!tQpkVEw1j!_L&V7Q1#tHYOPfXnCueZ9u*)Jf`!d9u)~E6 z|D50pIWiq%bFjn2J|Star3X&YY}F{ z8!1QQFPZPVW81b~WHg$Ew?5`%J;HZ5EC8WC(DDFcnOW~eM{kN%*QhN>H3Ce`jJkR6 zc^)g>*IG-%spVehzX8?YYRSrtX=rMyfSrn|si_yvd=Cm*loPT9C3eSrJif>^eqHT! zG~3IU7=9|8Jh=f{04$0nix<#JXS&bfLd%($@FL4P{~TtA>Oj(m4vF$I+|Y!0bNDf6 z^#wM^>Pj7;7K#(#KgqO{OM~E(TD<55$Z9LhyBKJ2Q23h9%$@06Vpn(dw1GhdcFzE| z6%Z7Z$8r>~E2U~ytz4PfDZ!&Z@$K6!s4IxI3awpU&Fg&d_K6*ga0SI84+#TauS18W zu(%T13!PoP+wnex7~KYIIjHmCQ9FEn!_eFjpTjG37a&%n=PX(@m+qr*A=;ZCiw)IJ z;trXJA&xknR%!5MdyyG%bAd~Fzim=%)I?Y2-Hc(+huAQLU>gh)1_A5=Q^|n@j;+wg z+iC{-Oi=v%`AQrX>)zI~Y#LhQJ{u)kfAXvwwB?jMR%J2@dCYwGezE{2IkAk2d6$l8 zOPox}wq-2I0$`h;$8LWk+w;V9Ska?2%n;!P>SKHWl0V`A1V#q_*isCrKk|}M?a$$Zz;4mp+R`a4SV`CH_?PL9*AmJRcq5Zu!BQ4!kvRU#GWA2xG{%fk6}5hptW6d z)dL*sC}}4llwh=>TlqKAH2WCS77R~G$VFTIOk5pnlkUuv0H2vu17NZK2KqX zVCGzRirNO_9r5*VULIjWz$P1~XdM-Az$p6;^)Tw1m0rSUHAJp&UOV_AUcE$v%m}(w z^Q{&;_2hlEeV3*3e?w#-Zl5WIsAp39mSR;OWjvIbxjB9dpRXy}MMSI$SpA2eA6U5+ zbNmVfOWND}Vb7Ke3aGP)ofA1XvDt6O2s(A-16@4t+{pj-}`itD? zd#j)7W|&|)x`Cni&_!6Jy1H7D+MU|m4063ZkNH|D#dqN`bQ)=DmW7n=%kpwBX<@(- zw@&5;MBkoA*L6Qy3z@LSer7{8s^E-QI?6!%P--Yw1q6&4C7g_w(V1Oo{t z>%GemxFGpU{B>g6vY2aysa5OfcOvE;@v(&6_io~MTshV^qP!`>^nY{Vk)z7B*d_xu zjrY0mHrO4L%_UyKY>p_5bwz?^J5Q#BWH12&Dyp=@6buNlf3qh-y`vj!ysr6JE!c=s zD-xr{X66{l_j?*0?fX3aQGRLWMlLRM3QVf|ynGdjE#4dllOA2k#OV7;wZ*r3Un#5b zWyA0CHLAsb=oQqxdzWZyY=2sA{+a}qJC5sWXq1m;iB9m*i1CldbO<*2VRYJF;K0vPEwiG(7GkI0KhV+{1)?=a>98)}s*V_6 zl9hzbzk5%gG%qX(U07efICsW)4ZsCPKBbiUp{U#HWcKy%FONCfCH_%sW=CTVwzRgA zVbT0wwYNq8kFEEB=eljfhksUCp&~R<$!w^!Wh+tHltQ+K3Ps_ItW;KnR7A?Ej7qjB z*((u3Mn;nC&3jxu&-4GkpZD|j>ACOwDg3_Uy3Xr7kMlT=GlnDl7pwe|trb5Ku}mYc z2l4{PKr%=dn6OJnJN4WWeV}+F9Dd=dSIUeNsf6Js3cU}2H{Fu=Ibo#n@Tnh{Q2dh( zWOCr$4TqmBoEe_E2DGmQ_$W7b$f&5GrvL=uU!JF=1it+iEfN`;*qG0SS8s4}KGmZn ze6oj-3b!0ZS*g4wFgZC{=0=vo4{GoP^YkW;X{3tUPQTHeP8Rt5&^sZT(tF&?WLMjZ z$|Z*_9O$%;%!BFcj8Cakfby6j%YuILUYNh>rH??*>-{5*o#`EzGA z+7pDIq+boMc6c7Uig#p&d)2Cu{WroNJ}hi&Tklq}BOu|%;t2uF@552@NE)Qz`cf?w zl^4OR!lPMi9309fCX}G16M&0nLrN|#F82L(ajd&7iwwiLV%j$Ay_v%_*kQWA`3pZ0 zQmt#+VTq}uX>$`p+lhm+?8HJ!YN`h11#zI_0^RoeCt${(LLCkaVKlR3$jnTuw(Q)^P_gz3*N4RiMueiu+wukor{ zay2}{2kfF=Rd-EL3RL{px<^K?K~8smlbt0f7R0WVCZ+Thu~lcqD;&tgn44s~Z^a8v z85n4|-$I*wb?J(kxSilT0WhNl-qTo%k@xULimTd#qrM0tLu1O?`sl4vo?5^MzXC<` zEgWDQQFRaDDdL?Q6ennx#22j+WJF>JBe_B#AwiLmcfcoZxB{fI7Q=%PW!?fs)w|kU zSyfex@d_lJd=Tx&1(uH&mK`a3LK#IMSRmuJe%V`CS^46e8h#+kTU%3GOLb^ltXe*e zMQ>tl%>t#}juN#>I9&y?W@8KAsR}}CcT_BBCzC(w_T*ir*(l=RC7SSjby676XA|gC zXl5U@AmlRoKBuA%Hq5cvO~`sY=#$XQS8d;cwtG03)3!^30c;k)Tf)R`4$Wt)zrj&a zQJNp9Oz2-pEuG<-DCII^e?tc57BJ21t|&u+BiH+kW*{)AXXFz1ZidipLt8E2x)F8n za+u-Ar8IQz3(pe?$jl>fRX(6G!}FvZW0qjCs?_@oH6Ek6tEQrfi6D^<4wOSgRDNSs z$*l8LB;rSvRx=CoNmCcXqklZbS&Y_x4(sl)eHV7VzJmD{pw2(Ko)6s>xKl6c7V%n$ z>*>rSP6slVs4b0~CFref`;$vi2c1@c2a_}2Nh$`Cw9uLyuS1grQbN3$3iAi3Gpv_z zqvh_^J8Sy^h6gg~XdyE(U2W~va5bS`y#T{T`{W^+cYLwvea+3y?LF*QZ2!x%e_Qmc zYrz3Sv;~2S`*0Y>v{bbFv)EOLanc93Dm6QO4YSud~MzDWRYvFhxwP8y27W zh!rbmXe^KfyK^akHuL93*yc|M$;ZN%P=|U+ zEa~8p5wbsTDH9WMz0GlWgaz}M8HGUaBLU2K!da=oW(Y4m)j z?WX|*=C)n4su&#&!u=^prndnfplaT`MHPeRj;-SaxM#rTc2zrfA*rt6s>;2G7MKoG zU+a4!PW-@u$gP{`3n-*~=d*6Gn!KY_Rm)Tk=b?0SII_{x80V5IuQ!jzxb8efP`Pu39mJD z!r=?kDucZHMa*^72I9=mJ9o;4O84As`)alZCC`0I?9ZpqpV_j!yT>sq>7klMUd!TW z)Zz~)3OoiT_leesnU)4`hRRBBW*%H@tLz(&j@mOYK6;$6aR_(+H)?TC#o7Snhc+E= z;J%|$p{0osG_$nkpNv@7$Wpb`0H}K}}5so-DF0?_`U-VV#}EsY1`coC7=l=w~`ay&^~)YLX3hq7O*J zuoJ@XL-W$gI|z$7g*)u)?K^jlZC0Tnj)ECY1{5+jl|>x7DedqaWK}Y54b{v`>5Jj!f7D8ulSKR`J8fBWbnkj5hau{tYgTQ0wECdAx`!6_YWtw>oGY|NL_bTi|GbA;k)7Kt$bWoL4Xqmj`B(iVb{Lg$GF|TwPNZ zNXKT^H|k~Q_yzEG8!GDYy|H!zz@~u%Dc@No3Fk%+yK0JtKa4^B%lNa&6b@MIcA{j#;UtGJk1z8=C0Y3W`y*B!5W!7A zeX*}n;)fbIx+#Yu_E@Jf0PamY;@s3NlX&*@si0NcO^heO3}$4A7H`2;K%)pH(K3v$ z-+&7sEd*?t=-24*aAfP4V}*d!D~`hg$UX-ci@-x&f7!T5&%qbLz6GpC&`HVEXv|ag z5Y>84Jh>%qBG|7zNI3@PKUs7>*ny?`oEl)DAyB=r%LX^5ljl%*MTH`cYbGl3VX3#% zOUyXohM5H68s+NZh6>AktuSB%&E7Bh@Wxn*0&?x48{7a>i@_Hp9^PR5))tV-;yTBk zsp9O1l@^9?F#(HAy+pO{_w@1ULViI=q99D+m}6hMbm_0V-=C|m!356~O#_s&F>-An ze_nQQ_yh=#zs&u z@eRb(7}is|%zuyKn^j+W?RcXo34mKvgE5#w3|NOwdY@2^>XZxLg>P za*j7{XhworaUZpW*U#2Gm&m@pzLPBoih)?k_i+wm)C!nETYyw-%*x7ohThXhqyo({ zZ~-thiGZ;GnKTG7W>`66lVPfH-Pjs$Tmf&?ze85RS`N>GIBB5ZZ|mOi!x-f)lvC@Q z@+m6rU67WhCK_V$zL+a#fEu_?YMNB$2H@p7jr9d-MuNz#n+*&rIk*!0!63f~Dyt)> zPCYCff%RW0h1)2_-Vt~CoO1%>G$i`gqF>M_1G=)Fl^wQ>O>9EKQ5qKlT`-I))9$Or z5krLfB{WdXlj}wUTM*KMfB}&7PUPhaoaqKI2XdPF`&2U$l_vNP4{J<;FUrcYiF^iV z^MD7IOxD@X(^nhPhf9K}jiDEijU2&4jX#5+d*tldCxs(<7z<}JiEv}{*hdIpiCXz} zJG(AtWvF>@tqHk6>20U6!+YLRfQVt58IAt_wQjrt)$)q6GSUnd#$FV_#Go99ALlS> zZ|prHxEU%|dZKWPT@%ksY`R${4ardG=+?3?>~B)_ERW zAL%}71O1(3pCPPG6ekT*&v`|=AgU>kjg9rW5)B{pni@7y;5U)10~7;pSK`KaN*7*e zL&ICtAAUTa57L1RHe7okE^P(aoPBdsd>8;NNgWUq^J{!EeO$+)`V9gox?wv)FTh#U z7s=p28%Vkmkyt`20Sczsw#x)ZMkf8$M^^!ssrRj{thi{;(70f|7%}MtWk(4S%~$L2 zAk-<9llIu_d00Ef=lb|8J*uw$^9>m9&rlB76Cq77EZyQ7jA=;8!Ch-b+UC#eo?M6V9zGjfqi4N*e8k^OLtee_^BO;7 zTKkiRmbqcj4a=*rwLP|3cs`+%ya+whdxD-Ehu+(>@7D@??S;AdJuP|ak&7;2PZq3y z%_VH#wd>hDO%SY|`?sl;ThiJ}c@6_aH$$UN^|Oj+NsnLm1P=X|nyt_lmi+pB`QA<7 zRaJ?mm~v}7jmVq4Z;MiICJH&BP}QSQ^Y<-+nEW&)2hUw&jZX1qpyWa*v{eU4fp!(% znJWMru8vJ2UhkSox{~y67?FFq`T2_%qtwFlis_AcBe+FWabit&BpW*Q05AXbJU*1M z1@m(6W6h`cJP&n2k$)RG)X^hHen@<&t`2y+p;7-3){prCl$dR!TA{FAse_@BSMRfU z7ecLQSgZVr49~{33f~S0u)MdA2NdBW7co;-78PL$9F?foZ+?Te5OjEPPOHXg9vXD9 z03*ePTpY6@XSZQji49@NxMVFN`sgt1o=dxZ@1A*UIe5Z6IYU4i_Rb0eHXlH{;a~8g zK#F87U$e`K>n(ajKMu**ojDqj>%3k^PcJMz8E1c;U2RQ`0C375wzSQ6*HZg5x&m(rbzVIq3y2NzXQV@OTn;bdK)nZNB<&&_31{%lN#@Yxw*Is zPGuxc4UdkRHtlTW;C>lFm1km~4m3Ug9sBgD9NO=?)|K8zk#WT5rloFNDOlZFy}ZY< zxS`>JohwWU{2e66;W+Sgot=v_^2kS@IayiH%}e7rc-1iNe3T?oPbP8EdoON7kKE=3 z=rg)$M1=780KF_o3!e>y(+8KavzK7AhBRY|P#N{tKqLL9HVM68SrINr;{p=XFxyLL zW8)@O1KMN9G!!B8+BbN|&rg4dQE7XlCuuwc=xJ&eCU#+m7S9j{?AQS`9jnq*j~z4U zx&|oT>srO`Y1pRPJ(vc39P{#}`k>FUHO1D|DmXO<9%^07FBcUNp#&f#eLew{AbAMn z`@ZY{T7kr>s@M!Q?nx_Ubo40863+GPvd?Qa>aij22=-J*amqd@bO3&!pL&i7tp$?cP@qh+g9U{0 z|GCpf!s67BnPY%@!#59OhHB?=l$E#Oc zp}e%hf%=<3_NrQ%!7p?kXZ8B{MqR)GnR}(xi_WVk9+iMha*JLOX{1+=3(XcW}2se2Qs$ z2!@kChKhbQ(@Fu4k)OD__wm}Wl0iRT51te zOehrLlDQ8(Y>YlQR4M(j64mkOAJpYv}?#m~h{*32WgvfOsB_4@RA? zg2#ty!Ur4dszvWfF9ZrfOp3IudL)M*3m5~NKpN9`nUL3cd3#e!plrAelRo4n(ZCW} zb%c5WV3Q&VTy?A@S)4CFFi9TY8H)lJ<*X7K4#-GTq4ordeOhw-HTW?0?)mwLku>@s z$T2QmO6WiMLpgl&Np!uz^6SDOLP(tI=~bcY4O{Q7*@Kzn7i1}{2Kp7}3bvv}qIJ?Ic=}a*YK0ulF$`;2xRugx4{J zAw~&hSy24JMGd17K9$uIgRJ`3M){iiDrOJDrQXP8Tqkb!Ak3vKShYhbv@A73^O1}^ z5Q`GRX7hu;fE>uXz}?6Q6y#V|#nPzRA@k6V59q5e>-M=wcyn&>2gC?1!w-x}A~_ef1WYn+qhjcs9;JHguYqfbl=*St zRd~5yMtcgU5rZceKuJctL}lbv#^^!9ptuXg@h-M#!_3oNeF)MCln z!yMe)7^_y$l2PEpxj375PQInxm7y&^?4sV0H**2K0EEQu1tBdZG-3(E_grK_A&Z5@ zc{I4e5XstuDg^$n6-0NI`>@7vB4b=}ndvF?|C@W!yf-y3PcFe__s?w<(0cb15*Uuj zKU8ZGzK(1CdVf@KvRYazVRhmT69jLLb$&?QX*Ar!k>1)6^Q|%nM3puPsy(uJ+GJD< zA{erEd8|gneC#GV!Vh16kPoo15+1p}DS`oA?di@n(`CDl2v zURha5C8$Fz$^tu;Bk!Z04z)<9DsgF#1+u>g0g~v zqle(q9gEd+8TPv&UoF5=ORg9i$vmNCKhSWmfyb>bk8&XI8*C6?Y?}^5MaVniOtq8- zcix=1KrVGDC$;_~HU_5bFyP1{)2D%I^)AI6$bJNS3mcw^VZ*1RJ>|TbYGV~Op z+D^7$d>R@W67o#kfCY-8y*I{02tt6z@MZeaK2(YyaUJCE*UC7Dzl#gJ9(9*CZ_>_-CIaMIWEG!&)0crj{G~Da*b8~aa z_KGvlh9C~lh9QiNLq1C{n4 zxef|U>xuosfVGMGx!GBq8*FgG0*yOwLHY*>dk9U^(xFmn2HFM&wSzc8p?1|b(fx~d z9zOd^=7Fu`u!T}x9*`e!*p&2iEyve58%?41$@$5@di6OJd}KS(2-puaP22IY9>EY& z-oE`A_QZ^n7s-iM+^pXQizQ(YQm|@k(7U61uk^M)srqHYg$dL+HArLP3EjE|+iI7_0iz+Qudr$;5qx zr&K9@WH@Ab?&Mf5q}2&J`BTdH>sExNvzu3L1tp7V-O`hfbfSgO)Z2{gqgV16oa#Fq z=66VOj3e(iGc!w(QWr^VN29_?#}OmUN^od|CZ59A24J5zvtAm2#9XAZJC-%kY^{)I zAQb2rM#6nIbSrYWvp+K)R_z`B(0Ay6eSbw?b+z1bw8pFtMes-42z3QMBG>t($$0}3 z+7WO%ZK5dTGzQr1I>tRSmqodW2W*Iim@DDXB7-Ugb+vC%#-Uhn-<2y@!p6)wH&SC5 zjFEwE*t$}zaqb6KaF{=sS=sm*EDS>6OaB9fqNRCm(v(KS&mr19ilKtHv@QltTSK6PA?B~Nt9nDOlzjf|LrCGHt*Ec{jy_gf4!9)w zZ0^HJgNA2E!(uErR~+2>GS$gUDLv zx+|(K5!9iNAbT+eG%6}0UGEETDlUE=MrX;niUMW>`GNQv+eD6y*Vdh?3^=KpqrC>ufU?=qB{V$(7m|r0ej4L$OI(`hko}V!HCk zi5_fziN#RjZ8_JzIKP2~oqZFEj!ls0e8l^r3829fbFGY$3PhHFACXft8!VY1(9gff z+)@-01WoEA^k@@Xy5B86zpLPxGJgoNo>J_Xqj<4`ir4a$RaCxIlvZ5R47uXVWgva- zdG!P6QVAd>Dk-_yvt;f(%CAqS0xlw}DXpsNN#62>S9W0J&@U!*|a=Wpq941 zo8soj6j(DfHtLTbJ9Y;dQXuS9YKMrKh3M(;cyZhhehv<*UsO|5gU6tXzROQM;4F^H zv6Y$+@Y(Xh#~&RTB>WTo^u&%Pco?z$Dv04S$0X+ZRbqDy;20j1M_ zemU>3;);1I2cRGzeA3f!C@Sd4g$n9PaN8^3aQiImGYbafIxBlKLEow$g3=VTl((jx6MDndcg z5POJ7NO(d`t5ecJ1Z}uP(tZIjfb~&}esQ9J9eiuhr=tZXT)`4f)8vOO;I1z|c<`D^ zoWh`PJ{4PlUQrI|Aca#0!yny%p424^;9M3Hy{w}s6!Zgt2VqSh3W=R*$F=w%V?in- zdc=V~P}G8CAvn4fu<2kYjLw-qxdqsKp!g~VNY0)3cl*$NEY>2l?j{|Znwp4wffjM& zHx54WCPVPhaGJrcg@SbgsV28=`-~iOITx23Vi=k12P>bxw%h~AqjBskmFs0IdG!+= zlMXyk5JsLZqLmh1riG3RFhz2q_;c&iy*)iKSoBtaq+zp3NE>J}8~jbp-0Sd*8`=** z8qV?Yl{{A_|C}LS1K@Dfjy*!eWrSb_4J$4(DMsf3&M{sxZnLDGde2>&NIXrjLA}wS zz}raAi#%|B>q|>0x6%8fE74w>S^tSfh&p(^ycal2(vlzTcVIt=V$?1MRtowwhi+rXMXfI-`||vN;VimIsgY4+4AAkM=QA)5IkeT8N1eA zyPk>;ont@GAxM$zlZcRg%j^JDy$qP!x3r}GQ#Zdj&+xCW;wV;Y?j9Slvxce(Q`?yU z{$nddW9zb3L$Sh!*uq33aw5~>yB*0dsJTPzG*bE@weQ_`5lOn6+Hk|?&o6>2K@i`L zX@DRqr66Bq0A5MP$`Tqg@CE_skpXO?e)@nxk9z|Qf+zaP5q+HXE!>%I=if3Mo zH{FkjAktmki3q&Pr$GNm5o(~92NOfWvwEdci3!k%@S6HPI37Cw{5hVUPfJrfQ(RnJ zf^iVjmKaaV2Md&(JV{S3Y?V%Y)ffeD*>HqK`B|K7R9f0GP}!D(`P6f;7-T7>MmyN3 zJ2oofne^`u5}HM(_t2ycC>ww*)^aSNTVN|z`fvJ-s0^&Nq_oszKI(icW=89gY32GC zU&AN>VGAoH>VY{JV?fnA1{R}iT0v^6yV4DwUktUl@Q`gaB1A=m-HMk#Hyo1=YO0vJbaj2w;Wh0sn>#oOrOv zeNwC901K6rmBmz(A-oU*eYtW5ARX75HAyLI^2`|YcpFba{17j&yr~A)P?7SDhR z8ui<~5F$JkY&y&kEu0~m1Ct9E?#9Fj<=mWnrRn_wd~C0QP{gbM{95FaF{ugmEm7$} zCsbSMDK?pns2xD!Hx)p*;^Wkok8ADdA}T*1iGrS(Y`+rgoZbyWk})m4-t_0^iYdXM zZMO;w-6w|JH8NB>s7Pu?*e^nTn*{WseV7q7Fp zX33^5(K~u@G^OH@-Jgg9>E?fOsrH+E3?64!f&>#BGaE3<#hG%HsXHF^ViBqoX^2O(MLfotJEd zi7}L)5y-YhcI~Qvc2{9F4#ot8vU9?hXlAfQgCSisdMX=X}Dp z8fAMwl2=CCZrgTHElP%3Cl<`0IY$8D3V6(}QzVN0T&sLcXnSc$&N z;n&?j1r)?!ZG4Lo*ivl!A59V|5QRkfv?0v+r-s4CK1!$ZiTr3a*TBF)H|(~@5?hI7 z1Atf#8sWo%sd8^73m`4*yzioLDMZReP&Zh}0+^!F&_+1d5CSqM00)_X>Yi4Cg9q25 z?Dz;v17i#o2GaV*wtYR^a_MML#>!VvQy9WTdJVBMaH!%!{)Gb)5t9x>kO14rqC<1f zVysqJMrJ)uEE+9T)eA7_z*}|9K1VkGaj~_v_0#X&GuYv>$dYN%z`5`^szVPgzs>&| zlt+zs7YuGK&;(~|(9a@?2wumB!a{nms;r>B#3acaAb}S3KHdaW#)@);i5)6}lZo1* z&NKZl;xXR9$_{;Xnj2Ql(FhyVdc?y8%%fYrFre@TZg8jRIY#e%n$`!Z(Ue!L`DQ1F zbz3aHd@r&q(#Sx02O`r`k{ZG*4~-P{k;j0s0Y^}scvs}WMo&v4%ifweaN&W{JkJA) zrjsq<>;Co2CfHU;9`q(ZZc%zv`vwN^(F0b=K`X$ntVPfG{v5gyojB}}hyuc)XXO#| z1fx0T+$XfMPyI;Hup2xo{c`7Y(whaCgFQn*nGmTKLFfM8fEDC`u$Y&gpr^z z+A1_ppA@{f+w1nI;(nHb!df@`cRdOl#2ql+xoCgU`A1`8QSstunHL&|x#G5zGxux!l*f5laAoav@k)bj-K*L)y1}dJ0@M?f8>);Gzq2hD(tx z@b7m;%U$FG&rd}86LFZZu<~~!gb0%3qn=>mTlb$B`4DZAE&zZuH&k#>ky$XUU0$@E zns%4zsL3t^V29(|e`?ARKAk9hV~7@M-KzTx`g_NEj61kS-+d?)^fqB#eaC~t&E#qs z_}?{PRcQvnZAv9!yVwuzQ6jDm2~{zt8-@p0NEaq)%0$JL|9tNf<4VdhJB;R zEW#Q@4Mcqa%1bzAcQ}KQbLwM)P~MY`|E_pi;i|lTrn&zo938J=`pEnGX9!u78(Qw+v!WkeXEv*m`&**+bZxcM|f4`l&aLO1OvYdhc z;8Iv}6vyCp_`Ipa*_Ky;M7pD|FWj=X@V^Vy$vq;1AErrn z;FJ|3@5PFvp8EHy|E**0y;$WX*Obrxdjf5ezRd71_=q}K1A!SXfxI{cyjA1mF(4G1 z{4Xq*YV(k4k>Pf%+t1;f++mamyn-413jA=MlVGcGZ#%mdq`lQwsPt*!B zBRb^@To#4@FB!kPtNvuUCqL#NU5r4{<^1c*f-3G}Et z(ApGI^6NI3Q(Mr&WMIShm%%?jfBtkqExF-}Y!%dzpN2FoEqm$xy)|O6Q95_Su~`lv zuF1uVX9_fSu>ZgA|KImSD#U4$@QjN_hDt=T+=IYRcL01RO+B#cZ1<|-Ue7#>I63q^ z^A;YFNb|nEdr1)D19B3{dhfsMy_)RWwjbTyj4;VLIBE%r1X6j_V%-rZdMbgmF>KrR z+JoY_MZUhx042b_^3K!#VDBup{dXPwpS37sMM5fCkpy=@QAC_9fNXdG8RV8c#ZAr< zz?eiui3w^v_V4jb>9oZ{_j*rAZEI7Szlx6!Jj7}iMBvad5t{-G-^l}*ruHEhA(4%xRhPI`?2hv}`YM_kOyXtBzo zEw7C@zvoG|TUsosh_uXkRddp~aqEwI(T<$XPMT0bDNDQ||KEuvGvf??ywy#a-AqT1 zy1jmVDUv38Us(3Df7UG3)kSys@Q%izO7M=eTzocci*7%cRL8t!z9-T0%E zzCM|y{-yW@^YYcN4rJ%tV7k!CIyK9%IKT8`F?~;ueD2ROx|gRJFiDYhQ5>gfZ15C3hL) zw0AzO3KeGx@(<}YlZzht9pOyPP9VvfaXFhwU!M@04QENT>ysC4+n2!XpHZQyW5yxc z=Iifuu>lub+-|RQ59ceoW?=#7FRSxC8qs+RrwnT1)F=OV6jTOQrCsStzB!k_$T-{( zC|GzX8pQs~l6P4$)jFigw=vPJ6UYgBBE#c(ws}Zl+19NE8z@@kL;i_ln+#_$7kQT6 zb8@aOu5en4ebl6o-zQAagOew0y9ehI@()JpIgOcxPWQF+!T!UC)=e$k6pXF>-DJD< z-J`2BZvuC3mTuTJ%e#%Sw!A65z<-_Fr!@~-ABBsUnX<6dN)EK>aosdHWifc{2$$!f z8Ajda_^*Z2j}C7$^B5kh5ED1lFQMN_<;{f``7qH%Q9{H7CDqdnMG9jVVA6ewV3w*UI^jNb#Vz3$3u(y1zY2YywKvMTwi z!*%|9=0>xF!7J8t*#FwZ9P*6+it@b`jGHuT@19zgd`6tv_Kn~Q&m+zDeFA3jwJDTM zXc}C+PH}DH*($+&L`dkarFt1l_H{P?v|a4$Sf^&_IXJT31+1q#-#}CH>w6F?!6KnA zVAgfjLFd&#pGLIawjZS{<=Ed`TvPKpYMAeZ8r|Ps+`GI zk7NuwWh3@?Fhh2EV~(8d zeI|>+8!_E_$u-hy@7u?3d(#Bd>!K)&*8C*B$n^sY^I0*1G(_U9eJz<^?4zDX>oTNU z^!s_!G8`lW@__ib3O5;q6U9TtmCMWf?%s#Kq@+fjP zzqusIZ2RbO`h=4svj^va1DTS`Q!bnQF|Ad(AW@UZb9p$%IbU_N8UMq5TGQ{RM;#`4 zw!y!bR-Y91_Vs0x4<`m1vL^E9=r?UCpS-u`#resp)T;*;hJC0%GJE!%8NL&Ix%{)r zq^o9vC%fXUr3~5ESop7%J@|S^`oP>c@87>q|7MvT{m~m`*IUi~a2K6T1NYge120;N zRh_5f(k`njG`@0+i~GBOSN*Q2=bAB$+pU8auHL-)BYP-qp*=;e5go1M@FkoN+Ha}i zu=+0UcKP1Oh%=wJ(;f=h!T(LYND{l&Wm| z*XABMyya;6o8Fs+7+^!b2Y^2%AS06(U1SpOyaf~xQe#6Kp$FE+-V@GeF&$~lN#o`N zZE0w-R=ObV-FBiAVA@KTC$K0u;oQTE#A>A}Ou|L_F8?n{aZ&-O-1pt!dcM!nzhCvr zK8u)#lu9=9U%Oi9&QLb{IY4h>s8h)7ZmqC@Y>6Rtzww}pn6LLM!=Ryz6dEARDsD#R z!sT&lhmBlW-e-$%_>!IRC$G2KedU(!+11jFG7I{R7|;51G3hH}ch)r-8TmQQ--8Z= z&yO=r3^K&ThITP|Cd*1o&y=eR3J451BiIyyXAeWNw~#qfSf33P?L8m+D68$`WL zWF#c+Mv@fq|Nhph`JT_YX;XJ)L2LGs0)8nUba zHC8IHXs+d;0xa3-wda@(Ws|6%4=m+LL$xy>n!kRNH7#>Zc8d!x=u7a}^G%c2PYle^ z%^;?e08u6T0{MiEGSMpd2ZxY~wb^=`;3EbP50CAC#;+Fdw5V_k3)Ay=QBqd+8{UoA z@%V0K)(&?JeZDON+R#0qvfa&=t{s@ZK>0-?*VeXAZT(-%e#yL zEMA$Z8YpDSEKpEYyF0Ql+UqWTu^XQB3^QXvL!*;R3ya*Tk)K|RC!&@JZTu-M78-a_ zIa2wa+YjUEJhVs|ZLVFD{p2TqEoo+)N$%)do3ddGUTf*(=jx-yy~ADdy06kLy=FVV ziWf~hw{N}S$d_@&foc6guCrS<^JnY{&2qTc5+Wk#TPGysRR@RyQ2^BB@ZY@}zV>CguLlghe5oJGjp`=v25p4m}y_mkb{SFJ{M z8laIMMXCn?QH%oYW4kJojHol2oA&K9SSxbX-rh4R%7{+o<94-6M2QDZ&ZfiQSoeRMe zC7yWz!&0FbySsMZicm2fvAz_9AI@)7s_D|LxldZ_(RR=-PvY15zi(dT%>G&P)KsSp zDhR@3FL@E=n|d z7Z?;Ic=oiO`X6OdtAP*kY0~~|@}>Xapoj^kcfp$UeGQ!#ECVv=?|M6}X?kP5a%POS*g1%z~AFInCspHJIpYI7d5V6`{JM(EvbvsH*yNuOTWt zoOyhJza9wY$)8@$KMo0l1}OU4(veqGlMxsX(^`#%Q3_3YuzqO&Y;rDLI$N+jx0P>Z zA?2-3o2}n=q3mbRe{I{jyTvb6ZF`g4=(@=Exv7mc&sa{TKX;AHxnR3_M?l=>?Ka%o zU9y}PXT}REBHY092}v?8;rhA%`*$}5A3#oC$x6>J|71m+N-@84X(INwz{RBR&fgQh z${&0|onTw!m1MOaSW=&~#jNYfW!mpSuXl8w?`C_W9yLx1r>03&9&dKld>S}5AFs(W z7=?3Dp-)Ixi9akXu)aF*vRV^ zIGE+*9ygw-o#4U7yEP}FCVVmwy1wWWmz1f>h=U)Rn)tD)i*P9XLv+2pV_>`h)UT+U z4d9Lf;FXT3y-nv+NW0nq&~V`PyYzd0JeaUUZ@2Y_HcyGgdtk z{pJ|X=>v8O0bXc7;97w`c3$la?)@Hm)^}3B{nC=&PIKEw+4cC^l@gvPFkIftiqmBF z(G|$mXa%4==!e^S#*56G*Rk)?T)TEehC`t<+=o+rw4*ORXL0=0!6u=@yMmB|`$e8J zrL(ma5Mf4wIQ_nHL*{yUbJnKC9O7tZQG5PQ)q~ zR7O$-Q537p@h25!XJa{qj5`&7ly9@!DuH!y?wn`ijMVyw!dGVuVxF?{xW%e0v)nGF zb9YqQI8g*^qHb1grny4x{hqeCQA@t(##6?g3c%*yLkmOmeVkEDsNZNn-O_taZ3dVM zXH72Lk~hrCfN>AQq0}(nbsa@G&>>HaY&Yx$80K9xV3er4p&&!+^5qoA{7}i$6Zh)D z@)K)I0wdWgLRq;oT+Pt%kz?zV{DhyZe9zs0L#`N_{We!(LQt%KzwaLdH)Zpy;{M;i zD_qv!SPl`SVwzjn`lj?#FN-00`3vB*>uEyWFHT>)5khoeT35VA zA)R$A4GC}Wy?Q6GdWIdaVcL&8{y(P`{$pBQS=oDP|CSg6s(0Y25`s0nYkqWgdLZK| z&}dKd5xxqpAb4*)AX|u>dLxoYO?BQeoUM?Aq>GTahcP=EtcC(0Vjx41rUfhS)<@#) zXo3(eG5~LrOPBmXseYPMc*GpK2LJVkh-q@c_wQ=~ixRtH4IsUSUu}s&%I()n+4%x( z$7zMX;ujZYw*l;f=P)O1jf0!HdUY4Lb%?lw%FBFqN3 zX9|GTg7$$GXdF1}M+C1b-@^EKndB+vbK<3eOf=)!3F42k(R;Y0!ahPaB?bXy=S_rvKe0nbVdLKu%3Q zpRnHgBJ%K7uV$r@b|rIZNfW`8px>sqA7r5>n=+)2w_8rShmD5Sk zzy0sz{ogjve{^6A`_=^Q!e90?%p6r|S_y7V>EkU`kzwW;RvJ%y=BhNF$}=JIpdoX6 z7E1@i56wkrvqas@dFmgDh>|Bl8XahI2+vMVPO9K+?tsTNh-&9x z)IxZKM6dB{n27vzPb+HOZ`Gnxj z-*NEr^z3^(i|446jRu2nM8%t<;Ie_|6Rnv*X2Y!9MvubinBw8|ap(BzNBgwikYNxs zW7O#)dau@!kz+6gJ%>vLEru70N92^8_jk%|IB<=9w@p2&G>R82dsy%9CD{azm^N^w z%1^&fH+6u${ujakv?$2;V2s%kbZM2rSs=Y$BqUvEtlbFMfU3eV59MaV`8`luG+DMo zWH;W7^Zd-4n{!jCG4}u^5+@THgk~QvRf4TBi1etB?`poM6G0++Ok~6zdJxfQOZv5wD5{@vX&s3Bw+Jyw z@%9RlvpIXq!nOrK5=eS-j%I~*W#C($W5<2n|J0Cu-BvqEcNq*JkpI#{YDf9!#~tha z)d8=Xm};;~TH{^N(;z)mC{Sa}N`uL=Mc$rbRV#$btfFt&NeO*0eB#FJrDTTc@8-8` zn;y?UD*#jFqg}(EKbU?b)C?nbX1B{j5UTJ*xlHhbGlUM9FHThxII=)>nA|o)=BFiS zQ7_6?^|$0WrS}s4MTMHtDW-9~8i2-`L~S%}3a~d2LkZg` zDE^RLH4Q!RB496`gye?51FbP0z6#_9ZoUrNoCiXR{^BZ^ogDoCx@mmgq9>ooFfHL+ za`v?yr!1F0Rq>c@?crDFZB0!bFBEc%>D0BNmGR|;KPLIS6el$a6H7M{jrLp@0&*BLibVQ1?Qsi zxHwGHV2ybum19cqC&;pdBokO9E-;L2i{dzo-#9X z6@9aVws!v}GwCuGF`b6zKe(*}JsgyGo@?=};R zAW*@kI|u@{e{Sz;7WNL40;b=n8GllH%#6FtOSOxpv%6> z=)C1V*z~Ai(P%=+(BGf%e)R=8q6h*FfGC$B#85)mI|#9mmK_fFhkr2 znU&>if^4ne{bnp*w{98qa4-PrvD&P^j2zn7>+r^B2g^eHl_5V!&zXKZP`!a6jBcb{ zkdV?zB!>`;c9(7W-+?_dJVL*AttVYr*Jm$xp7ZC=WN%clUtAnkF^|$bZ99~6XIx<; z$hiow6v_j zbEy%%e|!p-q6!H6mqE>nAaMzJMKBc{Ook)C$=w(ypzzm=Vx+D4k3pkxDePN)5$Kx4 z-Umc{(Fajr-$pr)un@PL)lNCj+Ci1Bg_Me@(jK(Gzrp}f%qP?_!=+oI`o?2*lZN4z zVT@M!l7VbwZzu*BPzpLHrOa94LO-&6ad)TO`C?L@nPb=!2<@8}Ht_S`hLK7%BE|l9 zXq2AJUtc+1)v+`xBO_z4$w4qkP~X?5I!%vkt~z3|8Ow;oVXK5g^qeLaWWThLtCNtp zdibuC(}>v|Ci`hgY!rge#!678NauT*MMV>^9n7&2co(sx^5!4~rVrB0Jh|bpYpq0% zMdVbsq_-S@Buk>6eZM9`zw+njh|K@^0nClj;O6<|`CGY&LULaC<%`zmKX?qCg_rOp zJYIKI3Dpzl&&v7fsj0rVE{%jUiNkCu4RXRdaj9H~fmN%v8Zy&}ta0^j%El9iX$LZE zscdX4=jE64)G@e3{sJ53zUrSk6@&h8jHCRmcUCWYIO`x74>h*WF?HnN0Dn)^jv%=+CA5}xg zu#}_z^dc^C2U3a0Lw~@oc?3heV-F0f~|ET0nYE z#-9{a+CV@Y;%~Q_PzzHtLDjWVP>esDyB{84qO&=T^*5m)Am;yoMhoyj?VT^S+EMrd9%o!ZKH2qf7<%{x0@&959-eu6Tz&40tvbR`N=qruHt_GZPum| zEhqK)Qp{MkyB@50pL)_ZR7|!sSoW#Bd~uw(9^;iDX)&EYX^q;+%wv6mW)Yf6Ugy_y z8Snj4S03h}f4uq7smwHi6gp1{-5U+`MIUYzX679OSk?HqWVv}zLpx<@zGmkhy(*ip zFCzu0^|O;ka`ufcBLj0QzF8`q6S~~V_;G?m3q7g%UY#KI)){_u1Hu+Q>u_3`7I#JJOW-}L#js7*RjtV zsFN@uD#5AKkVlJ0h6p4^N>yFGa^a{rA^nW}{Tsbvv#PHH5hk~trxvDPR1M<*ibDn+ z3<2WtWEyzb{*b>bLdp4MpEL%!`>m2xrIlcwgg0aXgf$I_>R{r@ryl;uG`2w@p!wuc zhA1IRCAYv5-Pad2VJUQJ-~^hbB2)B5T1z~#mbgGH;WwCxu@|vB=HR^&GY=ri%p7J2 zg3t%SCvs&MBm}xS*Pzue(O83#Vg_S?{*Ngt($UdT^|<`U16@5m$^ffFo;rVpa7@pkh~)$KfZr5*g{D6F@#h^Z3jX{CG7no!*R<|BUMHY&QnAhN-_B&f38BwE# z^P(WT{QV7jG2hQ%EgjK)2Wz(B_o&Gtzsuy$PeeL?M{S`lKS+_Du z4lGDH_?HD-4%np#5 z@x`l$#`rFtqAUW^GaOVkz`U*TFwd<8)p11tNK(3EE$|#{+W839@bDDv|Av1AVTPmQ zA9M#lKZj&ytX5@1Ev)!NV6(KI!XOyr=Ma4NxET(hhwY$cjG)*sa0 z;s2ZHM{3(qDcx>7zG>9UTH4ILjszcdHv_mjNB^%(4%j`9S#XCMeJ|A2|Z1bj-uieXo{qBBh z;UMl^1B6AiZ2tXG8q6;S0p7%Q^@4zT&I!mG#XWE;-!=Dm`{!->b1_y2aBS6DK+(0& zCLK&|UotiNsny1Nr_+J4sMk*TY;fJqH;I~B;z9Mp-N$M_<1jHB+NU1X4k30Q_5)5R z74mHG&Vn)4&gua2fZSdu%)@CKC;AhV-M%jR$bw>U>@KHWrLJEO8xIwAxyH3z5|qUT zVc=S^MEnRMC_>t8Vk8F7mXIcqR$fACzVL9t=psfVCD|=Q_4LXr8*ah17I^eGT0DLG zko&}3M831EIqy92^*EUEkE9GUtgzoM1~BnRtxg4150&t?8AOWGrY0%^3D)G2>K~Om zVUdt337~9~e=bP_g;yhTW}+T*gcWF*{+>ZVqoQ<_sF!V+|0=QJ0z=^^tCa zw`Q^njtVm{|M-+=4Yg9!WG^1>)%v#83-gmkwR-sM1T0ZT=B`6%tvrWeKn>J`4#i2x zUtcu#;uKKIMyV{`oNSn>`Gy}@3KF7wG?TUOfr#xQzQq`|wC})y@?Wi~rau0!VU!q{ zkwInrKGm3Pa38ba9NuBNXNiBP|EQ<8w^(uFZS~RCb8up5AU4gb@$=pSe*gY@`jXy^ zOT6ITz53GcI)D6-g^(5gKN2$lKR&fbBFv0oG+C+401CeFoS!|F$*#~BuC54~}n^Iguz)?cXSw+G*1vwLv#-ivl>pqIts5%0U)w)+2}>C5A(T-)zA z3K7XvhBBlw7YeCVhB8&=AwoqYii##Y3S|z36p08SGDT%Plnfb8WK)@&&>-{pTlaat zzklBMbIz&FexCcjhIOrLt&ZHdTLR9-ZD}!6GHySAYOPpNzbGerVYIv0tT;SOLfWRY zP)$xQ*v96<)cyN1bMG1@etiBke%`q2OH;#9>&=^G=GuQoUzrHW&}Luwuu<0F%+lO1 zEorlN%?)>Y*yH21M0`?vZia_bW0tq?tl&Af9y?Za7Z>g0CxYu^o5sHPirV~Z{XE|_ zIy&f3g6QlQA@R+dQySui)UW#wd2k5pT$odidi+vJHq5zAqHnPmCk zm!I^Fu+&P35-W^J5;5v2*QyO!jSY#H=Ly4hux!nL+v%x^RX2)pu|nQ}F=O%ZSAV|- z=$WMv+SaPPP#OZDvURNqdZYa93T2{OwN2zHi1y zI^On8wZ?9L?TJ(#7L2s??R;m_s@9SS$z}y>q09~U-hW2;ar!-#DG@u)Ki3 zQ$#MF^u3Q>fE8oboz_tuM~93VwZvYoDx}5_&U0L=&44nE&mb7yz3~5Wc;0O?=lHl*H%pE*ysHwy=mo+pxM2~)YVO2agHQY5j=M*#bE!n2$<|bjPO*GN(w5#znGLJ)Z z_iPOtveA1mRq09v#!E@2s>@h;woJ%{Jn94aeDx^EEmsrAK!EiyMy>9t-3^@dd#7G4 z=PbWdiF@C5Td~N(cOW!`l#fsJ=>}@vb9)fgN{81+h#TV4A@HsZ3q!-h)9~QldVdXc z*P*W10$WgaNjlocB|5$j`<=F}%G9vMG30*}f|2zpOJiKkj2o>3Vp2@{JbNrv6ggv6}PL zG8?orJ3t(%9|3YB360l^f8-5Q2M*jTCV(?32u`Md`lXH~*RB$eNBw07wNNx3Lhac9 z^$|)AP@i>L&1YX~pRmg`-dI~c1J@gJC$;7vZ!0;wOqx9;b;@4g;8LLCu= zT97WkP~>xb&6m&N(fM^fq?+%1`an)Bp15`rosaX>k3?0Ely{dwwl_jv(dxzv{66Xr zn$zmhJNyi$AW@2em?UkLFR^^tvTvOLZb_>Uz8LHCr$5j&<^~CLOx9tRJQFzY<$yPrG4Uv{TOAZHr zI`kRxldUma;1-PZNczK6aVyRnKQ?9qvAgCnK8@o9S$Yv4Xukr(7X1O)D-LM;0J1%R zm2Q=;rQ|;BenZAjbgsOqk!nmu?!1YLeLtR5I+lVXb8~Y)|2hk5w?hk3Cp5|~Z$l0A z143-!>vqNxjb7xvJJHd{qm!_%uLkLzCRjvbhi%jB%;@u;@x{+~qLZp&1*Lif!Y!aS z+<-KXKF=nDhp1uR`dT|#sE%XJdKRuqr^cXNJ~%L-Ns<^Um9Da@&p-F{3!=i?WNV^% z{?+XK`9Xg#y;`$(|xk_kI`HE)JpxgK)3We+36J`zc(JD{CU#51=Sp&diMeOtCvd+UY$YZoyrbwvx6Dwj_xg z`XCI*4K#=Bcpg9qN+V*6c+L)PZ>z-|$5-4eKC{u~-`6D%A8D=lr$2Cn6{~}bzm~#2 zoN_xj{xwN!v@2d-SX^9BS4>bv{;T+Xm5unPD~kyk`f|5ofjy>|s}gr=#mLC?s9?JN)8dD%$S%tdPYuOp{z%8P*p^+b z*jb&NxHI^PdDCo9TXbEA`IHTILO$!?D7h4sVYB1Y2{Y+^5~&vwt*~V$vPtqAZR|dZ zix%kI5da4T;AEJ1T zlJ(cYVC(#Ldw$%z&j;F0e;ppqx|FMH5q_J~{0_WJ?h$Dp>^G)EOT`%S6N&0R_fSk7 zz!G693Uf*IU1#X|+T-XLOwPHxW4~YN2j)Q#(+DQ+Af(BK{zJW@pj2?=7*nQuU2AL9 z6Z0HBS_TTuLik9qad4#JquY>@YTquqckP5vqS$3A0QBFua;yrd{hO4BdnzjWi{`dq zVw8#?z%PrNjJ9nXWTe7i*Ygc3RRFbA>Dy<=8n#T#nr5{twpsy4J%y>(I8Noi$O`&I z5}L%fQ4YWQSUG)Y#e|SgYZ}C+vJ8|RxZFOLL;++vfU#7T>|B_s>icq^tBh1YyZai* z_o5Z%6&5x?9LvvECU^mOa!~Z*$uyPz0r{?(nVD87M>zv{>URmackmRS2?v&bAk`*A zx*|E-S9i&C$3E3Kh8RX*Y><9I+42Z#ZvUhq<32+)e=E3_9l)h{jQvqHuaUcl_HW#| zliC@g_`8t2Ec&t(%urfUuuDlu1U$7Z(}OY|saxruAD$miGgSo&BF3p)j6EVE5e!xV zFJxG;EEwz9m1R}%_|WDjUlt>uj&$Qi)y~;oU9PQJjm2A{ek+><0i@5xt?tn0X#I@k zq(M%*{~Je8QqHM7njlp^_ilcWJ?1l)ugG?4MLE0zgqyM&W~JA{cJ*qEcToa5dPE7ej{#BUF2BkN4m3h@5Tujeym{JKTbR zXCo!cy-$`Cc7d&|QN=w2HX5n+QN7DqBl`l+MYYTm^mPF&bP0)YbXB?A);XBVkQI!3_ub0{GqQL$y$^@y& zoxov!IJB{Xs=9#K)9h?>BXV-{DaBLznMYq1cTOdL-wb2q5AkaysHOy9ACE09yaJ)B z7lOzBM8&JHJ5^cs?e2DM^JPPAk*G#+Tj&Xq~sj!V5gd2qF0narS;>M}os zg4i6_N$MAmaUU0rv^{^{SB(3>K|ByOP677J_yXSCp#7AhYDJF@Rz= zl&FB46-J#5%QDT3j%cw6F)dk<~I%_|uMw^tZ61GRIDzmRYZ&Ldr4j)WWshRCEMY!|rc9oTDFg?>soP z?P~Fu78Rpc$9R6J{MZtTl+~-%o8*|Yre;@f8?C$-k8apfG*=3VD@WIi~! zUW0pYUE7H;KU&J-La?-%;=xx#KM!p?%^$VmNS$lcU7hiPTQ>sD1KHVAL^dXRTzTRC zDm;Ag>i&Yq9WNz>)x0e&{qioJ?ej_Xu?=Kf@zw1|;l%W%s?V*hzdhy6s1WBmt`sGw za~I{oJ-`2Is#=T#%{n&~1XNM5sJX7L#fqT-%gm&!jjE`qsCxfI%~pd>Orw>pLU^;mUD21hdU5gxoyNTjAVX|gc-15`Bn|Og z8Q4NC%R6*%9-=)hwEegS+pq;`R5)YSBby^Ej#^Re^0=oU46^FggvN6mWRF(Cp1bXe zNIq2V9s1uwiAt~a(9ZMFkiFmkgL-I;EI}h1qP5L$z@@kihgD|_)UJvM5chKqNbkZv zV*KY>pq|bbj14bbD3pD9|1ppLiqVxi$N+}Ce6QcUX;9+_W|Z~q7tU7xJF*S=Juv`7 zFbTFW5RfEy?Xr2ZUs|}$701YYd=2)oO+`&c zeN<5vcMX8xghy)#>T0PjXZD-Oluj+_UiXE#4>%KBl59#+1;g;V{_T$1Jvi9d0YC1p zC?DlL_7gYl+y!|-JjNJ3#|wddL1KGt%?6HCUFvSr?QXaJjUY`@ZKZH!>Ox% zpC+lQ+G(GNsqajz2P%5<67Mrd3#{${~WEF$!p(cs22XSA>{wjg)t(3s-s(+fPh+jZtV zw%so)%X2pf7pxBX?YH%s=ht_utW|;TZd#{6#5K{QC*o2{4Xe-|sxCg2bM^A@NXD-k z1xl6VJKzwcxzfEFl3H0Q>$>`aB1AQ0lkhrG;lv4LDA5i;f4Qe6{$x0L9~M z6@BH^t$6belSDWScryf7Qvjvw9LP>T`%Fx#f&#!lZLo@!y#TzFaP`IM6IPRrzslz# z0yVGb9NZWZ`tPSylhd|k+yP4ZaokPu8(6OzjIK8ya|_`bY?lE|E68`zifc>GQT^22 zZX>nqj!TcD6AUhlrHW2ZOl-WT;O|_;nBqAMMb$C+hH&PhBRgz0O(+xNJGX*EdAfA$ zsNKoQTKAP#d@wj~+YraIu%ObKV{H}BR?tuvRo_1}!Yy+6!pPmo9WG6RDM`NEKXYnB zgS|#XUwFwm#S0W@UU?yU=}6g;k~d%8@dcPxRXq-_>F*Di_VEcE_>i+f?W+0ui^j$s z6K!z^dLhFn#Pgz|!0jxn&!64CvJOo)C5Ecld*8Xd%Ir{Go#+eqea=aN?8_B8`)joz zE~gv)4YZDH)zxB5Hs#({zg$@EiIjne{um>0Ls!>qBdV)f3`Rlz)zMI13NA*QwtdE? zXWNXNFwtQ#sA&guT(&9!rXIYT_hP9y3>-L)*X#NDH+8)%EsfviekMqtij$8YfB0ly zmwXq~SNgse_31dYKz8(VM^V~pS?}EWQusssy23zB43wBRpZi$iq4`Q4rNxEA$6mjG zDKC6a+3nuGIhb-96`FCs!$3Z?UjseLBmXLB;H}xo1(6FWNHM(GZ>{R!>K>&dFAP%v z#c3-^z6plwLHj8Y$ax+Ano?SNIilvCacZK@4Rifiwwt&!rwo6KCr+oFWegfWZ*#Re zfE#X`T%$4O&pJ>M4;Mx~HOM*J-Z#&MyzetfcS;GzwA$7VZ5iKArktFdgL+2I|3?Wv z%j3`N$kE}vIXlzFsxvp#_lf-7mpO}w3@bDSzllH62ao0Ig5l)4N>%MWYP<04#?}P3V z_vqD$A2B)STad1#O&*6CUHv{I>A0~I1S!iik_AP)->*Ie(@AZpZA#lZXGkwCccEyd4B22xRzOmf|3MYSG*g^7sG4uB_RJICbh#2dpb$`{x_&6QEzWm{%B`pjz%;f2Uql1GX zXxpy;e*6jwPskf2I0J#Xc$KpcDCa#G`6Qk@QJoAHUC0oSLB$st>sA{fo7A2dGggIe z&)EwxW55}MCqX5PPdSKA;kDvH?zc+&7r+b*Q9i4oT4-0WGM^!p9#vS-Smr%r+!)~m zv~i2UT?WacevyPV`YIROt=YZhr{l(!eQAhWE_6^NqM)GfEp?Y?^GQ0toV3A%STChr zK6>Nr78+nUiy%+*^w@2xJOz8S0G!}$S11>X5lk5|*c ze)FAl$jjEgxLs#C*qHJTZf*yW0eN;`9@8p><3<|Z?~#sG=Rlt>Q%^hb3+>Jg4oYZ0 zK(Qz~G^ze2XcHz$?;!!8y{F9}3J?1cCZ%t`>_uw@EF$AB2Mdi@N3>elvfqUAj_jNq zW_^}cJ%$kQ&H=#+WN8C}uLT-RGz~6h zEv9iSFx37g9U-~t4 z9g;7%w7YC45Yhn=ABn4+??2pp4xx`HM~b9LFAmCB=HKk!0Kf0;p~?+1QqS6otZpR? z!5JG`2Bt{)wZ2d{b0+zB5HlQHCFNhycu%*L)MvCTR8yUvV55nFFV1(fZQ&T$}PkVg-Ol zd=nUHD+Lsn0fhELd`MXUwiQJ^cp%^PqO??C{1+CLn$_o4(tblR2^9717JncUXXJhK z`I)1SCP@&r+l(c{j6+37!Z92SU7DMnCCWNiMxnDI{rMu^R)bMusUzNCUU6}UwQcD+ zNAyLj{oH-=k}jDfm#v0s{Z^O?`77ga^K^eBB105NqS@XsZ3nN6fksq+h{VmeT+B&` zZz>Jycd$9Hd*}lY$uVi)W601mK?S93sBNv8bWD{pp{+w*i-9%iIn3zl%w@a&Pa%Yv zmVk{JEr;>DfbJmbsdpBm=cd{Z=(Q{gtzLTNOwW>GDs|*5@;MCE8E8-yj5L} zrS4Z0W_h8x7(nKHID=moCdmvmqRa^D=*)Vd|0BH(Zp=C2SjM@s@yf!t)$V^E|L*Fx zp6-58^gE7qsrhr_oAx_P6pi}CudSz@=B%T!QT&d${`M0h4V{X0<>3ncRbf4p{oUM8 zopFs}+w)h}t4St{&&gY-8j9<74L|4+ar&8lepo)e?9v_x2qkted~BXcINx0Q>FALjx{BOGk(KoFIsRfw>kn!1XU!?^=G= zA+ooU=!u~E9IUVNJugh%w6D5GsQz9HKg4#mjPQoLv3auun&w6^s*6Hw4fVN6|EJUr3H)xk0-spW0 z7l$%^uoTi^75(Ew?kWzB_UB1*va+4$^z`(Cvp;lo9dQ%ry8<6BOYvd35LC7qa*v+m z0RJXphn{5o<>r&WR2#rGCe>Xg)L9|&?ZYa=3sS@1SRotnVy~&RZ&?(`)`3QAmAZVP7D0q zWKN$r;SYH$;_W`-TY~Yx79x6v*CX|czqp{_A9OQYF^-|Z^g*N-E4@z=F+kc62oh_< z9PrbboI)x-4k0_@NkA*79PdZ_L&AxX_$1m;S0R5$WLZnkLC~Hk%Ht2iobWT_1QB&d z+*@90_XY#;epJ_SFCRzgNDvwyU|D-uo6tI6Ufcq4eo2?Bp&B&wD=REdaA8b0jUi17uxI#F@Jmv0F)_>0 z0h9eito|5V=W2=X1BPlAyP9+Vr73~QZG@+bytf~EYB09(U0S$EmWB|NOt&mo&YQ2Bra1vX{L>0hV{a5X1yoRjqg=)ck#$^AFv~ z-}I#{YWW3rHI!`eBeQPB#3Am`GU}MjTG+ zzXo3O2Cw;?O8NU3i)}g^D_}?l(bQE~sO>Dfssisz*{KaLt>P0BZ0zm*#eXoSW1MGc zZ-kk}HTzWHHvHp6U5s#tN7YVK?=z~HDPqMi)TEDyP{hPg|943TEC{+&BiRPqt9;zp zZ$K5SwfNoZRnFMN7v1855n@1ObpQ9atHc=wnQk6RdS@NGd}WdA+n)Dt?abF|nQ9EY z*RMf2!?x_f;8LbefJ#ar6PM{~=A$H?$;jxRvf90ScdOfKTMLSvMO;#FR}aEr2dAgw z-vL!lU=6jP2hly_w8XEWy@dfVvF8CjJ72_VE8iQZ6pK=hEUX=uR z2A6hhVKeiIN|RgKo0Y=F>3SFrs9+LmE!2U!%$H%Jug{KohC{O*5vS@=*)Uwdy}lrS zh=TT^^4@ur0@)RB02OoONpaFI$L(P~T>KH70WZ$FE<%YSOX>fS)Rjx#ynA=ZttZBE z;~#Z4mL0)6J|Nbaa!PD*^`RM}MK0C#;$41FlKtKtIh9jw0SA9>xLOgwRF0;{QltVs zl+99xboHpwn%~ozGP$F6RKCLs3GU$fBQt z&-Ts9ZBpgqS`%i#Di)J?Hy5tKmHoAsZ^bkXOnhtH%Yd0LPDNgZu2A1Yz#lrI*UlW0kr;!h^P^@hDBm){<|7>~*rhnxxs$%JB zp(_k2T2N0Hc|u<;YI!UW{S(;P(Q7z#ck8wA9dDbOYB64AyJO_l6u)#9HwlIkY_yqS z1A$`|*8nVJ=z3y*F#AH=Jwn?Xy_FK-ab#jk5MzKENddD1U@k0lIvv`qHNa6kFz`vhJkMta zWfO5Ba)+r_Oo3BKsg>x+2;X@o4JsZAYej1sVf0kB#?ISl(DP0qmH~-B4SXY8T3f3C zuVLvYXVSAeTLR1&6;S$E{V2OTO)1g|o`3OYYh5lLQ{v=Ur^ZVyOc1?rBJW0wX%W~p z@H1PW#jd(@XI7aX53`IXg0!Ornw?Sc8c`jYLT0r)1vxG5y#q)zl zchK!a5vuBUZ%#-a#JF5`079E=_fN_1=`+as5#gzz^mzzn&Ev7lC+Oc)S3CZVvzQRT zI8bwbaqbuE%9XskclLos+0H3$@?k$5rx4dRfVB>DZ72dqH>AQqRO;eiqmC6+?Csl+ z@)Pr?f(KL~JZ}D7bQREpBBcXtXPNoGF6kISXWSlV;qH#}n^YtGk$kZ?BxSL`a|Ro{ zCPfTIcms$=rdlUYqV29s+&Lds*xI^xVfJzr%!tPdx)}W)Z)(Zpaqso>wvQT7e}02` zX_u5>pPv`KG42N|6BP(UMlAqF?M`WFEse_dy*fw~bwzu~qYqGz4tImCRud#ps{uHNK-h#4fU&&{4uOyVlSe@8l zW}u1H*lOJ*b*Jiwe2|;dIL!Y@_H+sCUMWT zfQ<``_15sMwXj6+kRA2_=^tDucrIS^&7OsxsMoZQ}l@eq6&j)f2m^qrXLK0NPDPW*0Q?`e`(D&)(GkFicNXD}@H)BjwP4 zKn0LC?Ty^patd`)3clq`nBbh5PpGkmYs)op1SVpvM)NiAo|g*dgA4UP6v_+*Fi*VB z|3$-RS?IhPVjTh4{p-Jfwwy@H8&>%G1bVXi$6yCG%PTREs`23W=-HJYVYdL899w(Q0AzE#mXGDWSl$sz zNR=&lBGD~LdEesZ;TcjK4oZ_AFmznIjFi&KdmS*Yx5TK?hUJmFxkSCP7VbEghP2YE zRqF1ny-YW-dCV?6_PdwqdVnJqk!{Nup|hbsQewOI5Fce z6l9f1@bSd&bIxCMtl{ljsXy7fs(l_rMvDH}e!NFWVdct|oiste3jQsbvI4oZ6%8d;ogm*lNNVNroyrnvG@4mnVa}agGwWrYEhF4r~KIQR~bZ z`O~59qETC&Yq0?jl=afWY*BS(a*D%%JhtoikNlN}9xfVB6DwdIQ_}w90bkF+`vTR) zduFRwz6(dr{M$S~XV&6(Yu+S|Ems+5H@ zhQi@pQ7cR+-UN)`@=__b2inU(e>Jw zdNfBCl={9(hy{56_*}sfv5x7?=VeehKX&-P&x@zPA8sT#`!_6AFN7CSbFHkM%_7vQ+Q8#(A_7#CS2`1lH#dXtH7 z{{5E+owjpN`EH0cn~BzC$bLNxiu3ni+}ItDyTs1NA`iyDl*U@@SL*E zT}L`b9J`6w6hs~=ul8l?3wRx}X@mTTLL1t6Q}nDuC2KT-E_wc-9f&Y2rr=&AR;zhU zbK^r0<4I?u?mHiZu|2I<$qz0c79rO@@wH<0sqfdXTiMyN#Mcn#g1`}7dl~z}8*o9wvt3^z8xi+QMvHEIC1xTPEcrp2jL_+tq=K9Go7QMmd zHnB$TN3t2$2UI(JnV6Uei^K6MMb-{q*zK3?Q>S1aj-cQEPUk9+GVZhfBUhHNYRRj5 z=a&24+pZm-qoHXVibPF>j!L{n1!1yoxz!Mr^3d!_w7tyO*ux%WFBTHd2{=RkrhU>R zcw^Qu$Y8Rphn3f6;3NT9a&+9ty#i7a(8)fN0R0zcSX&j@#V*elKLvv%jA$5se~oa~>hU%<~6;Qhm~a${`1CsT|!@tq;V+`y>h z`E?3I)#B`@+2Yk7b^*11e>z}TB1Ru1m(HnNw6Q_;C3u6M6kXPd1>8v*zkvPd8QS`1 zOHa?AzgD-pR-2>N#Yid~@N5=rK`u36M~LyRUu z+g(hvXY;9of@k(sjF5{?SpFeMY?HgrQNIYZ;L!Yr)>E=Oqj6uVQZ4-*r1A5HKyKta zW$+pk4|_-@4Jv1d?|N|OpsTEdt83#^89v}>4ktDt`)m6&qidGGe@P1_q^OXWD_UKK z&I#xy4gxo@y+fn1ha?}C57JC&yk76@LSkF$FIK%@X0v7Dc?*>m3|0cTdQop zYGzUX+bdWDxh~>6y-$9t&N^)#Zr;hYFpff%Ir06aq*(34be3%{%e)u1C!Nw6v>C!l z4l?FdHPrxK@H%2$1LI9{r|}IcpX|EAI)=1+`(*`0EEPJhm>fU`}y4h9u_Nx3+nUj#v zLth_JwR_Ea62b$NMHRP=sTvVuz~B+5eYs!r{@thABw}(wTUlj|)is~s;&go7mbNwx zSdtPPX*P6hl^#*;haxm?zl@r@Ygp^CbV#ni%&aCr8IV5H4IRZ-^te z!y?l;xHuUflwBbM^bL5NOzW%+wG*^X{k`)TM`%JtxahuuQ-H%nfrIbw`N(ZhuzD4%f!=MLs z{uSu+_1+v~y|wz;1jD>~tXq1@$_Ee(nntV7@cTnTzOSU!)=2#J?8fzbjG8gwfQGC; zh-J?vF%ZadGqF18CF{LvjKzSG+OTUA-)JsC0ed)=j@SSfH~=yfb_AGl)k;6$LKOtt z@+MkqpGxXH7r^zoEp2`k&bILp^_*?%rSE7@hRqjP@t@=mYcmCW4~MwS`OF}u;+L?P z!iCJcPxjdZplVkiFK}<$sp{B3rPr~`kM&00ZQN6DnnNAo1JO4CVH~S_^sBdHVgk_y zmgZxO?YO27OSc)~BcsN|t+{FT_}Q~|KN2CdJ~)W!9E(fC3a13F@?6Dgx|(TI>V^#o zWofTIm7Y&;8G_iM?3B&r^}~F%sOjiq7khlt^IcJKk^PFUgV7tjZ~g44xMgbfj+$#W zfO`)aN6-lAhFHDnq#1X_v(Qi^ZOQ$znzYwk#^U1>GqRer-;;8AUUC(#ZaJ4atgQSD zby(fMtgOg>#ANtN^G~?&j%c|pU^NU$aD!@3_U+Ny()Nd~HA->AakBSEx8ocBpQIac z*1-B7BQ>;papzWu>~zxlkwanIdhpl+4!rCDA%hW>ur#;JNX)P>wj2*Pg1F~RIbtz; za4fkF>iv2S55pq2{u3zc9yo|M7ssv`uj(O=%?Ygv+jddeK3W2)X??97_o?O2LNn)4 zUPQEL_F3R)*;`D_?qmPA=L7@C_Ao(h>$uqgAh8>{ruMMpA^DG@(0Y3Y8YoxzCObiam)~gjW)?Y<4q;1 zuidVG`$_s9))MlDh~TAkf!=ymcN&tgOz6qg8ak!)5``_!b@gnlt5~3K?nkp9Ht)T) z$FDZ@udnGw3;x$CLF>j$g7mYw2YP!So?B^n>hSu6J!>DTPE|uA(Xu6y0>Fwk2u|_- zphZJ5fCIl~4YVrhx1K%oc-CcWS?wnG&Wo*}80Bz3N}Iq%1tle!j^Ye)VgC(YukJrt zT;As^(>T53SYilc1mu$%8816jX&~at_G(`mrK9Lo?#puJ_QTAyfF^`!TQrbeAMJj% zcJdSJt+&+t*CBKOM0wF&m?c>2`bPa&QS>;VR}+8*uDx3>o&g@1WfS*%?dtCG>hXR5 zoyf?xDf%+b6-*L(r+<4b(s$|oy*_%L2zwxC#vXM98xZ;m58D`6tJwhxU5wQgatuiP ziTkwCI2{UfSH{0pt^ZnKB~88j&KA^g&NLxgXQOUo<4K_CuJrcyqRPM3VLP9kY-5T_VaX1dzJM>u0a0_&{_Y*O;?X!mg3)c8@N@#mvD z#Az$Rk^JMBt0LggykE=Ev-9{4S_w~83zTExLlpiX$-+dV;H_~q+g|^_Tc#n+)~9;j zYW_xC+>d-IDes4?p$eV3zC@!&01n0t$^_U$kYYk3ySyGNHg!#lzc3FzeR`Pt>@9a% znQY!0{kPY|UUnSD2(pS3%})#8<23F_9f@-)<5-n_^!vq6WTI)%_@iQD&82V4ghN=L zEjBimHXUq#W2Hb!-V*6_VaaYAoNK3AH-LkXa#CpVBbHh3Hx*yS7Vw21?^i&4!s3WJ zOziMyiK0lS?5hxrb@EQ=d3npQdyxsBKvCenGaJ{Xg53R@`NZ3f4N*G0Pv4+-I@qzn z)kK#?DP|c)V;vP#wTJNR)UR6zlDv{cs{%9Tqo$J+1a_Htiw^c7$cUIoNgMt|x*#e} z!`8!XX`yX?`~8|{Pr$u_b_r1L)x?yACpkGy*Ox+O*Vicu@=bciRH3LPpcVcQB;itC z-T$lG*52+|^$LbOI9t!#^1+m(3O?@Ue+Ey(80KGkX~r0rMKvAo0bg3&QJ8UX&K?}+ zTM)zu9Kyc)ADD3SpIVp1ME|;mW;7L3-vDC~%NyCCQXa>+hfRr#)ZiE`%MT-L>csPK za?s1y8E$>$_x2t0;>$tvy3VzcN7X8P*n_5BIQSH%)4qJ8S|1Uev9=@tMKM2SF$9)g z<1X`Z%9^+O*>3b{(z!f4B2EY#Xb5p1vG6GV7XwMT^t7}p7?ZW^sqTFgy-E1I*rqyc zT5Asl>;`U^-7E^N7*oSZ&}jx45Ar%L+peIn4-F0SViZQFumExTP*pvw0aO;Il#~EX z#G%GRm|eLZV|1=^mM5F0xVR&B*Y7#=1xY457iNEb_?kD`eD~8_n&plyV7|`Vu6{4i zj{vW!$7fs_Zw4qGdUa(&&us<`XJ$g0(w>L`sA4lumS7ru?ixqxHyrYCblKG(KMK>R zV}CI}lSzmnp7JA<#tunIR%|0n7a+)dzGKR~IbvY&%+ES`Uq?0}RtX4Ssu(T-RcB>m z(*RTX8@u{5&#+l=Pv~RF9eMUu*Yll?8IZmm z8c;OkL@M#JEm;K5ELrkGEX83MW0HTsc&mEw$W`;dwsp`X`I~w{{?%q%TfhC~wxw)$ zdsB>`K>q{^DV-|`50zA_=(qAPe4PhLgKXIuVJOXs~#Imf^a8W-1HUq;27e zv*E+eDr-y_V9+i~Qyy#9TG;rsDi*^^X6am5U^`@Pz0}L7 zrSi95n+CJA(Ce2Gr>2f?gcOPm20=s5GY;){3Rl@BRv&% zqc!f1R5B2_O5I5de&ZT7A*05cwp) zY1$Bgtq)Da+dhT>M5Gtzx*-D++T&bJ{7Dg(om3QwFaZW#F=@AA6foU`3|!6nRhWZ; z@L+6p5zQiL+d+Tg%L=b%H}{O!#9J7KL#dkAMA$p2846hjECQV=Y3v^L*Mh zF;L4QcWxrZ0*La@O`V?nnV2xUa@z;x2+`@Im7jM-5xTq{!(Mx=$Bz6Nq38wAAT<+X z<*6E>>Fg|tTxi$WVS$;G0ko0~*Y1jiIU0J-0o)Qt`nHp}h!qY?ItY0|ziFO0C*W;+N(X=yGI;{rbfP<_#qKpti zXK)jpEQX3;sr;ahp9w#Qb<57@XGMlZu3|JmbOAvC=Y-SlK7hr5bN+m=qn^dv>PNfx z<72(qO=+9N%xKHLiaULHZ=i=q9nWbs~pZ!5EZjUXuX^4UnzcR2CQgHHcEpKP2p)v_W z3yVqBg9j0)^ZK#jR)z8;1obiiS|%DX@&X-RlB1ftzM>flCY8)xnKIqr^t3W?VAdv^ z0okB*Lxy!R*S5MJ?w}6nea6Y_EQ^DF-Ej&8Spa+i&z>ou{kcIPdazW`03nJ!tQUCk zS840kN-TrGa+TPsVIWxzS7Q8kR5Z(hpL6;ptysXXFKd3%3%4D>!$0rdankl!&kMn?y?it&A0zerUlboYe@trMtlBg0ho+JU(5>BSPeVo2 zP#W_J+$Pcrjob}`wKW-Kgu$@HA zPTKVFmI+U`E#2)aRrYx)=Q-V)Rohc=DRa;N=Mrd>se1z&Ps(@)JxjXUV?&7Iw-pN; zzi`MN!j_IbgiGMIT60ooxE1A-79?-_(Q|_#uyhgaUZZm-T zq+JjDRu9*q_Fu-r!qlsD>C3MHo>=hVYCuV#{l1-Dx9Mw!WWd}o3JI42PL$4hvX*FQ zMy9Fg)rabGJ}|@$8QuClmtmr)p}J0fnnVNk%{y*mCRrY{+~0 z@@jB1m7qKQZWj6#pttnnI^$mLf?DPuIEU6ZJy5;)(}vi;qbq^9TP;AKP!NKNzb#=X z0x%)=&WMGW4GIx!ag5T~mksfnqd%mNumhGO19P<6olW(~JI7&FL7R@ZfP@+mAjMCt zZl~J$u>XIpyj&fPsz1`iaeyDL9f~+Mp!}3psyQ;vV_#AH$8~daTX1Na#%>u(Zva;* z@7c2|+?5tgC&PqPII%YkQXaXmIKnl|n+Z7)3UWr4w}3YjHBMYkre3FyE1?yMz8}x# zI=H2Mr2d5*D6yKymtR%Ha1_~eFAfKyBRf!|O1ROZX@gR*6B9G)r$M#KkpIFD{tm_$ zfyfles3nMIBxa)oTtg3R?geDhb?*URNN`mF{(XjvGx+7pafDu2UDvAgcoPaOp(JeF zNG->2b3Dlm8j1EwEoNTeXA(?ZHG-%$!Qd){KxjcRYtEuuY@&7-aOg$ssiPmgAe~f7vr>i=3SO zJK?>`7JEm#{GK?h`u8zIzQWCW_x$d9q^PU!Jt7#nMXG6YE>_B1qt^q>GRpOUl{q#@ z@}SrRfAU^vB2bt9e&Im<2w81T^yWYc|7mE@cifE`lk2DaYbAkha}Zo1bOYLas>+QU zH}o2oz`Dm~FH_}|Pte<~V7EJzl$IVtuqQ7Ps!GdIk-rB7N&Lo@UDvWlkpBe&jya*!nxQ3I-OYj z5dd|+rKJU~vjR3pk1E(#(uh@W-`5-BGgGH=c<;m(J%3J=;pns~+`XV9ERky@C8KoI z-)47!)dVWCXF1p6dGyu=aOe~UceRnRn2fICkyZvM?C;*i`_z`vydI7<()JL5M%zma6OAT}- zYizm}w`GQw(qcHoQyLIJ>n_gBJwNVjKd|3-Cbz=RV}R`tZNuL;B9(u?YP(!XQ^EKV zjfDt?$Aq=xJ2SLK7CLn;6X3wIVEK{djRz|v*{&SDj2b^1tLG(6^3jMy1#eTq@d|}0 zEyXKk-Gpb*cs&w81{;+BU^HVpI*b5-F_YfL+l2-QrbKiedLG2R5nz>t+WubD$SKz~ zVE58oevu$@$n$!erwELXkpX>R1u!UMNsV{f3D%YTC#EB||UE`}qnq*;bm2-}DI z_wGG8M>PO$PR8;V@i##1R{)I{6C{IUbj$@}j@-ETKkRnpN2fv5N(r#EWZB!-{Oa;w zOFGW}6QeF(k-zJBzV!GPI*G6Y6+}YA4VUePm<( z$j4HXWAVrSx$#Vqls2?rP&+mV*>vBr|XT;B^0L`U`Gp!0Xt$}U)0#^D* z&Z1N`JMtAEa4Ei$6c)ywm}z&h;3YbF!p~X&5^U4R$kr%(;r3>avFi<>%u`Zx;-3r_c#J1(-8( zwmw3x?`jf;n8o4MOHxnj-_+E&BTEF%Cgp>j!Z^X&eAV+SrSQhX2FrXIV`$1SBTZ~8 z#*T{C(|IVjm=>GF<9B)6p7pID{6Cr-{zobVudZOY3Bl#g#q+FUX|nS25m~78S^S1k zmt0m!uNnICK5AFXaDy(QGGWZ^LA&DQ>|6&vyVp9}-o6*A5d+9H2>WF2A1k8h zz$)`7E2{xZSsHy8Lx3K*<0K9tUgYMj?^9(xdi3ZSe2`uR#j~4QOt41F;&l3S)^F+( zpo*+4EEl8{o5cW+DVPPt1PP&oZ(9D`?D(m70041iY#^y^dD-!UY_*3ZgaSUI-cZdn zt$%>G^bz7gsu*sYzkBoMfSWa05VN<@w6wfiosTk6#&8m)rMXoFYE+r55$G>=;ReX; zIaa+a(9stvg61btHnKIq6+`z-b~hlvQp_q!74a4a07O^`fXMU{*J=5W7ub-$_UF({ zMJ~#lr1M^0UubpVJB_hmA}OhJCJblfJX^xQSQ+Sp`b%YmhZP+GRr(Y4zs}#)TUp>H zO?`}70DGTo@1RiU5)s*=Qec7px^oOa_zz&h0R(dJ=SW=%DI`Pt8|r{x2t)ufukWeZ50*@>8dvNhCAuC8pZ%nT|k+bwg##pTHbAKV7w zzKir|X;dXHS{_7{l$lV}ZsOl!8;0?$J*Ykea{wD-=tpZ?+jS^w5%&Nf1Hc=K zxU7#Ff}r3I5FfQX8w&6PAU6XcrjK067ciX|AMb2@fN?62UFmMlc*NdE&{8605YVS( zayi%2e;@+U7mbxSP&W-xxXOIcrStAw3JwagKZQ2mC`40VKQ!khx^7Yo;;>&Q98;hO z=f2}87(-A&3?&>Y{J+L3!JC6XES6HY8V$^AWNT{`` zAwJv!iYE%;(Wa*}z?3HEDMC6<^@;3(E^jMP5LufNF5JzNN(Fin++-aM3*bxuCAK_y zipqr4+60Y6;~9YqUhn7$8bl>NfIWp^q;!1BchpyY8I0DYJ|9Ok z+gCv#zs7r$L6Z79ls+K$iJ`|FqS}(W>2#t}PP&z+Pvs#J{?AEEKo?J{XQ5G4d2$u+ z?f6W5zma(}v(Fe6gIPt=EU{Kc{1H=7FnSt>_65iXu~|aHDxHvA0?v_Z5>`Gk-I5S; zGsZCDAUTpOsDK=U$;*ZvJL1hP`*Z~H(CrA_-By+eQTKN17x zpW1kF0oc37Hq1(B(h8ATiLy1PSn3#2M*)Mz2)pgFwy#QF-8bpS_T$^xRZfj}lku@!>lZq;fPrE!^&pjWa52MyZ0voqhuL+T!g8 zS;hG1&3l-@HF*2BvcL(B(R+tG{K7dR@3GmuSoZ2lZQRipyq3>7gMy8Ye!aJyLdR0M zFgJht)2C~g5wNhR3ylY|@Acl5q&y4Il^r z&OS7SXr9QB&C>ktJOH2@2wMZ_gWwMC5A|ZmK)1kt2s+UHD4K$1esSD~V;@bJh|JWc9O5SB5v0HI#%(4mO7uxOmS{n#KE+N=oG zPY~JH;50!YgA)%b3P>0ZZ@%kwM@LB9p!5U%W+xf_6F>hk*up z!8gEhTCgAUZFqQtq9QjE8Pgk&wQ3h6krQ6$XYr64Fp}+|_du+45^C6=01`ific*T< z0%L(56%{fJMAimD;^NfLqw;EMFEq+7Le>*Si7ar2-9G^>rsOx?c?^N(JOL3RDEwm> zxcs^)glNY2M{F_*3Q?ipZJ_hpsG_N>9l2*9(h%6|)Yy>zPaMpRiW?l@BC$qDsNqxD z)wruLg^GLdz;;AU#vgL>dCkN6E@-tD!FLTP$~eAappwMR}=fysfo`Qns zumB{CZ~67Uht3x)qV)j}wG++KUlf6We&E0{+=17a-XH!xjQ+V40s0(jp#*<_%~A*Q zK@Iiw{n!AMi(7Y~^%j0z6_0+cv0L!Zedgr!Pf>hSbkRZET6w`@6`$?r6E2EN=Xu+u3r7 zqk*w^?^?u#+_J?f(-K?pW+9FY4quqsI zBWUKP@8ra(R98?|SP(AIs{!UYfP>f+p!Xm0hVfzzZWIUdo`5O~vaB3S%8)Wv zh>|D@5oMN{|L56f@8AD>uIpU;?2}&WU28qhbKjro*1Ijg3|JH8gJb`najT^1TJDmFM*BZNBgC?z^BYnFW#v10LuhGO2_c zlN$N?j*pv{7luxHIKS1=(36Q!X*rOaRpAW-NF8wN7B^flL?4VC`AR^IP|ob6m7zBz zGfHS^+^@X~i9(M=PFLEsu06SJ|%6ShTyu3)1O!6Z4ncCuTy`lF7;#uldP zHA4Zx)NtH+>eML(mmdsBwk5Worw>!Wei8REzkonvp_?6oG}XTk>$L4ZPAMr?$XyF30 zfq)<*p^s`x5C8HzK?6(2)q)Gi5y?4B>j?)|@NCgw4zNzfI|g2;Q$Q`#(GVumh_WK4 z2b@0SdPz^`aO{AYSwi!H`uM}7xhQ0iI$`|fcoi_!B{Vg4!>wDl$}so_n35K0@I9H# zL=_ac$vg>Q`4Km%n@e%=*b5UOO@jUQ%I@{$*c~mj#Q_oK4OJ1$ytnrne*1UmmSneW z(?Svb9YdJ*KRQH5gKlZ~?;lHA8E8`T?zgv7U15{=o@M)-cfJ1oCi&w_F~cDuhOwHn z_)`|+3lG!3e>Nkfr>v)LZnUckGsm@ZsMA07RQ=b}&a^X!2wKnmg}7zf)2Nd}c=vnU zM93FHhzkhs3k+i7L!w@sV$Ok{gV3=0{39p)m-dKtyZ-Nr`CoN?!nl(|T#`(+d-{}~ zhSw_@fF&M0P6omrgSv&dw!C%)uRO(!s{DQp{lAq?ny3_;vm4a>W^*cx?D&+-_8&id z9?N>Se&BR=5%2iRp}}JK<^>O2J&ibN!Iaa3UAXIa6(aUE7!HCG>2abko(?eK?08G9 z|9e#dW=G&G{1*@*kSKzs#Hh{ba3&CYHFmqCM52brSJ2Ho^ioO2KEvC~X69Vk7bThE z4yvXLCdTGaJ`fahqh-Ej_jdNQt<%u`j*ArD9ftkk zYP;ila%(31MOyqvM$KQpbC z8V#y>$N1sHlkSoqPIZOXv4;nW{?qg@v-0m%)9gKRV?3=^ot@nyKkWcUAA@?JsI%+FI%U{+)9@ zvEi|kAO{!Yh7B}P(X#hISUn!vzn>T-JulTlt)|0QW_%**PHJ>5(znKSWdt=8?F za_JmA-1&|V!r~0ewSAvg_I=WyFACt~^m%#NSMq?*>)i`pr@yS`R=JgvyV;rFc|pZ_ zN|s~Qk`pKCtv$<~>JC_oulS*K`DTYV&H4OKYZ08{sFdfPzT8Q@EK8wr0 z6s&lDab@83C0PfU&N%um%#W$AS024Q9!Mo1(__~1%)=jJ?XT#a-Ap^YHFT#VocXI_ zE{McNwleWj(`Gv_maX`}G*y;(eXTDet4{|NyP_nrv4!cT3{z#8`H|i%^RjTY(Yn$F z7QF;^y?Ex|U(6prG?Nev82Qa3>&VN&QO2YvW>UQU@fn`sKLv~l*vOx|>3Cqgy}6{! zMJ-X0CH8IGWw;%>bvuVjV_IBhw|D-aPfZ_dJKyQ^gDSgT74Dm9>WgU{E#~R$!&QQw zWj8^Z(r>jJ_2r&E|Fvjj*IqEE(nH;^ znXcD_DJETE^=-|a?pZ0~BW2l~V=ck>@R4ty{esIyg;FoAk%1ftEj$xLCGf7z4*j-z zHX(Lqta}-i&9C}!?lY6m%*1q}k}aJH-qaADtb&vaPBwoIeJnqrq~!TH{NB3Zk(8Og z1HVQrO3s~|auY4RFoYD)>7GjyQ_LV7i zhTaLB8WN(lv0Idw_$XbT+qnKGe%9I6g7zN*R`5PQAG#s{8$X5;e=F_vU)5am{-^(* zR~6G$mo8x(v*7OGg`I*^!gcCy({c&#bhw`btzzQo?jEMwRa1jjOZJ>t!n>cb*4-_3 zHf46#=tlikZs06yfG?YiQM$UXe>6@nKHg`A;FQlzb^#yiXa&nn{XO6PJwG%}S9MT1 z3ie`~mru={_ExJtXgL;V`!sjDGbMU;XU{&H2BC$5&D{GUwXSzhYUMe2UY;1AdaJl) zOW6yLVE9^)Iir@WrwcXlUK^6H+^0Jg_LJ~4FRc&^E zojpH#;z)xLHTF|$1uwmih|Z{=W7dAgGiRpM<-)pJgzFs64Xs;TNKkrXS0=GcSlILC zy6-LrAFS!P>hRI0CCB&Lis|Dbkp1sY4V0Z?>C9zRR2sGLytlKi&X0?utaNR<;gV3k z*tgqPe|mq{qc@^NmCAPGFTHDFF=toN|8efGjFs5pp~Hu{7e3d%f6wZWWuoOG`n3Ne ziYgpFDM=+{J@8Sf8+L1tmhxzP?>oeY1xY{f_(MVUgcAN&uuhyZsygi zDVz9~oUw(I5WMTR8hXB%`D6c?V`?DhMb)ZJqjZYXp%h;glPXR9&UTO1ivh=ub^BkP z8UJpLm$dZJ(W7(u*$3(DPmh(aR-0#tS5ANKn3ObE5;47S+567>gx-$Q-umf8?p3Q6 z=FOW2Z#=eNNxeNc^gd_ndOz#Muf?7b!J9P=H0Tpz#FQ-;Chp(&99~{#m)-hAb6#nO zW3A=79oZNEP%SM?_w~Mqx2zEKu`KZEv{`uWH509-R^;9VSZ0(bM{eu&0s}}Vo6`-C zG(^1IN%r{57r)bUtd3qQf1!_K_~U_5i=q=@0#R!0K*Z}GNFJE#l2E#>W{vHx_xN$_ zPF!|O5{^zCqZV~~`o3R_eP8ePraDUA2?}ChyJXDa{1B%H4FSv4PTw2)kM5tF&b{FD ztz%}~*wY*O&h2fxnZtqtwmx`Nd9)&Q=E@auU)*=HT6)3 z|G<=^6e6K~hf2C)O--k(GBZ8uoxU(k&5qS+YkL}L#z-&@#3?{FZ9jLhvy}P9rcr2`Rc5?d6-rBi|wSs2!v~S-S{nJ?X-K^QmrLS-E?Zu0#>8(e&K85mmmT{Ib zNlwp>?Yh+8t){jxA6p;HK)ZU?lNSBdE#s&rY-nTaa;&YV&c_C{=K5BKa;a^KtQLH* za;0sPsD>`p`fow@e=D%+O)}6ggD`r7CXDE+uV9+A>%lmHG^-2OnT&!>YxOqVvqyaB zXT;tMMM~(zx=1HOgl5>{IQ;oXT-wrgh9go!zHIoRog-& zbi#&t+-aa|DHl2+ubFPC1ht$J&pnNebbtSJ!3p&HGu=b0ngV4J|3w_NX#9e$sVV0p z`?;N*RIi69W#yYcU!47GE_iNz9k)4ed_0Yb>7y+Mm!5N3T(*<;2*l#N^)keFahh5u+(W<$V9QR3k8w&%_A5w*0bWty*iz7gkOmQm5J^+83pz`v)Ap@n`I3)7@uko&WgtL&!cK7xdH5R=_7>d>Y6ZUn?cou&=B$m@KR<6#-(C=W{d)gqiz{4Qj0{WZ3(k%V z=o;)lvVF^wivvX$w>Y&{r1VWxKQ*9JFFwCmFZ<2onz_q4m#GVRDnDPYxVlv94f~qau@6`|L@Aw@o|z`bS53(yn8wstf+4dajYtN}E(XB|E z)72W41cSiIBlxug(@N+C1a2}vOnhivx^9nP$9_?RcxEr#=^biYL33*oM{Xh@@N*H1 z)Mez(1@f9h;}{GT=xVo6mP)9MMi#o(l3CA3Q}=uhs$X83@G-|)aB#YGkk($}#fQ*- zMj8p*bG$yXo>QR|~fB$+6^xrV~@;RfW$}nE}l6=AHfD1z` zqB(3u=NU!yXg8(mW*?b}+e{9=(37T)4Trn;8J!hpo(|t8E$#I6y}|rKQBk}m1T>72 z2Uf|83wH9Gb%#kGo3DBuP#2hcj4>jvM)8z>-1_M6cI!Xv&rd3FDlD0F;f>N>y={89 zt-xb|cX_(_m5vqi?zAj3@p_j&w@tC;u{^BRb$X1>JomYmR~9UDq#Un9xQX9m0{I*r@h1zn!!abcohmReLDWFoE^( zdrLIa(5cYM0E1}>T!a3}|AJtL>?Zx&ulcXB=mM1`W5qF5F;lNY0({CqgOJVBJxmj-E)t(dqv674*9%+&M|Mh$Pti3sqpJZBN#R}j1i(hr;nQK7!otD?E2I&z>|2*UWzh{A7=#7g!q<-T4Iw>|P;|6JkPuT^ z8+Od&jywiCou-6ru%iwRBuYC8Dg`mi{D+l2FxB!y6v0@g7y*@-QI{9bzc0t~$`*ax zN5szuV9Pm*{IlV)F-;OodA<11e?LQBC(i1>KH$0DLC- zkM+SRb~9S+wD#Sxko<7W{d4;IdiPd=+qGQLucGU7)Dj{^3iUJtXlRR~HZ2)dm@!PC z7fe^L_`uoMls@c!b8KN}Ll)QYu<&FlcDUzTV(IFrSM*{QCVd=#dlO(pZLI6D`|#cR z0Arf$e4W>*x@q5z-Gy>pi*AeCyVQVdc=%+S}V%oSbu3%Iu@Jx@(3oX3 z0wKD~AM;cOEXxFyDHDwh$Q&LYZ@xVU$?WPGKIE3To$Fv1PzqFHM{?_p+6>M0k!2VS zO_8^GADe3;K>ei%3Z$n&`s}aBzU9C5-ysAY>Ml%rU?|03D!B4K#W}= z)PR3FS0QU!%QSiSG6!o+x76Fxa?STYE(A zY`X%#qASHfBO-j571p|ia`zuB@l=GicnR&6s#j4)sQ<_*OrlTmm2y0D#u+c?vFw#8 z8L4TfM5`{Chy~~R^OvMl%uK{rXzzObSnQGjOKsliGN;mIl=7wMx_}gkCG3mPgEO+s zh@U!@X=Uw@by7(_MpEqdUqSEb+8M(<2cJ6>9|!+(74PNXDi)WN-PiZgt9-O#-Ew;o z?a_Ee4+G72x9N({i`TbDMJ)lE-B`yE5O*Qi^~ltpjLdZl?q^Q!pL*KEVRBAj`O>c0 zu3{H{L_$j=2!fSnZ`H8lZcYd^Mh))X0TYOw|FRS~y=|E0@<+K?KXCG}K+;Nm#zaLs z@v$P6a8VyfExm5am2QJmaCo@ujzX~v*@iVk{>i<-n1E0y3D>$g1{)w1&`ZX^&`{>* z7g<$C_yUr8pF^MY#_EnClQ4tb`*>fN?})-1@cP;dG5m?Ly0T?Z) zE=olSZ`;O&;%x2bXl{OFe!-3n{`ly;dMVsyKo%~~E6t68u2BmBLy`D#HpI5(Z|ByEJX<{4@ z&-Lsv(;K&{Z3}DOb}tMy&@odiKlnF^>UHuP_J3T}QE={5=U>OOU&|8vsvI2e7A(va zUYc&V>V!QmCUd!dEtC9^+TyHWuL6TJyT#eF>igI9Ix{+3oW8n3#_I4i$16n^I;)P3 zTpjnH83j&xy&EdRSFGYPzqs?$Ctzyru!Nb9jGy{e^x31~W6#1#+sslK>Vz%b?=4v4 zEdI9-uvfO|lCxR*K|{gdhKaOQ92`E-lY9pgCfjuN&hXl|04zxrON&4LrB~@2ILxAN zc&qD8L^a(&$!h1&?eO5Hue2jUGvg|H@d{6#Kd7+r{0MdHy(5MRlK!?XW8mwMp2$XP zK>l!F2E~`R`6QGZ>h+B<0bVawTCN@0@bROTnOXEJSLCJY#mO;h3sxxBoRA~>Ux<07 ze)4Yp?}3KM#934GT*xs=T@=I^A^i_0{yVr!G$Do~4)mbi>zVhyGt!6N!@gLH%eGR{ ze`i; z6IWKw@rq8W@#-=yOOTGfW2xef7f-9JxJF_8n$( zB+sGn(xkxuLf3HA(J>UOKMA|Bn01L~Gq(z4MUtQ5qs;2OjKaJ1=Ob236P!kes{B%vM9VYE*RY6!DPZm6I0>ypIEf z3F^ydFEKd8INM4Qvlo`)%dh%#fJO>L2hE=erE^4m0L}F{wcygd3Av*XWHHd9Wvtgh z-lFLnwh>zmIzNUo+ZmHJ98ny8WH4~!mc1BwWA^Bfx!GTE@$!$!n7~)_f1qN}ol!_> zKxQXcG8QEWy)#YIpe#V70n+zKXc&|X7#z(Hp@6fqvkzk4aEHwkyGoqDgT$og5D;MV z;dA>tJnVy^4(ED=@3TT1&Ok&pD4@UN+lm<{Qw$Pj89w0r%HO=v$^^fvq4xeJ%pgV{ zo9E8Milq&ayjBmiX++da^MQXPeroHs|Ee06xmlUVCxS-*1O>&9U5!T#e*{x?%MvJ8 z14X74^oI9n>qTp1{Gj1eqPM!aMomq6Yn|GnGyBH1G6++mJ94Cq8vGhOdtq?-+q-p- zW@ndBi)^cV++qZG%$CvFOuLpfn-$LGJ6@65C(x4P_Fin;X^Gg@mv`K<>7PH;+rJ;e zAos!$4QioAG3U%zmUyL@g2Q~RSAAZ*HKs+xKRoiS%~H9CKHjZ4gs1(Iy88Fok~@4C z2I*`%R8i@vR0(d$ccOZ~tVW`w#D25rCk6Fx+?R%FVwU?0OYSPO9hNtQrlc&<)U5cC9W0h`{1_78rIC4xq?m<0<6EB@LRhyqhfdW7T( z9+nA?+=7P=VbWeUJ|L-0Ep+^@7}O3jG-5B6c4eLYVl%8juL2ge-xvvP+Qg)& zsCcM&$orR<22WgE9CpJi_l}J0EW%tHo2>rR2Q-5pbl9CZp)MK~#7keg8XpR!^!49U zcMvffv^#m5e?dGZ#XYi%j;c9<7{m`#ye<3rHlhT3`}X0%K}JxZ_}X(?dEMGD~nlPiUO~)v}d4$e=J6#O>02f`XR8Y?7C}qjamzv17^m$G!Iv zcf;4JxPug`BZzJ`-%y44CuH4DUVM3?Nosy$vAQocPh?igG~udpc~RQX6~d=tqt!PGQ3L{=rY#Y$N&g=(KTEujT4 z`g{*h3N8o^4WkqchH|j3tB2I_)FP9Qu;x`8_@JOO{`L)UZ>jw>qdPy`lbXLt@j#7OG6rCl_YciK0BS{nUkQ-abqnL7qum54jilF2>qn0WfY zdtq@Tz;p;QFQb!>W9Jl*8XO-Mf9}dS^=HS~!#YgJD2u2HZ)oX_Ki1#U%g_Dy_xGnO zM>wUHbbfjJRY1XmQ`fLJY?~N!!|U$N=O421xz%-Orrr75R61yqfSAH;qmhNH-jdZ=u;V zi@6m3d{?Ntf;4E^**{zlWII!np+9^C4|IRqna!p)61{P$f0AvTViIDV1eWmcR#40rY+!IH*KEGjB_egI`Oi;}c;NeE&k zlTo#SwD!-;6vtnJOws7&CHFa4fnZ`;`DhHa4|H}*gk;)`5XlKaAF?>uF`W_e3w5As z*h~(Vdhc|9C@BqV{xA%kNvJAds)C~83a$frS67jslsAhgrMP@Mzqxmm6(bM{<(Tm6 z*HcPYY4i<2A3r{c3hDa2;#Ys;(36@7#71}P9EOcNCQ>;OJ|qT6GeBkw;QFv9A|L$< zI@ZR(i?>G+OsjWKg$Ei>e{6Y()JJR_rsc81tpH3iP@A?{3@)2zgFsI9OD5d9rQ5TK z(z}&t{1u712M9O<lX0(X0L(Ufe|XZ64Fq15&Z~ySrU$LNdOv`o1cU&q9QmQkdXiC#>Ra_`Ex5S zuDrUMS(FkLmEFEw`w9ye$>xEK;2Q6;u`vk$KFpAuM@J75_8UWCx^CqJ@i-y8+~(A& zWdLX)F(biYfJ3B>lu3?iigu_5J_p|!=aF%Vrw4fl5U$#Aa0Ozg?a7^iV!|&eNqBFx z7#-3{fb`fg!P6trN8%aCB_NOk zmq1=t+C&Y{ zKn8VP*#I_^ggooIrlyLm|e-X4>kKV=l7x#H@ zrq$;8k_-8-82II2zDDg%JF30GyoXkXZx|icGdhq%g$n7yr{sqjv?v)^@mMgRj15+p zl?)c}RB&H{=<>tTX{h&5TWEL6n_)5((aYoLz2fUjhx3%t&dx5h>H_)IxVGJ~ z@|{%ydql4uWG|Sf>t=w7XLMzXZ}@lC+KShzebmyqMk!B@dV>m%osgi`+%ru_*Iu}= z+}TT_)a_3EF8|R_7ykT^6B$YOK5@c14DL`mlg#J2pVKS9sn*9yi(YlRq~dn9E@HfU zSwZ*Eb%0>3{+?aP;P9a}*vDb?t4#;qeohq4p?~34%CnzFgPSG?ry^t4Ea;72B^2aT zDTk&50vM9h({*SOS1{@j>E=j9ET43dM#Z1CUJPJjpKm5%OUzof{d$zm~eGBB)Ngu zP`->&CmbC1!Uf7o4GXpwI3-kiRUkbHEq{>{PMcpC*<3k`2}BanrDwk?6?s84-wgq4 zDjG(K38YtWO+VlEroP_LXb{z~ZlPHP4uzZG+AOvg)QcAx?6zLn$ci-k35n?mSjb)jPdIfLi3~N^T$*p*jfv4B zwFdr=ZA#it28iq8++|PC7q9+e*XCOKY5jlvf5RUv9AnFLC|M;ID^{pwM5B9^+#)Y; zcE}orA}s7RPfxc<&$J=0QOljo9j~PTO&u{iiQU}nBxV$ISd@Z)YVG@J$AUY zX4w<~KF|Z&r#wKyj4Nj0o1)KrA+*N&Bcf1Rp}5x7W;g z;}kzy><-?K8`b89V*I?juqB2Lwk2sY8OH(iH{yZ$El&y6kEnvQdG9zv4N_bouM{)- z`X73+yt4NSrFP#0*OTT7ucx0Az58)K&*B(ttT+Z+&VMYmhZjXNycEAVSq_5+)8$ZM zCd=W5CdsQ#mlfg3jU-UX#QAE z0Gg=A`xAusiB3J$B(u+hTg_YLV;ADc4;?|pMlsXMZ)M$@-vOf%H#c{~7E$+ndRy4D zj2suEtVQOO?y7nn+9kbXM{L;6eQ72);0G3Jf`Kbbgi@}}Jo2)rQ>A}kAPVOh%bAyu zg`U7uefEwnf_RH}z6T3uQi#FIVxK*_e*EZraTRB}kwwY7gnHCJ3|<*0-%+=_@wU*% zp(y$Ql3zk3W^WC$rqUA!;1n~1TFrZ#)M%?3HlPoTnE^~C`Bz8kUqY{JQC zPEC7~cKD?=lG0C^82}|*#>Tc0fe11qfB%ZYw6pim)2xV^6Wss~pHXMNJPYDiaQ2B; zz7U2S@BFeEz#AwpG};DKEPex!iPy-ML?l2I9^#r`N`(l~-XYl<;SaH&=>!%O>>$?+yVC{~Q5#@mO__&W{nRwxUam8j=ccwO zBC?A72*`GcW;X>mdgCbt7e6v|=aUR*cyQ~MJyxRH<D?hrjOH}zr>^T8oZ z)v&T+-m?Autbow&H@&7Zy`=vf8u- zp!G=~7%E-%q@Wih3-hUO@KD`d=5lWJr*~g&NBF5oSG+be`x?W?@veN z%lobB;}taomM(3dKHGFPBADw6#(no@Q|5D{*N>HEStlr`k6!i*V;A=I^;q1O_M?cC zb9DT3+XJVl6@v0n9nXyqR?Y3}ecxYqOqTnN&Xy^D?;|9bkxcI*p&egz3Yx*9Bx7f1 zc?yYp>Hwm9v|-gC(2vjc_f3c{!EuwcI|#^zp$=ebgDL{AyM!}GWQbQOQGu><%K zEAj;Z;Wg@N00F>UjU2zQxs_$aTL{U^5*oTzLcWkHIE~sDtBux?#15h@`VJo~i*Y~q z7hw^RC1j}NyFgnzyKCs3l3+xjFN6gjka*X^{49-@X%>w0JqawWn8ni#N07J*?>;eJ zY~09*U^suIYs(Z~zbH&%hk#ANS@guM*MOGTx@r_5q3R-*_~eQTs>1)|QPP28Pc`}< ziqGEK+In&JuN0yk!qBV(8fD2gRGh0m4p#|*GXTLhz>)R5s3;lq#>I(#E^Gt-xHOv8 z(s+1kc(Cwg#$DE41yGU*xB`Rtc0D?j*HGYOO`v#m;0m5ovN&aEw9_qAHYuZTp4z%Q zfV118ntT#sQE9ZL?EK8fBds28+#5aBJwf;UtpaIzFo6!{dEyO$UZ0E;KoXZa7!2U? z-w}C)JDiAnl6(naPmMT;cA4c0dQZ_IOez3)0i2e@;Lb?a28iR7$@el>=4YwN<7i(a zyq4R->T5ne(6RzKfx9tlfy7vlydDpaqk*E|g;=V=Wqe2II+|O8X*gfFpo4NN0^J^N z`=ta6(_Uj z`VcVyt`HnXm4tMb??6f)clBQGA>;(2n?873;cSIdNDxtPmH0VgWyVH(6g7t5Z~rU= zfKuyDzP+_;5ihwC1Qv{l`~xyfCaB?iUeObUBKHA0TJ?k_SxD!6g(OXX_{51b8*zqY zYS%h7@8y_Nf&>8(@GT_L1!xi(!g8kl`AQI5eIL?T9hx(IQ5vIj{qS}hXsjwUzyuv` zlxzAxeNbnHcbM)_{+S?D7Ri`~hqtj3KjnfMO2qbsLyb%`L0*gV{EYjpx{B+oBcV|{ z2JbfZV={#IJ7$9dR`~&S8JUBCappo%HL@_o%b)qofvUv3fgoZbh%MHBh?ihDp1$#Y zJs#pZ-AHX@M6IrK@ZiCu+Wh=- zRck8`9*h=hV1`SmEy;c&A|yn_5ar=&3xOb{Fz$|}8@E6flEmBZ>`?9?@Wv$LfVTEh ztdb;5(tvg^2~aw*gk)w?;y0qA$kro_k3o*a#lOFKa3$DP*m$KZLU8yU#rk;mn}7iz zl{2hMRe>J)qF=&c#fQ{JmbgqLqXD^O1*;rdBB`XP*ce#k*)LglVIiixKHeUg$H`az zA7i{c4Ogh>tW5IP0o9uME-|PJda0pE+pBtrxtmp z*TDSw#e+(NC-54Ux)3H!fl7P>A(FB2x}SLwcypJ@`Rss+%?t(8S`@tFi>NVs2kv|iy}@=Y{;PQSmslugxhy9DHvc*piNYe|<%2|wAu7ZC;_|=ZF?-2;A2d8m zp>JX=vwg6Zk4XhyzkkLiDIXwD>UcN?3c|yzg-dh1U{0bsPg0>z5i5kQmTOP|p;QX4 z-OOaA0h|MudJGe}6x@D>sAm%`4QPS#7%ZWAyST=apk&oRCa{1o{kO^AhcVU zA)Ilz_p&e)yV&eVmJkfWViKlk-AFJg5s93blN7^V^$N(`W1MD6;nxlE6bZ0R;#A7N zmFtK`k(2?TuO_z@^`S9pY+Q+A^bN9Yqx&K4tpw-v7#`Sig_ry|K^I^EI*xB4;kcer z3x5^i3ZS7X@cg*)`QWF+>x223(vaVAP8Xm@5J1^(&RYvo_iNyNRD3Sz+z<58T zs2|7s`n*$m#}NM&@hKvT8?n=N^zp5@SdfYBq*C%ANB zh84jFye+IW95L5Wiu?ds&NBG`S4HNLCc9-%5%!$m>`?Y(cuA{a{I*70zT*?r|Ga2u z*Jgw*|G4?ca&8_v)-^O^S^n%RT8)RRjT~qnIMk z^K*Nw(nHx~ofAjp(hZsR-ekCMK=nM;OuO$Icc2@-Fl6o8zB|>Z5tSmXaPwx_ucmgz z!b>rA_o5PJCVT~yVdL4>GSJP?{D7%CJpmk#?d?XQfuz+u_~yRN2-xh_Y5YTtqwl ziN+<7k=-&gVbw7{(X(Kv&vml#Y5vcAkBb>sT2BM)~TE)-$p``_9ndpi)U~Oo$ zNK{QV0gA^ZvEhzFj-|o>lEZtyB;Ir+Vnx?ZLh*ujULr<+-#!tLn8}fnMBFohgUkfW zh?AE$OK1{_AiK^zhOc4+WX_F&JACSBJ_e`Ve zetw>7pn(`BW5H?$YR9%>Xo1F6CP!QT8@d~IA2<+;=vnGe(qSm_8CuOG%(GJEpS}e* zmSF`Nqk3oL<7KF&lH)!E-*BVqT(}3$~w5XlUXI|N-Dy`BqJ%^KqR>I zU2Z}bryD0{%TUimO9i-`T%QH7*|-%=VHB*~jidg4e$^<3%vDb(r=&2}N23%n%{;Sf zD?74*OdvcYunuD(Q0@=laU|MM-y3_)*+fYZBP{(|JJrt_j3$D+;y%Ae@Qat<@rCig^Ks%eA;7W6`yei0t7{j3-6m1 zRO`Q+Un*Xb8lnmPg5RXb6R{T0gO$Sn39!2cjv|LS7275GZ#N*w1e3x!9z+u;?Z&yT z9Shtw66I|Kl)B4G3V_pVVn)RRPdL?A*NF#t2_inB+#>9XdkhTr9}{_LWH}5Hk4%Z`PbhMdX&*g7HdC zwXv$@B%>+JZLIXy8@WEK7Z)SXqIV7cu*FCbU!nV&pm$=|foUpd1kWrPu-6dXbk$`@ zCnYn&JyrIe3DLjjtNl?SQxuVsCU*i4-V|C;2tu0DJMLy!*BKAb>9y6$>93XVicl&h zw)f043mJ>9Bq4CbeHH|3`O8C_yNoaoQmA4cPrD=g30vDV8$l(M79Ynz#XNYp)_+8toDIOmR;uJiOJQJwSr#eh==p9v)x!Nr zQIzdi54O~=aC84nocZE?4f0*+D`Oe{x|x&r#_Z(%X^c;l&(4)-JBak5Q;o@2v@b!% zsh@r1<)Oz*_hXFZRjO=hngd3(a@@V}6bN%@TOw+BKd&)_WVES7OMyyVm1jK$c1;)! zadUKhtWQsl#`=A^fE&H-Y{6Xpp^}{kznT=f+NQZ(W;TASuzJlJ zZ5(4}{qnN1LTEqsXN>_1b8sJy1^l!o;}3UmH&w5&p?BoC^!0aTz5&lpSIr+TyuqP= z|L@Mr+PcoN1*_NdtU<0xOU|;^lDiScDa^mV-K{J7kS8g8pRTEt3`(Xc$r9fON8lFJG&nNK_! z#dkXKhGO~dac4((L`H^6TwUz82eG0mp10EXsZOllbS+}lCT1Sxo62?q%A9hp8)9R$ zPZWh~%b$&JF-?L}JE zKi&i^=iRM)jXmIbv0Jappp|q}gs7LWRA^m@{8|1MDpo~BL13(R6(^6yu+rf|jcP&4 zxk%}p5M(jg^`qBr8QlHxW%r392H}7@deI@t-ueeJTa}A#MiCO;&n3K8nmK1@bSr>_yvE4J`c%&rm!`sM*#= zQyKzl!f#!4rA>{hlAs}B4%vrx+04O8$$Rd3r-;eNZhMUY|LN&X!E3Gp$1?W9LN=GQ zMluDx4@m#jN;}=~7TItDWqY}|Hy`||X)8Lx(YW8*IsrSqWOcNZ&Nh7d6{ZmEa-YB?yPnzI=g;SNE~Ncdy@;<~ z?sZ#c5@G?CZ%@!?2AfeD3Yu5j8;vazmuLi*l@r6o-n*tBM|LW(Lx6Sztm?9vw4$K( zZ(Rcawb)SQQ-7<66jEz`{QhmVp{=FmZTdNWWWLdhn`f<__rmOkWIB&-q(Ut#DK+;R zs`Egu&sR)E(VC|_9fSm^CReG=n}g-OEmz~Ml1I;-r!;+BAKivlmLT<3J!Ba-&`Mf<;s=LyQi>Hx`AbE040DamN$@`$LhEK@2@5uvlj3Q86k*>jYDQtP)JB> zel@O*An}->V~D;z=-Rbw8TTL>E>{TwPZ)(I8wm(55`sju*oJ61q?sG`Ls^Sj$+UV+ zy+7sT-`v zW@BCQCk=foLuY&>h(J9!glYiHR0Gwh29C+-w%zRAzP$zp_rvN(-LPC7h&~66vj$=D zY-|$Cj8Oqo2AO|?hZHtk(0-biw-MQ0qWGu{$eb0$C5QlXlQC*bP;w7O;5X~%~Z3IMxl zyf{7l-M_L3vThPI;cG8dngHyP5mSl`Tv{3O;h{^uA~A-|3xZPmvI#?LYwMU_ccr0(L!q!O3lY-y{Je*#Q70O(;Rsa}>nT-U2=zp7O6>?=Dc9 z_wL-R!YcT+tsf#1z*Ax$?I1UO`oeECPo)&n5i;XAtxbTRf4EMjv|$lXIhzuy7?PR0 z9LNnBr|})RIlO%G8;hY6#r-j_t-rO!U-b?OZUW%Nj6%?c#IuYKf9AXyX{$u_jArzj z8XJwpTlF)Of-dKG={K%uEkys(zBq@`ZEZ2b|YiCq5I$<~p zG-Wf3)LeTb;|6>(-}W=KvWZ!27#Y7=p$EUE9&p4HEN1 zrnu0`fbUTdy!)o5*(MFhR3Sq8<)sPwvajiYII@Jsuv|#aJq^VB6F7GI`aC-l9ztJ( z2g$V=Y_U4ZYoupOUS!w1CF`5N(~2b<0|XL%(I&9B#Xrm^=emYt8dL`Ipmx>?Y+?FEAYi8%^45;KWJAqne(Nuk{!b|%qpzp;8bL%E z?O3}mxQ#S*h|n0SjJf4AqwuTYKRAzcO^) zZ=?*WAga=rO>WV{6UbBEG$gqsM%|%~1lZU9nR9DIx^T=Sk0Q7R&ac*CqP=C9Lj>%- zQFs6PNao4_U{d-Z5H3v)hGK)HsRJW&?xo%fkccoMjKj7HOQA8qTHv|gA;Nur?$-^! zdw8kfd@H<(|sZWqKN8Gx_Oe1e8?z-&7VH-k|Kt>4T#cOQh z7zu3nXBntaGAqIl-PHNU34+UFm%vY?<`daC;gax1l;P|_j@70^>DZTziw&WaBw2^R zClbL~5LqIObPXk5mx0BTS@yK&lwB{13*(IK5d?X}fkx{b%!Tl&49r7g;;tnC$&;Z+ zCAbzY?w>?5bwqx7Lon&P4r~E%?PWjzRwq1}UTZ!^5fLfii64NPDsTh8BRdl3C{Z1g zVVaMjcaSJ{>C#rZCZdMr3s2M}MROh!0#eJp^O8Iw8(Bcb0gy_;D~_QE{CU2_Ujdw@EFwXTt`wUP1y{ z1Dki@G&yQ#$AmwJ@{$lZ+{))Sd7bRH9=rH%{HFOsw4GesQ26Ud7 z>Ht|U>BVqQn0#_~g!3z!>>_&uHYPe6WOoELB|Hwk>n^BZiT*7&R}(GRW1M>U2J!D( zkIHg)NbkGZ{V1G|DdSdAC2=<@}Hy< zK4+$+n05!ijI@Dc^F}=2DlbAb>(`VIk}9V0W>>`a!PBFvcfsU>oPBY5(m>MP z6nHj8X1~%oYp*bWUwFIo#}AE7d4#9(Uh3@s#r1d|*aq@+5@I<>}MYdweq``CO3d(;Vb`B#>V7Q>sV95_a5r4GxsqGsp z%m2_8|B;Qd$bg4Zp~)5j%w$~NytEqBidI%u9BbE3S`3f_3kH4Np3%H7NqEg0bF9@M z8iLv680sUEAJ9BF36!FUTF1@J483jMKI{w_rOHN2uno~=?`ZTivvd%0hyg1m3qbYd+3-?n-IF`xP!KjFc#6(jIA0jb!NQf|$#nvPxkkeB)S~WRk zu;GeUoXB2Oymvp#bw;rRgBZ;xy&>8R&{lh zZWB<$ERq}cce{t%CO{MTbww29B4QT9e8{i}+!N+}x_GJm>r|eC6^BkVMBDwi?6Y$4 zLMsu~;FDV@sNk$_Gqy4Xs4Gh;o>)*AlKIS8fs$~2Jpi@=$2A#xOB;-%es^`5Mr%$v z;B8?<8KAv5iCIp3R-Z7D1gH6CXA81=AdsawI}Rd{SH8glQ|RHD*IHc5Cqto*KYW1e zty$PX3jj6ppYNdOMvQz;bJ8LF8Z6j!gO^bKYF%Q2t4{H-AQ=G-A0H6{qUAVmR7HJ2 zl@_cNx`qp5eXyX#Uv?}a(0RNSL8_9xDkDGy{wg!X|@DlcB)k48}8@|uVazr8W^7sC&bcg=H2#tOBuyFYq*7*SfH(;^yN z6jU(rYoQ4uiggkhnbNn@6CJyTi2dnTh+lr}@KGYqbuQ>GE~x2?kQE3OW8ekt&s~sJ zT+`Q_xe7}v49Flc`cNo>XOcyN-&9pSnAdIIkk!m~TJ~qhrrUk+|Km7K$AE zd~s~wX~u@5B0Dy()EFk{H(N8UJ#ij?NOQLE0<`tW% zk_pWy?p@Em0P6kek6CGrZufD5gYpUAKaf=8(WxU9-QhnqEW+c!Y4HRc7n-@1AI*)H zu2>gB4j(BgHi#g0gM>kP3j_8HBANj8=pc}y$~3BLc)ONrhv_kZi0qtsfoQ z7P_<#5wtn3v)JQ*6)EW$mBg2pmcCjzR?Q|>OVk`FRJ^<&xQ%x!TlAI+cK4zKBCH;g zU(4dmc(mNJVoaN7dCP3qMe|jo#ty^nK!GlRtIz}$Weh3(BRi3zizDl|37w`y?xf-K z2m^)bSA;&q-_VBEoJIplmvq&BdIJglo|>8>1Mh+7r1}5y)I8^+{jj;xRF|E-hh}l|5jYhI+fZx_Ae#bDrH4`eMRg67hiv86IY3eZs)4MzU`c~&AK*2{s zo)N2)>U$4`(p4P_PEf3IT0rj?#7~mF2LFb7hz<@%nUlY7j(EMQ-+&Qux9Ayv^@yza z%hxt42`)>cXg!v^|$C_Smf_7o5V)!q7APq04_RRIGd|rL zkE~oE7rU5uVTPL5z3T66Ccx2H+;hHAKq20p`AC*Tz8-c0A{N0*q6tkBQeyWlDdGyi zaTTxPabrOZ{Bq3b=tHH^!V^FWn-6VQCAGF+%KyjKn}=ihzT3l3BdMgKB9fv+DMXPd zqR>EQQX&db<{`Q@n5W2?5E{%vh=j^eA(1gdNyt29p5Ar!-QWH0KYsf-_Gj`w($(b~iQi$GUz4JHEBC-Muk%g?u1gRnx6*Y-n z=)hHfpZfdV2of-uJ47n5hM!hXmb*6xt>e8^(_aUdyF}e`5#GLrQ!>A(b5qIiFrcq$ z&%Q@zg)TuO0?D)V?iX?=Pm-zgKmH9SSk7J^- zye>$GF>da%Yse9X|ck}G#;aRoj@+F;v!V6iNVUltC4q6;*KgY`y!Tv>R(;4=S zAT+=ZI_uZrE7(wlO#Q2jlPbn(z?Hb!{(i-ywPk<~&n)&}OxUMuYUWNYFkHfg8IDpMXp=L8wi6*rMgJpN!Ceh%kXV?H(D~x>?vfjI-c_ zpTy2U@!Az!d_HFjsDToIT&ks&mA-y)?nv+Gs}!2EP{a|3duW^2Z`$OBsBO|3Kl)2Apj;0UAcTSqk}`T?Cz@ZYU^Kn5@}o9|IyX>T){WRD_5R=HqFA+maS**G1>aCW5-f> z4q=y`S{NtJ^b2LZV`7gatXf-I;yaXXyFW#WlF`uEj5#2Mr6k7yH47ToaAWV^@5~k9 zXw6C#tq*P&ZGEN2uUGT5>LSO#Vn`p@9|LK8F}pQDQA59G4fC1}1pL4>s}x<#2qX`L zjT5&n^W3`^VU2sru+$a;JQs`&F^qNrz$8u!2bBl$50S_KrF$3!(u?*^dKnP_6)~K~ z=%|SEbRFB0-_@sS<{M0bYcX6fSN4%`wfLBxks*!v4JFYMlw%mtugvAttwUkyiGoib z5(>;&=n!Hr8yfO7N)i4LQwAn`zEK~s)X?21W{-00@$dBgZ^D_7L^n8bxlCF zL)mfY$)-`-Rr`>_L!W)C_AOSmIiHkucttH+6xja6<&^UAqPuHX+3Fv6bzxe~cb7+V zgRf*rqk$)fj*qfI!xK&Umx@#}J^cn5RaKwun@+sDtFXVOPiTm9F3fIo_8sQ(aj~ma zO_l>KR+Ft*O)|$gEbLiDJ{zsUUoF2DHD!KX1XcTga`1Fl&4h!VMnq%+h{A7txu36rMUSBaKu z6IYvJf*?1n(BG-3=z@?e7R$0QX%ATaP~WxI4%H(b<=4U04cEq+lam^W`GqKJJ{uza9?n-wx$t+e_R&Das_=#+QxjzBT(iG^56H^VIQi}@!$2PdsWQf5_p{*w_ z`NG+Q+1EpeBgQN9)<;#xzCoMmx{^`lgjN|BEx$uaD>nwV8jUo)Sm6^8QoP5U^3o;r zBNWi6S(8sHHcTlUdz(W?gUl|;*6fWaIUa@DakRT;<&KSJ_Uv1>cz~8W?5qc_uV_aW z6sc)%aQz6YSIX_P_>1-Dff}5*|8R?ig$2xDjXjZIsa~9Xjwg*%R6g(bCj%rC(5ape z>rrZ0|4I!Xr+BdO=NFavp^mw_&ojBnDDmDO8@k@d z`+VJ7$gAxhU!2pCZSp7j!nxP-?#??(VUHa}EVEqt2lj~5N9GSCd~~>=b2eJi7th)| z>u3;&9vgiG!|$z#PfR4)eEP5z$ahN&h7)q0k_ZN z%!}TiKV5L^HFVmq83u{;A3Ss@EnxlgzPcoL;D~zIQWriHLfKB@JeC#+ElzoFvjVCKvj}CgE5RF64kVs-bkn_5f{^^i^ zwY5yB{RO52n6IpY7^MU=S+Yg|hZnU43qGuqTp<32=%%@e!*m9` z#>RYy%*IC4ogbuLdA5Mwo$On2itN@MMcGf;9yrI3AFZaYIdsr+wH<6 zExDf%2~qA7(mBre^&bTssF;)ct~+l46`XGE+O?#;eDZ`WASa>%xB~1=zbI`TG(pJ5 zva+(AzmKu0rO@pdf-1!8#v+6{Yqm+e)~?z2DrNrW4wqU4(w{x*_nVlSYB;#9sm{1qEq+)FpBS&|-4Ov0 zpc<}~#Sm0w?cNJm&vn_?jyQo_74E-r z%j-*xMkw^qo+$Kn`*9DCk`%Dzep)|uTwdO@#ZLy*8P)jthxv{v0N9@(_7lZ?^fd5z z7I5^2CnieKcfjgo()U z;W6^$=~Lph_6|c(vit~~Y<5Yf*Y#VCp*J2|e>Cw{HkUk*gmu_s1D=8&GN(Q8s^@1T zKfl=71_g(!pG@r9}kAi|!_rYlZz!TZVrlF{vC99%B6bA&B2X5;DP~bgwT4BxG z`iKI}plj`;AhcpTD8Fd0`k5p@hMkB8karVDKL{}20U5lBi5-osds9+U+CEXH@`@%G zVCdDQT&)$5sG}A4=X0@q@6@h54q`rqs4o1rmlBVb+{DMncdad7Y^afS{rV+3I?Zf4 z7pLdB{PX9z@~V<7E1XW@cG!2;R7pUQg zNKp9FalVT`Z8P*^6a2E~W2HTAQkSesxkMA?rDs z`eoR%R*_Xz(_=tzK1aHet&$><&rE>b5)JrDO!TCWA728dm^c1S9Nqz12eNSZ0hdf- zknNdDBQ2m^7-A4LJft+(>K9Vswi2KCB;22%{>8ica55)|d3Mjn`4kP&4t*O>r~>Y< zqBc%d+(c1y7fmV|dE(@Q)5(lIWD&JPv$L~fLtq)G4PHwf{UTVc^^}>zB1K>eE9*!% zO5I?SMjpo>o6fvQB&Y&%C85~UT!wkmeC{7ps|NWPEKwmLe%e&6@_q&glX7Z)~hdShRtMMEbrdE#b{^j zTRol*n4mvK_~*j1H_RC}P`9EVeCO7V-YuTwZ0MP7x+lWn)cV-+Id-FaX{iG#^wpMj z2q4!UKb`Gm0^XdEdM^gKxbkuCV)z^1%h(IUT`Q@+`gxulL z7y^wf4FTKORovV|^y%UQ0F>@hi$IQlm-25=i3C3+-T2$C1?`%n`9j?~V99FI(aQ$; zUg1$}x-NFC#8eth+_cRhNbPV$Q&cI(Ko7siHu%|+%N`%}5uBQymY-X2KlV2d0HjWM zIxH}NZp)pynjjaSkU+NN&E+f`zFnwTe)|$uQ^rCfJrpUW+<8)X-y1g(NTdKlgly5# z)cm-07wuW$haWjA@qeg$8Cq{8RWCV@vlKR9?SSIo&?;F}t$-k?;3|f0NSTl=i4IUn zLxWD~yM1!9@;&a@!hz061L+v0!K3A+rEXG_lao{$<1>-z+2_cwPSMQF%pSgte2Rb` zFw~zi<>yZ_`Ba5t=Wultvu+G&Pa!NCDNNP^`yVWHQe zd-nf#axXMBA_4_i!ZQ1yprGxL^c*vu56;bXcot#>NC*?+s3q+xzfooGFPz(OtS5H{ z5qnR|DN08@Jp0)8pOGhFyN!tpBkdUK+me-`d@AZ^v7l`k$P6oJ=p^r%@Hg`oveWXN zWWa;fchAabL=}di{Su~2Y3n}W*-ejuX6tQJz;^@NZY|4;adJnXRrhv2X|2wvEFHrH zMD=F_3{+-LaU^eekiTyE(mS=3pNwI7V}bLV;)C=FYeC|ea{$+_C2ybciRLUaF^uUm z)7E~Z`Cr>1G=K$G|#MW-R;Hr6Rz<^(dFMc0l0sFH#6Do zg_l4~H^Fb}`mvtDbqXyA|D5QzhKBAcz?)>Twni_7ae4e#$DrML`(d3%uF@r6q!CYva%LPZ8EH+Dsbi-qM*7l9(6U1rp0%!it1=#GpW@4fb&K?QG zm{YjIh{4|8-Xm)stI$jEQJBB%{Ac_={nAO4!~MjI@x2iyZ^d7kHuy>HkD!*m6%9tc zFDe=$o?_1mIIyiKMLg#Y>Z(TTj?SEW*LdE8@n`s)Wy_Y2FCrHogGs**`wGJ(9E4nI z?k1!aE@)pr$g51&gHZ)Y2->E!t(!NOP3Gi>-{6aT`SQW$mpwofSR|JOM<0(DH7x5H7AR5mq{Vtg`cYAnFC zmnb<7JgieHjE|=WrYQ6c-h3PSmTQX!J~@cKk!)P!59s(Vj3wu&(rEBgxd#@-ei2b< zV{w_2Qj#rVg$+*SxodPe!OeI#=e&|Kf#O*rZokdX(7N&&?l`g1J}|MR1cvNaF*0@q z&(%O9I9$3&$_n^&R7`;gOYD)48Ox3U!7?mu5Uu{gmBD9lr^Y$#;X{=VVu@O@g^Flm z5`*MW;0!hX{QXXIkc*4Y^=4SWQh;KSgwt8nzh$@qtM zez=sLzNdG|<8S@PlW5`kVd_lHdgOb186M!oAAPJg2G-RdM>2Pa75-fh0KTtvY{nW@5k_W%8)d z8iaWupo#B)ZbEtV%g_YIfyBX-WI+^c48{zu(nNwpB+byb2-rR0(}LYjxqlkoR2&N8 z4INQOsavIEHSoP~z1p&G&7ci#~{zm1EkQu$Xs`h29Bilk;FveuXxf_N*acv^z8wdJX$~gn7)+9mE}bZw5S9 zk6^`y<={R4`Sa(Lb8YFG?Xi*TlJZhm#!EI|qK3O;?^(+4WM(Mom6k2s@12;)2f%C? z3GEju%|DIlJ6&q>vXS9EN^E;;J*Cnrm#mWQOTiB9_^+phW6NITn<+Or4)*TIJIccG zbim;KmX3in*em^;k$&6y^~wq2Z78W+Q`KU-{bRdVD1A3K2KR&bs5CyY*?smg75`8) z@IO_O9b zD$Vlb5ic*VM9a%ze16y>dhg8bC;;Vwg1r1h4t9>XhSL{71x@XTS_@4(v zU`WJs`^OR&#AwBRfBpUZHXT_BR@Uq~^H+S;Ka6m6gT?zi08FFQ)X`~54D#`z)*x4W zgn}epA#1Zj)H-n3p z%z0}b1`SHR)AKQ!*c^RhJ6QvX?GqOVos-Zafz%sxSbrEDm3{Yi8yg$Y<`k}PcOmNh zA5LRyU205&u_HODSb-P4#=;!5%nxRCFzZ(B|GvW)7!kUPPY@lv)$X-PIgJIu#f+-N5eh5GQV2jKQDEIB z)cDHgkI-oyb{ORzdU_K^&ej0GU_G}5loXhpt=hPe+DHJnV?9|$s56{a@*87u;&hRu z;ZrjWHMKg1WgP$!RP*j}an&9Fd-y1P+=FYF=$Gqfnmm?t)*iGw8zu26dJ+YKLiv27 zN1GJF!Os$}=MsdnsWv(9Bb;I@v8r9oA z@kvSLX6uv-;vVh5;F|wXX>)UgR9bJA2?Uw{N@?wNvdbE=ca!VB(__g9KN(mUwP%Kq z9u7pDVgN7{@oOZu1z3Z@ymjq+%;UjiBkdBX%g_bfL6;DNf&=DKt{71QWp%i$16B!8 z^kQw@-PJ3DM&)>Tv0d>%dN+b}G1P+7qEje4r@NttfQ28~*C+~pHBJupnXd=g9}_Cj zHV%{1y_JPpT7;Pn-(KhgRj%j#1?`_~qy)G?s7WBHtiZejmSVr~h%HYR%{-=Gz7(Pw z-$PF-s;b<*y}dP;Fe`}*kyd;EX(=!h3ebX+uS1NGA@UE1it@uX{5SiASp~-6U-(lJ z6A`k=WX7dNiYkV0FAti?BL5fQ@Q>aNLX;X-9(G&D>4!P^FVN75;ll0Pl2MY*49NIo zQ-i<#4PenCru82C#*mQn&!>Q7Wx1$%QcljRM3F5xSrmAD9G)7Sl!|~&41-Qj+GGIS zjc;!q0vJ&8f{v)%i4_r#U?e8UV@qd)f|x-sZwIjjt`pMe2yxQ|v9N&{VqIVSxWiEw zqjwH6=mpg3Jy3=>qxL~P{wP|o&W1VIGdPWuY$kC?iWAr}6W$$Ci2)KnX!U89_kJPn z+~L#!n)MD|jX(SPP?ulaPGPM!fYRNBAdK7j;qzw&b4MTw`ND2Wyu>^Z&H#4-9+8ia zcX0UTPZ|J9FuIephhAy3VnCa}fe*BQG{&HRen2S!eKi{(S>hB5>iM0dBvaSV@rjnO z4gr!&u05H~BPz?FZ}ak|yvcTS9FEJ7-8;F_qbu}`d{4^~6JA;vMs27{%7&aV%Akfi zRYQRt6AP=kap=1UEt6f`nx6RkU@HvB3fL+^Z3LmR14EqqL|DIet9pz|EvnmL+~6^@ zJoKZmnaq7JbW}pZo^AlRk*Dto-O}pn0RPAATeq&p9PIJYTbqD%d@L`Q0n!IwDMlc# z{~E`YatH{&7xy!)UQN7FU`4V4^!4{hrsP(FVNOhGC|IH%$HAD6PlzQOkTKh!6`@K0 zikg^gbeRG)J1r>?VUfTGk^u2HS#X>l?Ic5A6iM%{+P?umfY@(iMDYI8Cr>+{Auv2( zN~_#ys2H1=Nbbs512NbKeW7CLLg8)V_`o2A$+bp`!>;?(zr|@Ecg;|#$;r>f6Oxh) z&~MB^>itH_leI_cAk2(VImy8qnf7nSbNJ>H&5jCisoTM)AdvQAwC;rqJ{VkJW9<%1 zh!MN;Q{&Kxf~U^B)uABcMBf$wX8T3X{w>7%rB?p8?A5GZroRp^T!0rX^%K8|435$^ z;|zYlz3>JVxdRyHaYTUqL&lKvXtpME!w>b^kAkCAN^I<18jeL?&D(Pn%Z`SOI}agF9wikn_fZ9SbAd^RH85unqcO0W5m!?;mPC z7)v+NdA3b~zf=z{?owdoiAs5$IZ4URO1C4Z_{f}(FK&Q$@ARY<2Nz%Qi9(MDqxBou z`VqChNk78O2vXT-1O9btjgXWm*#G%!rGY&)>gm;0V(J+>h9b8E72kOSZ!fRn&#gG0 z`$d_|)q<&^!$*)+7v2n_Xww9*)onCTl ziAUw(J$;&K^1uh3ieM&uDn0MzIyJ*=0%u;w zZCeHsD)O53v(P0$^>rchj4braMI@;YZ2!Q41g?HzOAP6r$Hu;=opORm|52GJxWpzG zrcc5pF$@)cv0*BlZOfXPn>Aq6*;Ud013C1%YZVL!S!vJX;y&P@vnU9ILzD&A)ZRP- zQ#VH6o{?`iqq!ir__1u>ta!m3IBg#Yq59ObK^f7NZdx1KnWROj!@zU z9ZqecStit(s6ph-WwBr@_=N>lpm_?CD%U^2b{`9X!Nab<(6fDn8w&jexB^ zd{hsNG7Ll6ii2#T)b)?%EwA69sW1O=3wr$GzbA%P;|f(bphRzxP06U}8GKFH`huBOn`2tv{nxJ4LD!);ao{&V|KpIIl20D)%U ztfFZGTmT9a)?%mt9pU?-9vpo&1)24+vukwH-;j?47qY6b>P6!X(p z0~ZWDaFz`)7o3y8fx6%t2D=sSJ^cLTm1U?Iiw8vz4g>RA?%rK0diniw*9HraUN(Fi z`TI90JO@P%e|Gybux1SbikUr|AfC;da8{_WhMCNl4lp8EzMO;TwehWL%?+ya^FrB1 zXAI$09k>1~R57hj?Vo&`n3!0bppD{Q!^iY5K>TzBDi$3u^tK|*X6EEvMFK#H zXaU`mZzQNlQ7A~xjmRWXc!JluJHmCgTY>*otP>~q(I-DJA!f{QB}ggr=+;}0C>=% zpj(MT;%;=~MwQzd*7sy6NntKi3OCMY^*SMTc$EcC4&%ZgVKN;3y5btMga<}p0EjvRWuHeR`cg4rq1y>v8w8Mw(+3cN z`9(yzxQ_3|Ut-Re?|3TiSzifUDg%9?;@h$kY=y{L$ER}PLIr9<2k}czBd*1>mB`+j zpfuV)hILiNY|o1>L=%0(GT5UAIj%*MW`uECh<&t`jZLTV6!mZ3fEM<>0q|;`wyoIx zQQWJ5^Q?tV-}N>8vNBMIXFw{Q(6?#pR;=YcV^}|i8|;_Ux}JVH*oh8%;W}yZ2qcT@#$$g|IKVa_3af*V!F!>}kNUgo6T&43wNI_$w7qa}oV=zI5; z`9pW{q}YlF+QG#|{v+A6Q**F03s#F+D21spDE=BAZ(-sA)7B0PXe$z!$RaXPc*P_P zY~8|79aV>psrVacZh^U9{%3qOu4Tin?~@L^A{`ZJCfcBMaFGr`Grz!OR?#~+_yzbJ zSH0NNIpRcr9maP3`0vh#PBz~t9QljJ_+mmn2H0Vg9k4F_XrLk^ayP-SOH$24O?@3G zQUzYS$c?rO*Qu>&RmNV!zw;%yHNpTQ(xrBFazZ;`2*C5YVY4Vck6IcEK7xOS~QjpZt|I0H`I@;dbNT=&0_2%^Ze^ z82Xq|o}bi0lE=P;`9@T#NzW+Sp}l*3fYEx6qp@OTV;hxt^ytxO7z%{U6{|PBL;?G; z{~G+FKZ}~T@Mh^bqdJqAM`8J~GU8f(XlQ7KX0_B5j$CSs+=HJu`%X=Uks@awALgpJ zc+wA)w{&5$a1s7J2VH9l9LAm(e_4-l;C0mX{iqCm1gTP>jrQA6uJMQ*jZylTpP%1| zjgneGg^N>H!`$jT|Ign@INQ$2oHsBi-4X#(xj*=cUM&X^l@f0{A)V`FnFLobg7ju| z9U}Yp350?1yO3jDI_$s40ApQMnXrU4~yjn z@SCo~1u;23-oxS6t+9if_iAnzOd8;Hn9mM%XJeqZf!Oz7_XMdUW)@G#6P4Vv5k| zYW*rJs|kZxEzCa0#>P6z5##eJ4kCqM-$CF+@`wto8cK76TLzUF9w52JS63 z#&wCo?@ zRK0)O&ZM0;@vwyj1>4hQ+|~p{EFeocpyTYI3`pT@_M7LQPxA^&jKi0SjECjo&p0Xjip5~^9UfOgm+D$lxSN9K+spFi{O zpb6A}5sGIggbU^A0CR(Vi)(j1sz-zMTVNK?Eg2Kc4U1Aiy3~(L1Y=#s{2pyo}5})HT z()FSz7CZJk?0 z2kc!Z3*yF0wCw4qD_^1$5(NJ`-OLcI;>=||(B+r>LQcW}g_WSC3i zf>S7lU*gPU;$3FK*wT}OA0arPk<}imNqw`c$QQ+km>rs`H07@wDeL`rBBe6;@J`fGmK1ARkaDV4a_!xA@GOwz!8c?Fc1kEtI~mi zFpxAafL-9=%ABLltph!bsJkGVQMx)!+zN5YnE(ob;6m>z4O&?&`LQA*DVa_f9=78k zxd9yCCh0^kES$-v#b~ewT{ZCJ@^*K0Z9N`y{xC_f+3^o%;P!s z1EmDhfn7JT3?A>L>h8ga6)=CI%`Nn29e7^$3v*(~5qejy+y@b$u;FbikUXG*#kkLe zeFhl~Q%5#21XsTjaCSX6X7&D!*#5^ph3JtBvnn{xuye>slJ zN#wR(aZ?6<0fEv?>S`5XR)SE1P3yYf=<#AZF-{1i$z4Qb_4?7jy#>*^Z9`IW}2ojX|sz@bCh=?{Ku6!fSi*_^}T@ z23ctX*9XAxSp`v42JpHLd>5F!0X|)M@ZdqLmm#*Ou?V#`kw^a%$v&REf?+XXFQQ5W zZFTL=opE_zHcX#@NzXexBa}EqIuDC`lj~Y=sS^geyb6si-m{X{Mx1mu1Zwwi;j8cA z5rO|$$YA{M-v@z#JE)ONWIeJM2KqChZ-2gL@k2~d4N^E?_q4$TN@WB19zZS^xE0oz z4CqnQCkYb~6$r>!b92OT*7HYYRaF_PT-Y(M&^ptC2>BAaG|Yw-es$v_;dz8aXwas> zG_(NI2q#*>7mnzJhw^}el33{xo`pTUkfhTy$QTFDF3@o6zPi>w-wA! z=paxj&b2^a0idxx4Z@C>a4(}3;d&8ebhraiuc)V8Oi@B%w3+ZFa{JVtqaKEa#>jON zeBL!=rdvcnCbUR=K^G0}sav-UP$WXx5<2B@;>3N;`9@^BVUV;we8J6B$7H)Q+qM@p{@Yv63RYs(bkPrLo8dz3qD> zN@lO!|B74AZM}#81|TSGL4isyB0`Bkk~k){;{JogPvzzJus&yWYz)JN6NLMant9^m zRSbe|zXPtkgod%X`5gHU&uQ%zLfnB&2kiSw~`3?%7>V)ix6F+~xy9aW8F~B$SGm)8! zu&Ih%2Jj)Ev*Zzt-H%4c2WM+zTU#5k<->S^i;4a#_7eOZY~6}6_+5f^%gskeAh?0h zw+>PUdth)l-0pzJrJ2%+crIG91drh!G8xLHWgr&OP=(N?f04vAk|?fedKi*m6jxYb zy&vi)ge*SxXe>}WckUeMRa}@7t_w^?AVsbVqhL%yJ_3TjtKT(45NKS{zbvH@a|i58 z=FMKuw+FVypr_-4EWuoZ5J|~G$fN^PAMko!qCz1gBbfCmDJvhx)%gkyEjap1P!}$x zVTY`X-OXl92we@HgCq{vZVBz0si^^=G#u#RztrDilofX{Jps%ghoeMxT#>s*ruDrp zPw?$Xc0pA&C5S*-bVL??W(*P%Jb8F`oqbHt!4cWp!- z+}we%Ku)HlBPu@Ew{MRkumW~}SY@laT{=-mo&r~T=@`ZvPcX%R9A!BTuWlJEi?afb zN`G7&!N3DUej1G&+32Rge`q!NOV)mV{?*6{Uu$bmprPs&Kk|DcY?xMoHM4JD3R`pu zVfZ1?U2A;Vep19r3?c9wKo_omaozXVFMZ4s@?LyWroec|A6L_%W7T3{V0iNU`9xa} zhCMR6x^LMmRr&VsUx!jTE&DICvTpM)8P(0rna?q!Ij*IZTPnUyy-Z|3h8M^WS(XC- z82b;t%W$~Ub1g4|XNLtOeQ==CM@3>2$ssMt3gij~Od^P%%W$OAt42phH{S&4y7yxK z7St0o%%xTqRaK=4WGI9NG|{mh{&w&(#eaWV@Z;`hM%_ARpHoe?O zP`x31>H=7m$8>c$Xe0}xBoP0yKH&_|fnt=+OK51~-y<~jiOPteWb(1wgq;H)0NnQ- zKB;yH$QBgyG#c>KCE#M>PyYM*`*!@eD%|lEkCF`FX~)oSRs(Gk|5E*exVVE~O(Tj7 z;_GGecqe&AER;zp&fEwrL6_0K=iKl|zlRShDM>tS@b+A4-r3jH*7hDg$(e(SRnh~j z@stsytU5C{$)zOw!;^R|Z3>y=$%_{n_}W@4R;&m^BS9k-4zNs9P|4tK=eDM8Q`q{;mthd?;`Oc|FneFB+A6Tf+y z!b!t_PYnNVC4MWD^lwCFJ>9E!Lf4Dav9C-x_`gSr`gf$9M}-;j#keq>B;#&s2myU~ zcsQP^89FrUp|;4v0_fFX$U>t5b|ahPh~@|Y{{;wsfG(5!fTi+QEr2-)TBSnlw7Ts2LPalfInj zIxy=X=1wt69;7EgHA?vPShd0YMFo%rh7vBQ@OOYli1I*#jOwUW1s3QjjERY!*F%f+W#Uj1-KRWz&w~E5enDMk9qJYD1V2U|aAG z5Xd0I)qn@q+5QU&i71`6!?~V@EtGrgE=f63f9WA{r74X#1#{!+gAWS9!zLV$Cn+hu z$YGbEizlKqjGxigO$kciZX84xD=m(i0P7H-8W0mB1p$jbc^nY2(2C#zjnnE5>J6wR zY?#fTz`G!Qh`#<^n2QoydL$EeClj=nbZLAu)v{p)kiY&g>-nN0h#@zk`fb*uyc?@!wy_tiZI7> zn;2NY+hI8&E{===6$aI08I4q2DB=Og>f0 z{Q)em!?Id$DlH!veA{}I@r-zGDNLC_L}+jIDU7BMV7N1fQ8rEC2peVy>K+`}^$c<+jYg!>K#N_&06(>(A%B^!C7l{raWI7X zT2IxxVO&r61DuRfPoR+0?}C?v?b*Yu7T ziKyl&g2kiVdk^LVx}iDJZ~=5)Mg!I~Rg^zU`0`)|0W$dd^{dOBJMZY4&YlUukt4Ac z@ePc)`(W)#|3H-A`fq~{?8<%*eEJOzIEKs;2&_m}`z>htve@pk6Q4^YSwjz_E#lFC z67x7D9LTknK&lK%qq3`}7R81PcL#z6!ixf=O)fg&+5-}6!FmuMK^O)V;jEB-Uf7k~ zNny9bhh-=gmMP5`fK$Q_@){Dusne&ID~Wr5f`Ef)b5#w;O@pxMo=A4l`-9#^doV+n zHy1sNCqYD+c!&BuTH4wqD1(@Vnq;}s_nto>jgVI0nRx?JA*!v>vuybJ2!t-cR6v%u zn58DnU=Lt`_|Ztb==-{dhSotF-!?+5)L*8j-@$K1=wgB#7E>$KtYc?o>bPJbN7zfi zJZLmTp_eaTk{_t5syaM1Vn9}NqcVm=91+)nc*Cz-#8Zyb>+s?+Y2ATUqPiw{1z;KK z-IjY$cj4GtK;_3*`AY2}Ar%p}RRe(kI{@PdNQ4<0j341nlZlI#8t~o^?rreLWc~Q* z6Ol#c*$xmi1r7aAP-kc^D3>o|Ci9wFEGU*B2@SFRH%AT*bv3H}gJ$2EF}{RQUm~RoK;d2dZ1*P&3t=l-71j3&EbO zOhSAm=WZ4s=w)_x=^QmZCJ5Ld+?d=)*y~}-G+B28`CG}#ziQ}WK$9V>f)J^+%;^{r zEy1&3J7#=jCuEjH0M-cq8ffb5zoR280;-gl*J}?c6wcPoo*JItot-0-n6dc*?$C#s zNATG(9FoS@7CVVkA$n74p#T6Hs(x31GTVS;+$Te~AvpYPz|C4dyx5={I8-pphzasa z8sMw;x#_VD9k|Jtu>l#$6vz)$DYE@m;N}qy5A7v(T!JV06KoU0h(TPNdQyi|h-t_N zAlQQX#q=eIJlH|fC(J+#3MmLy5Z)YzQ1cPv8bAk^k#vP4k^De)C*AyOSU{jyDwfp1 z^FRki_Pn60JKjE_NA~`JdozN%g9|$W3U5F{id1?TYzBrjQ((x9+ax?l6OD2VOCy`uLLt$KYJP=jN1}DS|dt7<{3Y*z83u+J!0F4$s%D}@G^Uq zv=%r=U{?0H45F0N`i|4n(-LxOYQYJKi9&z^ZBdz|zmARFkqyja+iDlcP&Ld-Ov$=) zd&&9vd6t-2 z8jW_)9d7u>e6#=&l1uP88hZ&j85wL`N=V1VR$Vh2aoV(zddcPAnqlZ&{1?Bn!~nHp zDeeak<}f9&!^loYQdgXN{QpX8Jlx!XmS#M&vH~WMc>*5!!RJIXf?nPqHDU(N{AaU` zBYPC(+{znTMEMX*O??_g}wL?j@jth3}wA8a*P@Cy@Cq zbKqsm18=&}u+pGCD)ud)?{b%d*Qb6s8q?{Wg4kl5q~E8~hHaqbQc`w<6gvv^oE9q^ z%%6pFWH9k@=m+lftW;J`P6#~M+~y>F$$qd-ghIu@8F2M~hfA)|x)2L@f#UBw2j>#u&kd98+P?&8LKOs!Dp=O%`-O2p_UdG>0nOpC^UOioPpFEaSGCGYRnaBF zU341LeW)Ef&_E|K9lH7R7+Sv~*as4rnx4K0vsF+%c{ka?6YXD92y~wY8Wtv8&!s6x z#PuaK_(iIlZv*@P-Bsrd5QTGc>|EEgBa;$O6reG%^Hni_2yLW^_GwF=OR4{qn&@P~D1u>L}~Wl3e7 zshcYx$L{2~=IfhWh?{)H@D?kAb{Xei7Wi#eHy2PR0DWj(Qiq!W)Dcysq+%V;*#T-{ zj^Rfk*0Z_PD9dBCwBJSx1aJ6ST@>kx`hOf-du|?{7*vi<69jrbEJlfu%yj@}qy}q% zrI(^4vq0QV=mC$#097thC(Q-2=gV-y26y+Qxw!z6%t|mw5FOTlt&x>(!ub^HI+PA{ zknPYEuG(8zTi*xk1DpwfdvFQdiX!AE;;O!|l$ttz02s9(=^UlGfG02v06^v|#-eldEM4g!! zc&>GsG1%yAxY4e&yhgFHtevO#czLNXx~#dnxNtL_>Mjm13Xk&7x?nn#`J+2i>grIH zMXrDJWcPr2T_~he#*jFUPEIx)ootE2SuAgE)?vcFIhNP1=p+bfo7^=hE4{CRAUU6{xz+#?`x7KHa8SnY#Vkbog6FTOAuZ)XPq9vY37 znq?Z9Yn^q$qy$wlZ`_k7MaE9XBUn~zUx^WG?l z0c*645O%%Gr?8r)(Wh|i%Mtw77d_iQ?T)r?x(Fu`KSu4}B1^WCAgGF$CgX)p^wc9QaC6LOcr{@zc~`54-|wsnHl*2~T2`ElhSr zQ-=DfvL#%7bt>@W9JH2Y`|w)R_4AC?rBp8>kg#xU!y#Z*OH>u)X2nC6yM4Q$as(vT z2s4}h#%g07eBJ;4&s?~cMuDe~&p|xK-v_eswFlYV$)Cnf@bvIWgEY%Z@OypH@kHdV zq_Vwa?0z`sP}5Bx-oN+3kXg*9eDA$>914Kw4}z59qyR_bByPf#+re)}xa84w+PL#2}tl}e!v`dmEl zqwt4Ozk(k><2;DeZeXxhN+e-eD@0k~IE!{4kQb8kW7pcX>(HSF82%jOW(C2_wn{_0 z{Zm<48H#s&eUK&6x03bNV@}Af0#+?~zaDVMfsiBzwo5oEdv*#6R{Qj|zR77FV|Bx; zLP|F*MZeMP3+CRTm20->mEzbRNoGC>`Mv?TR*bXMl`U@R7;s12T7&|y(Kl0buN=rU z*Sj_&&1IiPHB;t;==}YH6n-9zfz15;;Ud<73C-ErT)Ux*J3&M=UV~SV${FRi!AsAy z$Ysgs__z%lSvPKV3+xf|tR5emy2rQ(p#!bF7>SvgOAQw-6XWX>}9}62BA9#E6HK?%0p#|5SkX-uTQ$?fQjMK@re5RFv-OL(b(fUG``13#; z{su2(&)5u_BpXOX<HbLWyH|D8l1xO}(ub&bNY zFSS_Vg6WCiBq#ub>J*S*LhgviQ7|ugBZToIj@zX`5<$;NP8XYZ#-SVR1A-fhh*2*3 z!@grJ%ce~Vfn-+{b*Rl=CMwGC#X8jg-&M^>ti_=7`bMj7XAULcS_BGw$>JMG%BmSM z0&i4H_8KXa)_mACh+&G6S^}`_M4(P*rQ9*p#T@SmmU{+^spE^zgXJdPQ z$JyPd93AtZ2DqGk1b67oWJ6WexlGEaQfSs0@AFKQM3Z?021P*f=Sr`BR!$_M8cb;l@@0tKHt`2ri+Ae`rLdiyT~Md<(q` z@W`m##Tv!^VrN7FIz?j*t7!prM!AOi3J>o*U5w^d<=egcB!&Q)tf3Dc=oLhThZ}Ev z1NyTSmvNscM{bAT5$f@m)rl%E{NRbp*YoRcLE+q}ce&`mbbn@5#Q@s0BlEK*I44z- zv&p%OSSF_a=kYUi2>QDQLGVdGoe_NHh#Vqk0#HHs0TJkK(LGr^eyJIX;EoV8#&e5j zKgDkOFlmirkNz(OG)a}tS3PAWGG-S)ikJVG;iS4cv*51^KJMZ9OzduB$hou)IF~rJ zke@sB#b$hBLNqD!R1KQ;$^q1{5$oho^x7pk`Xq5~Nal1xmev?PRltG-8|E#_2Z%GrJ4_?`bCEtL*5%o`bgT?a{||#Gv3{%5V@|fCaC}a{cr_FQRV? zO~O#iSY=;X0Lr?s=4S>S{^&lQ*|j;GMA`Wa9J?NM3jf6Lp@;rC@Elr9$~i#c>$;GJ zDP#^GYi`~o30yhg!FJZOZ|q|@0_=Z}m4u|^MPOvut92KFkH@=Y$GIx+zOc|;)H3k$ zWKeS>Qimd)X2G!h+0&U?fD8n>xoRp=d@1nr2!#uLjHkAdB_HoB03vE}dv9(1KD}&d3xH+xY$LHDWZ_B#(3sDd07%dro9f@W1B(|5u|7kZ9!q-bSYMX$P&U z{V3in`i{cGghjmJ83ckZ*nu@TY=8cIASC&>sFE)?oUyv@Z8^8#g|m3~=~~Ao-%hsi z+ijib@)>! z(B{~;uM_d&;W^2G%Uk$(dA|Y;tejC3ols%Y~;`5tavgv%>oYp-qpt<%)W=0 z_kg00qrH8+5mos}BTvU1tlR;X`O8lvFzE&FMql#uAX*3~J!@{n8OSG z{HuN)@BewhEq{_9E)g(HSMx5>H-Dqz`b@$i*mHcmpeDNT<;$-v6Mz4zLTmE5wUm>8 zd1qHw@0TM(2fOzo-t@${Z#$2*B_KSou(R(40wY{-6`l`98TXF}G~6w$31)rBl1tTF z_yyho1t`P~U={_UP2ldnOI3k*F|LaaSS)F@3md*7CY~kaAc!EZ36z zVz3yJNj@<#E&rnvVMMs?JU`wrgS21Sfp(w2AhxYPfzS=zz+05o$z{LdjL1NjUEIIdcF+u3~t zDj?By=`1#VDLIdtkuNpSR93!z`v_C@3%eLoE)}-545Hn2{bKO;`)BYVbxUtFXIJ)& z;O>Suqa{r#p8#AHnM##fy&WAM{+g20mI!h{0uU79)dEQrgz`@S%;@$F{+~d9vqVcn ze;O&Il3`aT2|HHJt$f=j64NdiW3_~l>-t-G%+9h^xYJYa8_&9;H{>kl* zdT1?Z0os6t=vX`shp{;(AgJ4;@FRhK?0Ihs-9(WlL$l- zf#C>e-%7&QH-S&rMdxufoLdu+!?n@( z6wLJUTU{O}-b(*pacBOPbK3s#VZLS@!^mi|FNYbT8mXAbl3^ah9VSvj8zL$~X+@t0 zGuDW7x5p6eBqb%4M^k20A}!jLl)EHLDXrhvHP3Q*j_)7v?I-KKe6H&{&-eL$zt8g$ zH>5c_UN3K^IC@%^vTxZq^M?MPehRpl^u>4P$q%0&dV+Yi-JvhgVE1|}v!){xme9(+ z!vTRSb$MNo&X(hOd;2=>%!HwI>H?YGSm#_Pc{9f_hVGKt^Uue{#l1@Ja*&NM^>%x9 za#lz-?luc(2dB}`PO;xLfIHpzRaLVq z9^~fk86AA~>@%HFqGGhk3WY{g;M;c`y@39>S$jS9LsppzHlxS>LzpBI&A)$4SEH$N zJSSVkrq|eM8!cgUPOka{thCPduX04M-UTO%4h8wgY#tm)-Y^HQ~w_+Xl zl<&BiQWO`H)j1>8DNjTO*x>U08Jp*RcJJhvhMBe^a0_zWL%+%0^i_9got@WgE`tom z;w5dvaau;bea^Gf_MK>0bEfq8;>Ms)KKXl0lOg-6M~Pm-R>zD!TIx!kd5hd-U~rr* zJ58}^z&YHL@{F^yBHvn;e?H#r&T`%Rxw(b*acT8hF7WNT$Mk7K&{Up5VgK&PF;>DB zMULu1UD*}plE*0NS?s@DsBAcIWA6`I&W*-g7MST!0q|`tTy2=LJzsbq8SomC!lPMx zQ|iF|dn;BhY*($+J#**jdv?1eT-t@vpKI+3Dc%yyn|VAWYr9!j(LunjTE{;2r>O^0uvD4W?)dt$nJ z;i;Q9Z=y})^>9{F_T{6e350EdpC)*bwSc^HYIrA(`aj{uI@h4SKZg^3g7YazUOaEN81i{ zLd#y-dVc>J{HDb~j}4Y)P4WR?!S9h$oh0OM4@k}J<4--1&drYjw%Gk~dSbmjly{>0 zRL$otEpguZolCLu_*@z_!$xGlzILfEf(m6Ctpn53J1$atUWJ?sEczux@gzPhaH>uG z+SWUtDoxeaUPY$|cc912gk_lD`tZlbvKui>WQoCM?f2}so-XqbDuEC_nyN9*3aMc6 z^xLlw^81@Q@c@kiK6N;SN(`h%Cj{U0WfPETU>!`zX3GV*h3L$+W?I$51-);Sg1g#* zhCwCixA7LWmSpr6RTvr=6hQXpsT;Gs4Y&aS6xIe32~Y}sFb^hCeRW@22-&pwkwOg=%8?JNox<%IE?x}yZg*% z|BhXp*ZbSBY1O}{{HM)4{fiso)H!v9x>jj7d{UZ#W&%B)w5TEF(K@RZ${Ew-gEDt1 zUmg{Jbc86Cb_BQ?$QNK+ByV+P(7#YjW4n5glK+a0X!Iw zH7`7+rMY?kItIOda!19c``Em9xibz=IwA>NAKXEEV!%Bzt~J(>%+s6QJUm8YY0!pQ z+yM!c7k(1qW;B@p=H6ZrQeTA!J2;j~)p+t8v#gf6R_s3V*glmJ>%ndyJFoHc%T%*- za&l;_(Pm}rfJbFx1-F{nwB8QlT5!0JJu93`EV6ms+#WNBl&p<=S@LWsn-_`mF<6ts zsXw(R7bBPpE|_QvAlvzd$NoiK*)-5#LLtpf@1L?!^=a!0b=ao+Dynn^U=+Z_|8 zxXe+v=j4eK3FY;`3NcM^#w+fqOB!=iaN}K0)%)A^nwtoi%@3luXmM@e6D;MK*rS)< z4*M|svWSS;n(Gt)u#Md~G8^^EJp1O3@?x(KM3Cle+PDI>fm1VRg;@V}72q?{{n(k54RoHrMl1GduV@Wmmj>!R{-3 zym0sV9y3edm-6NxpHCPG_H&6_97RnK^lYk@*29|i5iCrJ|AG}+6BV_`KZDClXW)aH zl9IP$+ALxUv3+HZpEPYt{917t`;O}L%G|;4yCx@1lp-;47IaY_U;l!6P zXCo554Iy3TLuLr4L~mlcPgXAAdYV4|T?Q z|E>*N>3)pCy3fyiR84fucrEe38M^iLS6@w3Y*O(i*>cLTdfKi&z-Uc(YFKo0kU$Mo|?mq-HS2nRkOt%ZfGIdFD3V2hy2vrL4?| zDI8FJF*Fa_1I;j{o@M%l6ifC|P+zQuecT>h;ah?&pb+n&tN@~*5c>Mdv9nj=ZF&eM z6F2}h(+($C=e-5J7Z{`{$Y6)IP&DhH_F1TqAQMq6%6Tpru_!rW@Sl0GYY4&9ZUAEM z_N0%cX!ssOXF~qjDU#tDSoQD83|nk$7+z(uGp)^!#k|Y_h|(+CrN~`u=?4`OF{9N# zqkA#O*90Ab2pq;N!fKICg<(a81W=3tqA9Tw=WVd{Me!WK+G|vcxe$lV74ObfVRnsR zg`8fiL2!?z&PbKG>D$;^8sB`ghTto6RXg-8 z0rU)6Fbb-PL%9s}qTxCL`?MH^2OkteysrN=WKv$}+>-B#Iv@V1q@YK3PfQ-3J$2q5 zB#Kkihk{RXe@XNzoN|8OH?sBDHXOk1XI0$vAOFz1NNkhFfP6Tys84n z&=&&-u3WnGD9dt5WZv!rbw-Tx4pchBwKO)HjEFI`B*%gf6_Ab5tHKVm-^17+2RC8_VJ@f zugUtyn8f&rc0iK6c<45<)p5xIfMSkdsv`o8&4I!KgQNT_Ii%=36`f~eXit&OHqi9DPpR(JyMj5wZQtOeBhEO_i76nt^tYrghG9+$U z6e&wIQHqlc_p(=3KsqlH{=d{W1pfl7zE0EB>I5TNpbNP%DmQ;EQFGkvU3XM%*gK>9O8WK@ifD6DRdFSs5%_mS8I^2p+AEmbBHKHMD~)uMZjM z+S`|LJ&I}r_U;nK8#{Jp87{+h2QC?J*zg_knufqk7me>s>j0i$+kaa=n z*x^b_N@;Jh#|S;M_ucuvZG<;y?Q6$u3nR~r-}ibU`a$vYyD66mRB=M0>2j@f_RoAW z_f_(boQ@goXMKFklb94c|J}Qk^)J`n>e%nBQcdft(@VW&o(f7n5qehOo+ZJhq`Z)J zMfQlP1&ySU}{y54X&AF^>H_0cLAD(Zz;xC)sMcMoRj zcC|G{5WVf#obySwr{}&j8GHBS4!}4z_SLYX zf_8d(r(delqUWMRIKgWtG;ywcyu+kZ>O)LyhM!nWlbNn*z^DbDw<*uE?90_Y3TnIl z6vic_+7}XG1~0h@H|U|IhN?z(KSEkb#91Qu^`$w&03QtGY4X9e^xa?!1BTVFba1!| zl>nH#%q;+p7CT+(u&`(2&EXDpR5jx?4`)0S5fw$Af&`b#CueGX%^AW~sNvV`lrnaK zr#Yh7DpdAw&>vOCjobfc$!s7WS#mY6O(nnQUEVAWb@fJ_wq!>^2Q%;&D~bv)^4EyJ z#s@@Pn>9UZ;|`fbo0rXbjSr(35zN=$KLP>qd-`wg?%*1s7BZ#yI-BFy=5AJ19}M)X z2tbZL`1^%1IiSax#Q$w1gX~vt1iuOsWXk)G>U*?gg5Gv9VNNV4jQRy;Z0X!%pi3)s zzb))ZeY6^8e&M)t8l|6CWKufph<@jbZ?Quws`j?rNNrrkCnoq^fmmJo7viOuC`-%# zXbJxL%L*y-U!Ug$)D~;2f{6khGn%s@XmN4dJ40!ktJ2E7&-WfVZB1(wiCoba>@Vs#O9X%Ys!Zu4?(gN3+CiDkKD+katfNc zId4<^R3Idoz{Q)n;R=y(*k2ljR_1z?0jBVl5Qi_vdmleu0M7Ca2ym0+dp`<(|9rPQ z@+GyWm4P0|Po46c1SMwqtm=-O2tk-P5z>$t`2(y~;Q16dgjRi;qhcwi)pT`>ue=vQ zo@(O?-AFiB3IW{Ckew)TQTz=6{v0yr_inE@U@^8_S7Ye9#of&$N`Li<{_!gZsPr@NuUXD-e;ek1XVn0e{*k}@zy0;d d$X|LV3^?_;rb9odMtmLF;-CL^b164VGxbNb>UET>NGH9@XH@hbl?A=Zc@d-X=r|RQ7zM?w zYCnwXs|HuD(*I{#OEv8*rT*zThf(6HL}t3m8=a}cx<2>R%O9H)wF_~M_V!`f*$fCa zHn!VrFq4k1?#$8796cjrRBSARSy6OUl$DhgH4%0vQNq!YTb_2$&z~0V?g{DXG_0(w z&x<-tOP&}So(CshAVxcro?D&Mosp4ujNE1d_^WCSIKwE#6kS~fJnBz^{#>U2n@bGSgaT5Glt-v0hR5ebR&zhyNta`Kgp zjaba36#Hw}+uGXZiEPhJ*}mK=8Vym+i5{-L`4FY37}d!oOirff;tF>ZVh^sG<;RMy?d!-1#DpE%@tc)OooVcgpp z1^OS;)6@$K3k3xQCRO9+RxQVSOFJI*F|o0o=XFzC@zJ3zEi%L;B(Pf9U%p_cs4~@m zhR5jW>WWIoXJB9`FE9V3p2y6}YU<--^kq`kU4oxKpl?aKP^Gqcxvt*HT)_ zTcDLc&vNV~eghLTFF!x}-+w$0YhKRXsC~UHYP zLh}Iar?SDtRo~aw*}c8JnPU}i-Dqydtvf364+yw^a(bF=S-;%bBE?KJdv?6H{-++WWXAF1%8K~lwqO7KN$G%G6BT3@mx7h%J1uVSW! zj=aQqd3l#NHyN3j0!K!)(5sb@FjLY_S<9Bs5TqL5>ETg4zCrQOEg~j{Xwv)85XA$p zwrCH#Z_wbCkeEm&A|fK>eZnVkzVeuqjBIuwTM5p3sRJ@4o|u$0R_`g;c(Gq(ZDV8l z@?|7!hgAA6GM}+?xgKmKnX|uNco?&;o9>75uiU}uk~>S)!TGsr-3*WN^y$;x679!k z;v@b`LoD~m{pQ+dkTX0XPhv{X5AZAvm*-o~%fD#;+E>l@{`>bYA}Oi+-#@;cogJs- z#uwuP>Q$M@mCrSP1(?p3GCY(p7 zGl!?<(LN?7ZwzZ)Q6>Mny1EkI6@B^~^n9VMP44961PYy&lT%e|xFifOXOJbIK~j$B z@_acnoL59d(ZYgJE>kUR2*B;RQ>0Y-_Zde7A2(1fe;AHs|F2(~+eqB^T;<2J4c4lU#rndIr z#01BEw=LJEUAKn=nT0hq30yuqAv%Rx|2)Lx!zs<5KSvkRBy#Bu?;UGIB+k#z5!n5& zM)j<*xbk5X&j6EQ(H>Ib)z{Y>d~*r@_s@lt`V>WD5%B8l=w+U*lB(+BN=}+l83V>q z=VPC@dWCIPd|e#l@eThN;8Mm44H~kt*Y&IH7tRl-;)xjpaqrKf&r)Y!Uru3R=-anf z0UDS&ITt&b(&m>WE`@85ni>p0Njm}ZBteHT>;9dR?aK?Qhr2iHEH z+a)e|0GowURB0n`hKn8CYdl-v%FD}(bh;m;>Mi3HshDlL^fM(b>G~R`#F6ii`+X6Y zo1t>j^75GQwkReB_NPuxPEe3-CfuL2^4(YSa+cTETV@s z(MhYCl$Mr`XzED)N!Mh}D=LbY>Z`66T3tm}pZ91AQ8ZLNpO!cu%*`{@AY|wKA@BJL z@Y(os-+0R9u<<0n@y~X;&*}Ko?shZYRJ8u=x&OVK5SoD8D6R1R>*vij73%u>N**2} zA}OkhE^>J4Kl#?f$;r9Dn+H(_~gEi*4KxU>}E z{hX6y9;>aP;nzNwo{{ku4(aXt)Mq{t6B845Qw`^z$9?varnUpPyspG|%W~g)4zL7p zlB~+Kva&KzWW0SC6&(#v8hQ6yt7&>eLn<(YI-??rL!8M!9UVbEcnS&%-|efEtYf6o zAEiH0K*~gYbA3!V{*%cxdQ>-p3^!`z50{oS=yf@9HQ&Bcf-mLbdwA?MzqAw$Sn=yu z1kq}(!Pg|O{t;uHmdlVi)rXrWr?+q4Mm;+`U$&fx8gb0=5qxXE?VfQT`cQFsc?;kR z>=oy?!NEaIP0j6}qFdXrY?*^Arkry2RY@~5&+>*w*Y<@da8ZQZ2wCXAg9Bbs(Gact z*5|w^4T(Fn$JU-V3fpa1e|UKPb^Dr-Kx)lbmCEj3*>8Rx)WtgqV<5D-||+BVx)X=rFjKYKQFAJ2KxV~qnUN9gq? z#nk1+VQ@%@zQ+E=s&->WZf+1X0(EtDw~OPYgw)hH-#jTQvYQyW1qCtfsS1iDe8IK8 z?Q=8xqsH-afj?eq-cuhL8u~Rhh60pJNJv2A3zs*tqV(L{9JTi!O+6_&IUNOVbWBXk z`g%dk)X?athK^20E@Cx;UsheD3Kq-*E@H6l^9mH~ggDcYp)8Rqn)= zd!M>qEEr$NDiS5D=9KdS|1p2|%(tOITtrlKLF<*HV}uDeGs7JyyS zrL|Y33ttUu+d|0&=Xtn!cxX0W+Xpr^ zNh&HT&bPj(c%q#%7@3sB*4je5tCCC8V^OyLVqtz>p{Xbrx!2OkK+Kxik7miwdDXPE zo9EuGp5ES0R>a&q;M=!vq880;v5LUP=aC-m)pb>V z|EuH&#UJG8u3ftZ5Rvlb3#+a@$=$m^RgPP^e0<037=?usN=kSt?I+%==9mHZEwk*~ z?Vt-Uezm5huAw39eNukQ`)`>g9tXm~fu;q(hwH1z$;rp69H^*#j%c7>l$DjAJbCg+ zO6mt0<4%f{)e)yr9doB6P}-=m^}XXKtg+;Z{IDh;KYsj_o_-~y^{rgy%<6Ey7xclF ztwRkXqo1*C+T01sA3{T+x4)UNm(N!3+-MSeVe8=Ac%0ODLXWM-E>j@={V)^(*Zn zZy!OgFg8wGKMxHLFS8!}9G)_8KmP||Y<=!fz=dj_roB%6Wdml75l{y}&%V%6ioc>S zkL>ZCktPC$)T0{8e9|G>Gaee6D@nbK0d;UZcU$hPItLwu1JKSm>ewgvr>DYFnwb38 zIQ1BM3luGyG&OjkfJE5#jQB4UHqZQyxOM0r*gkh9L;GiTcJ{|~f(H*CU|hXIfr|<4 zz0I^lfyO~yJ!-!HQTZjoO^lm?&B}<13aZ4z%j;oqaPaQ2)*%Z9128}}i@$}7iWArZ zNui627OBX^F@G50<$=Sc9AF2$@)jOm=IGkX+6gq_teW%)0hQPJJc?2cJK$|)r2w!p z&>ttt!tOwfT3TD5mFU=$5sz)S#>K}&tDu2;0zKin`M0wZiDt_C@Ey zCOa<=3-$?s0{{VJ7$J7R$E2j@1t&EF1NO`%hriX5Dk{D2yhmW8fJR{jHUkq6`S9Tu zFauc?mD_L_p>7AKro!bio9qOXuKDfmI{o?c$I8xb{$zh`{db9<^U_ZqLBaOX#*0^~ zNa{nBjSVYE3MYlZ(b4uA=Owq@PNs#|PxbXZ`_deGVjr=qlQYxqm1#9Srbe8&&YcpdAho`20#;cVGKY zy$NM}@b*9EhNOL_y^{c0_GMG@S`UB9{3YrOqqDl`27jQkP{%YOziG)r<*d!viKW%f z(f9;SM>X{!OQGKj0di0s?3|puJUlxIoI14rI!{#nou#!9En*xT9Ke|V!9nR)ulRv` z(K9eaMn(NN-D-UN;spojEuf@7(XAg5!Q=*DQg@tvyVoJ-(phc$F*QZoh zBSJq@#X{8c=mBWKjsOiAD3u;D6?s$rc7W7bv&7l8TGx$l4mW~%*b2hJaILFHCMSP_ zfst2GU@#Z#yx2p!BCM&a8w~BIFIDVASeT;J%D_O>5N}0Q)!^{3tcFJP2MI0~mS;e| zXJ<{JViq)&G^L?V0pm5))e&04cd{aZ_&RXP?T;2gB{|j@$k5)o>icG%0YrcQzyR$7 zyxTXfHJRXN_A`@|4-IS;Rp|gQm4Li?p;fGZD7+OCsFB zm94GzwS9GCV=jFT)>!QDb~>@|{~c5KK6*2CwkYT|M?UqNyP37)E7hV#>dyU8gPz})uimq=!l4=j40uW&Dz6uVvD@4 zgVFHxn2wGP&J!%SwSz;vDmnUIe$XWg1@y&k8rqAdVSfM`$Le?-N|n4Qb3S9gMCgxt2NVGn@}4?wdVU0bk}I3)$BN24ntld9v< z_A|80bsDV0H6y)wc$$4@a;7;j+i&gYNXVm}GE}aa@bSZ@;e73yoyd*D(S`&5N%y7u zHnz6z8x^ApD3oMoKYp-GZhpQQR9UjPH(5|Bj=Q@%Vo>80AW!A3xr?QpnVlT~!aD4} zXq#Z;ac|P+?4pJfR?svSSBZ#;0l9p^K$MU$7NG_^MAv(=YxGhTHl@aGhZc-RKnGBN zq+t}oQbtCUdwY9z+dpvXcG}2$`ufV8msBk*EPj`mwW~-nFZHEay1VN-F?oxpA;~BF ze0+|pLul3A+Sgi9(ul8&L`r3ku9Td@MdCa>k`4}BXpxM+Aid%uGWPLqUTp@d_5_3YQcOUA@$xem z0|1M~b#--%7f6*}LO9$qh&RAYiIYr;S-4B5S{v~C+G^lfn-11ry1&HWG zg2Tg?faUPUDdlM@;%Zx3G69pds?Y-yq3d8{ykyL7x6ExNJKk*}R(t!{%f-coTTn1? zYAfgKSKN?RDR+0_)5EPsKWY2><$52J{1D63U<*PYqJJPu(Jm19ol)ZEQmD&`EuVO3 za+SI zQRk=)XHYqeqYebNhcy@LctiGu4f1n2#8Ko*;l9G}_32rZNA-alAEtQT=PEi0) z>WGGh`PFFZ$rCT}yagiPa`1;vLcy@@jA1Pr?S^(F?D3Bo*=0hcJUvCBUaJi_SKhwN z=N|cZcO>C~?IyLXG1kFOWp<}_fRrF3fT3T9w-|uz9@NVDk9-_S!F0Q5Zv9he3oCjiJDu#y0eXabbP7ZH8C#7!q75C%w6Q?`x9~pOW zY{uZ?D}C^3q$zkNo>!iHi4E?}g{B_m>-VA)uUdahG*sP=PHS&(hi${S>ie-pK7SaS zT3e@TytnejxDYHF7~xsj*=W{gtfQ|_Zc#>>tI2#aGS~2Qi<2nN&OLWqqb4 zI=OjyO*R$0f`al;*HC5vvMb)@wxgV2bJ45)wy!edzUL^C5@n^YrPT(M5*&GWa>8I4 zG!htc>6MisWzgtmU2D;H9AmU~?*>SCxtu{BVPO<}B0_qSn=P%2abvMO(>rcf4h~G>;sB}A&}X&38qA~z zQfAcD=pGcc9Dk#vWo7_BX%&}7F$vH#6Ij&hstxJljH3`VmspOw;qB6TdSpOwfVKhV zhtH%M?h|+FOc)8%aqMcdoa-ep_(>aoo8FH(8{O?IkyGE;09ot^&=CUSb&wC zxpsz}g7kD>h}xo}qM-VpSy=@Ee1fK4SzV=NW5WUY=;shXIvv8*pa5;iwKfI%BKR%O zub?xhaa8_}=l!RbCu>DY(%999tp(?<_kb$O<_QVBrenA z)R-Nm*=yc20VGFYqxo7nJZeb{$(jZ)UQJtD-q{&&L~Lki$V4D%i%s6vhmwFW)3>ig z&)t+r42%%5uGS&6;{|uTQo|)h?*FuRc{#VvG02T|Mjg5=65Ku)iPJV=>gIMYv%dqP z#-3=^-wDv_z=8xA8z|N3cqqen`u8sd_*Mh29%-@x5#04&-uD3T`k~BKoVBS)Cp5i3aGnH1;D|=%Ob6O6gZavPrSTuN7op5cvcdtoaEL9)N2FdySS;xqTQB?G_GQoYeYvu4XAvyU>MgcJ;p!rNJH5U^X zhX!#$!Na(5vL^RCojssLQdHZ4X_@mE7Z<-7->`t#;M+HmtG@p+Mq@z%)Q4}9xZ8^{ zGG<)(mpIf4_deg>T6gtS{`qU8*eLGKn)_x;?d6=*!*BpK1Z1fuD}(H_dOP{Qufeo? zBxeAt7!CXfyzlbx{XUw1UbJ6qG7X#Lta&mdJd2{IhfC@E9}o@i)F0rrUdP^4{q9p$ zuY8BF>Z0seD4fu<>7?;SHdBJfNrbK8YB0?as!=!R9Al-8`Fip&BDq$yHPb2XU%2G?f~rTF}SvKPBj3ktqV@A z6e>7i<)T9*dV*&FFSf*JSgy*DT@&d~fv@%5O z_sGdJ>*|uhvuHTqaOn2B0-D*tfZ~Q9;>(okb`_wdGn%R2rUH0w6f8ZQT!<6P%lW|H zLPxh?X8!mg1-t;flG)vbZfIgkl1Hx__U<32g5+{`c7`?s35&g6kN^1*IDnwFKux24 z*0nXW!_A4Pzbpfv=jP^WypDPM#11K-=^wm-6uuMb?EH6lsG+YPGHerTcZmbX0orGb zA_G`I-lqqh0VXCUkJ{`Y=L1th9{N#pbMya*^}!!o_2T6}qoLX?ThwF42QBj5=vu5;HAGpue}k55z0H32UagygLo*MO%* z#hddcxSeiPVPRZN7W0hr!^G?I2^!yMo7?&Hgn4;oWd=+i2ov@W#vS(d0aWLbn=cEe zcizZd+utwtI6#WOb>Y9k#7* z-IYzEyQ-lPkw`W{`&C+7n*^*pJ^=v;7rLPFk1B61E-fARU7kJ0y)ROpZf7C}QNOjV zZS6`4<@AMV#rQ(b`EQ>_q`AeWpSC)la;$#VUP|e@p}dgmSE|w=lnzKGv{y^$)Gx0L zlAf$#xSYxlX)2b>6qAs{4V7}E|~)izd!^9F7w^z@>ddkSnD zIyi9v|MAz5NcjB{hYz{!IdY>L<2cp zYx|1tj#ig^r>=XyZzrxS%jnnE(Ftlbr2%3OvmdB{n;2KYz(Z}V`}bADY?T`t$ z#TQ?;g&+!_tv{SJgP=(L1bhe8SFbA8ANy=KVNwV=zBxE>CBhE)5Z>L+2@5iQP-o!G5?#R#oN}8;@@JNw-f-xv_EfjMT z^Kj+x3XIACKcNSL2%!?F)Ui@?oa@)G!)LvOwgFxrDHRnH1uiH^b4ESDuC0bsPB`3l z6W?NuFLvXHhKJPIZ}~Mn0?N&#nhZgNq+AeICIV#5xlb&xd1e}dc=?>c z8P{5vGEfh=K-*dQ!|!2dGBlcYSUC~SMM#Z+%|P9lf9U{O0$x=Mn7P*B-z`f_va`O+ zlbA`R<3mVj35#aMM+UeD2Lmqqfho-^D(!5Z37&W7XfE6th&0ehl&Y!)u+>KvPe6^p zkpx>AeXk?>4$lOVptK`GT)e%-K!C$w0`OmQ02Xve$$K#;Wsn^p{e89H9g<@7%oDh| zJ{S!{86QKgGwULfjeRYm#|~0K$U`AFvYV^}t=IYE z$5nJIx2Z_%O%ilo=(@{k_)SJm?gne@`@N+;$Pdh2;;RuIS=u{eh~CEe@W8;^VH@Bc zfk0>*j(Z&lumkRqlBSoJYm#ZaFMgV>4*fg}n&{WBoY3mP7inp4@1EmkV^dQ7?Q^l0 z)}3Y16yM#`^BlV6miLzD;Y!EJ8SUcRr$AGkoOpd2)7q(!WN3@l8Uw$72Mw;Y%CGAJ43L5E?Nb8DKIx-qQV^D0a6Co3RqCMG89$+ipwsx zkeC=pKDXM!T_U20ganqcZFnsMuny1!An`ovjlLOyCl3?=%^JbY;3I=hRk}WG#RugV znTRBQV|>a84q>BH{;;CCIRng7q1P|EQwD_!w)1#DG~IVqF0(vOK2P)Oj&o5_k)@Yc zvSpe#adcJ%wtqn`5f3u9eF3oxQ!WUvEMWfRGSQVcyrTaWsS=ff#XM z*6eOtUm)Oy#t<4#Bl0y_A>{7p?xrKchH_kxMV!YgU-0q`FYR@<_gnEfDX#2$fOd}h zGPC0b`sP??@He^Or_YP8B?P~n+jEQEu1dF`B>78-r^Nkt*oHs-$+FpH=!=ce&%E!Y@8dtNV@@%Jd8mew|F?@8&bx_#es>{nB6*H^YHQ^g$TyAYsDvjGSr)txMZ=e zh`P$?DhmrwA2xSk-@4uOx`8mI^){Y7)4<%^w2PGF6}t&+i8GpqXWYN-iV-Ac=nO?| z5wO48&6TN@4-Gm4RC^$$hgI(|vKOmuB$@uC7#vEdEihc?7Z-Qog@aI{-3z9Wok|?} zNl-&%MkgQk>(?)8x|S)gJr!Ikab;UuHUR2@uWn$Qi1_AUARqBGeft3h59oEDWUJ5Y zdvtbnnYy|jFs4`aTA0D;Ae2s$hX9zaOb;GtyiedKyA2}&IfI=L)3`JtFlPnV2Q%~o zjE_J8%8iC7uKOrD*kcCJ#vdIm2ak{A@O1n!X!Nd2{;Te z28Q-(z?_4sbP~yjgwA%krN#g30|X-IY@=e-Xl{F^na4Oum88(W{Liuh$36d}qbUwf z7M9{-e8lkAz+mRx(f9@o5-z!l{e*;m*|oM%X4$fGBJn@HnOe;~w+a+HPG#_&n|KqH zfnqs>SBz%#q@*+;V1VtAk&(?eP4@RU0pPuQ^=hC8-cKI2L{}{V*%%yZ;Q8I%-2+S= z7G;o-m*}tq9R+3E43ah^1PKA%Ems=f-PL7fZ5?@%2@a?i)yOI$1KU`p=% z+l<-|{y_|JkBUk@vp*s(jxk^bsLV`Z)#NNpa>X#=$gp(-vl{#KZrP>HqUDRQa747n@7MxYCQHK=*px0G31 zdm5MUM&fj=(D)Rh4%^Gil-DLx4a*X~p?VehxmIb+ruJ9>Z`-Hm;NqY@pRB%@=1T(n z8(IOez%3&3etaV?uMVgK1i*8SLi1TF#eep8cij#rUb`R71TtI`2IEZ6FX&x^QBnNB zmHc7W^A5z2lLQO8d_z$PX1@>@PUw!!mC&N ziC86Wp|d_eGPIRRXhiBE?!VN;q@|Op{ho`PXnr$V0;3?}vZe zMv@3P`rqP49>^ge!T7*UprfZp%fb>|U0uyzM=Y62IL_KHubYBYos?)Rp%8GqUL<|U zzg*PvO5Fb$es%3s144TcIjG?3yDgsR!6L$+Ah%dPT@+MBr7^+S5DM37MB4vTV(+5< z!LWeSVclIVWDDXtP!W&$Kg_!ZKX_Z;SY7Qlc<#Tck*5h$GUbfvD_Tc6$a_v56zbO~ z$;1E$hwSMS63$16tE)@xy_+&5<6H=2H^H3tJ`xn-6o%jbjL-C4+zPuNVo#Wp^UPgX z1cQLWxsfW>lk=nsp0>&#<|S!h=ntGdm>Keg9bG8~eg9FJ5#nW(iV7GuY6l81+5#4R zL>}q?96N|GVXp@;MSwYjxJ7-9XhKXPI0O2`PuZbNd1D3O13}8cJi8cVDd0+f`j7$< zRMEoK(xx1l0_^T#cFN_l7G}Iq)go;L3u64?t}OhlX0RmEwO{ z@hLxkya&vT5Im!l6!iQjs9!Mkz~GIao4NO6aTT2%$-@KD$D%5X^liIOD+f;E(7`_&Xf>&A-B_o}0pG zc?Eoaxj)lxtn@mJUO}5c8y_$Z0Mj?R>DCo`5Nj}%;RlpM3Wg-KwxTf}UI8Z&9u3A^ z`tCR8KziAMk(ZH?kyKs+quv1509S94+t&6yfB`_y&ICi+h7J=G6Fss6a0UYsE+Y3~ zd84%GKW7941i;kQ-vO$hTj2s7gM*ob^~uRrvgO72FWN#_yB6Ma+F)Vz%jTvdR}zyl zpK3boZhes}6J`}YIL-(kAH4P>rgbh@%jnRv={;!{t3nD~0O%5`2yUK(gBk=aM0Ie* zl^NXra}OB8v8mAeMgxgbTYI~?OEmQXa5ON0I=_Ga58WYs)w0zi0X44)dy7*6k&P;Y zTtMOR<9HeZ(4XiM`c_*DH3JiDXw!uSg|8n3P9$)s&Yqt6KNYs>sS%NplBT8^&&R@G zzz5dR`Zir92NXOoEa3zKW&4@u_}BsTJG^vs*FX&b`~jJSk>Kvd+%da`Lgur^YjSdO z9X&lj?P2^9gq`gd?K_lTMv*mh@i#_Ame40*Cd%_m?f7nP`^*uQ!-(Z))Xx?kucD9=3* zUYQzgw8?x0{k03Y*8(lJt;S1nfRmMQiUrtDFzq3tDFuQ6u8wJmNr?`8PoNzpO5gpP zfcl(m;MDr=*SdOT6N^^;Ykt0M^LTA-tvO9`;tF2Bb3tRH@gyf~P4So=aM>R-#pTtw z0a7lnUw8WsZ#^(gMH0ZH)5uW;BLgxwbkcf$dI+PN=z+Ku^0&d`V`H6*y-6@K{{z|q zgqsIrme%(65D`HyM`y7^dH<`-r?4jAjAGrte;*2vCDD*034hiylBODLCp37WaUdb?N%1H zRgvm*Vr}HUaJOM6)Iou>7sh}s$*vvz_ZrVpLJgd2YJBuOA*+NI+ano~P?#5BV2~xg z4yXpl?hbYUOtPYvfIe+Rguxr#-0dy;t>R?^26A_M@S) zOBmG*mK&IG3{Ns~WDp!kx*VDq*`(WW2)E;jV^CV-ch5E1RR z;Hwl>S9k8rwaF&&>x1Ox(% zsj5Xke#pwpcUBX8i;5zY`}V$OVt#3f>&X)-Pfw9BS;>2ETP^P~IH{{6>T7&5F%MtO z_Gc=%pLFS)*x5a{8RX~Z@96COqCu9Imseyyz~Fg0kfNreqrgXz_2Y+@%>pqIk*u6t zn1}MQ$M-M{?tft>guJNRcKq?8;+%AWuNp4V?K@4)(ZXl^$Ybo zb)6+8oD?{>(9qEOEtn}0J>=s3moHyzzWvmFNc{Zy^CRnRA}0m2a_t@0x^IIP%p}3m z>DYJfJbU)6adnji$5-OBbfPFinbIrc;!-uO)(-kD zE$r;V*4M2_F`sE`%UfEqvP6BEoi!vPBFb2O6?rr;FmQa~`PZTxTM9YutuT-h?UZKY z;Ak@`l}iqv+A=jW^E*9tmBYnrj#wKiEOk4w`PJ4I7aJSu-$+YK+y6#6Lsud%&Z2Z= z`^4S8J0&5ZYkK+tJ3IUOcvXZ;Se7j@6BCo4U>w0Y zHQvsH7uvrghsnO&{wIy(^o%Zz%Z6In*kn~z#rgR7BrqsX8atHJn-7vF5>hU}0r_ zI}#ZvNfUlV!{AAtkZoyH{`Qb$#|9VO`s$BfsIY@<)vq&JiT=Kj?pc>bD5^uS(+O+ZuFPx?Z26&roef3dG6x4G5UUL zLNYrmt7yb(eY8A4maeJ0J8(VVVxCbn^~VpAkT%-79xAu>$L6<7i;5m_5&3j^iKiOC z>xIL>MsI%g!V@|2+T66!U?H@&wst=oo6>U{+utu+FZD;`!^3#Q#noJ4J!V^_`$PBP zxu z;L}A$Z8#70>lLl7*^`oz5=A`XQ!e+jv2k(Vt@P7}M<5X+m2as~XE#}wpnr8b#LdeK zRV(@K9oLMl^ZkZ(Pg)xvdKyogK@$A_>&2(P688Pj7EI2Qow0t8nm7OXgEu%hxLZ7= zY$WpU)6!pN5-Q)hwwZO1Hq36?4yRZz|j6Kh#2$A@e=^hlk-YF>T%5*Z?W^ zi4E4s7*C!i>&nWC)o9s@Ix)r+cA5&4@@NoVpsQFDwvYJJJh2*h0tQCL1wL{^x&8zm zYZ^R^c7_Y6xEjZeaKjb2{`))nVoG;AI+c`^_K%L#b#)cR#l7;r&)uhb7^9%6;1$Sp zdDbUGWo%^?n3u<<$Phu11L)R1Pvawwd>mMzyXXH^Gxw#QURH5&Sbx7-bxnlWW~h(+t}Cu ztSo5@5cr92=>1d8Zo?9O83zhRlS?OqUNCR?uf={uyqE z+7~_PP%$$z`?+9$H%QuLy}0dofdi4Vw$6`Sdup{xI~xEcEa*Z&8hCnACMo->J&a$yazoM;cEsa~bT4RTLE?3mNbE<&GAt6yR-qYP3 zi66VZzW$ytI@FOVN*?B1#vd+fYUCpzEh!hFzvL!6>2bq(Ol;1pcU)}jjWTZUla$-^ zuMYI5Y>ygFa~rBxBrZ0c8s=tvsp=UkTh4AjRwkCfd`6#tKOO7Tx5p&CpddIf5PRRX zE^=9mE&9DZX+W#^vu9xraj&$sXH$JC!^0EJxH~`IxD}g})M~{uFg2y}?j6Flu&~hG z-QCVoSW!V#IHX`~Ob;c;`~CyQ&Fd)m@Hjgpw>jH*i*ym!Z=;Rd@TRKz#l?|YSX$au z>ArdMMz+DTacaP#Jg=aj6LZ3%y#LRipWEAyL{e0O&(PjlJ2+@8-lC(YpRwi3;FeES z{i@28(J+$qR6}&f@mkt54&;H}+UQiypv8!mKC5w6%*^KxS{T$7KhG{a;aJw{Kn~`&{&0e+IF;Y6sU3~WS%`7_e+%L6}v;3jnKrY66EYydCe(M(Wv5ASv zXH_OT;t9A>=mwXwjY^l}8DE%;cEq$HZO!fNej2Of*yxGM*+|CiSVactwb+CNiR^*b zJPDaODHvhrWy0P(hU|o|X+>;=hSt~fxZ}rUqib~<9#@X_+@f-SaJ$t~=Bh1(%GAY$ zGqK=FQhZi{i13We0(*tgLzeRL$Ix21L~KcVgq*PH23* zJn)c+hzJM7$w?u`$8xBkZ+u))Ss6!8POhx7a?Y0TTYi4#w{OeAzWi_NPZ&l` zf~n4@Z_&}w<>k5}2f6ys0O$aea&mHnj>g}i4)Lc(D()S0Z;j{AJB(E5GO^-YCVs9R z?2cL%%B4%p@UdVsTUuJKtgfb&l|=#)K73V;w8Yf!)0dQ%X1aU`odlYN?=K1P@Y=>k zx$`ZGjIprHrd*w*Ftf5Yo0JxNoH>#f)K*u28QWNlGfl2y6?4-y|J@P6Xl`i zrv;F>!NKP=VHk|ci3Y~TXbn@nTb@TXZY9F}_VXAvl?iZ|Fx>nmY-BRkIzDIl$&c21 z2wv^0T+z{E0?up4?H?Qz1GpiG=&M(+>~jUUxh0`v!~K$zDLJ{g_}vcKh#%@cAi?g$ z*h)|e*d&~onBYT_lxy`k;3VvF`?_K{;OT$ZFJO`deTE~M**hU2C8Y)gnVi%!2WFRx z`VQ^<=z@ZWIed8Y-KnY6(J?V`$;nxTg}882gSjuNJTIz+&|zFTiH zpKx{cz;$ z{#Dh}Q~G&>mF0cDQG`YA3pdZwWfADum8iQpS8R;2ubQtpxH{)3_17loiC&Z>6ez1sp|>=ikmA@#ycs+5t8W z&OMBqFzXEM?ZcH4cqU>v%nb|-l1Ac-YTpYa@zOVYOwJs*ejJW^=i^QYXtQ zwMwrEo9xU?pQU6so20pxhV!llRZtZ6_M9LDfYS(3;@-lgobTbCq5}>A;3*R+p8B<} zE@^ky9zaD@U3qeNnB?~D+gkbm$UjTr_3s&pS{Wb3pp9U(gQB$C7=7ZnKHOOKZYee; zWr=(WIYwtxNu~qd@Z}38f5ytz*3x*@JE4>Lh}{~vtD2==-c9)jL^%ocb)Xx}r?X&w zZM&q))&J;RJ8*>olE>ZRPk;KvSU(&vVE7U?NTE}U=;(Q{cK1e^t+3A@yXBJJ+!+4&=Z+c!1Eky4nJHjD1K z!{}2k{2@Wf?!4qGi$PccT7O!395*n8^?Jq5>yBL*JpeL3_L)UW5EV;t$0_#z z(qj9Zc6y(T%<$^+B2maiP5ai^hU1rjfS20ZP&m6>uZkowioYgeAWNS?ClV18$EBoX zS60Tv$CIh4tHTtce)1#=Xlh7xwSagk9y|%C&Sa>!zQI8$Xa_7Ttp45VA3tK7n#2LK zm9ryMnOX`x4_M(@ipDm07sRS`AENp--|BFEgm#P+ej!ody?nh*z9y@&JP2%P`u@H? zL`+=#x@m8+RE`Ep80_wHF8xlD01~w&do$;A^XtpYt*hi$HI5U!-Yq`}HBC*kxpmi; zmko|KCt%9u@3rv1TMXQ%`2PKS6O1R*UP|EMbg2$5E-x+Oz+hP3+yq(q@>fbsbTp@c zfIJXis2Kb2^xvs{#2d>khY%)q_Nd#)f`wV)lH4Pfkv1aMU{`@(EdiK{we@n|JB!kGWUgPCUQm|E+2r$j< z2>d0Xa(^!E_5JztK!=?r3bSyCRru$%6N*NI(5cJAO832lsakXQgs}}1PftDhD3~fg zY-g?mjRR4quBF9vren#>Xqof4sm2m;7tp`kq|`z4?!7D!ogF#^JLW7=tM$6FfIBcI z5eG*{wO6ltPWF~gnuzOv0!YLG6G~Mzctm1mX?erYxUSEv#iVrSueyre<~aX7eEdwm zQaJa{PI-~z=^Oks%1TP~j~|D@h=jg=Ocpe};YjJR|140Nwy<^=6JWI8!o=Mjs3!~8 z3j8BHr`1&bMSWykr8mimIFi=@z72kiM$MsT3g)ECElf@DDh;p3ox= z+_(EDL3lxVW*U6qn_iJTwGk7Pv_>QD{}bB zaX*iVX_(9|!Eu<%FJk$K^Cc-4=r;s+yh#I932gK}j<${tGdp``Ufz1vt)ftt+nm#b zxyXy#zRdtka{i&q2a+^l??2UmivZ%5kdSaK{*+2V+d=TW@Cxl}4{5qOrrMsm60F@p zcV7~`dHp{<_Z=w^9(1M_uCDPiR34GYu`e>+CVKf8v>zpKn*O@d>}+2+b_NE9A}zLz z%*@9kBEeHzF>?pdQqP`0f8UFIOup_|lLQ__S|1a@(wq11qX0k2$jNt3+|SodMQQo@ zEu!2-@2=6ix8L~qfxAi2{kX`%HYqmtr@^DIeP+(H{){O8G$dMb zad|q_@O$iSgVL*4_Y3P!S%9X3(u1oU92{6p*2ckPy;1xQp%xWQfhipd_ph;gdngT9 zBHVEBeZgOq^#}RnS(&IRa;?)dxlTYx_?VyHALh%~uTKRYfP?kYW3qGTN*NF1<`3r` z8g%cEzke$yd!Cwujur=g4UmJfBWo{D*Vx$Diry_Lo0yu86dSeO5o22FO))h#ma<0} zFFYF(PoMDc*suO&hGL+nr{^?LCJ>yRMd>#~!!(BmJVhi--1rg6Y?gHE#_JPz5f||! z07F$K2Z;r!#E(F5z;1*20YC}b$-Yno2#LNvRfzB*3ErH<%7N=hEMZPfnq1A{O`
}ytS`QA7;0jUHlP`B1O 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 toggling settings:(12) toggling settings:tsh - toggle scripts for the current host (temporarily) Date: Wed, 14 Mar 2018 10:19:23 +0100 Subject: [PATCH 042/223] Revert "Insert qutebrowser scripts on DocumentCreation and DocumentReady" This reverts commit fac0f66e52c189344c8d5c27e0018c5909cda7f9. --- qutebrowser/browser/webengine/webenginetab.py | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index c21d29fef..6206a7774 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -640,21 +640,17 @@ class WebEngineTab(browsertab.AbstractTab): utils.read_file('javascript/webelem.js'), utils.read_file('javascript/caret.js'), ]) - scripts = self._widget.page().scripts() - script = QWebEngineScript() - script.setInjectionPoint(QWebEngineScript.DocumentCreation) + # We can't use DocumentCreation here as WORKAROUND for + # https://bugreports.qt.io/browse/QTBUG-66011 + script.setInjectionPoint(QWebEngineScript.DocumentReady) script.setSourceCode(js_code) - script.setWorldId(QWebEngineScript.ApplicationWorld) - # FIXME:qtwebengine what about runsOnSubFrames? - scripts.insert(script) - # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-66011 - script2 = QWebEngineScript() - script2.setInjectionPoint(QWebEngineScript.DocumentReady) - script2.setSourceCode(js_code) - script2.setWorldId(QWebEngineScript.ApplicationWorld) - scripts.insert(script2) + page = self._widget.page() + script.setWorldId(QWebEngineScript.ApplicationWorld) + + # FIXME:qtwebengine what about runsOnSubFrames? + page.scripts().insert(script) def _install_event_filter(self): self._widget.focusProxy().installEventFilter(self._mouse_event_filter) From a22f973c9955e5601786daef8e62fef4b9e6bd07 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 14 Mar 2018 18:12:29 +0100 Subject: [PATCH 043/223] Don't emit predicted_navigation for reloads at all When we reload a page because of a config change, we won't get another titleChanged signal (at least sometimes). Also, the predicted_navigation signal is worthless when reloading anyways, as we're going to load the same URL and not something different. Fixes #3718 --- qutebrowser/browser/webengine/webenginetab.py | 3 --- qutebrowser/browser/webkit/webkittab.py | 3 --- 2 files changed, 6 deletions(-) diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index 6206a7774..fceec9074 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -706,9 +706,6 @@ class WebEngineTab(browsertab.AbstractTab): self._widget.shutdown() def reload(self, *, force=False): - if self.url().isValid(): - self.predicted_navigation.emit(self.url()) - if force: action = QWebEnginePage.ReloadAndBypassCache else: diff --git a/qutebrowser/browser/webkit/webkittab.py b/qutebrowser/browser/webkit/webkittab.py index 934fdb3c3..ac2610f5f 100644 --- a/qutebrowser/browser/webkit/webkittab.py +++ b/qutebrowser/browser/webkit/webkittab.py @@ -701,9 +701,6 @@ class WebKitTab(browsertab.AbstractTab): self._widget.shutdown() def reload(self, *, force=False): - if self.url().isValid(): - self.predicted_navigation.emit(self.url()) - if force: action = QWebPage.ReloadAndBypassCache else: From 7a861b711974707791683db95129f08793b28468 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 14 Mar 2018 18:17:55 +0100 Subject: [PATCH 044/223] Update changelog --- doc/changelog.asciidoc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index 605d2af6f..9fa7dc2cb 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -51,6 +51,8 @@ Fixed - Keys on the numeric keypad now fall back to the same bindings without `Num+` if no `Num+` binding was found. - Fixed hinting on some pages with Qt < 5.10. +- Titles are now displayed correctly again for tabs which are cloned or loaded + from sessions. v1.2.0 ------ From d232b3ea57a7379c3776a0d65bd5b8fa4f29b42e Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 14 Mar 2018 19:31:36 +0100 Subject: [PATCH 045/223] Disable test_software_rendering on macOS For some reason, macOS doesn't care about us disabling software rendering --- tests/end2end/test_invocations.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/end2end/test_invocations.py b/tests/end2end/test_invocations.py index 3b6d51e26..d6b4b1300 100644 --- a/tests/end2end/test_invocations.py +++ b/tests/end2end/test_invocations.py @@ -379,6 +379,7 @@ def test_qute_settings_persistence(short_tmpdir, request, quteproc_new): @pytest.mark.no_xvfb @pytest.mark.no_ci +@pytest.mark.not_mac def test_force_software_rendering(request, quteproc_new): """Make sure we can force software rendering with -s.""" if not request.config.webengine: From 84c7c37e8eb61f7e3dddbbfc6dbbcfd3f5afeffd Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 14 Mar 2018 09:34:56 +0100 Subject: [PATCH 046/223] Swap Control/Meta back on macOS Fixes #3697 (cherry picked from commit fd9e7bed7fd9842eac22ed304a094a92cc953577) --- qutebrowser/keyinput/keyutils.py | 12 +++++++ tests/unit/keyinput/test_basekeyparser.py | 9 +++-- tests/unit/keyinput/test_keyutils.py | 42 +++++++++++++++++++---- 3 files changed, 54 insertions(+), 9 deletions(-) diff --git a/qutebrowser/keyinput/keyutils.py b/qutebrowser/keyinput/keyutils.py index d0a17eca8..1f34fcae0 100644 --- a/qutebrowser/keyinput/keyutils.py +++ b/qutebrowser/keyinput/keyutils.py @@ -505,6 +505,18 @@ class KeySequence: not ev.text().isupper()): modifiers = Qt.KeyboardModifiers() + # On macOS, swap Ctrl and Meta back + # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-51293 + if utils.is_mac: + if modifiers & Qt.ControlModifier and modifiers & Qt.MetaModifier: + pass + elif modifiers & Qt.ControlModifier: + modifiers &= ~Qt.ControlModifier + modifiers |= Qt.MetaModifier + elif modifiers & Qt.MetaModifier: + modifiers &= ~Qt.MetaModifier + modifiers |= Qt.ControlModifier + keys = list(self._iter_keys()) keys.append(key | int(modifiers)) diff --git a/tests/unit/keyinput/test_basekeyparser.py b/tests/unit/keyinput/test_basekeyparser.py index e4837783a..7915e2b75 100644 --- a/tests/unit/keyinput/test_basekeyparser.py +++ b/tests/unit/keyinput/test_basekeyparser.py @@ -25,6 +25,7 @@ from PyQt5.QtCore import Qt import pytest from qutebrowser.keyinput import basekeyparser, keyutils +from qutebrowser.utils import utils # Alias because we need this a lot in here. @@ -153,14 +154,16 @@ class TestHandle: keyparser._read_config('prompt') def test_valid_key(self, fake_keyevent, keyparser): - keyparser.handle(fake_keyevent(Qt.Key_A, Qt.ControlModifier)) - keyparser.handle(fake_keyevent(Qt.Key_X, Qt.ControlModifier)) + modifier = Qt.MetaModifier if utils.is_mac else Qt.ControlModifier + keyparser.handle(fake_keyevent(Qt.Key_A, modifier)) + keyparser.handle(fake_keyevent(Qt.Key_X, modifier)) keyparser.execute.assert_called_once_with('message-info ctrla', None) assert not keyparser._sequence def test_valid_key_count(self, fake_keyevent, keyparser): + modifier = Qt.MetaModifier if utils.is_mac else Qt.ControlModifier keyparser.handle(fake_keyevent(Qt.Key_5)) - keyparser.handle(fake_keyevent(Qt.Key_A, Qt.ControlModifier)) + keyparser.handle(fake_keyevent(Qt.Key_A, modifier)) keyparser.execute.assert_called_once_with('message-info ctrla', 5) @pytest.mark.parametrize('keys', [ diff --git a/tests/unit/keyinput/test_keyutils.py b/tests/unit/keyinput/test_keyutils.py index 92e9292ef..37519ca85 100644 --- a/tests/unit/keyinput/test_keyutils.py +++ b/tests/unit/keyinput/test_keyutils.py @@ -28,6 +28,7 @@ from PyQt5.QtWidgets import QWidget from tests.unit.keyinput import key_data from qutebrowser.keyinput import keyutils +from qutebrowser.utils import utils @pytest.fixture(params=key_data.KEYS, ids=lambda k: k.attribute) @@ -346,20 +347,28 @@ class TestKeySequence: @pytest.mark.parametrize('old, key, modifiers, text, expected', [ ('a', Qt.Key_B, Qt.NoModifier, 'b', 'ab'), ('a', Qt.Key_B, Qt.ShiftModifier, 'B', 'aB'), - ('a', Qt.Key_B, Qt.ControlModifier | Qt.ShiftModifier, 'B', - 'a'), + ('a', Qt.Key_B, Qt.AltModifier | Qt.ShiftModifier, 'B', + 'a'), # Modifier stripping with symbols ('', Qt.Key_Colon, Qt.NoModifier, ':', ':'), ('', Qt.Key_Colon, Qt.ShiftModifier, ':', ':'), - ('', Qt.Key_Colon, Qt.ControlModifier | Qt.ShiftModifier, ':', - ''), + ('', Qt.Key_Colon, Qt.AltModifier | Qt.ShiftModifier, ':', + ''), + + # Swapping Control/Meta on macOS + ('', Qt.Key_A, Qt.ControlModifier, '', + '' if utils.is_mac else ''), + ('', Qt.Key_A, Qt.ControlModifier | Qt.ShiftModifier, '', + '' if utils.is_mac else ''), + ('', Qt.Key_A, Qt.MetaModifier, '', + '' if utils.is_mac else ''), # Handling of Backtab ('', Qt.Key_Backtab, Qt.NoModifier, '', ''), ('', Qt.Key_Backtab, Qt.ShiftModifier, '', ''), - ('', Qt.Key_Backtab, Qt.ControlModifier | Qt.ShiftModifier, '', - ''), + ('', Qt.Key_Backtab, Qt.AltModifier | Qt.ShiftModifier, '', + ''), # Stripping of Qt.GroupSwitchModifier ('', Qt.Key_A, Qt.GroupSwitchModifier, 'a', 'a'), @@ -370,6 +379,27 @@ class TestKeySequence: new = seq.append_event(event) assert new == keyutils.KeySequence.parse(expected) + @pytest.mark.fake_os('mac') + @pytest.mark.parametrize('modifiers, expected', [ + (Qt.ControlModifier, + Qt.MetaModifier), + (Qt.MetaModifier, + Qt.ControlModifier), + (Qt.ControlModifier | Qt.MetaModifier, + Qt.ControlModifier | Qt.MetaModifier), + (Qt.ControlModifier | Qt.ShiftModifier, + Qt.MetaModifier | Qt.ShiftModifier), + (Qt.MetaModifier | Qt.ShiftModifier, + Qt.ControlModifier | Qt.ShiftModifier), + (Qt.ShiftModifier, Qt.ShiftModifier), + ]) + def test_fake_mac(self, fake_keyevent, modifiers, expected): + """Make sure Control/Meta are swapped with a simulated Mac.""" + seq = keyutils.KeySequence() + event = fake_keyevent(key=Qt.Key_A, modifiers=modifiers) + new = seq.append_event(event) + assert new[0] == keyutils.KeyInfo(Qt.Key_A, expected) + @pytest.mark.parametrize('key', [Qt.Key_unknown, 0x0]) def test_append_event_invalid(self, key): seq = keyutils.KeySequence() From 523502785afdcc14a956318c67b6e68ffd910fa0 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 14 Mar 2018 20:17:34 +0100 Subject: [PATCH 047/223] Update changelog for v1.2.1 --- doc/changelog.asciidoc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index 9fa7dc2cb..86ff1eb84 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -32,8 +32,8 @@ Fixed - Using hints before a page is fully loaded is now possible again. -v1.2.1 (unreleased) -------------------- +v1.2.1 +------ Fixed ~~~~~ @@ -53,6 +53,7 @@ Fixed - Fixed hinting on some pages with Qt < 5.10. - Titles are now displayed correctly again for tabs which are cloned or loaded from sessions. +- Shortcuts now correctly use `Ctrl` instead of `Command` on macOS again. v1.2.0 ------ From a60bae30b7d0a880959c45dc7c01b3b73328012d Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 14 Mar 2018 20:19:14 +0100 Subject: [PATCH 048/223] Release v1.2.1 (cherry picked from commit 6145786e461b104f2b23faf46a24172ba81fbeea) --- qutebrowser/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutebrowser/__init__.py b/qutebrowser/__init__.py index 84831daaa..31fd5983f 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, 2, 0) +__version_info__ = (1, 2, 1) __version__ = '.'.join(str(e) for e in __version_info__) __description__ = "A keyboard-driven, vim-like browser based on PyQt5." From 1d562d919efb67d2a7d09527ad69316bd9cbb005 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 14 Mar 2018 21:10:35 +0100 Subject: [PATCH 049/223] Include requirements files in built release This is needed to use "tox -e mkvenv" --- MANIFEST.in | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index 9dace6f98..242fea292 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -8,6 +8,7 @@ graft icons graft doc/img graft misc/apparmor graft misc/userscripts +graft misc/requirements recursive-include scripts *.py *.sh *.js include qutebrowser/utils/testfile include qutebrowser/git-commit-id @@ -32,8 +33,6 @@ include doc/qutebrowser.1.asciidoc include doc/changelog.asciidoc prune tests prune qutebrowser/3rdparty -prune misc/requirements -prune misc/docker exclude pytest.ini exclude qutebrowser.rcc exclude qutebrowser/javascript/.eslintrc.yaml From c2b995edde122de0e20a7e42c0cf6639dd459cb3 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 14 Mar 2018 21:11:03 +0100 Subject: [PATCH 050/223] Update build_release for github3.py 1.0 --- 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 666e0432e..94f02dbf0 100755 --- a/scripts/dev/build_release.py +++ b/scripts/dev/build_release.py @@ -361,7 +361,7 @@ def github_upload(artifacts, tag): repo = gh.repository('qutebrowser', 'qutebrowser') release = None # to satisfy pylint - for release in repo.iter_releases(): + for release in repo.releases(): if release.tag_name == tag: break else: From f538fc8b745310fae559660f16023e289b7e15b8 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 14 Mar 2018 21:50:22 +0100 Subject: [PATCH 051/223] Update release checklist [ci skip] --- doc/contributing.asciidoc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/contributing.asciidoc b/doc/contributing.asciidoc index 9937434b9..2c95c125e 100644 --- a/doc/contributing.asciidoc +++ b/doc/contributing.asciidoc @@ -670,10 +670,11 @@ qutebrowser release ~~~~~~~~~~~~~~~~~~~ * Make sure there are no unstaged changes and the tests are green. +* Make sure all issues with the related milestone are closed. * Run `x=... y=...` to set the respective shell variables. -* Adjust `__version_info__` in `qutebrowser/__init__.py`. * Update changelog (remove *(unreleased)*). +* Adjust `__version_info__` in `qutebrowser/__init__.py`. * Commit. * Create annotated git tag (`git tag -s "v1.$x.$y" -m "Release v1.$x.$y"`). @@ -683,7 +684,7 @@ qutebrowser release * Mark the milestone at https://github.com/qutebrowser/qutebrowser/milestones as closed. -* Linux: Run `git checkout v1.$x.$y && python3 scripts/dev/build_release.py --upload v1.$x.$y`. +* 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). * On server: Run `python3 scripts/dev/download_release.py v1.X.Y` (replace X/Y by hand). From 1d25b212d534adc121a36569b5f289c10b6c2338 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 15 Mar 2018 09:06:40 +0100 Subject: [PATCH 052/223] Add missing qapp fixtures to tests See #3723 --- tests/unit/config/test_configcommands.py | 3 ++- tests/unit/misc/test_utilcmds.py | 2 +- tests/unit/utils/test_version.py | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/unit/config/test_configcommands.py b/tests/unit/config/test_configcommands.py index 6434a1c9c..056bdb455 100644 --- a/tests/unit/config/test_configcommands.py +++ b/tests/unit/config/test_configcommands.py @@ -371,7 +371,8 @@ class TestEdit: """Tests for :config-edit.""" pytestmark = pytest.mark.usefixtures('config_tmpdir', 'data_tmpdir', - 'config_stub', 'key_config_stub') + 'config_stub', 'key_config_stub', + 'qapp') def test_no_source(self, commands, mocker): mock = mocker.patch('qutebrowser.config.configcommands.editor.' diff --git a/tests/unit/misc/test_utilcmds.py b/tests/unit/misc/test_utilcmds.py index c63c7bf86..cfa115412 100644 --- a/tests/unit/misc/test_utilcmds.py +++ b/tests/unit/misc/test_utilcmds.py @@ -153,6 +153,6 @@ def tabbed_browser(stubs, win_registry): objreg.delete('tabbed-browser', scope='window', window=0) -def test_version(tabbed_browser): +def test_version(tabbed_browser, qapp): utilcmds.version(win_id=0) assert tabbed_browser.opened_url == QUrl('qute://version') diff --git a/tests/unit/utils/test_version.py b/tests/unit/utils/test_version.py index 163df0e84..fe45fec97 100644 --- a/tests/unit/utils/test_version.py +++ b/tests/unit/utils/test_version.py @@ -963,7 +963,7 @@ def test_version_output(params, stubs, monkeypatch): assert version.version() == expected -def test_opengl_vendor(): +def test_opengl_vendor(qapp): """Simply call version.opengl_vendor() and see if it doesn't crash.""" pytest.importorskip("PyQt5.QtOpenGL") return version.opengl_vendor() From 5dbda3016be82985ee16671615aa3c2e147952f0 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 15 Mar 2018 14:16:10 +0100 Subject: [PATCH 053/223] Clean up predicted_navigation handling This also adds some more logging for #3718 --- qutebrowser/browser/browsertab.py | 11 +++++++++-- qutebrowser/browser/webengine/webenginetab.py | 2 +- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/qutebrowser/browser/browsertab.py b/qutebrowser/browser/browsertab.py index f04fa5c24..cbcca0c00 100644 --- a/qutebrowser/browser/browsertab.py +++ b/qutebrowser/browser/browsertab.py @@ -665,8 +665,7 @@ class AbstractTab(QWidget): objreg.register('hintmanager', hintmanager, scope='tab', window=self.win_id, tab=self.tab_id) - self.predicted_navigation.connect( - lambda url: self.title_changed.emit(url.toDisplayString())) + self.predicted_navigation.connect(self._on_predicted_navigation) def _set_widget(self, widget): # pylint: disable=protected-access @@ -715,6 +714,14 @@ class AbstractTab(QWidget): evt.posted = True QApplication.postEvent(recipient, evt) + @pyqtSlot(QUrl) + def _on_predicted_navigation(self, url): + """Adjust the title if we are going to visit an URL soon.""" + qtutils.ensure_valid(url) + url_string = url.toDisplayString() + log.webview.debug("Predicted navigation: {}".format(url_string)) + self.title_changed.emit(url_string) + @pyqtSlot(QUrl) def _on_url_changed(self, url): """Update title when URL has changed and no title is available.""" diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index fceec9074..195ddb3c6 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -930,7 +930,7 @@ class WebEngineTab(browsertab.AbstractTab): @pyqtSlot(QUrl) def _on_predicted_navigation(self, url): """If we know we're going to visit an URL soon, change the settings.""" - qtutils.ensure_valid(url) + super()._on_predicted_navigation(url) self.settings.update_for_url(url) @pyqtSlot(usertypes.NavigationRequest) From 1e4b80d1ac4b234de5d9cf139c9518d2859337db Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 15 Mar 2018 14:44:44 +0100 Subject: [PATCH 054/223] Don't emit predicted_navigation when reloading because of it When we reload because of a config change in _on_load_finished, we can't use self.reload() as no URL is set yet. Instead, we call self.openurl with the current URL. However, we need to make sure we don't emit predicted_navigation again at that point. This should (finally) fix #3718 --- qutebrowser/browser/browsertab.py | 5 +++-- qutebrowser/browser/webengine/webenginetab.py | 14 ++++++++++---- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/qutebrowser/browser/browsertab.py b/qutebrowser/browser/browsertab.py index cbcca0c00..b1fab3c9d 100644 --- a/qutebrowser/browser/browsertab.py +++ b/qutebrowser/browser/browsertab.py @@ -822,9 +822,10 @@ class AbstractTab(QWidget): def load_status(self): return self._load_status - def _openurl_prepare(self, url): + def _openurl_prepare(self, url, *, predict=True): qtutils.ensure_valid(url) - self.predicted_navigation.emit(url) + if predict: + self.predicted_navigation.emit(url) def openurl(self, url): raise NotImplementedError diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index 195ddb3c6..e9423043b 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -669,9 +669,15 @@ class WebEngineTab(browsertab.AbstractTab): self.zoom.set_factor(self._saved_zoom) self._saved_zoom = None - def openurl(self, url): + 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. + """ self._saved_zoom = self.zoom.factor() - self._openurl_prepare(url) + self._openurl_prepare(url, predict=predict) self._widget.load(url) def url(self, requested=False): @@ -914,10 +920,10 @@ class WebEngineTab(browsertab.AbstractTab): if ok and self._reload_url is not None: # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-66656 log.config.debug( - "Reloading {} because of config change".format( + "Loading {} again because of config change".format( self._reload_url.toDisplayString())) QTimer.singleShot(100, lambda url=self._reload_url: - self.openurl(url)) + self.openurl(url, predict=False)) self._reload_url = None if not qtutils.version_check('5.10', compiled=False): From 6f8eb419ae96804d4c43a4caf8987a47feebe78b Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 15 Mar 2018 14:51:03 +0100 Subject: [PATCH 055/223] Emit predicted_navigation when loading sessions This avoids reloads (because of changed settings) after a session has been loaded. Related to #3718 --- qutebrowser/browser/webengine/webenginetab.py | 3 +++ qutebrowser/browser/webkit/webkittab.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index e9423043b..87adbc81e 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -506,6 +506,9 @@ class WebEngineHistory(browsertab.AbstractHistory): return qtutils.deserialize(data, self._history) def load_items(self, items): + if items: + self._tab.predicted_navigation.emit(items[-1].url) + stream, _data, cur_data = tabhistory.serialize(items) qtutils.deserialize_stream(stream, self._history) diff --git a/qutebrowser/browser/webkit/webkittab.py b/qutebrowser/browser/webkit/webkittab.py index ac2610f5f..29dd5f03b 100644 --- a/qutebrowser/browser/webkit/webkittab.py +++ b/qutebrowser/browser/webkit/webkittab.py @@ -537,6 +537,9 @@ class WebKitHistory(browsertab.AbstractHistory): return qtutils.deserialize(data, self._history) def load_items(self, items): + if items: + self._tab.predicted_navigation.emit(items[-1].url) + stream, _data, user_data = tabhistory.serialize(items) qtutils.deserialize_stream(stream, self._history) for i, data in enumerate(user_data): From 69a013bc82d2ecc485e4b0659c16af7b78606f6d Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 15 Mar 2018 14:53:20 +0100 Subject: [PATCH 056/223] Update changelog --- doc/changelog.asciidoc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index 86ff1eb84..8fb273116 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -31,6 +31,10 @@ Fixed ~~~~~ - Using hints before a page is fully loaded is now possible again. +- Tab titles for tabs loaded from sessions should now really be correct instead + of showing the URL. +- Loading URLs with customized settings from a session now avoids an additional + reload. v1.2.1 ------ From 01845faac550cb486fb8e1acf7f77226f0a11259 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 16 Mar 2018 08:20:27 +0100 Subject: [PATCH 057/223] Set window title/icon on correct object This was a regression introduced in #3613. Fixes #3727 --- qutebrowser/mainwindow/tabbedbrowser.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py index 9ee87a874..11e898cd0 100644 --- a/qutebrowser/mainwindow/tabbedbrowser.py +++ b/qutebrowser/mainwindow/tabbedbrowser.py @@ -196,7 +196,7 @@ class TabbedBrowser(QWidget): fields['id'] = self._win_id title = title_format.format(**fields) - self.window().setWindowTitle(title) + self.widget.window().setWindowTitle(title) def _connect_tab_signals(self, tab): """Set up the needed signals for tab.""" @@ -532,11 +532,11 @@ class TabbedBrowser(QWidget): if config.val.tabs.favicons.show: self.widget.setTabIcon(i, tab.icon()) if config.val.tabs.tabs_are_windows: - self.window().setWindowIcon(tab.icon()) + self.widget.window().setWindowIcon(tab.icon()) else: self.widget.setTabIcon(i, QIcon()) if config.val.tabs.tabs_are_windows: - self.window().setWindowIcon(self.default_window_icon) + self.widget.window().setWindowIcon(self.default_window_icon) @pyqtSlot() def on_load_started(self, tab): @@ -556,7 +556,7 @@ class TabbedBrowser(QWidget): else: if (config.val.tabs.tabs_are_windows and config.val.tabs.favicons.show): - self.window().setWindowIcon(self.default_window_icon) + self.widget.window().setWindowIcon(self.default_window_icon) if idx == self.widget.currentIndex(): self._update_window_title() @@ -628,7 +628,7 @@ class TabbedBrowser(QWidget): return self.widget.setTabIcon(idx, icon) if config.val.tabs.tabs_are_windows: - self.window().setWindowIcon(icon) + self.widget.window().setWindowIcon(icon) @pyqtSlot(usertypes.KeyMode) def on_mode_left(self, mode): From a6ce188e0d5ef741f4b8002b9cc1f45547d4bdde Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 16 Mar 2018 08:21:11 +0100 Subject: [PATCH 058/223] Update changelog --- doc/changelog.asciidoc | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index 8fb273116..d02dfcc2a 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -35,6 +35,7 @@ Fixed of showing the URL. - Loading URLs with customized settings from a session now avoids an additional reload. +- The window icon and title now get set correctly again. v1.2.1 ------ From 3b0b4ffe66608e6c9749a6d19fa2d124df2267d0 Mon Sep 17 00:00:00 2001 From: Bryan Bugyi Date: Fri, 16 Mar 2018 03:28:44 -0400 Subject: [PATCH 059/223] Fix: restrict output of `task` to one line (closes #3726) --- misc/userscripts/taskadd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/userscripts/taskadd b/misc/userscripts/taskadd index b1ded245c..c7c7d8c67 100755 --- a/misc/userscripts/taskadd +++ b/misc/userscripts/taskadd @@ -25,7 +25,7 @@ [[ $QUTE_MODE == 'hints' ]] && title=$QUTE_SELECTED_TEXT || title=$QUTE_TITLE # try to add the task and grab the output -if msg="$(task add "$title" "$*" 2>&1)"; then +if msg="$(task add "$title" "$*" 2>&1 | head -n 1)"; then # annotate the new task with the url, send the output back to the browser task +LATEST annotate "$QUTE_URL" echo "message-info '$msg'" >> "$QUTE_FIFO" From fa282d574d7302290800c5888ccdb6413f7d6828 Mon Sep 17 00:00:00 2001 From: Bryan Bugyi Date: Fri, 16 Mar 2018 03:44:22 -0400 Subject: [PATCH 060/223] Fix: preserve exit status of `task` command (#3726) --- misc/userscripts/taskadd | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/misc/userscripts/taskadd b/misc/userscripts/taskadd index c7c7d8c67..59095e1bc 100755 --- a/misc/userscripts/taskadd +++ b/misc/userscripts/taskadd @@ -25,10 +25,10 @@ [[ $QUTE_MODE == 'hints' ]] && title=$QUTE_SELECTED_TEXT || title=$QUTE_TITLE # try to add the task and grab the output -if msg="$(task add "$title" "$*" 2>&1 | head -n 1)"; then +if msg="$(task add "$title" "$*" 2>&1)"; then # annotate the new task with the url, send the output back to the browser task +LATEST annotate "$QUTE_URL" - echo "message-info '$msg'" >> "$QUTE_FIFO" + echo "message-info '$msg'" | head -n 1 >> "$QUTE_FIFO" else - echo "message-error '$msg'" >> "$QUTE_FIFO" + echo "message-error '$msg'" | head -n 1 >> "$QUTE_FIFO" fi From f7074b80d0a68eec6fdfd13f2f82acc94ff2951e Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 16 Mar 2018 09:07:25 +0100 Subject: [PATCH 061/223] Fix lint --- qutebrowser/browser/browsertab.py | 2 +- qutebrowser/browser/webkit/webkittab.py | 4 ++-- qutebrowser/mainwindow/tabbedbrowser.py | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/qutebrowser/browser/browsertab.py b/qutebrowser/browser/browsertab.py index b1fab3c9d..d3345c88b 100644 --- a/qutebrowser/browser/browsertab.py +++ b/qutebrowser/browser/browsertab.py @@ -827,7 +827,7 @@ class AbstractTab(QWidget): if predict: self.predicted_navigation.emit(url) - def openurl(self, url): + def openurl(self, url, *, predict=True): raise NotImplementedError def reload(self, *, force=False): diff --git a/qutebrowser/browser/webkit/webkittab.py b/qutebrowser/browser/webkit/webkittab.py index 29dd5f03b..17757a761 100644 --- a/qutebrowser/browser/webkit/webkittab.py +++ b/qutebrowser/browser/webkit/webkittab.py @@ -671,8 +671,8 @@ class WebKitTab(browsertab.AbstractTab): settings = widget.settings() settings.setAttribute(QWebSettings.PrivateBrowsingEnabled, True) - def openurl(self, url): - self._openurl_prepare(url) + def openurl(self, url, *, predict=True): + self._openurl_prepare(url, predict=predict) self._widget.openurl(url) def url(self, requested=False): diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py index 11e898cd0..3d9eb27f4 100644 --- a/qutebrowser/mainwindow/tabbedbrowser.py +++ b/qutebrowser/mainwindow/tabbedbrowser.py @@ -536,7 +536,8 @@ class TabbedBrowser(QWidget): else: self.widget.setTabIcon(i, QIcon()) if config.val.tabs.tabs_are_windows: - self.widget.window().setWindowIcon(self.default_window_icon) + window = self.widget.window() + window.setWindowIcon(self.default_window_icon) @pyqtSlot() def on_load_started(self, tab): From 18146e2fbc9e40669941c5f572ff58a50321ef93 Mon Sep 17 00:00:00 2001 From: Bryan Bugyi Date: Fri, 16 Mar 2018 06:16:16 -0400 Subject: [PATCH 062/223] Fix: prevent unmatched quote (#3726) --- misc/userscripts/taskadd | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/misc/userscripts/taskadd b/misc/userscripts/taskadd index 59095e1bc..36e1c2ced 100755 --- a/misc/userscripts/taskadd +++ b/misc/userscripts/taskadd @@ -28,7 +28,7 @@ if msg="$(task add "$title" "$*" 2>&1)"; then # annotate the new task with the url, send the output back to the browser task +LATEST annotate "$QUTE_URL" - echo "message-info '$msg'" | head -n 1 >> "$QUTE_FIFO" + echo "message-info '$(echo "$msg" | head -n 1)'" >> "$QUTE_FIFO" else - echo "message-error '$msg'" | head -n 1 >> "$QUTE_FIFO" + echo "message-error '$(echo "$msg" | head -n 1)'" >> "$QUTE_FIFO" fi From f57e47c7423be68fdde2c2bd2a922c584e13f499 Mon Sep 17 00:00:00 2001 From: gammelon Date: Fri, 16 Mar 2018 11:42:51 +0100 Subject: [PATCH 063/223] Separate tests for _get_search_url --- tests/unit/utils/test_urlutils.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/tests/unit/utils/test_urlutils.py b/tests/unit/utils/test_urlutils.py index 03c5239fa..9df7ed0d0 100644 --- a/tests/unit/utils/test_urlutils.py +++ b/tests/unit/utils/test_urlutils.py @@ -299,19 +299,15 @@ def test_get_search_url(config_stub, url, host, query, open_base_url): """ config_stub.val.url.open_base_url = open_base_url url = urlutils._get_search_url(url) - if open_base_url and not query: - assert not url.path() - assert not url.fragment() - assert url.host() == host assert url.query() == query -@pytest.mark.parametrize('url, host, query', [ - ('test', 'www.qutebrowser.org', ''), - ('test-with-dash', 'www.example.org', ''), +@pytest.mark.parametrize('url, host', [ + ('test', 'www.qutebrowser.org'), + ('test-with-dash', 'www.example.org'), ]) -def test_get_search_url_open_base_url(config_stub, url, host, query): +def test_get_search_url_open_base_url(config_stub, url, host): """Test _get_search_url() with url.open_base_url_enabled. Args: @@ -319,7 +315,12 @@ def test_get_search_url_open_base_url(config_stub, url, host, query): host: The expected search machine host. query: The expected search query. """ - test_get_search_url(config_stub, url, host, query, True) + config_stub.val.url.open_base_url = True + url = urlutils._get_search_url(url) + assert not url.path() + assert not url.fragment() + assert not url.query() + assert url.host() == host @pytest.mark.parametrize('url', ['\n', ' ', '\n ']) From a52d18b700df262945384b70f1ffb58fc2fa0360 Mon Sep 17 00:00:00 2001 From: AlternateData Date: Sat, 17 Mar 2018 17:59:31 +0100 Subject: [PATCH 064/223] Add correct maximum and minimum value for tabs.switching_delay --- qutebrowser/config/configdata.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml index a6a2d5317..9cfd6cb90 100644 --- a/qutebrowser/config/configdata.yml +++ b/qutebrowser/config/configdata.yml @@ -1325,7 +1325,10 @@ tabs.show: tabs.show_switching_delay: default: 800 - type: Int + type: + name: Int + minval: -2147483648 + maxval: 2147483647 desc: "Duration (in milliseconds) to show the tab bar before hiding it when tabs.show is set to 'switching'." From 62d30fe58904f5bb0608037fa88855cdd7cef9eb Mon Sep 17 00:00:00 2001 From: AlternateData Date: Sun, 18 Mar 2018 10:06:41 +0100 Subject: [PATCH 065/223] use 0 and maxint as bounds --- qutebrowser/config/configdata.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml index 9cfd6cb90..d75772e4c 100644 --- a/qutebrowser/config/configdata.yml +++ b/qutebrowser/config/configdata.yml @@ -1327,8 +1327,8 @@ tabs.show_switching_delay: default: 800 type: name: Int - minval: -2147483648 - maxval: 2147483647 + minval: 0 + maxval: maxint desc: "Duration (in milliseconds) to show the tab bar before hiding it when tabs.show is set to 'switching'." From 8a3d9c0c01476e999d80ba921e07f35d9fe31cac Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 18 Mar 2018 18:58:29 +0100 Subject: [PATCH 066/223] Adjust ignored log messages for Qt 5.11 --- tests/end2end/fixtures/quteprocess.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/end2end/fixtures/quteprocess.py b/tests/end2end/fixtures/quteprocess.py index 0b5f683cc..acd5bcc0c 100644 --- a/tests/end2end/fixtures/quteprocess.py +++ b/tests/end2end/fixtures/quteprocess.py @@ -101,6 +101,9 @@ def is_ignored_lowlevel_message(message): ' Error: No such file or directory', # Qt 5.7.1 'qt.network.ssl: QSslSocket: cannot call unresolved function *', + # Qt 5.11 + # DevTools listening on ws://127.0.0.1:37945/devtools/browser/... + 'DevTools listening on *', ] return any(testutils.pattern_match(pattern=pattern, value=message) for pattern in ignored_messages) @@ -169,7 +172,7 @@ def is_ignored_chromium_message(line): # /tmp/pytest-of-florian/pytest-32/test_webengine_download_suffix0/ # downloads/download.bin: Operation not supported ('Could not set extended attribute user.xdg.* on file *: ' - 'Operation not supported'), + 'Operation not supported*'), # [5947:5947:0605/192837.856931:ERROR:render_process_impl.cc(112)] # WebFrame LEAKED 1 TIMES 'WebFrame LEAKED 1 TIMES', From 8ae3047f2a156b181e0974a3faa6082aeb45e160 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 18 Mar 2018 19:00:07 +0100 Subject: [PATCH 067/223] Update changelog --- doc/changelog.asciidoc | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index d02dfcc2a..ccd49d0fc 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -36,6 +36,7 @@ Fixed - Loading URLs with customized settings from a session now avoids an additional reload. - The window icon and title now get set correctly again. +- The `tabs.switching_delay` setting now has a correct maximum value limit set. v1.2.1 ------ From f2864c62539c762c2096bfb5ba2f657405af77b5 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 19 Mar 2018 09:13:50 +0100 Subject: [PATCH 068/223] Break greasemonkey_wrapper lines differently --- qutebrowser/browser/greasemonkey.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/qutebrowser/browser/greasemonkey.py b/qutebrowser/browser/greasemonkey.py index 6879f4cf6..aed4e04cf 100644 --- a/qutebrowser/browser/greasemonkey.py +++ b/qutebrowser/browser/greasemonkey.py @@ -104,12 +104,12 @@ class GreasemonkeyScript: browser's debugger/inspector will not match up to the line numbers in the source script directly. """ - return jinja.js_environment.get_template( - 'greasemonkey_wrapper.js').render( - scriptName="/".join([self.namespace or '', self.name]), - scriptInfo=self._meta_json(), - scriptMeta=self.script_meta, - scriptSource=self._code) + template = jinja.js_environment.get_template('greasemonkey_wrapper.js') + return template.render( + scriptName="/".join([self.namespace or '', self.name]), + scriptInfo=self._meta_json(), + scriptMeta=self.script_meta, + scriptSource=self._code) def _meta_json(self): return json.dumps({ From 1b84bbd61d5d626dcb1dbdbe9a4752a2fbc01b3c Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 19 Mar 2018 09:14:55 +0100 Subject: [PATCH 069/223] Refactor initialization of internal JavaScript - Initialize JavaScript in webenginesettings.py instead of webenginetab.py - Move JavaScript snippet into a .js file - Make sure scripts can be re-run and do nothing if already run. - Run scripts on DocumentCreation *and* DocumentReady. Closes #3717. - Give each script an unique name for debugging. - Also make custom stylesheets work on chrome:// pages --- .../browser/webengine/webenginesettings.py | 65 ++++++++++++++----- qutebrowser/browser/webengine/webenginetab.py | 21 ------ qutebrowser/javascript/.eslintignore | 1 + qutebrowser/javascript/global_wrapper.js | 12 ++++ qutebrowser/utils/javascript.py | 9 +++ tests/unit/utils/test_javascript.py | 9 +++ 6 files changed, 81 insertions(+), 36 deletions(-) create mode 100644 qutebrowser/javascript/global_wrapper.js diff --git a/qutebrowser/browser/webengine/webenginesettings.py b/qutebrowser/browser/webengine/webenginesettings.py index 46bfcab59..57a4b7583 100644 --- a/qutebrowser/browser/webengine/webenginesettings.py +++ b/qutebrowser/browser/webengine/webenginesettings.py @@ -169,31 +169,65 @@ class WebEngineSettings(websettings.AbstractSettings): self._ATTRIBUTES[name] = [value] +def _inject_early_js(profile, name, js_code, *, + world=QWebEngineScript.ApplicationWorld, subframes=False): + """Inject the given script to run early on a page load. + + This runs the script both on DocumentCreation and DocumentReady as on some + internal pages, DocumentCreation will not work. + + That is a WORKAROUND for https://bugreports.qt.io/browse/QTBUG-66011 + """ + for injection in ['creation', 'ready']: + injection_points = { + 'creation': QWebEngineScript.DocumentCreation, + 'ready': QWebEngineScript.DocumentReady, + } + script = QWebEngineScript() + script.setInjectionPoint(injection_points[injection]) + script.setSourceCode(js_code) + script.setWorldId(world) + script.setRunsOnSubFrames(subframes) + script.setName('_qute_{}_{}'.format(name, injection)) + profile.scripts().insert(script) + + +def _remove_early_js(profile, name): + """Remove an early QWebEngineScript.""" + scripts = profile.scripts() + for injection in ['creation', 'ready']: + full_name = '_qute_{}_{}'.format(name, injection) + script = scripts.findScript(full_name) + if not script.isNull(): + scripts.remove(script) + + def _init_stylesheet(profile): """Initialize custom stylesheets. Partially inspired by QupZilla: https://github.com/QupZilla/qupzilla/blob/v2.0/src/lib/app/mainapplication.cpp#L1063-L1101 """ - old_script = profile.scripts().findScript('_qute_stylesheet') - if not old_script.isNull(): - profile.scripts().remove(old_script) - + _remove_early_js(profile, 'stylesheet') css = shared.get_user_stylesheet() - source = '\n'.join([ - '"use strict";', - 'window._qutebrowser = window._qutebrowser || {};', + js_code = javascript.wrap_global( + 'stylesheet', utils.read_file('javascript/stylesheet.js'), javascript.assemble('stylesheet', 'set_css', css), - ]) + ) + _inject_early_js(profile, 'stylesheet', js_code, subframes=True) - script = QWebEngineScript() - script.setName('_qute_stylesheet') - script.setInjectionPoint(QWebEngineScript.DocumentCreation) - script.setWorldId(QWebEngineScript.ApplicationWorld) - script.setRunsOnSubFrames(True) - script.setSourceCode(source) - profile.scripts().insert(script) + +def _init_js(profile): + """Initialize global qutebrowser JavaScript.""" + js_code = javascript.wrap_global( + 'scripts', + utils.read_file('javascript/scroll.js'), + utils.read_file('javascript/webelem.js'), + utils.read_file('javascript/caret.js'), + ) + # FIXME:qtwebengine what about subframes=True? + _inject_early_js(profile, 'js', js_code, subframes=True) def _update_stylesheet(): @@ -288,6 +322,7 @@ def _update_settings(option): def _init_profile(profile): """Init the given profile.""" + _init_js(profile) _init_stylesheet(profile) _set_http_headers(profile) _set_http_cache_size(profile) diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index 87adbc81e..6f4deebae 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -630,31 +630,10 @@ class WebEngineTab(browsertab.AbstractTab): self._set_widget(widget) self._connect_signals() self.backend = usertypes.Backend.QtWebEngine - self._init_js() self._child_event_filter = None self._saved_zoom = None self._reload_url = None - def _init_js(self): - js_code = '\n'.join([ - '"use strict";', - 'window._qutebrowser = window._qutebrowser || {};', - utils.read_file('javascript/scroll.js'), - utils.read_file('javascript/webelem.js'), - utils.read_file('javascript/caret.js'), - ]) - script = QWebEngineScript() - # We can't use DocumentCreation here as WORKAROUND for - # https://bugreports.qt.io/browse/QTBUG-66011 - script.setInjectionPoint(QWebEngineScript.DocumentReady) - script.setSourceCode(js_code) - - page = self._widget.page() - script.setWorldId(QWebEngineScript.ApplicationWorld) - - # FIXME:qtwebengine what about runsOnSubFrames? - page.scripts().insert(script) - def _install_event_filter(self): self._widget.focusProxy().installEventFilter(self._mouse_event_filter) self._child_event_filter = mouse.ChildEventFilter( diff --git a/qutebrowser/javascript/.eslintignore b/qutebrowser/javascript/.eslintignore index 036a72cfe..65143f360 100644 --- a/qutebrowser/javascript/.eslintignore +++ b/qutebrowser/javascript/.eslintignore @@ -2,3 +2,4 @@ pac_utils.js # Actually a jinja template so eslint chokes on the {{}} syntax. greasemonkey_wrapper.js +global_wrapper.js diff --git a/qutebrowser/javascript/global_wrapper.js b/qutebrowser/javascript/global_wrapper.js new file mode 100644 index 000000000..a302bd5d1 --- /dev/null +++ b/qutebrowser/javascript/global_wrapper.js @@ -0,0 +1,12 @@ +(function() { + "use strict"; + if (!("_qutebrowser" in window)) { + window._qutebrowser = {"initialized": {}}; + } + + if (window._qutebrowser.initialized["{{name}}"]) { + return; + } + {{code}} + window._qutebrowser.initialized["{{name}}"] = true; +})(); diff --git a/qutebrowser/utils/javascript.py b/qutebrowser/utils/javascript.py index 335b1b983..93df8e70f 100644 --- a/qutebrowser/utils/javascript.py +++ b/qutebrowser/utils/javascript.py @@ -20,6 +20,9 @@ """Utilities related to javascript interaction.""" +from qutebrowser.utils import jinja + + def string_escape(text): """Escape values special to javascript in strings. @@ -70,3 +73,9 @@ def assemble(module, function, *args): parts = ['window', '_qutebrowser', module, function] code = '"use strict";\n{}({});'.format('.'.join(parts), js_args) return code + + +def wrap_global(name, *sources): + """Wrap a script using window._qutebrowser.""" + template = jinja.js_environment.get_template('global_wrapper.js') + return template.render(code='\n'.join(sources), name=name) diff --git a/tests/unit/utils/test_javascript.py b/tests/unit/utils/test_javascript.py index 864483bf0..29e090fd0 100644 --- a/tests/unit/utils/test_javascript.py +++ b/tests/unit/utils/test_javascript.py @@ -100,3 +100,12 @@ def test_convert_js_arg(arg, expected): def test_assemble(base, expected_base): expected = '"use strict";\n{}.func(23);'.format(expected_base) assert javascript.assemble(base, 'func', 23) == expected + + +def test_wrap_global(): + source = javascript.wrap_global('name', + 'console.log("foo");', + 'console.log("bar");') + assert 'window._qutebrowser.initialized["name"]' in source + assert 'console.log("foo");' in source + assert 'console.log("bar");' in source From a4530797ea92fd7d679aa4487279b5792d619a0a Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 19 Mar 2018 09:40:57 +0100 Subject: [PATCH 070/223] Add a ProfileSetter class to webenginesettings Easier than passing a profile around everywhere. --- .../browser/webengine/webenginesettings.py | 255 +++++++++--------- 1 file changed, 129 insertions(+), 126 deletions(-) diff --git a/qutebrowser/browser/webengine/webenginesettings.py b/qutebrowser/browser/webengine/webenginesettings.py index 57a4b7583..92ea0f4cb 100644 --- a/qutebrowser/browser/webengine/webenginesettings.py +++ b/qutebrowser/browser/webengine/webenginesettings.py @@ -169,65 +169,130 @@ class WebEngineSettings(websettings.AbstractSettings): self._ATTRIBUTES[name] = [value] -def _inject_early_js(profile, name, js_code, *, - world=QWebEngineScript.ApplicationWorld, subframes=False): - """Inject the given script to run early on a page load. +class ProfileSetter: - This runs the script both on DocumentCreation and DocumentReady as on some - internal pages, DocumentCreation will not work. + """Helper to set various settings on a profile.""" - That is a WORKAROUND for https://bugreports.qt.io/browse/QTBUG-66011 - """ - for injection in ['creation', 'ready']: - injection_points = { - 'creation': QWebEngineScript.DocumentCreation, - 'ready': QWebEngineScript.DocumentReady, - } - script = QWebEngineScript() - script.setInjectionPoint(injection_points[injection]) - script.setSourceCode(js_code) - script.setWorldId(world) - script.setRunsOnSubFrames(subframes) - script.setName('_qute_{}_{}'.format(name, injection)) - profile.scripts().insert(script) + def __init__(self, profile): + self._profile = profile + def init_profile(self): + """Initialize settings on the given profile.""" + self._init_js() + self.init_stylesheet() + self.set_http_headers() + self.set_http_cache_size() + self._profile.settings().setAttribute( + QWebEngineSettings.FullScreenSupportEnabled, True) + if qtutils.version_check('5.8'): + self._profile.setSpellCheckEnabled(True) + self.set_dictionary_language() -def _remove_early_js(profile, name): - """Remove an early QWebEngineScript.""" - scripts = profile.scripts() - for injection in ['creation', 'ready']: - full_name = '_qute_{}_{}'.format(name, injection) - script = scripts.findScript(full_name) - if not script.isNull(): - scripts.remove(script) + def _inject_early_js(self, name, js_code, *, + world=QWebEngineScript.ApplicationWorld, + subframes=False): + """Inject the given script to run early on a page load. + This runs the script both on DocumentCreation and DocumentReady as on some + internal pages, DocumentCreation will not work. -def _init_stylesheet(profile): - """Initialize custom stylesheets. + That is a WORKAROUND for https://bugreports.qt.io/browse/QTBUG-66011 + """ + for injection in ['creation', 'ready']: + injection_points = { + 'creation': QWebEngineScript.DocumentCreation, + 'ready': QWebEngineScript.DocumentReady, + } + script = QWebEngineScript() + script.setInjectionPoint(injection_points[injection]) + script.setSourceCode(js_code) + script.setWorldId(world) + script.setRunsOnSubFrames(subframes) + script.setName('_qute_{}_{}'.format(name, injection)) + self._profile.scripts().insert(script) - Partially inspired by QupZilla: - https://github.com/QupZilla/qupzilla/blob/v2.0/src/lib/app/mainapplication.cpp#L1063-L1101 - """ - _remove_early_js(profile, 'stylesheet') - css = shared.get_user_stylesheet() - js_code = javascript.wrap_global( - 'stylesheet', - utils.read_file('javascript/stylesheet.js'), - javascript.assemble('stylesheet', 'set_css', css), - ) - _inject_early_js(profile, 'stylesheet', js_code, subframes=True) + def _remove_early_js(self, name): + """Remove an early QWebEngineScript.""" + scripts = self._profile.scripts() + for injection in ['creation', 'ready']: + full_name = '_qute_{}_{}'.format(name, injection) + script = scripts.findScript(full_name) + if not script.isNull(): + scripts.remove(script) + def _init_js(self): + """Initialize global qutebrowser JavaScript.""" + js_code = javascript.wrap_global( + 'scripts', + utils.read_file('javascript/scroll.js'), + utils.read_file('javascript/webelem.js'), + utils.read_file('javascript/caret.js'), + ) + # FIXME:qtwebengine what about subframes=True? + self._inject_early_js('js', js_code, subframes=True) -def _init_js(profile): - """Initialize global qutebrowser JavaScript.""" - js_code = javascript.wrap_global( - 'scripts', - utils.read_file('javascript/scroll.js'), - utils.read_file('javascript/webelem.js'), - utils.read_file('javascript/caret.js'), - ) - # FIXME:qtwebengine what about subframes=True? - _inject_early_js(profile, 'js', js_code, subframes=True) + def init_stylesheet(self): + """Initialize custom stylesheets. + + Partially inspired by QupZilla: + https://github.com/QupZilla/qupzilla/blob/v2.0/src/lib/app/mainapplication.cpp#L1063-L1101 + """ + self._remove_early_js('stylesheet') + css = shared.get_user_stylesheet() + js_code = javascript.wrap_global( + 'stylesheet', + utils.read_file('javascript/stylesheet.js'), + javascript.assemble('stylesheet', 'set_css', css), + ) + self._inject_early_js('stylesheet', js_code, subframes=True) + + def set_http_headers(self): + """Set the user agent and accept-language for the given profile. + + We override those per request in the URL interceptor (to allow for + per-domain values), but this one still gets used for things like + window.navigator.userAgent/.languages in JS. + """ + self._profile.setHttpUserAgent(config.val.content.headers.user_agent) + accept_language = config.val.content.headers.accept_language + if accept_language is not None: + self._profile.setHttpAcceptLanguage(accept_language) + + def set_http_cache_size(self): + """Initialize the HTTP cache size for the given profile.""" + size = config.val.content.cache.size + if size is None: + size = 0 + else: + size = qtutils.check_overflow(size, 'int', fatal=False) + + # 0: automatically managed by QtWebEngine + self._profile.setHttpCacheMaximumSize(size) + + def set_persistent_cookie_policy(self): + """Set the HTTP Cookie size for the given profile.""" + assert not self._profile.isOffTheRecord() + if config.val.content.cookies.store: + value = QWebEngineProfile.AllowPersistentCookies + else: + value = QWebEngineProfile.NoPersistentCookies + self._profile.setPersistentCookiesPolicy(value) + + def set_dictionary_language(self, warn=True): + filenames = [] + for code in config.val.spellcheck.languages or []: + local_filename = spell.local_filename(code) + if not local_filename: + if warn: + message.warning( + "Language {} is not installed - see scripts/dictcli.py " + "in qutebrowser's sources".format(code)) + continue + + filenames.append(local_filename) + + log.config.debug("Found dicts: {}".format(filenames)) + self._profile.setSpellCheckLanguages(filenames) def _update_stylesheet(): @@ -244,93 +309,29 @@ def _update_stylesheet(): tab.run_js_async(code) -def _set_http_headers(profile): - """Set the user agent and accept-language for the given profile. - - We override those per request in the URL interceptor (to allow for - per-domain values), but this one still gets used for things like - window.navigator.userAgent/.languages in JS. - """ - profile.setHttpUserAgent(config.val.content.headers.user_agent) - accept_language = config.val.content.headers.accept_language - if accept_language is not None: - profile.setHttpAcceptLanguage(accept_language) - - -def _set_http_cache_size(profile): - """Initialize the HTTP cache size for the given profile.""" - size = config.val.content.cache.size - if size is None: - size = 0 - else: - size = qtutils.check_overflow(size, 'int', fatal=False) - - # 0: automatically managed by QtWebEngine - profile.setHttpCacheMaximumSize(size) - - -def _set_persistent_cookie_policy(profile): - """Set the HTTP Cookie size for the given profile.""" - if config.val.content.cookies.store: - value = QWebEngineProfile.AllowPersistentCookies - else: - value = QWebEngineProfile.NoPersistentCookies - profile.setPersistentCookiesPolicy(value) - - -def _set_dictionary_language(profile, warn=True): - filenames = [] - for code in config.val.spellcheck.languages or []: - local_filename = spell.local_filename(code) - if not local_filename: - if warn: - message.warning( - "Language {} is not installed - see scripts/dictcli.py " - "in qutebrowser's sources".format(code)) - continue - - filenames.append(local_filename) - - log.config.debug("Found dicts: {}".format(filenames)) - profile.setSpellCheckLanguages(filenames) - - def _update_settings(option): """Update global settings when qwebsettings changed.""" global_settings.update_setting(option) if option in ['scrolling.bar', 'content.user_stylesheets']: - _init_stylesheet(default_profile) - _init_stylesheet(private_profile) + default_profile.setter.init_stylesheet() + private_profile.setter.init_stylesheet() _update_stylesheet() elif option in ['content.headers.user_agent', 'content.headers.accept_language']: - _set_http_headers(default_profile) - _set_http_headers(private_profile) + default_profile.setter.set_http_headers() + private_profile.setter.set_http_headers() elif option == 'content.cache.size': - _set_http_cache_size(default_profile) - _set_http_cache_size(private_profile) + default_profile.setter.set_http_cache_size() + private_profile.setter.set_http_cache_size() elif (option == 'content.cookies.store' and # https://bugreports.qt.io/browse/QTBUG-58650 qtutils.version_check('5.9', compiled=False)): - _set_persistent_cookie_policy(default_profile) + default_profile.setter.set_persistent_cookie_policy() # We're not touching the private profile's cookie policy. elif option == 'spellcheck.languages': - _set_dictionary_language(default_profile) - _set_dictionary_language(private_profile, warn=False) - - -def _init_profile(profile): - """Init the given profile.""" - _init_js(profile) - _init_stylesheet(profile) - _set_http_headers(profile) - _set_http_cache_size(profile) - profile.settings().setAttribute( - QWebEngineSettings.FullScreenSupportEnabled, True) - if qtutils.version_check('5.8'): - profile.setSpellCheckEnabled(True) - _set_dictionary_language(profile) + default_profile.setter.set_dictionary_language() + private_profile.setter.set_dictionary_language(warn=False) def _init_profiles(): @@ -338,16 +339,18 @@ def _init_profiles(): global default_profile, private_profile default_profile = QWebEngineProfile.defaultProfile() + default_profile.setter = ProfileSetter(default_profile) default_profile.setCachePath( os.path.join(standarddir.cache(), 'webengine')) default_profile.setPersistentStoragePath( os.path.join(standarddir.data(), 'webengine')) - _init_profile(default_profile) - _set_persistent_cookie_policy(default_profile) + default_profile.setter.init_profile() + default_profile.setter.set_persistent_cookie_policy() private_profile = QWebEngineProfile() + private_profile.setter = ProfileSetter(private_profile) assert private_profile.isOffTheRecord() - _init_profile(private_profile) + private_profile.setter.init_profile() def inject_userscripts(): From 232fd1942244f25847770cb0db6904b685be8117 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 19 Mar 2018 09:53:35 +0100 Subject: [PATCH 071/223] Fix unit tests after refactoring --- tests/unit/browser/webengine/test_webenginesettings.py | 4 +--- tests/unit/javascript/stylesheet/test_stylesheet.py | 5 +++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/tests/unit/browser/webengine/test_webenginesettings.py b/tests/unit/browser/webengine/test_webenginesettings.py index 00549f7f7..e396e168b 100644 --- a/tests/unit/browser/webengine/test_webenginesettings.py +++ b/tests/unit/browser/webengine/test_webenginesettings.py @@ -40,9 +40,7 @@ def test_big_cache_size(config_stub): """Make sure a too big cache size is handled correctly.""" config_stub.val.content.cache.size = 2 ** 63 - 1 profile = webenginesettings.default_profile - - webenginesettings._set_http_cache_size(profile) - + profile.setter.set_http_cache_size() assert profile.httpCacheMaximumSize() == 2 ** 31 - 1 diff --git a/tests/unit/javascript/stylesheet/test_stylesheet.py b/tests/unit/javascript/stylesheet/test_stylesheet.py index d640e8a19..df11d22a3 100644 --- a/tests/unit/javascript/stylesheet/test_stylesheet.py +++ b/tests/unit/javascript/stylesheet/test_stylesheet.py @@ -56,8 +56,9 @@ class StylesheetTester: """Initialize the stylesheet with a provided css file.""" css_path = os.path.join(os.path.dirname(__file__), css_file) self.config_stub.val.content.user_stylesheets = css_path - p = QWebEngineProfile.defaultProfile() - webenginesettings._init_stylesheet(p) + profile = QWebEngineProfile.defaultProfile() + setter = webenginesettings.ProfileSetter(profile) + setter.init_stylesheet() def set_css(self, css): """Set document style to `css` via stylesheet.js.""" From 6465d64738eadec439a5e758f80c55cfd2f464ae Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 19 Mar 2018 10:22:21 +0100 Subject: [PATCH 072/223] Fix lint --- qutebrowser/browser/webengine/webenginesettings.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/qutebrowser/browser/webengine/webenginesettings.py b/qutebrowser/browser/webengine/webenginesettings.py index 92ea0f4cb..2302a0df4 100644 --- a/qutebrowser/browser/webengine/webenginesettings.py +++ b/qutebrowser/browser/webengine/webenginesettings.py @@ -193,8 +193,8 @@ class ProfileSetter: subframes=False): """Inject the given script to run early on a page load. - This runs the script both on DocumentCreation and DocumentReady as on some - internal pages, DocumentCreation will not work. + This runs the script both on DocumentCreation and DocumentReady as on + some internal pages, DocumentCreation will not work. That is a WORKAROUND for https://bugreports.qt.io/browse/QTBUG-66011 """ @@ -279,14 +279,15 @@ class ProfileSetter: self._profile.setPersistentCookiesPolicy(value) def set_dictionary_language(self, warn=True): + """Load the given dictionaries.""" filenames = [] for code in config.val.spellcheck.languages or []: local_filename = spell.local_filename(code) if not local_filename: if warn: - message.warning( - "Language {} is not installed - see scripts/dictcli.py " - "in qutebrowser's sources".format(code)) + message.warning("Language {} is not installed - see " + "scripts/dictcli.py in qutebrowser's " + "sources".format(code)) continue filenames.append(local_filename) From 07e831cee5f0232954c9708bb8c314833eb51b79 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 19 Mar 2018 10:28:25 +0100 Subject: [PATCH 073/223] Update changelog --- doc/changelog.asciidoc | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index ccd49d0fc..db91eedca 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -37,6 +37,7 @@ Fixed reload. - The window icon and title now get set correctly again. - The `tabs.switching_delay` setting now has a correct maximum value limit set. +- The `taskadd` script now works properly when there's multi-line output. v1.2.1 ------ From 39d25c11279cb3f8f9469ac26ecbb2b7a26808c5 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 19 Mar 2018 11:15:19 +0100 Subject: [PATCH 074/223] Update _chromium_version comment [ci skip] --- qutebrowser/utils/version.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/qutebrowser/utils/version.py b/qutebrowser/utils/version.py index 016adaa03..86973a3de 100644 --- a/qutebrowser/utils/version.py +++ b/qutebrowser/utils/version.py @@ -317,8 +317,10 @@ def _chromium_version(): Qt 5.8: Chromium 53 Qt 5.9: Chromium 56 Qt 5.10: Chromium 61 - Qt 5.11: Chromium 63 - Qt 5.12: Chromium 65 (?) + Qt 5.11: Chromium 65 + Qt 5.12: Chromium 69 (?) + + Also see https://www.chromium.org/developers/calendar """ if QWebEngineProfile is None: # This should never happen From bee04a1eecf18c887a5fc676aa31f0bf0eec952c Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 19 Mar 2018 11:43:08 +0100 Subject: [PATCH 075/223] Wait until runner is finished in test_custom_env This seems to at least lead to less warnings when running the test. --- tests/unit/commands/test_userscripts.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/unit/commands/test_userscripts.py b/tests/unit/commands/test_userscripts.py index cfbfa0a86..1e4aadbdd 100644 --- a/tests/unit/commands/test_userscripts.py +++ b/tests/unit/commands/test_userscripts.py @@ -97,10 +97,11 @@ def test_custom_env(qtbot, monkeypatch, py_proc, runner): f.write('\n') """) - with qtbot.waitSignal(runner.got_cmd, timeout=10000) as blocker: - runner.prepare_run(cmd, *args, env=env) - runner.store_html('') - runner.store_text('') + with qtbot.waitSignal(runner.finished, timeout=10000): + with qtbot.waitSignal(runner.got_cmd, timeout=10000) as blocker: + runner.prepare_run(cmd, *args, env=env) + runner.store_html('') + runner.store_text('') data = blocker.args[0] ret_env = json.loads(data) From f28a39571c96fa44b5189cc662f4b40300505030 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 19 Mar 2018 11:49:24 +0100 Subject: [PATCH 076/223] Fix caret.js indent --- qutebrowser/javascript/caret.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/qutebrowser/javascript/caret.js b/qutebrowser/javascript/caret.js index 5088c3e2f..3bb4de49f 100644 --- a/qutebrowser/javascript/caret.js +++ b/qutebrowser/javascript/caret.js @@ -324,9 +324,8 @@ window._qutebrowser.caret = (function() { const color = axs.color.parseColor(style.backgroundColor); if (color && (style.opacity < 1 && - (color.alpha *= style.opacity), - color.alpha !== 0 && - (el.push(color), color.alpha === 1))) { + (color.alpha *= style.opacity), color.alpha !== 0 && + (el.push(color), color.alpha === 1))) { iter = !0; break; } From 6a971e28468dbe3af963a4339a6002c0f9c0af0c Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 19 Mar 2018 12:13:10 +0100 Subject: [PATCH 077/223] Ignore OnDidStopLoading error message See #3661, https://bugreports.qt.io/browse/QTBUG-66661 --- tests/end2end/fixtures/quteprocess.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/end2end/fixtures/quteprocess.py b/tests/end2end/fixtures/quteprocess.py index acd5bcc0c..8e6db5164 100644 --- a/tests/end2end/fixtures/quteprocess.py +++ b/tests/end2end/fixtures/quteprocess.py @@ -195,6 +195,11 @@ def is_ignored_chromium_message(line): # [2734:2746:1107/131154.072032:ERROR:nss_ocsp.cc(591)] No # URLRequestContext for NSS HTTP handler. host: ocsp.digicert.com 'No URLRequestContext for NSS HTTP handler. host: *', + + # https://bugreports.qt.io/browse/QTBUG-66661 + # [23359:23359:0319/115812.168578:WARNING:render_frame_host_impl.cc(2744)] + # OnDidStopLoading was called twice. + 'OnDidStopLoading was called twice.', ] return any(testutils.pattern_match(pattern=pattern, value=message) for pattern in ignored_messages) From 33066af51dc427485cae34b1f710a775c51b14fc Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 19 Mar 2018 13:59:30 +0100 Subject: [PATCH 078/223] Break long comment --- tests/end2end/fixtures/quteprocess.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/end2end/fixtures/quteprocess.py b/tests/end2end/fixtures/quteprocess.py index 8e6db5164..41dee1275 100644 --- a/tests/end2end/fixtures/quteprocess.py +++ b/tests/end2end/fixtures/quteprocess.py @@ -197,8 +197,8 @@ def is_ignored_chromium_message(line): 'No URLRequestContext for NSS HTTP handler. host: *', # https://bugreports.qt.io/browse/QTBUG-66661 - # [23359:23359:0319/115812.168578:WARNING:render_frame_host_impl.cc(2744)] - # OnDidStopLoading was called twice. + # [23359:23359:0319/115812.168578:WARNING: + # render_frame_host_impl.cc(2744)] OnDidStopLoading was called twice. 'OnDidStopLoading was called twice.', ] return any(testutils.pattern_match(pattern=pattern, value=message) From da8b6fb50a2d02ac9e34cfada448d17f5d93171d Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 19 Mar 2018 14:11:01 +0100 Subject: [PATCH 079/223] Decrease maximum repetitions for QtWebEngine scrolling At least for Qt debug builds, 5000 seems to be much too much. See #3661 --- 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 6f4deebae..f9308163d 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -379,7 +379,7 @@ class WebEngineScroller(browsertab.AbstractScroller): def _repeated_key_press(self, key, count=1, modifier=Qt.NoModifier): """Send count fake key presses to this scroller's WebEngineTab.""" - for _ in range(min(count, 5000)): + for _ in range(min(count, 1000)): self._tab.key_press(key, modifier) @pyqtSlot(QPointF) From 99ea4b98e8ee691e181a4b1d20596569ab9f32c9 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 19 Mar 2018 17:13:15 +0100 Subject: [PATCH 080/223] Update flake8-builtins from 1.0.post0 to 1.1.0 --- misc/requirements/requirements-flake8.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-flake8.txt b/misc/requirements/requirements-flake8.txt index f467fa474..64c9b83ce 100644 --- a/misc/requirements/requirements-flake8.txt +++ b/misc/requirements/requirements-flake8.txt @@ -3,7 +3,7 @@ attrs==17.4.0 flake8==3.5.0 flake8-bugbear==18.2.0 -flake8-builtins==1.0.post0 +flake8-builtins==1.1.0 flake8-comprehensions==1.4.1 flake8-copyright==0.2.0 flake8-debugger==3.1.0 From 5a26858e07c882bda43778cc81e444a0ce6a166c Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 19 Mar 2018 17:13:17 +0100 Subject: [PATCH 081/223] Update flake8-per-file-ignores from 0.5 to 0.6 --- misc/requirements/requirements-flake8.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-flake8.txt b/misc/requirements/requirements-flake8.txt index 64c9b83ce..eaf8e3ef7 100644 --- a/misc/requirements/requirements-flake8.txt +++ b/misc/requirements/requirements-flake8.txt @@ -11,7 +11,7 @@ flake8-deprecated==1.3 flake8-docstrings==1.3.0 flake8-future-import==0.4.4 flake8-mock==0.3 -flake8-per-file-ignores==0.5 +flake8-per-file-ignores==0.6 flake8-polyfill==1.0.2 flake8-string-format==0.2.3 flake8-tidy-imports==1.1.0 From b77e43d74fd2455b6ca4a3390c99b769e87ef4b0 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 19 Mar 2018 17:13:18 +0100 Subject: [PATCH 082/223] Update setuptools from 38.5.2 to 39.0.1 --- misc/requirements/requirements-pip.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-pip.txt b/misc/requirements/requirements-pip.txt index 4784f27df..31f1c27bf 100644 --- a/misc/requirements/requirements-pip.txt +++ b/misc/requirements/requirements-pip.txt @@ -3,6 +3,6 @@ appdirs==1.4.3 packaging==17.1 pyparsing==2.2.0 -setuptools==38.5.2 +setuptools==39.0.1 six==1.11.0 wheel==0.30.0 From 2d5d485dafd77a7c480e52655134e7820df2abb4 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 19 Mar 2018 17:13:20 +0100 Subject: [PATCH 083/223] Update github3.py from 0.9.6 to 1.0.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 6364f0fcf..6d2f9f95e 100644 --- a/misc/requirements/requirements-pylint-master.txt +++ b/misc/requirements/requirements-pylint-master.txt @@ -3,7 +3,7 @@ -e git+https://github.com/PyCQA/astroid.git#egg=astroid certifi==2018.1.18 chardet==3.0.4 -github3.py==0.9.6 +github3.py==1.0.1 idna==2.6 isort==4.3.4 lazy-object-proxy==1.3.1 From 1f3fc756db14161a80b4024addc434343ac631a4 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 19 Mar 2018 17:13:22 +0100 Subject: [PATCH 084/223] Update github3.py from 0.9.6 to 1.0.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 de27257e1..87e7d7769 100644 --- a/misc/requirements/requirements-pylint.txt +++ b/misc/requirements/requirements-pylint.txt @@ -3,7 +3,7 @@ astroid==1.6.1 certifi==2018.1.18 chardet==3.0.4 -github3.py==0.9.6 +github3.py==1.0.1 idna==2.6 isort==4.3.4 lazy-object-proxy==1.3.1 From 650aa532cd094cef43943c8c08a132673d0139b2 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 19 Mar 2018 17:13:23 +0100 Subject: [PATCH 085/223] Update astroid from 1.6.1 to 1.6.2 --- 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 87e7d7769..6b3d581d0 100644 --- a/misc/requirements/requirements-pylint.txt +++ b/misc/requirements/requirements-pylint.txt @@ -1,6 +1,6 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -astroid==1.6.1 +astroid==1.6.2 certifi==2018.1.18 chardet==3.0.4 github3.py==1.0.1 From 3b7e1b3fe2b24cd5753621211e9ba09bfe305f68 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 19 Mar 2018 17:13:25 +0100 Subject: [PATCH 086/223] Update pylint from 1.8.2 to 1.8.3 --- 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 6b3d581d0..75ae02694 100644 --- a/misc/requirements/requirements-pylint.txt +++ b/misc/requirements/requirements-pylint.txt @@ -8,7 +8,7 @@ idna==2.6 isort==4.3.4 lazy-object-proxy==1.3.1 mccabe==0.6.1 -pylint==1.8.2 +pylint==1.8.3 ./scripts/dev/pylint_checkers requests==2.18.4 six==1.11.0 From 8eb4d1580580b9ccb49266acacd64abfbfa45ce2 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 19 Mar 2018 17:13:26 +0100 Subject: [PATCH 087/223] Update hypothesis from 3.49.0 to 3.50.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 409ad4e33..a485b54a7 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -11,7 +11,7 @@ fields==5.0.0 Flask==0.12.2 glob2==0.6 hunter==2.0.2 -hypothesis==3.49.0 +hypothesis==3.50.0 itsdangerous==0.24 # Jinja2==2.10 Mako==1.0.7 From e43f0a61b9576e645067ebc1782f637d0333d240 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 19 Mar 2018 17:33:02 +0100 Subject: [PATCH 088/223] Move all QWebEngineScript related code out of webenginesettings It looks like there's some issue with QWebEngineScript in a profile, at least with older Qt versions... See #3497, #3377 --- .../browser/webengine/webenginesettings.py | 127 +----------------- qutebrowser/browser/webengine/webenginetab.py | 119 +++++++++++++++- tests/end2end/features/javascript.feature | 12 ++ 3 files changed, 130 insertions(+), 128 deletions(-) diff --git a/qutebrowser/browser/webengine/webenginesettings.py b/qutebrowser/browser/webengine/webenginesettings.py index 2302a0df4..417465929 100644 --- a/qutebrowser/browser/webengine/webenginesettings.py +++ b/qutebrowser/browser/webengine/webenginesettings.py @@ -26,16 +26,12 @@ Module attributes: import os -import sip from PyQt5.QtGui import QFont -from PyQt5.QtWebEngineWidgets import (QWebEngineSettings, QWebEngineProfile, - QWebEngineScript) +from PyQt5.QtWebEngineWidgets import QWebEngineSettings, QWebEngineProfile -from qutebrowser.browser import shared from qutebrowser.browser.webengine import spell from qutebrowser.config import config, websettings -from qutebrowser.utils import (utils, standarddir, javascript, qtutils, - message, log, objreg) +from qutebrowser.utils import utils, standarddir, qtutils, message, log # The default QWebEngineProfile default_profile = None @@ -178,8 +174,6 @@ class ProfileSetter: def init_profile(self): """Initialize settings on the given profile.""" - self._init_js() - self.init_stylesheet() self.set_http_headers() self.set_http_cache_size() self._profile.settings().setAttribute( @@ -188,64 +182,6 @@ class ProfileSetter: self._profile.setSpellCheckEnabled(True) self.set_dictionary_language() - def _inject_early_js(self, name, js_code, *, - world=QWebEngineScript.ApplicationWorld, - subframes=False): - """Inject the given script to run early on a page load. - - This runs the script both on DocumentCreation and DocumentReady as on - some internal pages, DocumentCreation will not work. - - That is a WORKAROUND for https://bugreports.qt.io/browse/QTBUG-66011 - """ - for injection in ['creation', 'ready']: - injection_points = { - 'creation': QWebEngineScript.DocumentCreation, - 'ready': QWebEngineScript.DocumentReady, - } - script = QWebEngineScript() - script.setInjectionPoint(injection_points[injection]) - script.setSourceCode(js_code) - script.setWorldId(world) - script.setRunsOnSubFrames(subframes) - script.setName('_qute_{}_{}'.format(name, injection)) - self._profile.scripts().insert(script) - - def _remove_early_js(self, name): - """Remove an early QWebEngineScript.""" - scripts = self._profile.scripts() - for injection in ['creation', 'ready']: - full_name = '_qute_{}_{}'.format(name, injection) - script = scripts.findScript(full_name) - if not script.isNull(): - scripts.remove(script) - - def _init_js(self): - """Initialize global qutebrowser JavaScript.""" - js_code = javascript.wrap_global( - 'scripts', - utils.read_file('javascript/scroll.js'), - utils.read_file('javascript/webelem.js'), - utils.read_file('javascript/caret.js'), - ) - # FIXME:qtwebengine what about subframes=True? - self._inject_early_js('js', js_code, subframes=True) - - def init_stylesheet(self): - """Initialize custom stylesheets. - - Partially inspired by QupZilla: - https://github.com/QupZilla/qupzilla/blob/v2.0/src/lib/app/mainapplication.cpp#L1063-L1101 - """ - self._remove_early_js('stylesheet') - css = shared.get_user_stylesheet() - js_code = javascript.wrap_global( - 'stylesheet', - utils.read_file('javascript/stylesheet.js'), - javascript.assemble('stylesheet', 'set_css', css), - ) - self._inject_early_js('stylesheet', js_code, subframes=True) - def set_http_headers(self): """Set the user agent and accept-language for the given profile. @@ -296,30 +232,12 @@ class ProfileSetter: self._profile.setSpellCheckLanguages(filenames) -def _update_stylesheet(): - """Update the custom stylesheet in existing tabs.""" - css = shared.get_user_stylesheet() - code = javascript.assemble('stylesheet', 'set_css', css) - for win_id, window in objreg.window_registry.items(): - # We could be in the middle of destroying a window here - if sip.isdeleted(window): - continue - tab_registry = objreg.get('tab-registry', scope='window', - window=win_id) - for tab in tab_registry.values(): - tab.run_js_async(code) - - def _update_settings(option): """Update global settings when qwebsettings changed.""" global_settings.update_setting(option) - if option in ['scrolling.bar', 'content.user_stylesheets']: - default_profile.setter.init_stylesheet() - private_profile.setter.init_stylesheet() - _update_stylesheet() - elif option in ['content.headers.user_agent', - 'content.headers.accept_language']: + if option in ['content.headers.user_agent', + 'content.headers.accept_language']: default_profile.setter.set_http_headers() private_profile.setter.set_http_headers() elif option == 'content.cache.size': @@ -354,43 +272,6 @@ def _init_profiles(): private_profile.setter.init_profile() -def inject_userscripts(): - """Register user JavaScript files with the global profiles.""" - # 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. - greasemonkey = objreg.get('greasemonkey') - for profile in [default_profile, private_profile]: - scripts = profile.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() - - # Then add the new scripts. - for script in greasemonkey.all_scripts(): - # @run-at (and @include/@exclude/@match) is parsed by - # QWebEngineScript. - new_script = QWebEngineScript() - new_script.setWorldId(QWebEngineScript.MainWorld) - 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) - - def init(args): """Initialize the global QWebSettings.""" if args.enable_webengine_inspector: diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index f9308163d..5d7dfeb90 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -33,7 +33,7 @@ from PyQt5.QtNetwork import QAuthenticator from PyQt5.QtWidgets import QApplication from PyQt5.QtWebEngineWidgets import QWebEnginePage, QWebEngineScript -from qutebrowser.config import configdata +from qutebrowser.config import configdata, config from qutebrowser.browser import browsertab, mouse, shared from qutebrowser.browser.webengine import (webview, webengineelem, tabhistory, interceptor, webenginequtescheme, @@ -73,10 +73,6 @@ def init(): download_manager.install(webenginesettings.private_profile) objreg.register('webengine-download-manager', download_manager) - greasemonkey = objreg.get('greasemonkey') - greasemonkey.scripts_reloaded.connect(webenginesettings.inject_userscripts) - webenginesettings.inject_userscripts() - # Mapping worlds from usertypes.JsWorld to QWebEngineScript world IDs. _JS_WORLD_MAP = { @@ -633,6 +629,119 @@ class WebEngineTab(browsertab.AbstractTab): self._child_event_filter = None self._saved_zoom = None self._reload_url = None + config.instance.changed.connect(self._on_config_changed) + self._init_js() + + @pyqtSlot(str) + def _on_config_changed(self, option): + if option in ['scrolling.bar', 'content.user_stylesheets']: + self._init_stylesheet() + self._update_stylesheet() + + def _update_stylesheet(self): + """Update the custom stylesheet in existing tabs.""" + css = shared.get_user_stylesheet() + code = javascript.assemble('stylesheet', 'set_css', css) + self.run_js_async(code) + + def _inject_early_js(self, name, js_code, *, + world=QWebEngineScript.ApplicationWorld, + subframes=False): + """Inject the given script to run early on a page load. + + This runs the script both on DocumentCreation and DocumentReady as on + some internal pages, DocumentCreation will not work. + + That is a WORKAROUND for https://bugreports.qt.io/browse/QTBUG-66011 + """ + scripts = self._widget.page().scripts() + for injection in ['creation', 'ready']: + injection_points = { + 'creation': QWebEngineScript.DocumentCreation, + 'ready': QWebEngineScript.DocumentReady, + } + script = QWebEngineScript() + script.setInjectionPoint(injection_points[injection]) + script.setSourceCode(js_code) + script.setWorldId(world) + script.setRunsOnSubFrames(subframes) + script.setName('_qute_{}_{}'.format(name, injection)) + scripts.insert(script) + + def _remove_early_js(self, name): + """Remove an early QWebEngineScript.""" + scripts = self._widget.page().scripts() + for injection in ['creation', 'ready']: + full_name = '_qute_{}_{}'.format(name, injection) + script = scripts.findScript(full_name) + if not script.isNull(): + scripts.remove(script) + + def _init_js(self): + """Initialize global qutebrowser JavaScript.""" + js_code = javascript.wrap_global( + 'scripts', + utils.read_file('javascript/scroll.js'), + utils.read_file('javascript/webelem.js'), + utils.read_file('javascript/caret.js'), + ) + # FIXME:qtwebengine what about subframes=True? + 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() + + def _init_stylesheet(self): + """Initialize custom stylesheets. + + Partially inspired by QupZilla: + https://github.com/QupZilla/qupzilla/blob/v2.0/src/lib/app/mainapplication.cpp#L1063-L1101 + """ + self._remove_early_js('stylesheet') + css = shared.get_user_stylesheet() + js_code = javascript.wrap_global( + 'stylesheet', + utils.read_file('javascript/stylesheet.js'), + javascript.assemble('stylesheet', 'set_css', css), + ) + self._inject_early_js('stylesheet', js_code, subframes=True) + + def _inject_userscripts(self): + """Register user JavaScript files with the global profiles.""" + # 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. + 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() + + # Then add the new scripts. + for script in greasemonkey.all_scripts(): + # @run-at (and @include/@exclude/@match) is parsed by + # QWebEngineScript. + new_script = QWebEngineScript() + new_script.setWorldId(QWebEngineScript.MainWorld) + 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) def _install_event_filter(self): self._widget.focusProxy().installEventFilter(self._mouse_event_filter) diff --git a/tests/end2end/features/javascript.feature b/tests/end2end/features/javascript.feature index 637f4696c..fb48a5932 100644 --- a/tests/end2end/features/javascript.feature +++ b/tests/end2end/features/javascript.feature @@ -174,3 +174,15 @@ Feature: Javascript stuff When I set content.javascript.enabled to false And I open 500 without waiting Then "Showing error page for* 500" should be logged + + Scenario: Using JS after window.open + When I open data/hello.txt + And I set content.javascript.can_open_tabs_automatically to true + And I run :jseval window.open('about:blank') + And I open data/hello.txt + And I run :tab-only + And I open data/hints/html/simple.html + And I run :hint all + And I wait for "hints: a" in the log + And I run :leave-mode + Then "There was an error while getting hint elements" should not be logged From 460bd86579de6d677ab8213469fd3a2d93ccbd1c Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 19 Mar 2018 18:18:21 +0100 Subject: [PATCH 089/223] Initial attempt at using the tab API for tests/unit/javascript --- tests/helpers/fixtures.py | 55 ++++- tests/helpers/stubs.py | 3 + tests/unit/browser/test_tab.py | 110 ---------- tests/unit/javascript/conftest.py | 206 +++--------------- .../position_caret/test_position_caret.py | 23 +- .../javascript/stylesheet/test_stylesheet.py | 6 +- 6 files changed, 106 insertions(+), 297 deletions(-) delete mode 100644 tests/unit/browser/test_tab.py diff --git a/tests/helpers/fixtures.py b/tests/helpers/fixtures.py index f8729d3f8..d1380a322 100644 --- a/tests/helpers/fixtures.py +++ b/tests/helpers/fixtures.py @@ -43,12 +43,24 @@ import helpers.stubs as stubsmod import helpers.utils from qutebrowser.config import (config, configdata, configtypes, configexc, configfiles) -from qutebrowser.utils import objreg, standarddir +from qutebrowser.utils import objreg, standarddir, utils +from qutebrowser.browser import greasemonkey from qutebrowser.browser.webkit import cookies from qutebrowser.misc import savemanager, sql from qutebrowser.keyinput import modeman +try: + from PyQt5.QtWebKitWidgets import QWebView +except ImportError: + QWebView = None + +try: + from PyQt5.QtWebEngineWidgets import QWebEngineView +except ImportError: + QWebEngineView = None + + class WinRegistryHelper: """Helper class for win_registry.""" @@ -143,6 +155,47 @@ def fake_web_tab(stubs, tab_registry, mode_manager, qapp): return stubs.FakeWebTab +@pytest.fixture +def greasemonkey_manager(data_tmpdir): + gm_manager = greasemonkey.GreasemonkeyManager() + objreg.register('greasemonkey', gm_manager) + yield + objreg.delete('greasemonkey') + + +@pytest.fixture +def webkit_tab(qtbot, tab_registry, cookiejar_and_cache, mode_manager, + session_manager_stub, greasemonkey_manager): + webkittab = pytest.importorskip('qutebrowser.browser.webkit.webkittab') + tab = webkittab.WebKitTab(win_id=0, mode_manager=mode_manager, + private=False) + qtbot.add_widget(tab) + return tab + + +@pytest.fixture +def webengine_tab(qtbot, tab_registry, fake_args, mode_manager, + session_manager_stub, greasemonkey_manager, + redirect_webengine_data): + webenginetab = pytest.importorskip( + 'qutebrowser.browser.webengine.webenginetab') + tab = webenginetab.WebEngineTab(win_id=0, mode_manager=mode_manager, + private=False) + qtbot.add_widget(tab) + return tab + + +@pytest.fixture(params=['webkit', 'webengine']) +def web_tab(request): + """A WebKitTab/WebEngineTab.""" + if request.param == 'webkit': + return request.getfixturevalue('webkit_tab') + elif request.param == 'webengine': + return request.getfixturevalue('webengine_tab') + else: + raise utils.Unreachable + + def _generate_cmdline_tests(): """Generate testcases for test_split_binding.""" @attr.s diff --git a/tests/helpers/stubs.py b/tests/helpers/stubs.py index b5e30bb0b..ea7cbf268 100644 --- a/tests/helpers/stubs.py +++ b/tests/helpers/stubs.py @@ -472,6 +472,9 @@ class SessionManagerStub: def list_sessions(self): return self.sessions + def save_autosave(self): + pass + class TabbedBrowserStub(QObject): diff --git a/tests/unit/browser/test_tab.py b/tests/unit/browser/test_tab.py deleted file mode 100644 index 05a178b81..000000000 --- a/tests/unit/browser/test_tab.py +++ /dev/null @@ -1,110 +0,0 @@ -# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: - -# Copyright 2016-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 . - -import pytest - -from qutebrowser.browser import browsertab -from qutebrowser.utils import utils - -pytestmark = pytest.mark.usefixtures('redirect_webengine_data') - -try: - from PyQt5.QtWebKitWidgets import QWebView -except ImportError: - QWebView = None - -try: - from PyQt5.QtWebEngineWidgets import QWebEngineView -except ImportError: - QWebEngineView = None - - -@pytest.fixture(params=[QWebView, QWebEngineView]) -def view(qtbot, config_stub, request): - if request.param is None: - pytest.skip("View not available") - - v = request.param() - qtbot.add_widget(v) - return v - - -@pytest.fixture(params=['webkit', 'webengine']) -def tab(request, qtbot, tab_registry, cookiejar_and_cache, mode_manager): - if request.param == 'webkit': - webkittab = pytest.importorskip('qutebrowser.browser.webkit.webkittab') - tab_class = webkittab.WebKitTab - elif request.param == 'webengine': - webenginetab = pytest.importorskip( - 'qutebrowser.browser.webengine.webenginetab') - tab_class = webenginetab.WebEngineTab - else: - raise utils.Unreachable - - t = tab_class(win_id=0, mode_manager=mode_manager) - qtbot.add_widget(t) - yield t - - -class Zoom(browsertab.AbstractZoom): - - def _set_factor_internal(self, _factor): - pass - - def factor(self): - raise utils.Unreachable - - -class Tab(browsertab.AbstractTab): - - # pylint: disable=abstract-method - - def __init__(self, win_id, mode_manager, parent=None): - super().__init__(win_id=win_id, mode_manager=mode_manager, - parent=parent) - self.history = browsertab.AbstractHistory(self) - self.scroller = browsertab.AbstractScroller(self, parent=self) - self.caret = browsertab.AbstractCaret(mode_manager=mode_manager, - tab=self, parent=self) - self.zoom = Zoom(tab=self) - self.search = browsertab.AbstractSearch(parent=self) - self.printing = browsertab.AbstractPrinting() - self.elements = browsertab.AbstractElements(tab=self) - self.action = browsertab.AbstractAction(tab=self) - - def _install_event_filter(self): - pass - - -@pytest.mark.xfail(run=False, reason='Causes segfaults, see #1638') -def test_tab(qtbot, view, config_stub, tab_registry, mode_manager): - tab_w = Tab(win_id=0, mode_manager=mode_manager) - qtbot.add_widget(tab_w) - - assert tab_w.win_id == 0 - assert tab_w._widget is None - - tab_w._set_widget(view) - assert tab_w._widget is view - assert tab_w.history._tab is tab_w - assert tab_w.history._history is view.history() - assert view.parent() is tab_w - - with qtbot.waitExposed(tab_w): - tab_w.show() diff --git a/tests/unit/javascript/conftest.py b/tests/unit/javascript/conftest.py index 254ead335..f555d9530 100644 --- a/tests/unit/javascript/conftest.py +++ b/tests/unit/javascript/conftest.py @@ -27,91 +27,9 @@ import pytest import jinja2 from PyQt5.QtCore import QUrl -try: - from PyQt5.QtWebKit import QWebSettings - from PyQt5.QtWebKitWidgets import QWebPage -except ImportError: - # FIXME:qtwebengine Make these tests use the tab API - QWebSettings = None - QWebPage = None - -try: - from PyQt5.QtWebEngineWidgets import (QWebEnginePage, - QWebEngineSettings, - QWebEngineScript) -except ImportError: - QWebEnginePage = None - QWebEngineSettings = None - QWebEngineScript = None import helpers.utils -import qutebrowser.utils.debug -from qutebrowser.utils import utils - - -if QWebPage is None: - TestWebPage = None -else: - class TestWebPage(QWebPage): - - """QWebPage subclass which overrides some test methods. - - Attributes: - _logger: The logger used for alerts. - """ - - def __init__(self, parent=None): - super().__init__(parent) - self._logger = logging.getLogger('js-tests') - - def javaScriptAlert(self, _frame, msg): - """Log javascript alerts.""" - self._logger.info("js alert: {}".format(msg)) - - def javaScriptConfirm(self, _frame, msg): - """Fail tests on js confirm() as that should never happen.""" - pytest.fail("js confirm: {}".format(msg)) - - def javaScriptPrompt(self, _frame, msg, _default): - """Fail tests on js prompt() as that should never happen.""" - pytest.fail("js prompt: {}".format(msg)) - - def javaScriptConsoleMessage(self, msg, line, source): - """Fail tests on js console messages as they're used for errors.""" - pytest.fail("js console ({}:{}): {}".format(source, line, msg)) - -if QWebEnginePage is None: - TestWebEnginePage = None -else: - class TestWebEnginePage(QWebEnginePage): - - """QWebEnginePage which overrides javascript logging methods. - - Attributes: - _logger: The logger used for alerts. - """ - - def __init__(self, parent=None): - super().__init__(parent) - self._logger = logging.getLogger('js-tests') - - def javaScriptAlert(self, _frame, msg): - """Log javascript alerts.""" - self._logger.info("js alert: {}".format(msg)) - - def javaScriptConfirm(self, _frame, msg): - """Fail tests on js confirm() as that should never happen.""" - pytest.fail("js confirm: {}".format(msg)) - - def javaScriptPrompt(self, _frame, msg, _default): - """Fail tests on js prompt() as that should never happen.""" - pytest.fail("js prompt: {}".format(msg)) - - def javaScriptConsoleMessage(self, level, msg, line, source): - """Fail tests on js console messages as they're used for errors.""" - pytest.fail("[{}] js console ({}:{}): {}".format( - qutebrowser.utils.debug.qenum_key( - QWebEnginePage, level), source, line, msg)) +from qutebrowser.utils import utils, usertypes class JSTester: @@ -119,14 +37,14 @@ class JSTester: """Common subclass providing basic functionality for all JS testers. Attributes: - webview: The webview which is used. - _qtbot: The QtBot fixture from pytest-qt. + tab: The tab object which is used. + qtbot: The QtBot fixture from pytest-qt. _jinja_env: The jinja2 environment used to get templates. """ - def __init__(self, webview, qtbot): - self.webview = webview - self._qtbot = qtbot + def __init__(self, tab, qtbot): + self.tab = tab + self.qtbot = qtbot loader = jinja2.FileSystemLoader(os.path.dirname(__file__)) self._jinja_env = jinja2.Environment(loader=loader, autoescape=True) @@ -139,9 +57,9 @@ class JSTester: **kwargs: Passed to jinja's template.render(). """ template = self._jinja_env.get_template(path) - with self._qtbot.waitSignal(self.webview.loadFinished, - timeout=2000) as blocker: - self.webview.setHtml(template.render(**kwargs)) + with self.qtbot.waitSignal(self.tab.load_finished, + timeout=2000) as blocker: + self.tab.set_html(template.render(**kwargs)) assert blocker.args == [True] def load_file(self, path: str, force: bool = False): @@ -161,77 +79,13 @@ class JSTester: url: The QUrl to load. force: Whether to force loading even if the file is invalid. """ - with self._qtbot.waitSignal(self.webview.loadFinished, - timeout=2000) as blocker: - self.webview.load(url) + with self.qtbot.waitSignal(self.tab.load_finished, + timeout=2000) as blocker: + self.tab.openurl(url) if not force: assert blocker.args == [True] - -class JSWebKitTester(JSTester): - - """Object returned by js_tester which provides test data and a webview. - - Attributes: - webview: The webview which is used. - _qtbot: The QtBot fixture from pytest-qt. - _jinja_env: The jinja2 environment used to get templates. - """ - - def __init__(self, webview, qtbot): - super().__init__(webview, qtbot) - self.webview.setPage(TestWebPage(self.webview)) - - def scroll_anchor(self, name): - """Scroll the main frame to the given anchor.""" - page = self.webview.page() - old_pos = page.mainFrame().scrollPosition() - page.mainFrame().scrollToAnchor(name) - new_pos = page.mainFrame().scrollPosition() - assert old_pos != new_pos - - def run_file(self, filename): - """Run a javascript file. - - Args: - filename: The javascript filename, relative to - qutebrowser/javascript. - - Return: - The javascript return value. - """ - source = utils.read_file(os.path.join('javascript', filename)) - return self.run(source) - - def run(self, source): - """Run the given javascript source. - - Args: - source: The source to run as a string. - - Return: - The javascript return value. - """ - assert self.webview.settings().testAttribute( - QWebSettings.JavascriptEnabled) - return self.webview.page().mainFrame().evaluateJavaScript(source) - - -class JSWebEngineTester(JSTester): - - """Object returned by js_tester_webengine which provides a webview. - - Attributes: - webview: The webview which is used. - _qtbot: The QtBot fixture from pytest-qt. - _jinja_env: The jinja2 environment used to get templates. - """ - - def __init__(self, webview, qtbot): - super().__init__(webview, qtbot) - self.webview.setPage(TestWebEnginePage(self.webview)) - - def run_file(self, filename: str, expected) -> None: + def run_file(self, filename: str, expected=None) -> None: """Run a javascript file. Args: @@ -250,24 +104,34 @@ class JSWebEngineTester(JSTester): expected: The value expected return from the javascript execution world: The scope the javascript will run in """ - if world is None: - world = QWebEngineScript.ApplicationWorld - - callback_checker = helpers.utils.CallbackChecker(self._qtbot) - assert self.webview.settings().testAttribute( - QWebEngineSettings.JavascriptEnabled) - self.webview.page().runJavaScript(source, world, - callback_checker.callback) + callback_checker = helpers.utils.CallbackChecker(self.qtbot) + self.tab.run_js_async(source, callback_checker.callback, world=world) callback_checker.check(expected) + def scroll_anchor(self, name): + """Scroll the main frame to the given anchor.""" + # FIXME This might be useful in the tab API? + assert self.tab.backend == usertypes.Backend.QtWebKit + page = self.tab._widget.page() + old_pos = page.mainFrame().scrollPosition() + page.mainFrame().scrollToAnchor(name) + new_pos = page.mainFrame().scrollPosition() + assert old_pos != new_pos + @pytest.fixture -def js_tester_webkit(webview, qtbot): +def js_tester_webkit(webkit_tab, qtbot): """Fixture to test javascript snippets in webkit.""" - return JSWebKitTester(webview, qtbot) + return JSTester(webkit_tab, qtbot) @pytest.fixture -def js_tester_webengine(callback_checker, webengineview, qtbot): +def js_tester_webengine(webengine_tab, qtbot): """Fixture to test javascript snippets in webengine.""" - return JSWebEngineTester(webengineview, qtbot) + return JSTester(webengine_tab, qtbot) + + +@pytest.fixture +def js_tester(web_tab, qtbot): + """Fixture to test javascript snippets with both backends.""" + return JSTester(web_tab, qtbot) diff --git a/tests/unit/javascript/position_caret/test_position_caret.py b/tests/unit/javascript/position_caret/test_position_caret.py index c47f94e30..edfe502c8 100644 --- a/tests/unit/javascript/position_caret/test_position_caret.py +++ b/tests/unit/javascript/position_caret/test_position_caret.py @@ -21,8 +21,7 @@ import pytest -# FIXME:qtwebengine Make these tests use the tab API -pytest.importorskip('PyQt5.QtWebKit') +import helpers.utils from PyQt5.QtCore import Qt from PyQt5.QtWebKit import QWebSettings @@ -53,24 +52,26 @@ class CaretTester: def check(self): """Check whether the caret is before the MARKER text.""" self.js.run_file('position_caret.js') - self.js.webview.triggerPageAction(QWebPage.SelectNextWord) - assert self.js.webview.selectedText().rstrip() == "MARKER" + self.js.tab.caret.toggle_selection() + self.js.tab.caret.move_to_next_word() + + callback_checker = helpers.utils.CallbackChecker(self.js.qtbot) + self.js.tab.caret.selection(lambda text: + callback_checker.callback(text.rstrip())) + callback_checker.check('MARKER') def check_scrolled(self): """Check if the page is scrolled down.""" - frame = self.js.webview.page().mainFrame() - minimum = frame.scrollBarMinimum(Qt.Vertical) - value = frame.scrollBarValue(Qt.Vertical) - assert value > minimum + assert not self.js.tab.scroller.at_top() @pytest.fixture -def caret_tester(js_tester_webkit): +def caret_tester(js_tester): """Helper fixture to test caret browsing positions.""" - caret_tester = CaretTester(js_tester_webkit) + caret_tester = CaretTester(js_tester) # Showing webview here is necessary for test_scrolled_down_img to # succeed in some cases, see #1988 - caret_tester.js.webview.show() + caret_tester.js.tab.show() return caret_tester diff --git a/tests/unit/javascript/stylesheet/test_stylesheet.py b/tests/unit/javascript/stylesheet/test_stylesheet.py index df11d22a3..8a7b91ac6 100644 --- a/tests/unit/javascript/stylesheet/test_stylesheet.py +++ b/tests/unit/javascript/stylesheet/test_stylesheet.py @@ -56,9 +56,7 @@ class StylesheetTester: """Initialize the stylesheet with a provided css file.""" css_path = os.path.join(os.path.dirname(__file__), css_file) self.config_stub.val.content.user_stylesheets = css_path - profile = QWebEngineProfile.defaultProfile() - setter = webenginesettings.ProfileSetter(profile) - setter.init_stylesheet() + self.js.tab._init_stylesheet() def set_css(self, css): """Set document style to `css` via stylesheet.js.""" @@ -82,7 +80,7 @@ class StylesheetTester: def stylesheet_tester(js_tester_webengine, config_stub): """Helper fixture to test stylesheets.""" ss_tester = StylesheetTester(js_tester_webengine, config_stub) - ss_tester.js.webview.show() + ss_tester.js.tab.show() return ss_tester From e50068021d084cf01507cd20693318189193e073 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 19 Mar 2018 18:30:44 +0100 Subject: [PATCH 090/223] Use signals to update statusbar in caret mode This means we don't use objreg anymore to get the status bar, and also makes the bar more accurately reflect reality. See #3583 --- qutebrowser/browser/browsertab.py | 9 ++++++- qutebrowser/browser/webengine/webenginetab.py | 9 +++---- qutebrowser/browser/webkit/webkittab.py | 7 +++-- qutebrowser/javascript/caret.js | 4 ++- qutebrowser/mainwindow/mainwindow.py | 4 +++ qutebrowser/mainwindow/statusbar/bar.py | 26 +++++++++++-------- qutebrowser/mainwindow/tabbedbrowser.py | 3 +++ 7 files changed, 40 insertions(+), 22 deletions(-) diff --git a/qutebrowser/browser/browsertab.py b/qutebrowser/browser/browsertab.py index d3345c88b..7e2793166 100644 --- a/qutebrowser/browser/browsertab.py +++ b/qutebrowser/browser/browsertab.py @@ -333,7 +333,14 @@ class AbstractZoom(QObject): class AbstractCaret(QObject): - """Attribute of AbstractTab for caret browsing.""" + """Attribute of AbstractTab for caret browsing. + + Signals: + selection_toggled: Emitted when the selection was toggled. + arg: Whether the selection is now active. + """ + + selection_toggled = pyqtSignal(bool) def __init__(self, tab, mode_manager, parent=None): super().__init__(parent) diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index 5d7dfeb90..6ecb82661 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -230,7 +230,7 @@ class WebEngineCaret(browsertab.AbstractCaret): self._tab.run_js_async( javascript.assemble('caret', 'setPlatform', sys.platform)) - self._js_call('setInitialCursor') + self._js_call('setInitialCursor', self.selection_toggled.emit) @pyqtSlot(usertypes.KeyMode) def _on_mode_left(self, mode): @@ -297,7 +297,7 @@ class WebEngineCaret(browsertab.AbstractCaret): self._js_call('moveToEndOfDocument') def toggle_selection(self): - self._js_call('toggleSelection') + self._js_call('toggleSelection', self.selection_toggled.emit) def drop_selection(self): self._js_call('dropSelection') @@ -352,9 +352,8 @@ class WebEngineCaret(browsertab.AbstractCaret): self._tab.run_js_async(js_code, lambda jsret: self._follow_selected_cb(jsret, tab)) - def _js_call(self, command): - self._tab.run_js_async( - javascript.assemble('caret', command)) + def _js_call(self, command, callback=None): + self._tab.run_js_async(javascript.assemble('caret', command), callback) class WebEngineScroller(browsertab.AbstractScroller): diff --git a/qutebrowser/browser/webkit/webkittab.py b/qutebrowser/browser/webkit/webkittab.py index 17757a761..537e89822 100644 --- a/qutebrowser/browser/webkit/webkittab.py +++ b/qutebrowser/browser/webkit/webkittab.py @@ -196,9 +196,10 @@ class WebKitCaret(browsertab.AbstractCaret): if mode != usertypes.KeyMode.caret: return + self.selection_enabled = self._widget.hasSelection() + self.selection_toggled.emit(self.selection_enabled) settings = self._widget.settings() settings.setAttribute(QWebSettings.CaretBrowsingEnabled, True) - self.selection_enabled = self._widget.hasSelection() if self._widget.isVisible(): # Sometimes the caret isn't immediately visible, but unfocusing @@ -363,9 +364,7 @@ class WebKitCaret(browsertab.AbstractCaret): def toggle_selection(self): self.selection_enabled = not self.selection_enabled - mainwindow = objreg.get('main-window', scope='window', - window=self._tab.win_id) - mainwindow.status.set_mode_active(usertypes.KeyMode.caret, True) + self.selection_toggled.emit(self.selection_enabled) def drop_selection(self): self._widget.triggerPageAction(QWebPage.MoveToNextChar) diff --git a/qutebrowser/javascript/caret.js b/qutebrowser/javascript/caret.js index 3bb4de49f..7e862befc 100644 --- a/qutebrowser/javascript/caret.js +++ b/qutebrowser/javascript/caret.js @@ -1269,13 +1269,14 @@ window._qutebrowser.caret = (function() { funcs.setInitialCursor = () => { if (!CaretBrowsing.initiated) { CaretBrowsing.setInitialCursor(); - return; + return CaretBrowsing.selectionEnabled; } if (window.getSelection().toString().length === 0) { positionCaret(); } CaretBrowsing.toggle(); + return CaretBrowsing.selectionEnabled; }; funcs.setPlatform = (platform) => { @@ -1361,6 +1362,7 @@ window._qutebrowser.caret = (function() { funcs.toggleSelection = () => { CaretBrowsing.selectionEnabled = !CaretBrowsing.selectionEnabled; + return CaretBrowsing.selectionEnabled; }; return funcs; diff --git a/qutebrowser/mainwindow/mainwindow.py b/qutebrowser/mainwindow/mainwindow.py index b15e98e22..3db56c0a7 100644 --- a/qutebrowser/mainwindow/mainwindow.py +++ b/qutebrowser/mainwindow/mainwindow.py @@ -479,6 +479,10 @@ class MainWindow(QWidget): self.tabbed_browser.cur_link_hovered.connect(status.url.set_hover_url) self.tabbed_browser.cur_load_status_changed.connect( status.url.on_load_status_changed) + + self.tabbed_browser.cur_caret_selection_toggled.connect( + status.on_caret_selection_toggled) + self.tabbed_browser.cur_fullscreen_requested.connect( self._on_fullscreen_requested) self.tabbed_browser.cur_fullscreen_requested.connect(status.maybe_hide) diff --git a/qutebrowser/mainwindow/statusbar/bar.py b/qutebrowser/mainwindow/statusbar/bar.py index 9efc26858..c3ef53b1b 100644 --- a/qutebrowser/mainwindow/statusbar/bar.py +++ b/qutebrowser/mainwindow/statusbar/bar.py @@ -289,17 +289,9 @@ class StatusBar(QWidget): log.statusbar.debug("Setting prompt flag to {}".format(val)) self._color_flags.prompt = val elif mode == usertypes.KeyMode.caret: - tab = self._current_tab() - log.statusbar.debug("Setting caret flag - val {}, selection " - "{}".format(val, tab.caret.selection_enabled)) - if val: - if tab.caret.selection_enabled: - self._set_mode_text("{} selection".format(mode.name)) - self._color_flags.caret = ColorFlags.CaretMode.selection - else: - self._set_mode_text(mode.name) - self._color_flags.caret = ColorFlags.CaretMode.on - else: + if not val: + # Turning on is handled in on_current_caret_selection_toggled + log.statusbar.debug("Setting caret mode off") self._color_flags.caret = ColorFlags.CaretMode.off config.set_register_stylesheet(self, update=False) @@ -377,6 +369,18 @@ class StatusBar(QWidget): self.maybe_hide() assert tab.private == self._color_flags.private + @pyqtSlot(bool) + def on_caret_selection_toggled(self, selection): + """Update the statusbar when entering/leaving caret selection mode.""" + log.statusbar.debug("Setting caret selection {}".format(selection)) + if selection: + self._set_mode_text("caret selection") + self._color_flags.caret = ColorFlags.CaretMode.selection + else: + self._set_mode_text("caret") + self._color_flags.caret = ColorFlags.CaretMode.on + config.set_register_stylesheet(self, update=False) + def resizeEvent(self, e): """Extend resizeEvent of QWidget to emit a resized signal afterwards. diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py index 3d9eb27f4..cedb0cf33 100644 --- a/qutebrowser/mainwindow/tabbedbrowser.py +++ b/qutebrowser/mainwindow/tabbedbrowser.py @@ -104,6 +104,7 @@ class TabbedBrowser(QWidget): cur_scroll_perc_changed = pyqtSignal(int, int) cur_load_status_changed = pyqtSignal(str) cur_fullscreen_requested = pyqtSignal(bool) + cur_caret_selection_toggled = pyqtSignal(bool) close_window = pyqtSignal() resized = pyqtSignal('QRect') current_tab_changed = pyqtSignal(browsertab.AbstractTab) @@ -217,6 +218,8 @@ class TabbedBrowser(QWidget): self._filter.create(self.cur_load_status_changed, tab)) tab.fullscreen_requested.connect( self._filter.create(self.cur_fullscreen_requested, tab)) + tab.caret.selection_toggled.connect( + self._filter.create(self.cur_caret_selection_toggled, tab)) # misc tab.scroller.perc_changed.connect(self.on_scroll_pos_changed) tab.url_changed.connect( From f5d7605ae04fd6cced0af94a0646fc46517801d9 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 19 Mar 2018 19:18:33 +0100 Subject: [PATCH 091/223] Add a :scroll-to-anchor command Fixes #2784 --- doc/changelog.asciidoc | 5 +++++ doc/help/commands.asciidoc | 10 ++++++++++ qutebrowser/browser/browsertab.py | 3 +++ qutebrowser/browser/commands.py | 9 +++++++++ qutebrowser/browser/webengine/webenginetab.py | 5 +++++ qutebrowser/browser/webkit/webkittab.py | 3 +++ tests/unit/javascript/conftest.py | 10 ---------- .../javascript/position_caret/test_position_caret.py | 4 ++-- 8 files changed, 37 insertions(+), 12 deletions(-) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index db91eedca..ae5d69e3c 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -18,6 +18,11 @@ breaking changes (such as renamed commands) can happen in minor releases. v1.3.0 (unreleased) ------------------- +Added +~~~~~ + +- New `:scroll-to-anchor` command to scroll to an anchor in the document. + Changed ~~~~~~~ diff --git a/doc/help/commands.asciidoc b/doc/help/commands.asciidoc index 3a12f08eb..31ed75486 100644 --- a/doc/help/commands.asciidoc +++ b/doc/help/commands.asciidoc @@ -93,6 +93,7 @@ It is possible to run or bind multiple commands by separating them with `;;`. |<>|Scroll the current tab in the given direction. |<>|Scroll the frame page-wise. |<>|Scroll the current tab by 'count * dx/dy' pixels. +|<>|Scroll to the given anchor in the document. |<>|Scroll to a specific percentage of the page. |<>|Search for a text on the current page. With no text, clear results. |<>|Continue the search to the ([count]th) next term. @@ -1024,6 +1025,15 @@ Scroll the current tab by 'count * dx/dy' pixels. ==== count multiplier +[[scroll-to-anchor]] +=== scroll-to-anchor +Syntax: +:scroll-to-anchor 'name'+ + +Scroll to the given anchor in the document. + +==== positional arguments +* +'name'+: The anchor to scroll to. + [[scroll-to-perc]] === scroll-to-perc Syntax: +:scroll-to-perc [*--horizontal*] ['perc']+ diff --git a/qutebrowser/browser/browsertab.py b/qutebrowser/browser/browsertab.py index 7e2793166..dd3673aa6 100644 --- a/qutebrowser/browser/browsertab.py +++ b/qutebrowser/browser/browsertab.py @@ -446,6 +446,9 @@ class AbstractScroller(QObject): def to_point(self, point): raise NotImplementedError + def to_anchor(self, name): + raise NotImplementedError + def delta(self, x=0, y=0): raise NotImplementedError diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index a4786e5e0..d492eb513 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -768,6 +768,15 @@ class CommandDispatcher: self._current_widget().scroller.to_perc(x, y) + @cmdutils.register(instance='command-dispatcher', scope='window') + def scroll_to_anchor(self, name): + """Scroll to the given anchor in the document. + + Args: + name: The anchor to scroll to. + """ + self._current_widget().scroller.to_anchor(name) + @cmdutils.register(instance='command-dispatcher', scope='window') @cmdutils.argument('count', count=True) @cmdutils.argument('top_navigate', metavar='ACTION', diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index 6ecb82661..c92d40ffa 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -427,6 +427,11 @@ class WebEngineScroller(browsertab.AbstractScroller): js_code = javascript.assemble('window', 'scroll', point.x(), point.y()) self._tab.run_js_async(js_code) + def to_anchor(self, name): + url = self._tab.url() + url.setFragment(name) + self._tab.openurl(url) + def delta(self, x=0, y=0): self._tab.run_js_async(javascript.assemble('window', 'scrollBy', x, y)) diff --git a/qutebrowser/browser/webkit/webkittab.py b/qutebrowser/browser/webkit/webkittab.py index 537e89822..9d5305f10 100644 --- a/qutebrowser/browser/webkit/webkittab.py +++ b/qutebrowser/browser/webkit/webkittab.py @@ -426,6 +426,9 @@ class WebKitScroller(browsertab.AbstractScroller): def to_point(self, point): self._widget.page().mainFrame().setScrollPosition(point) + def to_anchor(self, name): + self._widget.page().mainFrame().scrollToAnchor(name) + def delta(self, x=0, y=0): qtutils.check_overflow(x, 'int') qtutils.check_overflow(y, 'int') diff --git a/tests/unit/javascript/conftest.py b/tests/unit/javascript/conftest.py index f555d9530..f4750815a 100644 --- a/tests/unit/javascript/conftest.py +++ b/tests/unit/javascript/conftest.py @@ -108,16 +108,6 @@ class JSTester: self.tab.run_js_async(source, callback_checker.callback, world=world) callback_checker.check(expected) - def scroll_anchor(self, name): - """Scroll the main frame to the given anchor.""" - # FIXME This might be useful in the tab API? - assert self.tab.backend == usertypes.Backend.QtWebKit - page = self.tab._widget.page() - old_pos = page.mainFrame().scrollPosition() - page.mainFrame().scrollToAnchor(name) - new_pos = page.mainFrame().scrollPosition() - assert old_pos != new_pos - @pytest.fixture def js_tester_webkit(webkit_tab, qtbot): diff --git a/tests/unit/javascript/position_caret/test_position_caret.py b/tests/unit/javascript/position_caret/test_position_caret.py index edfe502c8..d62175ed9 100644 --- a/tests/unit/javascript/position_caret/test_position_caret.py +++ b/tests/unit/javascript/position_caret/test_position_caret.py @@ -86,7 +86,7 @@ def test_simple(caret_tester): def test_scrolled_down(caret_tester): """Test with multiple text blocks with the viewport scrolled down.""" caret_tester.js.load('position_caret/scrolled_down.html') - caret_tester.js.scroll_anchor('anchor') + caret_tester.js.tab.scroller.to_anchor('anchor') caret_tester.check_scrolled() caret_tester.check() @@ -103,6 +103,6 @@ def test_invisible(caret_tester, style): def test_scrolled_down_img(caret_tester): """Test with an image at the top with the viewport scrolled down.""" caret_tester.js.load('position_caret/scrolled_down_img.html') - caret_tester.js.scroll_anchor('anchor') + caret_tester.js.tab.scroller.to_anchor('anchor') caret_tester.check_scrolled() caret_tester.check() From 0ea7a1457d9fea84abbcc597d860ab4af061f650 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 19 Mar 2018 19:36:27 +0100 Subject: [PATCH 092/223] Make test_position_caret work again The tests only work properly with QtWebKit (and aren't needed on QtWebEngine). Also, for some reason the scrolled_down tests only work without Xvfb. --- .../javascript/position_caret/test_position_caret.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/unit/javascript/position_caret/test_position_caret.py b/tests/unit/javascript/position_caret/test_position_caret.py index d62175ed9..06c54bb15 100644 --- a/tests/unit/javascript/position_caret/test_position_caret.py +++ b/tests/unit/javascript/position_caret/test_position_caret.py @@ -24,8 +24,8 @@ import pytest import helpers.utils from PyQt5.QtCore import Qt -from PyQt5.QtWebKit import QWebSettings -from PyQt5.QtWebKitWidgets import QWebPage +QWebSettings = pytest.importorskip("PyQt5.QtWebKit").QWebSettings +QWebPage = pytest.importorskip("PyQt5.QtWebKitWidgets").QWebPage @pytest.fixture(autouse=True) @@ -66,9 +66,9 @@ class CaretTester: @pytest.fixture -def caret_tester(js_tester): +def caret_tester(js_tester_webkit): """Helper fixture to test caret browsing positions.""" - caret_tester = CaretTester(js_tester) + caret_tester = CaretTester(js_tester_webkit) # Showing webview here is necessary for test_scrolled_down_img to # succeed in some cases, see #1988 caret_tester.js.tab.show() @@ -83,6 +83,7 @@ def test_simple(caret_tester): @pytest.mark.integration +@pytest.mark.no_xvfb def test_scrolled_down(caret_tester): """Test with multiple text blocks with the viewport scrolled down.""" caret_tester.js.load('position_caret/scrolled_down.html') @@ -100,6 +101,7 @@ def test_invisible(caret_tester, style): @pytest.mark.integration +@pytest.mark.no_xvfb def test_scrolled_down_img(caret_tester): """Test with an image at the top with the viewport scrolled down.""" caret_tester.js.load('position_caret/scrolled_down_img.html') From 1162e640c5621c99fc8974fb7527bb002d828735 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 19 Mar 2018 19:42:56 +0100 Subject: [PATCH 093/223] Remove unused imports --- tests/helpers/fixtures.py | 11 ----------- tests/unit/javascript/conftest.py | 3 +-- .../javascript/position_caret/test_position_caret.py | 1 - tests/unit/javascript/stylesheet/test_stylesheet.py | 5 ----- 4 files changed, 1 insertion(+), 19 deletions(-) diff --git a/tests/helpers/fixtures.py b/tests/helpers/fixtures.py index d1380a322..0e147b211 100644 --- a/tests/helpers/fixtures.py +++ b/tests/helpers/fixtures.py @@ -50,17 +50,6 @@ from qutebrowser.misc import savemanager, sql from qutebrowser.keyinput import modeman -try: - from PyQt5.QtWebKitWidgets import QWebView -except ImportError: - QWebView = None - -try: - from PyQt5.QtWebEngineWidgets import QWebEngineView -except ImportError: - QWebEngineView = None - - class WinRegistryHelper: """Helper class for win_registry.""" diff --git a/tests/unit/javascript/conftest.py b/tests/unit/javascript/conftest.py index f4750815a..f55ca75b5 100644 --- a/tests/unit/javascript/conftest.py +++ b/tests/unit/javascript/conftest.py @@ -21,7 +21,6 @@ import os import os.path -import logging import pytest import jinja2 @@ -29,7 +28,7 @@ import jinja2 from PyQt5.QtCore import QUrl import helpers.utils -from qutebrowser.utils import utils, usertypes +from qutebrowser.utils import utils class JSTester: diff --git a/tests/unit/javascript/position_caret/test_position_caret.py b/tests/unit/javascript/position_caret/test_position_caret.py index 06c54bb15..34b92dc66 100644 --- a/tests/unit/javascript/position_caret/test_position_caret.py +++ b/tests/unit/javascript/position_caret/test_position_caret.py @@ -23,7 +23,6 @@ import pytest import helpers.utils -from PyQt5.QtCore import Qt QWebSettings = pytest.importorskip("PyQt5.QtWebKit").QWebSettings QWebPage = pytest.importorskip("PyQt5.QtWebKitWidgets").QWebPage diff --git a/tests/unit/javascript/stylesheet/test_stylesheet.py b/tests/unit/javascript/stylesheet/test_stylesheet.py index 8a7b91ac6..6d3a7ce7f 100644 --- a/tests/unit/javascript/stylesheet/test_stylesheet.py +++ b/tests/unit/javascript/stylesheet/test_stylesheet.py @@ -27,11 +27,6 @@ QWebEngineProfile = QtWebEngineWidgets.QWebEngineProfile from qutebrowser.utils import javascript -try: - from qutebrowser.browser.webengine import webenginesettings -except ImportError: - webenginesettings = None - DEFAULT_BODY_BG = "rgba(0, 0, 0, 0)" GREEN_BODY_BG = "rgb(0, 255, 0)" From 9031b3e535492ca4c1a65c819f9f4cf3e6c77b90 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 19 Mar 2018 20:17:15 +0100 Subject: [PATCH 094/223] Remove @pyqtSlot for on_download_requested For some reason, this breaks when test_pac is run... --- qutebrowser/browser/webkit/webpage.py | 1 - 1 file changed, 1 deletion(-) diff --git a/qutebrowser/browser/webkit/webpage.py b/qutebrowser/browser/webkit/webpage.py index 7b0a5caf5..6bbc27109 100644 --- a/qutebrowser/browser/webkit/webpage.py +++ b/qutebrowser/browser/webkit/webpage.py @@ -239,7 +239,6 @@ class BrowserPage(QWebPage): printdiag.setAttribute(Qt.WA_DeleteOnClose) printdiag.open(lambda: frame.print(printdiag.printer())) - @pyqtSlot('QNetworkRequest') def on_download_requested(self, request): """Called when the user wants to download a link. From b588f54a5315fadf0ee8192e1604f88adb489c73 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 19 Mar 2018 20:43:55 +0100 Subject: [PATCH 095/223] Fail javascript tests on logging messages --- tests/unit/javascript/conftest.py | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/tests/unit/javascript/conftest.py b/tests/unit/javascript/conftest.py index f55ca75b5..a71baef11 100644 --- a/tests/unit/javascript/conftest.py +++ b/tests/unit/javascript/conftest.py @@ -17,7 +17,7 @@ # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see . -"""pylint conftest file for javascript test.""" +"""pytest conftest file for javascript tests.""" import os import os.path @@ -41,11 +41,18 @@ class JSTester: _jinja_env: The jinja2 environment used to get templates. """ - def __init__(self, tab, qtbot): + def __init__(self, tab, qtbot, config_stub): self.tab = tab self.qtbot = qtbot loader = jinja2.FileSystemLoader(os.path.dirname(__file__)) self._jinja_env = jinja2.Environment(loader=loader, autoescape=True) + # Make sure logging via JS fails tests + config_stub.val.content.javascript.log = { + 'error': 'error', + 'info': 'error', + 'unknown': 'error', + 'warning': 'error' + } def load(self, path, **kwargs): """Load and display the given jinja test data. @@ -109,18 +116,18 @@ class JSTester: @pytest.fixture -def js_tester_webkit(webkit_tab, qtbot): +def js_tester_webkit(webkit_tab, qtbot, config_stub): """Fixture to test javascript snippets in webkit.""" - return JSTester(webkit_tab, qtbot) + return JSTester(webkit_tab, qtbot, config_stub) @pytest.fixture -def js_tester_webengine(webengine_tab, qtbot): +def js_tester_webengine(webengine_tab, qtbot, config_stub): """Fixture to test javascript snippets in webengine.""" - return JSTester(webengine_tab, qtbot) + return JSTester(webengine_tab, qtbot, config_stub) @pytest.fixture -def js_tester(web_tab, qtbot): +def js_tester(web_tab, qtbot, config_stub): """Fixture to test javascript snippets with both backends.""" - return JSTester(web_tab, qtbot) + return JSTester(web_tab, qtbot, config_stub) From ea1e52ea7c44a167e63e6f30911a12b71830b99a Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 19 Mar 2018 21:44:47 +0100 Subject: [PATCH 096/223] Load page before loading stylesheets If we don't do this, when doing: self.config_stub.val.content.user_stylesheets = css_path then _update_stylesheet gets called before the stylesheet QWebEngineScript did run (as there was no load yet), so we get: [:2] Uncaught TypeError: Cannot read property 'stylesheet' of undefined! Instead, load the page first and then update the stylesheet. This tests that live updating works properly, and also makes sure we don't run into the problem described above. --- tests/unit/javascript/stylesheet/test_stylesheet.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/tests/unit/javascript/stylesheet/test_stylesheet.py b/tests/unit/javascript/stylesheet/test_stylesheet.py index 6d3a7ce7f..6b4de4849 100644 --- a/tests/unit/javascript/stylesheet/test_stylesheet.py +++ b/tests/unit/javascript/stylesheet/test_stylesheet.py @@ -51,7 +51,6 @@ class StylesheetTester: """Initialize the stylesheet with a provided css file.""" css_path = os.path.join(os.path.dirname(__file__), css_file) self.config_stub.val.content.user_stylesheets = css_path - self.js.tab._init_stylesheet() def set_css(self, css): """Set document style to `css` via stylesheet.js.""" @@ -83,8 +82,8 @@ def stylesheet_tester(js_tester_webengine, config_stub): 'stylesheet/simple_bg_set_red.html']) def test_set_delayed(stylesheet_tester, page): """Test a delayed invocation of set_css.""" - stylesheet_tester.init_stylesheet("none.css") stylesheet_tester.js.load(page) + stylesheet_tester.init_stylesheet("none.css") stylesheet_tester.set_css("body {background-color: rgb(0, 255, 0);}") stylesheet_tester.check_set("rgb(0, 255, 0)") @@ -93,8 +92,8 @@ def test_set_delayed(stylesheet_tester, page): 'stylesheet/simple_bg_set_red.html']) def test_set_clear_bg(stylesheet_tester, page): """Test setting and clearing the stylesheet.""" - stylesheet_tester.init_stylesheet() stylesheet_tester.js.load('stylesheet/simple.html') + stylesheet_tester.init_stylesheet() stylesheet_tester.check_set(GREEN_BODY_BG) stylesheet_tester.set_css("") stylesheet_tester.check_set(DEFAULT_BODY_BG) @@ -102,16 +101,16 @@ def test_set_clear_bg(stylesheet_tester, page): def test_set_xml(stylesheet_tester): """Test stylesheet is applied without altering xml files.""" - stylesheet_tester.init_stylesheet() stylesheet_tester.js.load_file('stylesheet/simple.xml') + stylesheet_tester.init_stylesheet() stylesheet_tester.check_set(GREEN_BODY_BG) stylesheet_tester.check_eq('"html"', "document.documentElement.nodeName") def test_set_svg(stylesheet_tester): """Test stylesheet is applied for svg files.""" - stylesheet_tester.init_stylesheet() stylesheet_tester.js.load_file('../../../misc/cheatsheet.svg') + stylesheet_tester.init_stylesheet() stylesheet_tester.check_set(GREEN_BODY_BG, document_element="document.documentElement") stylesheet_tester.check_eq('"svg"', "document.documentElement.nodeName") @@ -119,14 +118,14 @@ def test_set_svg(stylesheet_tester): def test_set_error(stylesheet_tester): """Test stylesheet modifies file not found error pages.""" - stylesheet_tester.init_stylesheet() stylesheet_tester.js.load_file('non-existent.html', force=True) + stylesheet_tester.init_stylesheet() stylesheet_tester.check_set(GREEN_BODY_BG) def test_appendchild(stylesheet_tester): - stylesheet_tester.init_stylesheet() stylesheet_tester.js.load('stylesheet/simple.html') + stylesheet_tester.init_stylesheet() js_test_file_path = ('../../tests/unit/javascript/stylesheet/' 'test_appendchild.js') stylesheet_tester.js.run_file(js_test_file_path, {}) From 7eaad59be36e9f7a32c1806cf1bbdd3bbbbb8188 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 19 Mar 2018 22:32:26 +0100 Subject: [PATCH 097/223] caret: Ignore None value from setInitialCursor See #3583 --- qutebrowser/browser/webengine/webenginetab.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index c92d40ffa..6fd2c20b2 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -230,7 +230,14 @@ class WebEngineCaret(browsertab.AbstractCaret): self._tab.run_js_async( javascript.assemble('caret', 'setPlatform', sys.platform)) - self._js_call('setInitialCursor', self.selection_toggled.emit) + self._js_call('setInitialCursor', self._selection_cb) + + def _selection_cb(self, enabled): + """Emit selection_toggled based on setInitialCursor.""" + if enabled is None: + log.webview.debug("Ignoring selection status None") + return + self.selection_toggled.emit(enabled) @pyqtSlot(usertypes.KeyMode) def _on_mode_left(self, mode): From f6c00babbe31370e083a7837af72d15782d1d73d Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Mon, 19 Mar 2018 18:29:51 -0400 Subject: [PATCH 098/223] Prevent minimumTabsizeHint from being called when booting on mac Move workaround higher up to the start of tabSizeHint --- qutebrowser/mainwindow/tabwidget.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/qutebrowser/mainwindow/tabwidget.py b/qutebrowser/mainwindow/tabwidget.py index 7faf99df6..02292448f 100644 --- a/qutebrowser/mainwindow/tabwidget.py +++ b/qutebrowser/mainwindow/tabwidget.py @@ -562,6 +562,12 @@ class TabBar(QTabBar): Return: A QSize. """ + if self.count() == 0: + # This happens on startup on macOS. + # We return it directly rather than setting `size' because we don't + # want to ensure it's valid in this special case. + return QSize() + minimum_size = self.minimumTabSizeHint(index) height = minimum_size.height() if self.vertical: @@ -574,11 +580,6 @@ class TabBar(QTabBar): else: width = int(confwidth) size = QSize(max(minimum_size.width(), width), height) - elif self.count() == 0: - # This happens on startup on macOS. - # We return it directly rather than setting `size' because we don't - # want to ensure it's valid in this special case. - return QSize() else: if config.val.tabs.pinned.shrink: pinned = self._tab_pinned(index) From b56988f0a4974dcc86d7ca2043f5339d76d2fb7a Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 20 Mar 2018 06:27:48 +0100 Subject: [PATCH 099/223] Update changelog --- doc/changelog.asciidoc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index ae5d69e3c..50b3d840f 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -22,6 +22,8 @@ Added ~~~~~ - New `:scroll-to-anchor` command to scroll to an anchor in the document. +- New `url.open_base_url` option to open the base URL of a searchengine when no + search term is given. Changed ~~~~~~~ @@ -43,6 +45,9 @@ Fixed - The window icon and title now get set correctly again. - The `tabs.switching_delay` setting now has a correct maximum value limit set. - The `taskadd` script now works properly when there's multi-line output. +- QtWebEngine: Worked around issues with GreaseMonkey/stylesheets not being + loaded correctly in some situations. +- The statusbar now more closely reflects the caret mode state. v1.2.1 ------ From a374698693df698515c5046f5c29778484631cb5 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 20 Mar 2018 06:38:11 +0100 Subject: [PATCH 100/223] Fix lint --- qutebrowser/utils/debug.py | 3 ++- tests/unit/mainwindow/statusbar/test_textbase.py | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/qutebrowser/utils/debug.py b/qutebrowser/utils/debug.py index 06bdd2909..2868d8390 100644 --- a/qutebrowser/utils/debug.py +++ b/qutebrowser/utils/debug.py @@ -87,10 +87,11 @@ def log_signals(obj): return ret obj.__init__ = new_init - return obj else: connect_log_slot(obj) + return obj + def qenum_key(base, value, add_base=False, klass=None): """Convert a Qt Enum value to its key as a string. diff --git a/tests/unit/mainwindow/statusbar/test_textbase.py b/tests/unit/mainwindow/statusbar/test_textbase.py index c73a87831..9299e3a5f 100644 --- a/tests/unit/mainwindow/statusbar/test_textbase.py +++ b/tests/unit/mainwindow/statusbar/test_textbase.py @@ -63,7 +63,7 @@ def test_settext_empty(mocker, qtbot): autospec=True) label.setText('') - label.repaint.assert_called_with() + label.repaint.assert_called_with() # pylint: disable=no-member def test_resize(qtbot): @@ -92,7 +92,7 @@ def test_text_elide_none(mocker, qtbot): 'fontMetrics') label._update_elided_text(20) - assert not label.fontMetrics.called + assert not label.fontMetrics.called # pylint: disable=no-member def test_unset_text(qtbot): From 59602ec5b5e4c6a667928394df7343c6f41b9722 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 20 Mar 2018 06:49:46 +0100 Subject: [PATCH 101/223] requirements: Blacklist flake8-builtins 1.1.0 See https://github.com/gforcada/flake8-builtins/issues/19 --- misc/requirements/requirements-flake8.txt | 2 +- misc/requirements/requirements-flake8.txt-raw | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/misc/requirements/requirements-flake8.txt b/misc/requirements/requirements-flake8.txt index eaf8e3ef7..90bb33f94 100644 --- a/misc/requirements/requirements-flake8.txt +++ b/misc/requirements/requirements-flake8.txt @@ -3,7 +3,7 @@ attrs==17.4.0 flake8==3.5.0 flake8-bugbear==18.2.0 -flake8-builtins==1.1.0 +flake8-builtins==1.0.post0 # rq.filter: != 1.1.0 flake8-comprehensions==1.4.1 flake8-copyright==0.2.0 flake8-debugger==3.1.0 diff --git a/misc/requirements/requirements-flake8.txt-raw b/misc/requirements/requirements-flake8.txt-raw index 1f30b83ae..291313930 100644 --- a/misc/requirements/requirements-flake8.txt-raw +++ b/misc/requirements/requirements-flake8.txt-raw @@ -1,6 +1,6 @@ flake8 flake8-bugbear -flake8-builtins +flake8-builtins!=1.1.0 flake8-comprehensions flake8-copyright flake8-debugger @@ -15,3 +15,5 @@ flake8-tuple pep8-naming pydocstyle pyflakes + +#@ filter: flake8-builtins != 1.1.0 \ No newline at end of file From 51318d66c84d30d6d071d571b2eb30bacb1912a4 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 20 Mar 2018 06:54:04 +0100 Subject: [PATCH 102/223] Fix ignore for new flake8-per-file-ignores release --- .flake8 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.flake8 b/.flake8 index 5b0360ab7..8828a3aa8 100644 --- a/.flake8 +++ b/.flake8 @@ -44,7 +44,7 @@ ignore = min-version = 3.4.0 max-complexity = 12 per-file-ignores = - /tests/*/test_*.py : D100,D101,D401 + /tests/**/test_*.py : D100,D101,D401 /tests/unit/browser/test_history.py : N806 /tests/helpers/fixtures.py : N806 /tests/unit/browser/webkit/http/test_content_disposition.py : D400 From f230fd3abb8dff2f00c6309fc1cb2c2bfdc62a4e Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 20 Mar 2018 06:59:57 +0100 Subject: [PATCH 103/223] Rebuild requirement files --- misc/requirements/requirements-flake8.txt | 2 ++ misc/requirements/requirements-pylint-master.txt | 2 +- misc/requirements/requirements-pylint.txt | 2 +- misc/requirements/requirements-tox.txt-raw | 3 --- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/misc/requirements/requirements-flake8.txt b/misc/requirements/requirements-flake8.txt index 90bb33f94..5e8f557c1 100644 --- a/misc/requirements/requirements-flake8.txt +++ b/misc/requirements/requirements-flake8.txt @@ -17,9 +17,11 @@ flake8-string-format==0.2.3 flake8-tidy-imports==1.1.0 flake8-tuple==0.2.13 mccabe==0.6.1 +pathmatch==0.2.1 pep8-naming==0.5.0 pycodestyle==2.3.1 pydocstyle==2.1.1 pyflakes==1.6.0 six==1.11.0 snowballstemmer==1.2.1 +typing==3.6.4 diff --git a/misc/requirements/requirements-pylint-master.txt b/misc/requirements/requirements-pylint-master.txt index 6d2f9f95e..c5e1b20ed 100644 --- a/misc/requirements/requirements-pylint-master.txt +++ b/misc/requirements/requirements-pylint-master.txt @@ -9,10 +9,10 @@ isort==4.3.4 lazy-object-proxy==1.3.1 mccabe==0.6.1 -e git+https://github.com/PyCQA/pylint.git#egg=pylint +python-dateutil==2.7.0 ./scripts/dev/pylint_checkers requests==2.18.4 six==1.11.0 uritemplate==3.0.0 -uritemplate.py==3.0.2 urllib3==1.22 wrapt==1.10.11 diff --git a/misc/requirements/requirements-pylint.txt b/misc/requirements/requirements-pylint.txt index 75ae02694..1827150cb 100644 --- a/misc/requirements/requirements-pylint.txt +++ b/misc/requirements/requirements-pylint.txt @@ -9,10 +9,10 @@ isort==4.3.4 lazy-object-proxy==1.3.1 mccabe==0.6.1 pylint==1.8.3 +python-dateutil==2.7.0 ./scripts/dev/pylint_checkers requests==2.18.4 six==1.11.0 uritemplate==3.0.0 -uritemplate.py==3.0.2 urllib3==1.22 wrapt==1.10.11 diff --git a/misc/requirements/requirements-tox.txt-raw b/misc/requirements/requirements-tox.txt-raw index 61e0c0849..053148f84 100644 --- a/misc/requirements/requirements-tox.txt-raw +++ b/misc/requirements/requirements-tox.txt-raw @@ -1,4 +1 @@ tox - -# The latest tox release still depends on pluggy < 0.4... -pluggy==0.4.0 From 81827a3150978d8749f90256e3793f8d26633c38 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 20 Mar 2018 07:04:09 +0100 Subject: [PATCH 104/223] Try to stabilize stylesheet tests --- tests/unit/javascript/stylesheet/test_stylesheet.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/unit/javascript/stylesheet/test_stylesheet.py b/tests/unit/javascript/stylesheet/test_stylesheet.py index 6b4de4849..cba3942a7 100644 --- a/tests/unit/javascript/stylesheet/test_stylesheet.py +++ b/tests/unit/javascript/stylesheet/test_stylesheet.py @@ -24,6 +24,7 @@ import pytest QtWebEngineWidgets = pytest.importorskip("PyQt5.QtWebEngineWidgets") QWebEngineProfile = QtWebEngineWidgets.QWebEngineProfile +from PyQt5.QtCore import QUrl from qutebrowser.utils import javascript @@ -118,6 +119,7 @@ def test_set_svg(stylesheet_tester): def test_set_error(stylesheet_tester): """Test stylesheet modifies file not found error pages.""" + stylesheet_tester.js.tab.openurl(QUrl('about:blank')) stylesheet_tester.js.load_file('non-existent.html', force=True) stylesheet_tester.init_stylesheet() stylesheet_tester.check_set(GREEN_BODY_BG) From 0fd3674d9e080794ac9ec3274aa0a375e675a460 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 20 Mar 2018 07:07:05 +0100 Subject: [PATCH 105/223] Update changelog --- doc/changelog.asciidoc | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index 50b3d840f..15b89efcf 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -24,6 +24,7 @@ Added - New `:scroll-to-anchor` command to scroll to an anchor in the document. - New `url.open_base_url` option to open the base URL of a searchengine when no search term is given. +- New `tabs.min_width` setting to configure the minimal width for tabs. Changed ~~~~~~~ From 0e670a597e8e96428ba95565ee61985d45f368e6 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 20 Mar 2018 08:53:59 +0100 Subject: [PATCH 106/223] Update docs --- doc/help/settings.asciidoc | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc index a428d65bd..b3c9c3894 100644 --- a/doc/help/settings.asciidoc +++ b/doc/help/settings.asciidoc @@ -240,6 +240,7 @@ |<>|Padding (in pixels) for tab indicators. |<>|Width (in pixels) of the progress indicator (0 to disable). |<>|How to behave when the last tab is closed. +|<>|Minimum width (in pixels) of tabs (-1 for the default minimum size behavior). |<>|When switching tabs, what input mode is applied. |<>|Switch between tabs using the mouse wheel. |<>|Position of new tabs opened from another tab. @@ -259,6 +260,7 @@ |<>|What search to start when something else than a URL is entered. |<>|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. |<>|Search engines which can be used via the address bar. |<>|Page(s) to open at the start. |<>|URL parameters to strip with `:yank url`. @@ -2866,6 +2868,16 @@ Valid values: Default: +pass:[ignore]+ +[[tabs.min_width]] +=== tabs.min_width +Minimum width (in pixels) of tabs (-1 for the default minimum size behavior). +This setting only applies when tabs are horizontal. +This setting does not apply to pinned tabs, unless `tabs.pinned.shrink` is False. + +Type: <> + +Default: +pass:[-1]+ + [[tabs.mode_on_change]] === tabs.mode_on_change When switching tabs, what input mode is applied. @@ -3102,6 +3114,14 @@ Default: - +pass:[path]+ - +pass:[query]+ +[[url.open_base_url]] +=== url.open_base_url +Open base URL of the searchengine if a searchengine shortcut is invoked without parameters. + +Type: <> + +Default: +pass:[false]+ + [[url.searchengines]] === url.searchengines Search engines which can be used via the address bar. From 561295238d9968c44a3552111e17cf080e2face1 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 20 Mar 2018 08:58:48 +0100 Subject: [PATCH 107/223] Another try at stabilizing test_set_error --- tests/unit/javascript/stylesheet/test_stylesheet.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/unit/javascript/stylesheet/test_stylesheet.py b/tests/unit/javascript/stylesheet/test_stylesheet.py index cba3942a7..e6d47db4f 100644 --- a/tests/unit/javascript/stylesheet/test_stylesheet.py +++ b/tests/unit/javascript/stylesheet/test_stylesheet.py @@ -117,11 +117,12 @@ def test_set_svg(stylesheet_tester): stylesheet_tester.check_eq('"svg"', "document.documentElement.nodeName") -def test_set_error(stylesheet_tester): +def test_set_error(stylesheet_tester, config_stub): """Test stylesheet modifies file not found error pages.""" - stylesheet_tester.js.tab.openurl(QUrl('about:blank')) - stylesheet_tester.js.load_file('non-existent.html', force=True) + config_stub.changed.disconnect() # This test is flaky otherwise... stylesheet_tester.init_stylesheet() + stylesheet_tester.js.tab._init_stylesheet() + stylesheet_tester.js.load_file('non-existent.html', force=True) stylesheet_tester.check_set(GREEN_BODY_BG) From 85d3d4babaeb54a2d91cad90752a3d0dbcb7e8cf Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 20 Mar 2018 10:25:03 +0100 Subject: [PATCH 108/223] Mark test_set_error as flaky --- tests/unit/javascript/stylesheet/test_stylesheet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/javascript/stylesheet/test_stylesheet.py b/tests/unit/javascript/stylesheet/test_stylesheet.py index e6d47db4f..7b507b0c0 100644 --- a/tests/unit/javascript/stylesheet/test_stylesheet.py +++ b/tests/unit/javascript/stylesheet/test_stylesheet.py @@ -24,7 +24,6 @@ import pytest QtWebEngineWidgets = pytest.importorskip("PyQt5.QtWebEngineWidgets") QWebEngineProfile = QtWebEngineWidgets.QWebEngineProfile -from PyQt5.QtCore import QUrl from qutebrowser.utils import javascript @@ -117,6 +116,7 @@ def test_set_svg(stylesheet_tester): stylesheet_tester.check_eq('"svg"', "document.documentElement.nodeName") +@pytest.mark.flaky def test_set_error(stylesheet_tester, config_stub): """Test stylesheet modifies file not found error pages.""" config_stub.changed.disconnect() # This test is flaky otherwise... From a5f1022330c5d222a449f07c929f1987ed9842ec Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 20 Mar 2018 11:56:46 +0100 Subject: [PATCH 109/223] Log document.body in JS tests --- tests/unit/javascript/conftest.py | 4 ++-- tests/unit/javascript/stylesheet/test_stylesheet.py | 10 ++++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/tests/unit/javascript/conftest.py b/tests/unit/javascript/conftest.py index a71baef11..89db706e5 100644 --- a/tests/unit/javascript/conftest.py +++ b/tests/unit/javascript/conftest.py @@ -46,10 +46,10 @@ class JSTester: self.qtbot = qtbot loader = jinja2.FileSystemLoader(os.path.dirname(__file__)) self._jinja_env = jinja2.Environment(loader=loader, autoescape=True) - # Make sure logging via JS fails tests + # Make sure error logging via JS fails tests config_stub.val.content.javascript.log = { + 'info': 'info', 'error': 'error', - 'info': 'error', 'unknown': 'error', 'warning': 'error' } diff --git a/tests/unit/javascript/stylesheet/test_stylesheet.py b/tests/unit/javascript/stylesheet/test_stylesheet.py index 7b507b0c0..1553d4f14 100644 --- a/tests/unit/javascript/stylesheet/test_stylesheet.py +++ b/tests/unit/javascript/stylesheet/test_stylesheet.py @@ -60,10 +60,12 @@ class StylesheetTester: def check_set(self, value, css_style="background-color", document_element="document.body"): """Check whether the css in ELEMENT is set to VALUE.""" - self.js.run("window.getComputedStyle({}, null)" - ".getPropertyValue('{}');" - .format(document_element, - javascript.string_escape(css_style)), value) + self.js.run("console.log({document});" + "window.getComputedStyle({document}, null)" + ".getPropertyValue('{prop}');".format( + document=document_element, + prop=javascript.string_escape(css_style)), + value) def check_eq(self, one, two, true=True): """Check if one and two are equal.""" From f9d976880e3c570dd20fa7f5f41c536b2bf76451 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 20 Mar 2018 21:14:04 +0100 Subject: [PATCH 110/223] Disable shared web workers on Qt < 5.11 --- doc/changelog.asciidoc | 5 +++++ qutebrowser/config/configinit.py | 11 ++++++++++- tests/unit/config/test_configinit.py | 16 ++++++++++++++++ 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index 15b89efcf..fc58346b0 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -29,6 +29,11 @@ Added Changed ~~~~~~~ +- QtWebEngine: Support for JavaScript Shared Web Workers have been disabled on + Qt versions older than 5.11 because of security issues in in Chromium. + You can get the same effect in earlier versions via + `:set qt.args ['disable-shared-workers']`. An equivalent workaround is also + contained in Qt 5.9.5 and 5.10.1. - The file dialog for downloads now has basic tab completion based on the entered text. - `:version` now shows OS information for POSIX OS other than Linux/macOS. diff --git a/qutebrowser/config/configinit.py b/qutebrowser/config/configinit.py index c7c9362b0..5c06a4a3d 100644 --- a/qutebrowser/config/configinit.py +++ b/qutebrowser/config/configinit.py @@ -26,7 +26,8 @@ from PyQt5.QtWidgets import QMessageBox from qutebrowser.config import (config, configdata, configfiles, configtypes, configexc, configcommands) -from qutebrowser.utils import objreg, usertypes, log, standarddir, message +from qutebrowser.utils import (objreg, usertypes, log, standarddir, message, + qtutils) from qutebrowser.misc import msgbox, objects @@ -161,4 +162,12 @@ def qt_args(namespace): argv += ['--' + name, value] argv += ['--' + arg for arg in config.val.qt.args] + + if (objects.backend == usertypes.Backend.QtWebEngine and + not qtutils.version_check('5.11', compiled=False)): + # WORKAROUND equivalent to + # https://codereview.qt-project.org/#/c/217932/ + # Needed for Qt < 5.9.5 and < 5.10.1 + argv.append('--disable-shared-workers') + return argv diff --git a/tests/unit/config/test_configinit.py b/tests/unit/config/test_configinit.py index e7d217d8e..084c2c0d9 100644 --- a/tests/unit/config/test_configinit.py +++ b/tests/unit/config/test_configinit.py @@ -347,6 +347,12 @@ class TestQtArgs: mocker.patch.object(parser, 'exit', side_effect=Exception) return parser + @pytest.fixture(autouse=True) + def patch_version_check(self, monkeypatch): + """Make sure no --disable-shared-workers argument gets added.""" + monkeypatch.setattr(configinit.qtutils, 'version_check', + lambda version, compiled: True) + @pytest.mark.parametrize('args, expected', [ # No Qt arguments (['--debug'], [sys.argv[0]]), @@ -382,6 +388,16 @@ class TestQtArgs: config_stub.val.qt.args = ['bar'] assert configinit.qt_args(parsed) == [sys.argv[0], '--foo', '--bar'] + def test_shared_workers(self, config_stub, monkeypatch, parser): + monkeypatch.setattr(configinit.qtutils, 'version_check', + lambda version, compiled: False) + monkeypatch.setattr(configinit.objects, 'backend', + usertypes.Backend.QtWebEngine) + parsed = parser.parse_args() + expected = [sys.argv[0], '--disable-shared-workers'] + assert configinit.qt_args(parsed) == expected + + @pytest.mark.parametrize('arg, confval, used', [ # overridden by commandline arg From 11696f00733ab44b3414b82cb049e10a6919f2cb Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 20 Mar 2018 22:16:16 +0100 Subject: [PATCH 111/223] Fix test_configinit --- tests/unit/config/test_configinit.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/unit/config/test_configinit.py b/tests/unit/config/test_configinit.py index 084c2c0d9..2ee572860 100644 --- a/tests/unit/config/test_configinit.py +++ b/tests/unit/config/test_configinit.py @@ -393,12 +393,11 @@ class TestQtArgs: lambda version, compiled: False) monkeypatch.setattr(configinit.objects, 'backend', usertypes.Backend.QtWebEngine) - parsed = parser.parse_args() + parsed = parser.parse_args([]) expected = [sys.argv[0], '--disable-shared-workers'] assert configinit.qt_args(parsed) == expected - @pytest.mark.parametrize('arg, confval, used', [ # overridden by commandline arg ('webkit', 'webengine', usertypes.Backend.QtWebKit), From e316f1768e750daeaba22aaa0c438c28029fa9c7 Mon Sep 17 00:00:00 2001 From: bitraid Date: Tue, 20 Mar 2018 23:28:42 +0200 Subject: [PATCH 112/223] More compatible icon for Windows Fixes #3601 --- icons/qutebrowser.ico | Bin 45806 -> 82214 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/icons/qutebrowser.ico b/icons/qutebrowser.ico index f1831adad34ec8a207054142917d048ed29493f1..e8c7102c7d0b58eb27a1cae96f784c22100822b5 100644 GIT binary patch literal 82214 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
rfv>y7Y;VIr>G(D0ad(iEnZ}B4@ge4xrdd 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 literal 45806 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 Date: Tue, 6 Feb 2018 11:14:01 -0500 Subject: [PATCH 113/223] Add unit tests for urlmarks. --- tests/unit/browser/urlmarks.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/unit/browser/urlmarks.py b/tests/unit/browser/urlmarks.py index df7b3286d..8e17bd326 100644 --- a/tests/unit/browser/urlmarks.py +++ b/tests/unit/browser/urlmarks.py @@ -19,6 +19,7 @@ """Tests for the global page history.""" +from unittest import mock import pytest from PyQt5.QtCore import QUrl @@ -45,7 +46,7 @@ def test_init(bm_file, fake_save_manager): fake_save_manager.add_saveable.assert_called_once_with( 'bookmark-manager', bm.save, - bm.changed, + mock.ANY, # TODO: compare signal argument for equality filename=str(bm_file), ) From 16bda94e2ba9fbcd8bfcc5451482a60f7a03954e Mon Sep 17 00:00:00 2001 From: Ryan Roden-Corrent Date: Mon, 12 Feb 2018 19:25:24 -0500 Subject: [PATCH 114/223] Remove unused import and TODO from urlmarks test. --- tests/unit/browser/urlmarks.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/unit/browser/urlmarks.py b/tests/unit/browser/urlmarks.py index 8e17bd326..df7b3286d 100644 --- a/tests/unit/browser/urlmarks.py +++ b/tests/unit/browser/urlmarks.py @@ -19,7 +19,6 @@ """Tests for the global page history.""" -from unittest import mock import pytest from PyQt5.QtCore import QUrl @@ -46,7 +45,7 @@ def test_init(bm_file, fake_save_manager): fake_save_manager.add_saveable.assert_called_once_with( 'bookmark-manager', bm.save, - mock.ANY, # TODO: compare signal argument for equality + bm.changed, filename=str(bm_file), ) From d6463d5adeac2d2528f319954b24a3b6f48d9279 Mon Sep 17 00:00:00 2001 From: George Edward Bulmer Date: Tue, 20 Mar 2018 22:33:11 +0000 Subject: [PATCH 115/223] Remove Qt.KeypadModifier as a special key --- qutebrowser/keyinput/keyutils.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/qutebrowser/keyinput/keyutils.py b/qutebrowser/keyinput/keyutils.py index 1f34fcae0..efe56d084 100644 --- a/qutebrowser/keyinput/keyutils.py +++ b/qutebrowser/keyinput/keyutils.py @@ -58,7 +58,7 @@ def is_special(key, modifiers): _assert_plain_key(key) _assert_plain_modifier(modifiers) return not (_is_printable(key) and - modifiers in [Qt.ShiftModifier, Qt.NoModifier]) + modifiers in [Qt.ShiftModifier, Qt.NoModifier, Qt.KeypadModifier]) def is_modifier_key(key): @@ -298,6 +298,8 @@ class KeyInfo: elif self.modifiers == Qt.NoModifier: assert not is_special(self.key, self.modifiers) return key_string.lower() + elif self.modifiers == Qt.KeypadModifier: + assert not is_special(self.key, self.modifiers) else: # Use special binding syntax, but instead of key_string = key_string.lower() From a5dc8a3025c847b493dacbdc3314a19d3d7839ca Mon Sep 17 00:00:00 2001 From: George Edward Bulmer Date: Tue, 20 Mar 2018 23:13:56 +0000 Subject: [PATCH 116/223] Fix crash in string representation of key --- qutebrowser/keyinput/keyutils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qutebrowser/keyinput/keyutils.py b/qutebrowser/keyinput/keyutils.py index efe56d084..3e2ccad5e 100644 --- a/qutebrowser/keyinput/keyutils.py +++ b/qutebrowser/keyinput/keyutils.py @@ -300,6 +300,7 @@ class KeyInfo: return key_string.lower() elif self.modifiers == Qt.KeypadModifier: assert not is_special(self.key, self.modifiers) + return key_string.lower() else: # Use special binding syntax, but instead of key_string = key_string.lower() From bc885cc9ee8ae71269e05f764222a3395967ef70 Mon Sep 17 00:00:00 2001 From: George Edward Bulmer Date: Tue, 20 Mar 2018 23:19:36 +0000 Subject: [PATCH 117/223] Modify the keypad test The new behaviour is for the keypad to yield its usual key, such that instead of a special keypress, it yields <2> --- tests/unit/keyinput/test_basekeyparser.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/unit/keyinput/test_basekeyparser.py b/tests/unit/keyinput/test_basekeyparser.py index 7915e2b75..732c62162 100644 --- a/tests/unit/keyinput/test_basekeyparser.py +++ b/tests/unit/keyinput/test_basekeyparser.py @@ -215,14 +215,14 @@ class TestHandle: @pytest.mark.parametrize('modifiers, text', [ (Qt.NoModifier, '2'), - (Qt.KeypadModifier, 'num-2'), + (Qt.KeypadModifier, '2'), ]) def test_number_press_keypad(self, fake_keyevent, keyparser, config_stub, modifiers, text): - """Make sure a binding overrides the 2 binding.""" + """Make sure a binding yields the 2 binding.""" config_stub.val.bindings.commands = {'normal': { '2': 'message-info 2', - '': 'message-info num-2'}} + '': 'message-info 2'}} keyparser._read_config('normal') keyparser.handle(fake_keyevent(Qt.Key_2, modifiers)) command = 'message-info {}'.format(text) From 4d7f8e48786b907fd61e88ea9eb29183e7b283b6 Mon Sep 17 00:00:00 2001 From: George Edward Bulmer Date: Wed, 21 Mar 2018 00:28:52 +0000 Subject: [PATCH 118/223] Pylint fix --- qutebrowser/keyinput/keyutils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qutebrowser/keyinput/keyutils.py b/qutebrowser/keyinput/keyutils.py index 3e2ccad5e..f6eff4627 100644 --- a/qutebrowser/keyinput/keyutils.py +++ b/qutebrowser/keyinput/keyutils.py @@ -58,7 +58,8 @@ def is_special(key, modifiers): _assert_plain_key(key) _assert_plain_modifier(modifiers) return not (_is_printable(key) and - modifiers in [Qt.ShiftModifier, Qt.NoModifier, Qt.KeypadModifier]) + modifiers in [Qt.ShiftModifier, Qt.NoModifier, + Qt.KeypadModifier]) def is_modifier_key(key): From 2d655a7230b2a2c5594c10989b947fdde7a14cd7 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 21 Mar 2018 08:27:09 +0100 Subject: [PATCH 119/223] Update changelog --- doc/changelog.asciidoc | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index fc58346b0..ba1259d42 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -54,6 +54,7 @@ Fixed - QtWebEngine: Worked around issues with GreaseMonkey/stylesheets not being loaded correctly in some situations. - The statusbar now more closely reflects the caret mode state. +- The icon on Windows should now be displayed in a higher resolution. v1.2.1 ------ From a8bbd5fa4d6d816fc87555d146fbe736df27c841 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 21 Mar 2018 10:14:48 +0100 Subject: [PATCH 120/223] Update docs for TimestampTemplate --- doc/help/settings.asciidoc | 2 +- qutebrowser/config/configtypes.py | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc index b3c9c3894..6399b88af 100644 --- a/doc/help/settings.asciidoc +++ b/doc/help/settings.asciidoc @@ -3293,7 +3293,7 @@ See the setting's valid values for more information on allowed values. |TextAlignment|Alignment of text. |TimestampTemplate|An strftime-like template for timestamps. -See https://docs.python.org/3/library/datetime.html#strftime-strptime-behavior for reference. +See https://sqlite.org/lang_datefunc.html for reference. |UniqueCharString|A string which may not contain duplicate chars. |Url|A URL as a string. |VerticalPosition|The position of the download bar. diff --git a/qutebrowser/config/configtypes.py b/qutebrowser/config/configtypes.py index 20e240690..ecf1efe86 100644 --- a/qutebrowser/config/configtypes.py +++ b/qutebrowser/config/configtypes.py @@ -1625,9 +1625,7 @@ class TimestampTemplate(BaseType): """An strftime-like template for timestamps. - See - https://docs.python.org/3/library/datetime.html#strftime-strptime-behavior - for reference. + See https://sqlite.org/lang_datefunc.html for reference. """ def to_py(self, value): From 300d873b188b4994a865885253a68e7bbc34dec1 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 21 Mar 2018 10:54:21 +0100 Subject: [PATCH 121/223] Update Debian install instructions Supersedes #3707 Fixes #3737 [ci skip] --- doc/install.asciidoc | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/doc/install.asciidoc b/doc/install.asciidoc index c48fc71d6..572839208 100644 --- a/doc/install.asciidoc +++ b/doc/install.asciidoc @@ -47,17 +47,26 @@ Debian Stretch / Ubuntu 17.04 and 17.10 Those versions come with QtWebEngine in the repositories. This makes it possible to install qutebrowser via the Debian package. -Download the https://packages.debian.org/sid/all/qutebrowser/download[qutebrowser] and -https://packages.debian.org/sid/all/python3-pypeg2/download[PyPEG2] -package from the Debian repositories. +You'll need to download three packages: -Install the packages: +- https://packages.debian.org/sid/all/python3-pypeg2/download[PyPEG2] (a library + used by qutebrowser which is not in the earlier repositories) +- https://packages.debian.org/sid/all/qutebrowser/download[qutebrowser] itself +- Either https://packages.debian.org/sid/all/qutebrowser-qtwebengine/download[qutebrowser-qtwebengine] + or https://packages.debian.org/sid/all/qutebrowser-qtwebkit/download[qutebrowser-qtwebkit] + (or both) depending on the backend you want to use. QtWebEngine is the + default/recommended choice. + +After downloading, install the packages: ---- # apt install ./python3-pypeg2_*_all.deb -# apt install ./qutebrowser_*_all.deb +# apt install ./qutebrowser*.deb ---- +For an update after the initial install, you only need to download/install the +qutebrowser package. + Debian Testing / Ubuntu 18.04 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ From 1cf3d66a2228ce2ad50202dd7f20a0c56255bad1 Mon Sep 17 00:00:00 2001 From: George Edward Bulmer Date: Wed, 21 Mar 2018 15:34:32 +0000 Subject: [PATCH 122/223] Revert test and modify returned key --- qutebrowser/keyinput/keyutils.py | 2 +- tests/unit/keyinput/test_basekeyparser.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/qutebrowser/keyinput/keyutils.py b/qutebrowser/keyinput/keyutils.py index f6eff4627..fe5f64684 100644 --- a/qutebrowser/keyinput/keyutils.py +++ b/qutebrowser/keyinput/keyutils.py @@ -301,7 +301,7 @@ class KeyInfo: return key_string.lower() elif self.modifiers == Qt.KeypadModifier: assert not is_special(self.key, self.modifiers) - return key_string.lower() + return "".format(key_string) else: # Use special binding syntax, but instead of key_string = key_string.lower() diff --git a/tests/unit/keyinput/test_basekeyparser.py b/tests/unit/keyinput/test_basekeyparser.py index 732c62162..7915e2b75 100644 --- a/tests/unit/keyinput/test_basekeyparser.py +++ b/tests/unit/keyinput/test_basekeyparser.py @@ -215,14 +215,14 @@ class TestHandle: @pytest.mark.parametrize('modifiers, text', [ (Qt.NoModifier, '2'), - (Qt.KeypadModifier, '2'), + (Qt.KeypadModifier, 'num-2'), ]) def test_number_press_keypad(self, fake_keyevent, keyparser, config_stub, modifiers, text): - """Make sure a binding yields the 2 binding.""" + """Make sure a binding overrides the 2 binding.""" config_stub.val.bindings.commands = {'normal': { '2': 'message-info 2', - '': 'message-info 2'}} + '': 'message-info num-2'}} keyparser._read_config('normal') keyparser.handle(fake_keyevent(Qt.Key_2, modifiers)) command = 'message-info {}'.format(text) From 991ba5449909dd0f69e36cb422a457ec570b23bc Mon Sep 17 00:00:00 2001 From: George Edward Bulmer Date: Wed, 21 Mar 2018 15:41:08 +0000 Subject: [PATCH 123/223] Change the formatting of the numpad keys This makes it consistent with as before --- qutebrowser/keyinput/keyutils.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/qutebrowser/keyinput/keyutils.py b/qutebrowser/keyinput/keyutils.py index fe5f64684..f0cf9c22a 100644 --- a/qutebrowser/keyinput/keyutils.py +++ b/qutebrowser/keyinput/keyutils.py @@ -299,15 +299,13 @@ class KeyInfo: elif self.modifiers == Qt.NoModifier: assert not is_special(self.key, self.modifiers) return key_string.lower() - elif self.modifiers == Qt.KeypadModifier: - assert not is_special(self.key, self.modifiers) - return "".format(key_string) else: # Use special binding syntax, but instead of key_string = key_string.lower() # "special" binding - assert is_special(self.key, self.modifiers) + assert (is_special(self.key, self.modifiers) or + self.modifiers == Qt.KeypadModifier) modifier_string = _modifiers_to_string(modifiers) return '<{}{}>'.format(modifier_string, key_string) From 0645865d229293c89fc07529554472a26be0bde1 Mon Sep 17 00:00:00 2001 From: George Edward Bulmer Date: Wed, 21 Mar 2018 16:38:58 +0000 Subject: [PATCH 124/223] Add test case for is_special in keyutils --- tests/unit/keyinput/test_keyutils.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/unit/keyinput/test_keyutils.py b/tests/unit/keyinput/test_keyutils.py index 37519ca85..dc8fe0a53 100644 --- a/tests/unit/keyinput/test_keyutils.py +++ b/tests/unit/keyinput/test_keyutils.py @@ -518,6 +518,8 @@ def test_is_printable(key, printable): (Qt.Key_Escape, Qt.ControlModifier, True), (Qt.Key_X, Qt.ControlModifier, True), (Qt.Key_X, Qt.NoModifier, False), + (Qt.Key_2, Qt.KeypadModifier, False), + (Qt.Key_2, Qt.NoModifier, False), ]) def test_is_special(key, modifiers, special): assert keyutils.is_special(key, modifiers) == special From 51f9464eb2f2cdef35ed46cee4826c2ebe37278e Mon Sep 17 00:00:00 2001 From: George Edward Bulmer Date: Wed, 21 Mar 2018 17:06:13 +0000 Subject: [PATCH 125/223] Add test for hints with numberpad numbers --- tests/unit/keyinput/test_modeparsers.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/unit/keyinput/test_modeparsers.py b/tests/unit/keyinput/test_modeparsers.py index cd1f110bc..72f32af32 100644 --- a/tests/unit/keyinput/test_modeparsers.py +++ b/tests/unit/keyinput/test_modeparsers.py @@ -96,3 +96,11 @@ class TestHintKeyParser: assert match == QKeySequence.ExactMatch keyparser.execute.assert_called_with('follow-hint -s as', None) + + def test_numberkey_hint_match(self, keyparser, fake_keyevent): + keyparser.update_bindings(['21', '22']) + + match = keyparser.handle(fake_keyevent(Qt.Key_2, Qt.KeypadModifier)) + assert match == QKeySequence.PartialMatch + match = keyparser.handle(fake_keyevent(Qt.Key_2, Qt.KeypadModifier)) + assert match == QKeySequence.ExactMatch From 4fb940241cf4e2ad0c8b2db912e005ac7b98ee46 Mon Sep 17 00:00:00 2001 From: bitraid Date: Wed, 21 Mar 2018 23:13:26 +0200 Subject: [PATCH 126/223] Add 32bit color for Windows icon --- icons/qutebrowser.ico | Bin 82214 -> 115490 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/icons/qutebrowser.ico b/icons/qutebrowser.ico index e8c7102c7d0b58eb27a1cae96f784c22100822b5..42419d94414f5aa953effaaed31536be091c89dd 100644 GIT binary patch literal 115490 zcmeEv2UrwI^Y_AuB6>zlXTUS(fZ;qm#c<{XC}Kd^#Q;bakeqW6B!eIl1wjPNAV^XX z$s!^GN)U6vi~`$V&&HYAnGML@`F_uTo(HNZ%lOa`@K;K0gry+0JxOmD?M%mX7IcE#C+ERiMD%?o|c=#cpVH4=&faG;Az=1D4Pe<=fb~J@b5d! ze@9nqxe3fy1KbL1fckI2!z%+oQP^0J@BIa)I|GV>q`~dbOsJy*^O)6Da_Shp_+&7> zG}^lJY1w+nn+9(Bx4^VtK%UnJ$lD5vBiGRHM8ttR;TK>z&*&wQM`2#qKzaOhsOJv< z8bNWyNocbyq+dcCVqqCIU9BbF(B9+p+hIviwiogW=tU=-x}KD|LcJ5vH|^ktZxQrG zCcVJ-6ex+&rni<~0yq3p>9tqy(Mw{Cps!3iicWTR-M<)2FOPcz?w>OSr3uTxy$cKU z|C9nS9{@6K!OOCXAkK0Jh(Ejtygs83`K16{vjYZl{XplFQee2WKh(1WCh~(o*OL-( zWZ3`!%LnuamqAy3Hh@?GEDO*p69N5BIOuwu2jz~StK|-$7X}0R{dDlWBpuK*Y(Pg# z2{2nd95`&72+lgIgPLG9K))RdnhFxZ=eh#We$gEGA6N>Sqcnl}s=>f?)lg`Q1EAka z0bNZu0l4W0z;zd3y+#_ismuaJr(HpXuL_`-#euGd9MIKV2&^}Z0Z)rk0lhj6d}+7= zyj16dYeB}qcC!osxgLQ2FcZ)#PJ@C|wxF##3z)1J0s^(pekSwxNNZ+&~F8Rva{acQEn_q_c#cS)0P0c^&sZis z_6Vp8*8s2$f>S12!Fg{j;G?z(Bsr*pFx|g_r_y*3W3C9243>hN6Xqb*RRg3L{skUp zo&;acn}Jm0WuP`A0vuDG2rAAU2U)i3L9EsS5Mi_dWLmEUAjb`SeRK=Fs<;e-_ssy2 z2V}uXqfHF{6@LTpC}Kaz@HYfuYO_GB#dc7dL)2igY>#bL$061%$_$xixfCZplif%!2gF{ z&=mxoDfd0bbpS&3d>#A;ISQe((|5zf*TVSPB6!Az7J%@c;ZSFc%Ukp_-Er z_PCsi$nAjitgsaF@#3D*F*R#pk017>gme*tWHFPth!cGNQ zV6rEVbhqw)I33oco~wVBBehIWCqme=u(2No(ya`zHuPls=QgA$d?6(9ZUOkQL_C%R za?S2BN>|vi@W-%-o|*?ejrLFA*YF`Ih?pfhh95{lWWqm!Uqkrizz@I?F)0yx8trJ0 zwR&_$M~Mp~Yj^eiLw~{$Cra-=xMZ5JqH<^lO=hXM3U~+s*tIO3S{wmYWsr)kHpOQ zlu95H5=wW}2ukCyEb)fleQ^%Y>Rw#PRWQer?jRE?4W|SG!h+}M_Yk%GYWD!+QVG8V zVA&-U7}1poP;*({IHNT*4pFJpPltHeqBBGr01=Z!$0kGyE>;5S2=C!1f}d(o>MA;5 zT#5vO@&vGifFr63-x5z5;%tUd27an2oxrB)kh^=8qhkPaIXXJJ=if!V=PT?%0NO-R z;R(=3XU$885#8@(;TPNqT-{TjKCXCD?twHI{H5(tURdkU@TGXaEpNH}I068PCgxWV z&4PI4&%saCdCa#H9K>TLjvLt5X8Svz;A;w6C>lb(+URWYw0>F}Z`(&WT>Mn(@pxh# z23JV@Ot0wp)kR-8j5mTX&KHs>Lc-jcOtV1JWj`7}mHH;1)B_^nXLup0_G7US-9Z$L z{B%k2AK|y>!{Vpvkj@06<42re_dGg<)G(HSA1L8vWTlw#Ecq~1jKfc*<^*8moJjZZ zLl@}OL|Bs<$KVT(k&h-x3HpJ&Q$7b0Lh#_HQg4)CG+2>t<42l7ctZ?{fe4!qEMY|? zxtkdiK|%8f@Kbe~31)&H!Vi~N?^?*mLt#;p>XE=t<5}*bp)+VA{BR~nwZUutAb!{g z&Nh%vi=@DW6Oh0sT=3v<5#y&)3-O)c$MI9Co(^Q)KscNvS|sp`1ay5q^^Xi&kDD;A z>IwW*YAl%tNT3PBCV@{g#n?|0A*+Ar0gv10PS7*>p$D>E2n`knDX|V=c96+$+UIzK z4}L23i91J6qo?pwsY%g9t0H1-LU768^PY8Z!EhU@QT!Tjy&$wa za(o7yIPXCx2QilT5Ajoz5;^v=2p)j^8TjO!crd|99~L(|_Fv+s>O^xaW)b}H;-&c1 zoOmu9p1|-w#ZRR=;5J2ggnZ*1xOl>h=%ka_75_1QY8`GA)p#G~fMg&q8$Oa5#8 z)aTuH02eQg5_0NiG>HEkKlM3wHH@ueJ~13Q7rG(*4EU)HDD#Cp4#cFuL_~YU;itf_a|we0Rm-F#CILEf zOz@fCPgnm`&rmc3QqWuhS{+M^F!v#T=)w9geDmhvacy{T)Q!vUA{qr3CkcgC#?m4U zem#r-5aLgpLjf6Y^hEanb_^te&0%9Pqhb~$w>c5h2(KQje+GU=A?FeuPKxS+Rul?B zcSO*tSep1IHz9H+^B=))OrzN*)``{%LekMlI5ka3pf%C7rf_FoXm|W0_?eFb$7IKf z(g@noNC;uGnP^2c9VF7s{}0wb`x6No0J!y$J7S_@5gqxJXb{Ldc$anC}h z#|i6zkXI-;r1()f7_xhBw)SxSo10`h;h%&(9HAqm2R@)G=E|6Q_+vg5#Z;6jA3+R2>kc( z)9wr7fXiq=ybeH1d1xcyZh3PnA5vw6&vTxRjhFme`+OCB$Fu>`PSB(H zLj`XIlhK$&!JlYP;7vX(?Kh4X4>S>Xf*!?BtK(x3Q)EZF2S7W9hd%LHV9vY>H$GrI z;9kh9LS^FDttSv+S%T(&hRN1~M}P}xZ0H7n=HqE=Kddpr#wysSHVjAD#2@EL$i(k` zfJcC52!)>}_%!G|8k>;#uh`+O5jJj+fmsy>qA@~sOjeN{7a5V?*0S12EqMpJ}D?n#X zL>r(7K-8@;3}kfV8otRA<;s_bdjZ-e!;9h&^b~51zCrg00iNGFDN$=nsrJ%`^}kVSFm`W1lR`A23xUPA~v0mP4d-KppB+X@;02q!>{PdJg& z-CXwZB4=@Muf3hgFgU2;iyZ9t9DbUhbFdH&JO;rW0I@uFOkiIk1xhNwcY=5=#tpq5 z#4l(Cpo3WeusQ%6F(z~o?ydqUR$@UR>a{ocVGril4SNv3z)HiuCCaD%g*s9u2FZ`B zUC({@(}(qYCq0P&3cs=u8QcL{a6ha{jz30(^A15}V&&}D-n0VQH}!fDe<-#r-dJa# ziI#?zPa3mq27XMlXGi{_>Sq|lc=ywo`&FkN#Q%yHF_z=V+KxnqF+6}cfDIWPUKIY( z5^VlL#k8M&V^sj7%pSx~6Ep#`@x#F<5YD}1_@jlw(S0R5^ozO?0uOG4JO4nOX)nO0 z09O4Yfv!mu8UCK0`+2PjN4=$)70xTiaD>M%D5q2uen^twcgB?d&qTEAxO0*1dH%Dl z@c0GY2Dv2^{zv}|WL&PX9o!d`T(bGCwuQ$Zhbsdg<;9O4_E@y7DEnT$hlQUi3M)PE zB?%MvL*0_Rz^Cej$A3pavGLgYhaG?bf1`j*IAXnL@%ik1-QBA%ZlKO~I68a@?P}CF zjJ}Y%U3mP@`4&f|;`%=^{s#gwVTq65#^3C74S371853pe8NuxYukMqi79PK#`bUrd zNK+uJ|FcAf;(6$mEUHm8-Qs`G8=npQcn#`ZW$RjAIpheBKbbH+mw6RsDjEJ1t~xoT zhtG+du_O0{k32nE@*MTL`V}JQ3y)vW_~*zqlR**H|9P}+B(w)+BZtdWqv{DsG7@&| zd+tj*7bAENU4_Rl=*UhO{M95@qSyx9yL-N-M9;JuPGgH3RGYG_3wZ^<935DXfvVQ`=Okh<+n%)Kn3@t+nn19LvQ!?KwG|0T3hgtTW0 z>VYUcc=!W8W~@(wI#v=UCuN?z#$AlW6ri98jlYXDZ>B(-y;@;gO@RNsfIO(w4w^pU zi{M&;Mn(w<%#FQOrm;!j#RgA-nPC*%P3o)e!V3 z$rCnicF2qW1c6Oh7OIUF!FPg<^=X=cx`w9g<2*EYgtl$KjAhuoh+@~BxmkoV7Bs@( zXS|_E^pp*{h_`Iq?2s4#1++d9jlG9r<)wkVlUGEB9)XhXfEvq)vc&OpP)7r_OZGyo ze>IVx-#RB~&TVGnW`|ll`#*uhKjt}V_`46eB=4rUplVIv2@lQ#=nvVwx~zxFiQ6== z6yp(9LBk*QHXAoPv? zB4sHq6P~Ce8#g=T#qYqA2+PppF4eIn;A8u#)RX>Prl_3&eLlv`g^Vg<01~*~$Yy8}mRK z;sf;fcPs(k8_^K7eRn_?%a3N@ry4xsHr~VM*%LaCWyB2h`53>VpbS}&3!43?Jkvi1 zzrMEIKr|F&DG_7jd`n=kR-P zd{(Hl8oUwMI2S)vr+|CbPa{9(#23YkpT$13g)M{~gKucx6;yLnsv`zF5@F!CK)v5x zMr;I!i+h5AT|PmG!VkYzf0H|wo#1=c&nbyZ+z~wgLj-~bZkQi__shhukN%u*KDSsx z3ikvF0d6)6gCBmG7S(aA1szl3iq`88H0fGt2-^CCs4Odx3K3-f0bkpd_@e)xgv2yQiA z>=4{tF{WJSyY6%rW{-ux`NQb{IG1TlUp`k3=H!C619JX|!k?h1EP#kAl=gOIDr$2h1+lm~w*u0VYBxggja zISGEaD?+`BFY5UiG{+$BVYH}$n>ZW`wJg}`hU0} z*c>^3{O}{?s1u%I5f6#+*o24^js8shOpA|sGd4kj#(#SbK0$;a{8a4-ZWg#3UP9mz zRRwj+lG_k4&wrUAZ9vfa$Bt{W07@W!>H%~PqMnb5h~IeGh@VCi^^jO34`gsF9YrDa&piRxhr39DN64u48!>J+S7iJ&TTu^*h4+9S@75Q( zK^Kdk1{;98pgd7e>#&g$hoa+$cS_KAeFa$}ikwgNbL*d`Z=!KmP_EI$XnDEa!B3-! zdPpqDT&Dau{z)}vA7E5J3O~)vP~iC&>+UBnUOY!={Qj8Z9gz=-h4BF4;Ln`?QT-nx zD6;erke|>CAffRe(7TKoR9!?pBo<&XmHO};YRp5C%=$++02)ouo)q&D1$>>OG?lBy;$~HUJu*$8ed&pl*Mo;quU;;D=ix)QuvT)g{trVFh3% z67wE=Go8Syer)|8Aj$#Fi{?Q}%n%Jf=3D?~c!Krl1G?mH(uG7DKg0us_#*_B@{SlT z4_8F|*t?}LH&G9X$?QH(aO}mW{#(iNVe{>{4=nRwMl(dlk3AQFSs{-;{P1N!wC`i! zXLo<>03h%cGHN3lB{|&<{8WQ%%mE+i1L4AZMLU35*98CB%^!OL z3VloRK)BHOwNnXzvslFV1x|p}5bntZdF|6^-NWCroj`}Y_PN6!^aC2lXMxT|9pa-C z;^^+;r|O`d0fc#qe5ZtKzlS4v?Q58G>mP$(@VkG)odI?mKTjtR`A!MT1C@vPz~{_< zh#SyVcY*6a5=sZNU`MA#(?5WpN<}{d2s16_J_}1?69M}m@ur9E6IAtM93b!`3RLXD z4H`3={vrJEcRaZ##7M4J+(bMh&EePfp%1vTAHD$ydWQraUkh^x@?-e1C&Y*cLLUfy zr-Mq>%iv%6n9&c{0R*l9snlx3AOY8p;>SKWgA{hczgCF5+^{F;H4`59uvwZ@ZkYjV0*n7*^$N?l%@NkkJur^T*()*<-RJjJR3SCpA2a&n2Y?5Jkg?2YGT0n`kKo7NCgJ3L(b+${)*d$^ zCS7;9245#N!rafofJQ}d%^%YM1b;^i{@}d8=fHXhKUJrleYckbpGBMqZSjwexSX9G z5pn)f7H7WT`*IG;1zP;@3-$i9z#h<3_~FB2WDdYPgc;XFKhK4#ax}>UAF~9?#{4@(6k`6$O zpJL>l%NjY&=|b)`Jjri|{`dH4!Nj8=34HXUfLh;ieVr_-^PcQ9>?goaBVG!T;YT~b zmG^ViBrb3xS)Ke0_z8cxj1)c^zs7A6xV-F>*qe7h$Na~%;uO&UK#m{nfOF-%NXa<0 zM4N%1q5g{q@biI>#xL+mAha`L?{jt^?CS6K`8PUW7A@3yBd1^#LG+8={P1HO@QlC` zQdX=3ei--}>K}Qc2Y2y>kHKFi^bpAKiov@}n5p2Wzz?73!Cn0EV;mspSx}zFVE?Fv zdvo-sz)w4Z+Xe`LPeHzDz_~=~0Zear)^=Qa%{UzUIq>7ZP9q3ErUAGLe4&`92{60D z$?&*1!M7|}&wmPh;zHE@KWYGkpL@mh0DB za9Te?1mMmXMZzaY`QO10cfv;*Uk8A2ut-T_7zex%1mXwe{}z6D8p3?XkpwV~jlnPY zrSk4O;NQbfquoPL;ee7wF%CE<;%BTxaL2!iUlX1Mkm1GiF+-rnKj(md6F=>Ut}ufi z(-oKo@XtBmU&T-J!B;b%IL-k=yjm_ov*)P(yZHIQi}0lzvwKQ16cS4ef|DN@ke$KKF_BC5Py*Wn5}5g8QLuTG@76{e!6}Bi8KTY z^n8S|!$1t_djo|T z4+M4>KR;N)%Xl#37>90u>fUVv68tot4+Q?V_<8oi5WkOASRVJIks9Z!GTw+jBaq?e z=>*-xFMJgX&}3G)k2m+ZQY*Ny)HiZs-&bHL9EyMZP^1x&96#>e&+g$D07?`w1K$zW zhu)Ehv1!;T_{neN`{$VJTuH#wXAgz!4*c+AUVG~neo^oV)I~t1!74{TQmB<_YGj1D zyU2Eh&7rE)+Yaxp+Y^7E%hNu*_Q6MgXUw~<*gY!}j2~j4!9o*)-Ys$ zGfP9mJkkUuVEVz3`1cUVuoCiFKL#W+ej)IPtm36%0(*QEr;^)(gIVKYU zXa%Caj9(PS`n6X=oaD@uI=EO66~D;XgiC<@TDb+M(oG>`*5WWT%F5_e8t+ixxEmKW$n0zEHXGO&?9GY%uX+X@JC#sPCZOUm4WQ5^SrV9|~pnmg}4_+S8#gC{T z$>WjoGP(gw*d)5%G+H1FDq{&?NroH6H@^peY4sQ~bLg5R1a+?w@S-H;2#cj#dRlN$ zvtr&G@IjF18yN*Ne*Fc7mzUJCguS1o=Ii8*{OJ2Xc6AGu@QL8Bz0iWA5D%EMyS5cC zU;++$sc2%_y}?ijrwNawyP8Pt9f1Jvv8}mBfL0JoEB7C11l`3XQaYc&FmG6!#_-qM zj~w;MBfbup&3_#IrK!2ULs=nF?BcylD%BS*DGAmn(X0|;{;99woKHD zk;4~`_%S@9r=|%v?Bl!&g5mON1B&*gD1P3-Df^i@&!4DYd2WGKn`xOpFQq^7|XM=}~i5s#6+IXV`-nek?0tJz!$StnvhGJ7@j^k*HJukD1)VsfF}_mPuY<;8A#NsFj%; z7?|sHG&c-s6Z*NZop3q?t6%UBdf5IS&rV1WU^)a6F^@t#;BBs9BEs7D$IbmGa^S%x z9K$yZo+QCzMPv5xz`y2c-FTSupFm5LXEz&xiPaJ0=WB&J5g;n|XX16!1xQm7V)6eo zAtke=JBva_Pw^Gd0musc`S=VtqW{A!4Kd@u&j*sfMcrxzWFr9oqJCDX=tpGm{u1x? zv%(X~F439+@AgNiLVj{3VSS*5t^S`Jm5AmE(HXdhgCgerKcvoQQeaGf|3grEz*N{M zdLWk{$pT=nPI+t`7eh+IvCvU_ME(n!gQ43HF@++7VWJ{TF=+|yY&l5=_G~$^1)Xd$ zg<^*)ltZ$tL$n5Rm?*-Yn_djXW|OGw2@61FPgp=MH+w=KqQwL*!W2)T$g|C2hnV6C z48RmmV1PZge7pzfSTT+ zCpr!rfFQX&S|3lt2JoYDe3sZa|K@|NgRuJe82{!48yM2O#PO-PafX~5<)Q5OHZRx! ze^k!5`N0PIqjJ8@6E@)gTHeUxatx!-e=X0&n8OqKhvG@}J#5|7Rb*MPuW?#9M@o{~|!%=bx4fm_PdnkpJWJ?~kq@1f>_S z{*e(N7r1`nyFGyGF9C9a>pKDKOFIE_f$LNL>u0#WwWsj8gjj#)@=1=I)e4d~3P{ga zF2w$YZwG(}v4Z4$8=wpTg5-P~pgaJAXd?g*t!WQ-O?gc0qDJ6)1!Fhff8`3y|9(ay}Kvkn^bkBsrf7Vz{4A4`#_pD}dpC zJ|_SS_w(r?tlJo*4ra+o9W29uNA6&GrkvD)c1$^`16g9SA&%u9QU|i!Lpp@9+(YU> zj-13nEccKOAu??HNm`&hTTbF2JGPv}K^!r$gE;U>9KeA`(n2`!NC3)k%O?aV&y^Db zwByPN0iwi2lMo6XVGW|-5duWPBLpaqE}sB^9a_#?1DwXmRO9Kx81o4LV9X;hPzF;x zZy&V7%JCk?na9&aaOUwCh%=AJKzUs8_)!!u#&u$z;&D~TQ#@{%x8uzpT}3?T5p~o& zMvy(G0HW+s2nei43g8SQGDwWzVo#(5A_j|S=A9ztUoPPg29kf)8hVjoZKYJLy2Q8(O zx}KD2c0MV)L%$QA*7daPASm#C1zIbobhegl1r_nDx{4zM=+$ZM^rDDZP!yVhln)BS zN?Ln#?1i8=>=NKCxeRUDWE!O z9KA5Kw5v46qw7xi8Bm@uo?aAo2eecVq2CIq>MB0v393)cqgS1M0n>h@7Y0{=8@{hW zS@fTv_+&J+*SPCm@=@#TJI%so!*Q4A=pfYh5)PF;Nod1R1kljYFPA>y@PBuWh ziuM1L0#F7(rY$g8F$5Hyb_I7lcL0;+1DX6RM_?e=4;cORD*)GE&1E|ShYh13Z4bUT z-vRB7w}JbvSs=sT5Y%Nyf#)S@z+~l6K+muRU5z(@G0X>K!gM$NK%C_ca68T!n6DWD zx|#|By)*`RD9V7=+bQ64T>2C`3FvCM1LzeAfPO0wwB64J zT^ZIOFWeH)Yc7G#CnW&Z4FCl`Fuz;ig6Rr4$an(i7tmFg3H%N$1zk@{f&ZSVz)N*L zXo=PYh3<-=*n1}^J9`X(>n^}~`&3}GY#<0Y_!s#2Fb^0m?GI91sNh@EZJ0+EsJajc zK)x4f%ufI>6ZJqiZ4Piym;^F|jKPOgQ}F#Vw594Cs66Kjs)JO)nT^Kk{(0bC^)(P0wMfv_kOQ6;rGiSo-JsZ46?C+efIN3)@F>Lr zTs(FdTn{q`kMiQcy^BHMs`WZheq053DvbvoyXS((kq1C)j5c_CFB8;X@&^SjTR~%z z1*ki97?`gb0v^Zcg6sAhLEQ-&NHUNEF`5ei{dNercOd|DUa^ApZx7?4AjN1Iu-Y&N zSZ^Eyu2}pH%vKHt&l3$nfa+{;?#MC_WwIF*g&V`;JIry2+~@C zpI_L6#C|H95)@KZSeKV7yaZ{pm0_5H=<`ougHl&fF`(%3O`)nxJ_Cb2%FOF<)_uz= zM2htkWFVQtYp=4hp8G|PK9VXXHWa51)gzedh`zG2#$$H=NK9Gzh!G|MR6iDp)>qzx z)mK(FJixUGs;i!6sWbEE)mOe$#Yq6w-DrgR>i!R{nHDK4YqIsx)-dow4VFE7RqRwB z9Ac&j)@CJu`Zk>GvG(kZfWq(+W&%T|KIEvX8t6nNRX&P?M)@#=to(N|ga?@==;bgH zAgro;8Vt1p_cJFdY$b?nCIpJc@7cM2K#H{kS@!#YEc z1|dwgJ*OBN8Y=q{eQicWx%Juc<9kVE$pQK2xPYMFr7!WatM!Rk@0(5*+4{=Lhw~5KJi9BP2m(*Us*ZmXy}bFRtio~4_g=X z55<(hIf>R+-fgCunZ?m$5_L>{Rc7PS#Om+%wsJOlk4_P-!_q&&;QHe9mDNv}nVnz) zMHJF=p}IcOKyNYh)ry%3b}?eRk@oXpw{vn)5F@T}!2zm6^P#?3FoR_t&PagLS623h ztVXSXgbFWa2SoHKx7!)|$`*{;!RWKw1MIP$tn6V8d}di`o(Z!f(N`_!=u=caG7Es^ zYnC#NW7yAapNOS)4C@htR2-P?gPjV71p3Ep5naZm^5!K<7aR-@&mMN@2KTyTs%2oI4daP1*Lycq$bpBX< z_^ZzPo>V)kN?gJB=^$BcYvzRF&O$;tO*)b<~VpG`qFw|!i^41o~ zA0^;RQsKx)c)@c*xR6G6SN3ps2}5{fV26b56iFE*Y@|^7AYm@@@GK1N;eHB+jFSaB zZrF+BE>EE_4&7uZ6vm!!AUAA8CG77$L;Tz>C7q_6x^PFhhplU`7l-!UPPzvi;2RJF-t55C2xc zKSBFcWX}iplAhpkjwIY)!To9J3GlMIKcriby&rgaXC&NX&H*jA`oewMcc_00*#|;- zGbjwM26q!zKso~MZ9g*hm9KC83aZYIfO@;Yi{c4T_6^ya!Y~wu${@$B6Ev2L0*@}q z&~rV$c0J6x5BH0{pg8gi{m#ieP?anPO5;>Owo4biBTPPiw{3NZndsmI}d z)dSQ=Xn-u+4RB9+1nTz(^m{3w{c#~cPzJv*`|F7{Ri?h508$F4chzEtog z*$D2zF9L7_Vu1TQWN%)6QXkOoUtsJrk$rDhOEF{r2-`lW3#UOJx`5J9ZN}aZwpZ}& zk_C8HoCdyDUj`NDj)Q1TS@1kw7w(y}Vf~B&nKtV{)X{Cgd*1@!q%;-8X)glL^CJPh z@(h4&74FNAG4|+}om3d-5?zlA;NDvmcq&hYd(U8amaq=)Kdl*O58gW_F!t#mYjeTP zXnXLo>>`M@Py{CqE(WeUXM&6XBM@h_8t(7sf@5&MT^+O!IBgycZn-Id4!DQ?oPHE` z*%T12DF;r%{ru^}i$VDQ1t8u^8NlHHeeL2--e@+1R$^Rsii(OnBFHdfij|a}JSEUj zRJ>-xR#2*g3fMdpZ{%dTCB`5chIfzDt2$<56co+%m6UcW?7d{Iq@=8XTq;-6(m7Tr zD%!z>J0ZJ_Er+x=m8GDlm}kq9E2HEJ8nH|T#eA4MGd)WlMLVYgbGXQmD=KCpVx{s{ z1qGHoL{Tv^9xCiW6ciOp3|)>Ky4hm$F&N6*Ab&f=M}wgPL+n_IkP5*rpFX=O9p3>- zM6Or>2@jZD-3gH**N68?UI9UnV93=V(LtfOzeYr?>+2UqMfH75`7Ox%4%g9$=zv3p z!F^vy1giNdx~iHY`4_?{oJHQiVBh(a+d36B&X82__e6w>iaN(&4s zDBofKT+b@*q~_u4cNk(yBjlf;a^(sN_i~Z^6t6PnH<7B;2-~xtl^zBR|0*8zvP9~S zl8PuWFC>3RG3AP<8Da{>U@uBu#g((f6v~;M7M%3%TH37i95IEm+bALV&{@sHrHEb8 z`;CzRC8khPbYTnAEjn(FNEG$(VhUv!(gb%s3OH(nC}0yabAlQT56x;H5KCF5?Pfye zmd#imG{T!fa9M^d$>8!q9<~}8xSSZ+2$vSfUoKq1Ab<97>45uVtc!3wF+`XB*9g-i zc_Dcsc{7)Na0!?T%Y;ipWL+`{{*8x!g4QK)J~UNPAlr!!=fh5@Uq!#4j+|o$z;((o7~Z27hIGPn^lV7`!MU>wbe#9ELdv={p}UEYEYfF ztUfC$x+>~OM_H`xi`JqPK!;IU59H9ClIPOlX32PH$4D6&OZ&# z==}-qrul=afW070dpToW5~jHno-MmE&aRK`oCL~zR6wHsU*L@WUhtyyJZL;;4{rJ& z1f|K|;MBqSjPr1R)mc!M4r;EP0Cxk`7;BB|Ui;wr@Fpl93EbfN=5^&2;0Mpdk@NMO z6XxLkWe1REqrh1EcyFHo-c(+Nxg|y)2Mwx)QDthJyMt)*wiIF8EZJ4@_4K z1z~z?K)|78AV_sO_;TJH)Sq(#Ch`M87@YfH6XLyAFIcc(-Bvb#+m`if7i^Z>Mcv8Z zsP0{$IDg6F`S4nXFmx6yP@cbd@qEoc4d%nBy{X(mwIwS1=P#5sJOUX`h~hpCE%}AA zvX(GD-@(>I_t0O<&MN1Wo1{cSpJv3v6ZVE5{G&V(rVMXCcvOryE6G*@m}Nui|s_Uoicz<6K7^}CHJlmP|EpID(m z;sFX}y~~EbSMD%Zdvmgnwe*@1xsP8@jvKK)_0xp)8zhx>WKMXHJ|bzvw?KxiUmQb;yh3o->{+ z(WV#$4R4Fpysu^YV$hc8)q(r$yu+4#nHQfHFzCVWLugJ#QsJgvEB8K^T5)i=ZuHoh zXPcL;9Xd+u;_)x7ha@X+tELWH92PxsySU!Oliz2~ew}`D*?mgJ;a?BL2P*Gw43=GxeYO5?@r!>oy^7Hs;&N4?1{_({Bx$v0%8>7SwOpD~ zFY9>}eev{JS~1Li^sb1l*FyH(NZ6R}3d zer>YftmWro2W5&YxbNPpbt2w>YvRS@Gw*C3U$E!Q?C+PQqj!%V5xBBf)GNu~A1isa z=^d6WdfFl<8#9wK>f4c}dIk&c)@<5*V14@FRo~KT{Kl;PeNkn~j4cPOT~b~xzLMW05<=sVuF2}V zSB4sI5HD)nFE=1+CZ&(Lb>GiAW2NSfOm6HOFf2bsEwkRO59Oj~)S$c0tJV&7m?)uX z5MZ0~x7HoC8tGn1?vh>;EEj_;>@Ab*AIz=URPLmExXS(i|*sj#_YtD;0C)dhhE0%3mTe-Kr zBR`^fYFd%M#PBe%GLSBTLNJUg^PPicP|_ zytZn}s(F+R3Bye!6#8x7S@HMfJ2pEHJN~X~kXlqOIq^e&myw3jytWN{cS|T7n51xP zwRW#B3;b3}i-+5nY8T!wm|>mvV*I??K6ZV=AM}mZZLSG@F!E^D?C|x;c8fwTM-94Z z8`9^9_mEKQC0j#{Xa2r(`RRhnC#5OF4LUcqh)Y^4X~fmIODBXb&Z}OsXr=RUxy2Lv zeC+%B&`h&;2c+f?=skS@@w-nqi`PzmcD<`CU3$+t^EmOuLvyQi4dN$_9G;fqa7E_+ zgJb6?BU3v!{!&nI)7R@*euM5!m3Q{V11fsu#O&)TG(337{$L-8qKHDR?4z$~zH$Wt z-dm`TLtMEiN;C}Z6s`|iHAJT3i<(Sd<7T7e7qNVGkgVHv;MdG3s) z@xcQv7H*(PXD%Ie((Mqiy!_!=_4yAqZmNA(CasK;nr1!gHDNAptC z$BsR0nKN@{pud?)RYFRy^IloHAFdpJTK{C7VRB>1lYy<*DRX~6LHTB_bJ?`|%dCfy zzKf)ObCF1MR5~>~`Nj9xm36P+E`(3PL>lh!2whz?mb;s5I zp1eJNdzY8xr`rvV!y{7JMKd#bzNy=Vn zhVIho%ePM7d{ATPLgV+=b>X=Wt#`$4({7844xH}Tl^}Vb+I?V~;{$uwv^2A?x0=kG zObvdW@L{7w+MnT5`rZ3J*~-dB@s_gQYFEm)*1N@Ot811>&90^A{2q6T3XB$A>}xmh zdw-wdgCzA={XW~z@l@fsU*yF1X?~tx-!RdY`b{=9|CMy^i14!p-`bB)4IMSw{3O-7MtOQwKLm(lW40O@NnS; z@%x$~E0!+bG4rZMQ)EW|rWyBZ>fK^v3f8Z*_FdxW@mC^cP)XaXNI0~F{NCmwnZIBA zeVn7O`9ig5%J9=w#oL0b;vH9r&;K_0cE}dW(bRVbhDK#7^f`V0^o^6p+-IKcRTMZo zb;6*hnjO)xE`NVFi|l3VP?@*G@>D{x$CH!q@-O!;?ANq;{oEA-t*x4}s65{5K`(WSC$C2-hf7M0D7-I!O_8=EHS`hX z?YHm?K{+uT%{1S@WiOI(uTp!vpUhm6$6KI{8m81|*DewU3eMJ4de^!NGw zB2Uns`$;thlrB7XS8{LIC>f_=hvy}X&Fh=hHObHaKv|zHVAI~ThS4E|7k)JktMyi& z__R7!qH*(%i2C(mW+i90^_OthC|}}n<-p)2iWB8?rd)mID;YPa=+XXJ9TsyvD?Sdo zUEcebjAWfz@2?t9@!d{Q*NvT?vBqReMp!SCGm23|mNl4f-8aJgfJ?H*q9xz{I5cR< z?xd5xlm)N-Ydi8Rt)CaJm@#{N(37OFL?@N2r+hWv^%*8{`nhHOh!-}xe<(DaIOj1b zpm?0ax`sd0bz^EYL-NdmlpD8SSn%ucbDN);zIqw6&A9Hsncn3WE)5#xu4Xms;b58k zoi<;r>BCzePZXEFra6BeD%E_}JF_qp&?UZVWjIu+y5Ewkg^{U!OX z^xHTe&8PXzVFBZpPjSp?-kr8LN!s09=d1bScUX1~%c5d=2dQT@dR!Z6LKfPkz(QR$JZyYR*v5=Uj zlYKSxO_jl!<)`Fxrj8B1o_}DtRbr&cueUJXKq? zN`3x*y_>Z1dG6(3SMJ!ZrdTv?%Cyvlt6VB2gMuy>*>})lU*sFEv6ENVYTmyDp-hoU z9U77zWH)HUgq!`u9-Gt zmi>AERbQsLCV6T|PF!P{88ve2h~V7mUsQLh^*yNjp?C{5N`8B9he5-K^dG!s_4p|h z#!LuzS*g2Y!TPQ48onbXKYeYHf1@UAytSZ@v8}!7-mf409=@L-F+NZKnu^kp{;5Y4 zw`(bhS)X628lE0P8kD4k+Mt0QnaRnNa#zY3}e`~#c@wepc*H^DR3fg4f zWvBbgR_dia(TR=H5oN^=&G}V#jp=8`Oj{DvPg!}++fmXQ59~KJx>TAO7A=d)0M*-y zX?ecDJ|n4VQh$T(l!zLoq5bE6)G3YnQ(~T1N@wk}l+J4EyloRMQ05)VnKM;w%i*OR z>wLd^Ot6o*d%;5di_(tJfwhIcE4Ci(ec<32hpd3G*Jg6~&6;!NkNjaf9=5sMs7OXr^b^;6AjjjOI+bA}Fet6pwjOwvck$211*hNiX?O23PZmEO)n;-y zrfJ`tGd_o;a|?A|T$x(jcxX=2stt-_V;<#<_8I%I->AX?4z;??spbPW&a{_`?R&lb zt--F&x{ZlRv!~Pz$l4<|Z`cY+nj3?Zo=rZ7Z!NA^tt+cr(upQwfyLk zs|J0Z$W}e7Q&bY0rGDYpLuFvMT%DQ3TosCBoz2~A7Y9v^$`o(64)|Q#vL;{JOh5kE zuvSCryQ2@X^@bie*_JtD+vgZ(@fF50U%!pM^5)sZbCr_Pl(MWDQO#-hdObd9{r9jP z$`tjus?pn|^S+LJRO}}G%SltMhX!?5fAbl0W4YhBuw;zc~fF56IOp zEvrvGwll#+M}gnYI@fRcO`fIqTP%%bhcxFmuE$U@$yuv5nZ15kYN=V@GHy9vp1< zIC8Z`y4=_1qY`Q6f9UzEi!UwS{I{Qe)<}!5Uk$vP0?gDMF3k+Kyjgj2cy8j&fy=?* zwce**-m8q$T*f>U7^<*2y+idQ>&p$=FIrtckbYv951gfzsCLpTaNH ze*aD8NI!iz5$VUM90-vrH&Psa6?CSu;NN5?L?wazrK z*D%U(&!RcQr<86>_~lz#^VKzC*4d}lMxM1-fAXO{e9PMQkI!7v8ZUgEG&ySGz?k(- zH3wfcU7l~S{^{~PDspedUTve^{jJQeUiN#P@7^Xv-UTIL2HKP zo;_wVsIJPpIi@Ob_=voia{0gro9{`fEh%v~wqHFLD_Ns9DSw#W8m+-ouBS~7U;J$D zOh*kVN_oeo8BOcFoUUj#@~p@|wtK#;s??4+UR)O;J*-Y@S`1~lZo(g@W=(d=c)p~h zq;TG&VVmp%#T89Ve)}_3uQ+CU=boJhR47#gm2x69LRD0%j=8STe4P5&=69F(FP}f3 zH?93pi09)n)s;@)+;wlCqlim2JsUD{NzeoPgI8_${WaptyN7qynV6UyU;S;BPM(Zy zV~p8 z`vs>y4&2aaNOz$v%!r?-@#QxQapwbylr?L=I~6szzk7H^XJ+S>PO~{Lo78 zgiBee$vQ#1d*w~K-7?5zx7e)uRecVI$VImJ&b~;Apn4-fA>;?f4!i*w$@ ztqU2D(w;0Q_e^83*iM-%Tdb`ThMWs`X+75=uIgEJ3a(b)8b-H8#_F{D%Z$j4)C<4* zq?W4l_TE;#D4XO-S~aqRA6|5K3cY=L#k~0B-VdZY?pJ~qf6IF+!#hjE`q7F!kEts8 zuH7+QRb%gBhy5;PTW$t#x#{us^SrF0HEu=^#gF@R%_th}vz4B`bg@KW~zTs zPUn0WIV-HA_vWy+@%47E239oYYMgYt>jUYc8-D<`Hp-Set*O!d|9*dwPl;Kcj7 zZBlbG^6qS&6qV@m+~k5{f^_c8!0%5~Bz=!m>IZ}OPK5`*NVNaqu-Emoi-BggWa={0 zxvD4b_wAK;u*Sn@Km>KB{Mv6B8*Rhdrk#FlT={!MX24!OT5^ulSu<5NXH!Y zp(8D-sO@iKL#VI2{#f<5`82yV>k3T`E9G|-kIGrFV^7ouqd&9C*SHz(5~CV9X32h# z9PvhcwGH^zVl_wm)bfU<`5{%2PKsu#ztW zdUnE%fmgNu7%-i(aeC3rq2r#$>4ko>zn~F#YS(o7ynn=g+6PrHXIK zm73vfHD=jh!}Vgb9!yxSzN_!iub&DY9xA*%$L+k*8##JK^w)~9tDCw)#<`rP%~76L zRhb^{c;uU}XK`tx$|I$S8*8q?v^RPMjJBG*{rQk}(QCF#<=J++?8&W4Kc_gX?W<>y z_1{*#dVeptHdMydN3E)T~EZ zn&=h6@gcB}RkD7GbkxS^)0-x*)smZg^3lzkz0)6yk8i%vt{!d}oI8awcKQ#r&3Sq0^j__|nmXbiZvLn@e4O|Kwb8$xe0aHWqMY0vr}<#CY+30$6~`e%v&!a< zzA;B4ZNBfV^Jnb(jnI)$p|4Q4pHeT8ZvA=Z?WOw;3|-i3D?Kb#en3ii;m5&a$F@#i zEPnQ{2`kjXKI|=ZJfiid!RR+PZd#^q5^LXcqa#gr`iM8p*W~FfDi8BgJ?3tzqIB+%saz$wcmD7%s;K0USGcE+LFkyepUmulXd1! zdA`~F)6s_4HHu^EA3X~kGCjwxcBPn)^dN1e%m7Cz+2=vSF6I^7>OCy?{;YmqW|$>r zJo68FSp20xH+$xsZLR)3KC)E%fx|!VvU6Pjt?TMLm0!M?wcm2f-L85z!1W5{Z*zC` zT8S@#;FY@G&CdPD%d`JbFPc1dwo}4_P`4(J#tAu#!f*f9{7P!r+V3NC&IVX68!}o} z+j;Kb3l9!1n@WFnUb{nV=?P_xLML5DbGz!CM){}D*qPkzs&pejCm@%CutsCuV61de)#Yqla`%ul~}Ez zD(&36M&=i{HOtbjy6w!Z2;Lc8arnTtBY^|=t;n|1vkW%v-{CMYLu*9UQ|p0`LG;O% zoP>1^;(w^tx@~$HJ<~o@`>|Z#NXxb<&mZi(zV*Q&FA1M9<*u=#G7J|k=;L#e>U&mo zNtlYg?QHsXpTTzmSAJ~Qx|+CUh-&@y$yOJSe^__CURFF@_TGYTuO>`a?R*)%slS== zd-0dbl)dL?B|q@})Ecv|af{C%*M>iTl#wFcoa`dCEK~gIpa_M5kLUM4H@3d6b^T_q zYALG$A>ao3GQ z<9bEJpN$zpZ%7-u^IYA4yY?vBbbKLctXNuGn zwH@L1(p7V+UGJJlASUi&+x6{MA0|p99BDc=Sw8oynVMtJCOhXlp0adL%f>^6s%uVN z*)T#WL7dt#X$NIMI<0QGZTy_xPL!_=FL(GVjdfX3_~@f=!+;BqM$UZmZLCB}@z+Ck z18aOON2!6fnY+TT_en|%z1}W#pP%1OR0Tw{$Z|14*juwlUJBQUi#{nvwa?HGo<%EaqFfr%-;R)ET3hI#!OUj znmB4|{KVmx@|2NMxQ{mL9;CmM^tLtX{nzJPQ;%Q&7*!Q% zHCsV-2j#KNxA~p#t7?*V{nB1EYULP%fU@}e8@4*A-kkg9P>rqLS^BbTN9~T7FpO`79gTJ7dgs@4BVgyrV6DIrjCVOtI^9-QT2#M?9VQ zHa34&|HhDKh7RHnW(SQpeTi;p^Ln|VxB8jlVRliJ$_n6`owt=ZA;$Et9K^sEqhyi>%duind@}v z0k3Pz)&&-7NGKf~G&k{?MbmJ5$<^P28lwJKJab)FQqC_OCP6!YAN$pzYe+?X;bLu{ zan}w#jd0a`+__oy+od6YJ)M)bgP!X3EaQ^OS{Il4TXV||n{=Za+vxJ)Hhw$Ln#Rd? zS(&z~$Zd2Qd;DFN?RZ$Z5^kk7e_a4iUOlEg3D~}H*wSYGX~Sqm1JyH1hfG@#(9hF3 zB9?;en!entoKjoa? z)`7)w=3TdZw)C~``{(XE2GKJ&H-d%%mSy96()FM;%m}C3DGlXsyLvzemOfrLGa%C9HS!N#CKIx2>P%>fIaW%P98#Yrw4e z9$z0$8vSeXN|Ko^Gb^$R+3x#)&Ruu!J8#wd{r-Kve~-uIT-SAX zRN4SK0C%7Cusd>lxaymub>ttbfF0T!h_fv|4|W9TWSF#Un?Y%@eDanscfmj3Z?}k3 zTQX23bqDqNi_z}+7Sh_@CNb?dvX)bN)Ay6ixY#2V=FSCWOn|+A$ni_&eSNjvNE%QgC}$=*QjD=S$EB=tW_-Z!sX?RAc795PPXw!~WnT--Yv^RV21n5zTRoiM#6pT zy_nr~ke3MyB#>@paLC*Pzl=PJT{8gOoLi#JaF8fNQ&P4~wI_@%e6}Tm_%Q zEcm*7>eGi&w})om3U~IHJWYvwL9=i7p#Mwxn%m92va`qoR1!}1o_Xza4L#k(dU@2G z9D?=cL8?ere97wVL3oBPWIOz5Db(%j$ClH*tJAXZo6kNYh^`l>Py_k7cu2SF@8J8yblOP_;hqt7?w7P zneUBUSAO4qm%TjYWLmN4YSJFFaUm=BR+YUO%)!=&gB0d;bI(qcUQ|W6hp-%vQC#FA zw-~5)zd3a%aQhLtgn@ueWWs#vs@A@B1#s_wf;!!z)F5< z`_m{~yF%yu=U!h6h|Iayd|Y zB8f(6jL64Xb-jg1cfK^&-?(S#TzAhR@O`zO4z9(1+nsCH{P>Isss^Di_%?LtWDHE7 zSVwa>WiJ=LoazbKQEo30qcD>!v33Quv|tY=|1&qpJbbuh{Yv)}XdCJ%UA*i-S^pSy z1Zc*5{A_q{5L->Rk*JD3CGO`U28%vVgAYvW?c1fcvk#AOc3E?56EGTJZ)eR&8GL6< zH@di1BYovgtv37ig9iph=2q@aPS(h~$=TKGEzb6{jq<1Cvt{UZM05bn+hBv^+Sd)e zS3VcJLUJD*+qVmqzjMrfDU40iPsM#5Gx?Q9!+UV=gp1M$L=D|*%Uq?}RgR$Riu%0AKD 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
rfv>y7Y;VIr>G(D0ad(iEnZ}B4@ge4xrdd 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 From a6b92dbbd3d43e11df30739e6aa4d084e54ceb07 Mon Sep 17 00:00:00 2001 From: rien333 Date: Thu, 22 Mar 2018 02:23:21 +0100 Subject: [PATCH 127/223] General window decoration hiding option --- doc/help/settings.asciidoc | 6 +++--- qutebrowser/config/configdata.yml | 4 ++-- qutebrowser/config/configfiles.py | 14 ++++++++++++++ qutebrowser/config/configinit.py | 3 --- qutebrowser/mainwindow/mainwindow.py | 3 +++ tests/unit/config/test_configfiles.py | 12 ++++++++++++ 6 files changed, 34 insertions(+), 8 deletions(-) diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc index 6399b88af..d81619743 100644 --- a/doc/help/settings.asciidoc +++ b/doc/help/settings.asciidoc @@ -264,7 +264,7 @@ |<>|Search engines which can be used via the address bar. |<>|Page(s) to open at the start. |<>|URL parameters to strip with `:yank url`. -|<>|Hide the window decoration when using wayland. +|<>|Hide the window decoration. |<>|Format to use for the window title. The same placeholders like for |<>|Default zoom level. |<>|Available zoom levels. @@ -3158,8 +3158,8 @@ Default: - +pass:[utm_content]+ [[window.hide_wayland_decoration]] -=== window.hide_wayland_decoration -Hide the window decoration when using wayland. +=== window.hide_decoration +Hide the window decoration. This setting requires a restart. Type: <> diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml index 35b514880..f41044d35 100644 --- a/qutebrowser/config/configdata.yml +++ b/qutebrowser/config/configdata.yml @@ -1533,11 +1533,11 @@ url.yank_ignored_parameters: ## window -window.hide_wayland_decoration: +window.hide_decoration: type: Bool default: false restart: true - desc: Hide the window decoration when using wayland. + desc: Hide the window decoration. window.title_format: type: diff --git a/qutebrowser/config/configfiles.py b/qutebrowser/config/configfiles.py index fdb1583e0..b224fe0a2 100644 --- a/qutebrowser/config/configfiles.py +++ b/qutebrowser/config/configfiles.py @@ -262,6 +262,20 @@ class YamlConfig(QObject): del settings[old] self._mark_changed() + # window.hide_wayland_decoration was replaced by a more system agnostic + # Qt based equivalent + old = 'window.hide_wayland_decoration' + new = 'window.hide_decoration' + if old in settings: + settings[new] = {} + for scope, val in settings[old].items(): + if val: + settings[new][scope] = True + else: + settings[new][scope] = False + del settings[old] + self._mark_changed() + # bindings.default can't be set in autoconfig.yml anymore, so ignore # old values. if 'bindings.default' in settings: diff --git a/qutebrowser/config/configinit.py b/qutebrowser/config/configinit.py index 5c06a4a3d..7f40a74fb 100644 --- a/qutebrowser/config/configinit.py +++ b/qutebrowser/config/configinit.py @@ -90,9 +90,6 @@ def _init_envvars(): if config.val.qt.force_platform is not None: os.environ['QT_QPA_PLATFORM'] = config.val.qt.force_platform - if config.val.window.hide_wayland_decoration: - os.environ['QT_WAYLAND_DISABLE_WINDOWDECORATION'] = '1' - if config.val.qt.highdpi: os.environ['QT_AUTO_SCREEN_SCALE_FACTOR'] = '1' diff --git a/qutebrowser/mainwindow/mainwindow.py b/qutebrowser/mainwindow/mainwindow.py index 3db56c0a7..dfb2f416b 100644 --- a/qutebrowser/mainwindow/mainwindow.py +++ b/qutebrowser/mainwindow/mainwindow.py @@ -169,6 +169,9 @@ class MainWindow(QWidget): objreg.register('message-bridge', message_bridge, scope='window', window=self.win_id) + if config.val.window.hide_decoration: + window_flags = Qt.CustomizeWindowHint | Qt.NoDropShadowWindowHint + self.setWindowFlags(Qt.Window | window_flags) self.setWindowTitle('qutebrowser') self._vbox = QVBoxLayout(self) self._vbox.setContentsMargins(0, 0, 0, 0) diff --git a/tests/unit/config/test_configfiles.py b/tests/unit/config/test_configfiles.py index 96f5d4976..40eec89d0 100644 --- a/tests/unit/config/test_configfiles.py +++ b/tests/unit/config/test_configfiles.py @@ -223,6 +223,18 @@ class TestYaml: mode = 'persist' if persist else 'normal' assert data['tabs.mode_on_change']['global'] == mode + @pytest.mark.parametrize('hide_decoration', [True, False]) + def test_merge_persist(self, yaml, autoconfig, hide_decoration): + """Tests for migration of window.hide_wayland_decoration""" + old = {'window.hide_wayland_decoration': {'global': hide_decoration}} + autoconfig.write(old) + yaml.load() + yaml._save() + + data = autoconfig.read() + assert 'window.hide_wayland_decoration' not in data + assert data['window.hide_decoration']['global'] == hide_decoration + def test_bindings_default(self, yaml, autoconfig): """Make sure bindings.default gets removed from autoconfig.yml.""" autoconfig.write({'bindings.default': {'global': '{}'}}) From 7b7faa9f661fae74642398f7b3e2ad8876ce33d7 Mon Sep 17 00:00:00 2001 From: rien333 Date: Thu, 22 Mar 2018 03:42:57 +0100 Subject: [PATCH 128/223] Fix silly redefinition --- tests/unit/config/test_configfiles.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/config/test_configfiles.py b/tests/unit/config/test_configfiles.py index 40eec89d0..4364da814 100644 --- a/tests/unit/config/test_configfiles.py +++ b/tests/unit/config/test_configfiles.py @@ -224,7 +224,7 @@ class TestYaml: assert data['tabs.mode_on_change']['global'] == mode @pytest.mark.parametrize('hide_decoration', [True, False]) - def test_merge_persist(self, yaml, autoconfig, hide_decoration): + def test_decoration_hiding(self, yaml, autoconfig, hide_decoration): """Tests for migration of window.hide_wayland_decoration""" old = {'window.hide_wayland_decoration': {'global': hide_decoration}} autoconfig.write(old) From 764e79e505329ef1f9e7e3429fb4bc3ef869e811 Mon Sep 17 00:00:00 2001 From: rien333 Date: Thu, 22 Mar 2018 03:44:24 +0100 Subject: [PATCH 129/223] Small refactor --- qutebrowser/config/configfiles.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/qutebrowser/config/configfiles.py b/qutebrowser/config/configfiles.py index b224fe0a2..a0fa165bf 100644 --- a/qutebrowser/config/configfiles.py +++ b/qutebrowser/config/configfiles.py @@ -269,10 +269,7 @@ class YamlConfig(QObject): if old in settings: settings[new] = {} for scope, val in settings[old].items(): - if val: - settings[new][scope] = True - else: - settings[new][scope] = False + settings[new][scope] = val del settings[old] self._mark_changed() From b67a03115188679af81b66eb091b2e59e9a95117 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 22 Mar 2018 08:37:12 +0100 Subject: [PATCH 130/223] Rephrase autoconfig.yml docs [ci skip] --- doc/help/configuring.asciidoc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/help/configuring.asciidoc b/doc/help/configuring.asciidoc index 266315d56..3c686d15a 100644 --- a/doc/help/configuring.asciidoc +++ b/doc/help/configuring.asciidoc @@ -242,10 +242,10 @@ To suppress loading of any default keybindings, you can set Loading `autoconfig.yml` ~~~~~~~~~~~~~~~~~~~~~~~~ -By default, all customization done via `:set`, `:bind` and `:unbind` is -temporary as soon as a `config.py` exists. The settings done that way are always -saved in the `autoconfig.yml` file, but you'll need to explicitly load it in -your `config.py` by doing: +All customization done via the UI (`:set`, `:bind` and `:unbind`) is +stored in the `autoconfig.yml` file, which is not loaded automatically as soon +as a `config.py` exists. If you want those settings to be loaded, you'll need to +explicitly load the `autoconfig.yml` file in your `config.py` by doing: .config.py: [source,python] From d2c01d7ee65e203d4bf36076835a2e3d40e1e51a Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Wed, 21 Mar 2018 23:41:53 -0400 Subject: [PATCH 131/223] Always display plain titles in tab tooltips Closes #3741 --- qutebrowser/mainwindow/tabwidget.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/qutebrowser/mainwindow/tabwidget.py b/qutebrowser/mainwindow/tabwidget.py index 72d3cff22..fa8b9939a 100644 --- a/qutebrowser/mainwindow/tabwidget.py +++ b/qutebrowser/mainwindow/tabwidget.py @@ -148,9 +148,13 @@ class TabWidget(QTabWidget): title = '' if fmt is None else fmt.format(**fields) tabbar = self.tabBar() + # Only change the tab title if it changes, setting the tab title causes + # a size recalculation which is slow. if tabbar.tabText(idx) != title: tabbar.setTabText(idx, title) - tabbar.setTabToolTip(idx, title) + + # always show only plain title in tooltips + tabbar.setTabToolTip(idx, fields['title']) def get_tab_fields(self, idx): """Get the tab field data.""" From 477da6002a4a1dbbbed045edff145a552652dbe0 Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Thu, 22 Mar 2018 12:55:33 -0400 Subject: [PATCH 132/223] Fix minimum size for vertical tabs --- qutebrowser/mainwindow/tabwidget.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qutebrowser/mainwindow/tabwidget.py b/qutebrowser/mainwindow/tabwidget.py index 72d3cff22..4a34a08cb 100644 --- a/qutebrowser/mainwindow/tabwidget.py +++ b/qutebrowser/mainwindow/tabwidget.py @@ -494,8 +494,8 @@ class TabBar(QTabBar): self.iconSize().width()) + icon_padding pinned = self._tab_pinned(index) - if pinned: - # Never consider ellipsis an option for pinned tabs + if not self.vertical and pinned and config.val.tabs.pinned.shrink: + # Never consider ellipsis an option for horizontal pinned tabs ellipsis = False return self._minimum_tab_size_hint_helper(self.tabText(index), icon_width, ellipsis, From 07d043fe81326d701dfc6fc5299b93b9a39d98ef Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Thu, 22 Mar 2018 14:24:35 -0400 Subject: [PATCH 133/223] Add basic tests for tab width sizing --- tests/unit/mainwindow/test_tabwidget.py | 54 +++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/tests/unit/mainwindow/test_tabwidget.py b/tests/unit/mainwindow/test_tabwidget.py index 36e6a0c48..de68481a9 100644 --- a/tests/unit/mainwindow/test_tabwidget.py +++ b/tests/unit/mainwindow/test_tabwidget.py @@ -61,6 +61,60 @@ class TestTabWidget: with qtbot.waitExposed(widget): widget.show() + # Sizing tests + + def test_tab_size_same(self, widget, fake_web_tab): + """Ensure by default, all tab sizes are the same.""" + num_tabs = 10 + for i in range(num_tabs): + widget.addTab(fake_web_tab(), 'foobar' + str(i)) + + first_size = widget.tabBar().tabSizeHint(0) + first_size_min = widget.tabBar().minimumTabSizeHint(0) + + for i in range(num_tabs): + assert first_size == widget.tabBar().tabSizeHint(i) + assert first_size_min == widget.tabBar().minimumTabSizeHint(i) + + @pytest.mark.parametrize("shrink_pinned", [True, False]) + @pytest.mark.parametrize("vertical", [True, False]) + def test_pinned_size(self, widget, fake_web_tab, config_stub, + shrink_pinned, vertical): + """Ensure by default, pinned min sizes are forced to title. + + If pinned.shrink is not true, then all tabs should be the same + + If tabs are vertical, all tabs should be the same""" + num_tabs = 10 + for i in range(num_tabs): + widget.addTab(fake_web_tab(), 'foobar' + str(i)) + + # Set pinned title format longer than unpinned + config_stub.val.tabs.title.format_pinned = "_" * 20 + config_stub.val.tabs.title.format = "_" * 2 + config_stub.val.tabs.pinned.shrink = shrink_pinned + if vertical: + # Use pixel width so we don't need to mock main-window + config_stub.val.tabs.width = 50 + config_stub.val.tabs.position = "left" + + pinned_num = [1, num_tabs - 1] + for tab in pinned_num: + widget.set_tab_pinned(widget.widget(tab), True) + + first_size = widget.tabBar().tabSizeHint(0) + first_size_min = widget.tabBar().minimumTabSizeHint(0) + + for i in range(num_tabs): + if i in pinned_num and shrink_pinned and not vertical: + assert (first_size.width() < + widget.tabBar().tabSizeHint(i).width()) + assert (first_size_min.width() < + widget.tabBar().minimumTabSizeHint(i).width()) + else: + assert first_size == widget.tabBar().tabSizeHint(i) + assert first_size_min == widget.tabBar().minimumTabSizeHint(i) + @pytest.mark.parametrize("num_tabs", [4, 10]) def test_update_tab_titles_benchmark(self, benchmark, widget, qtbot, fake_web_tab, num_tabs): From 2d2bdad2ca1e08ee16f16862a3ce11556bc46181 Mon Sep 17 00:00:00 2001 From: rien333 Date: Thu, 22 Mar 2018 23:26:45 +0100 Subject: [PATCH 134/223] Do not require restart after decoration option change --- doc/help/settings.asciidoc | 2 +- qutebrowser/config/.#configfiles.py | 1 + qutebrowser/config/configdata.yml | 6 ++++-- qutebrowser/config/configinit.py | 3 +++ qutebrowser/mainwindow/mainwindow.py | 16 +++++++++++++--- 5 files changed, 22 insertions(+), 6 deletions(-) create mode 120000 qutebrowser/config/.#configfiles.py diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc index d81619743..dbee21b8f 100644 --- a/doc/help/settings.asciidoc +++ b/doc/help/settings.asciidoc @@ -3157,7 +3157,7 @@ Default: - +pass:[utm_term]+ - +pass:[utm_content]+ -[[window.hide_wayland_decoration]] +[[window.hide_decoration]] === window.hide_decoration Hide the window decoration. This setting requires a restart. diff --git a/qutebrowser/config/.#configfiles.py b/qutebrowser/config/.#configfiles.py new file mode 120000 index 000000000..3c80a2a2a --- /dev/null +++ b/qutebrowser/config/.#configfiles.py @@ -0,0 +1 @@ +rw@domper.local.3624 \ No newline at end of file diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml index f41044d35..0122bbb0d 100644 --- a/qutebrowser/config/configdata.yml +++ b/qutebrowser/config/configdata.yml @@ -1536,8 +1536,10 @@ url.yank_ignored_parameters: window.hide_decoration: type: Bool default: false - restart: true - desc: Hide the window decoration. + restart: false + desc: | + Hide the window decoration. Does require a restart for + Wayland users. window.title_format: type: diff --git a/qutebrowser/config/configinit.py b/qutebrowser/config/configinit.py index 7f40a74fb..5c06a4a3d 100644 --- a/qutebrowser/config/configinit.py +++ b/qutebrowser/config/configinit.py @@ -90,6 +90,9 @@ def _init_envvars(): if config.val.qt.force_platform is not None: os.environ['QT_QPA_PLATFORM'] = config.val.qt.force_platform + if config.val.window.hide_wayland_decoration: + os.environ['QT_WAYLAND_DISABLE_WINDOWDECORATION'] = '1' + if config.val.qt.highdpi: os.environ['QT_AUTO_SCREEN_SCALE_FACTOR'] = '1' diff --git a/qutebrowser/mainwindow/mainwindow.py b/qutebrowser/mainwindow/mainwindow.py index dfb2f416b..796c1f1b9 100644 --- a/qutebrowser/mainwindow/mainwindow.py +++ b/qutebrowser/mainwindow/mainwindow.py @@ -169,9 +169,6 @@ class MainWindow(QWidget): objreg.register('message-bridge', message_bridge, scope='window', window=self.win_id) - if config.val.window.hide_decoration: - window_flags = Qt.CustomizeWindowHint | Qt.NoDropShadowWindowHint - self.setWindowFlags(Qt.Window | window_flags) self.setWindowTitle('qutebrowser') self._vbox = QVBoxLayout(self) self._vbox.setContentsMargins(0, 0, 0, 0) @@ -233,6 +230,8 @@ class MainWindow(QWidget): config.instance.changed.connect(self._on_config_changed) objreg.get("app").new_window.emit(self) + self._set_decoration(config.val.window.hide_decoration) + def _init_geometry(self, geometry): """Initialize the window geometry or load it from disk.""" @@ -347,6 +346,8 @@ class MainWindow(QWidget): elif option == 'statusbar.position': self._add_widgets() self._update_overlay_geometries() + elif option == 'window.hide_decoration': + self._set_decoration(config.val.window.hide_decoration) def _add_widgets(self): """Add or readd all widgets to the VBox.""" @@ -496,6 +497,15 @@ class MainWindow(QWidget): completion_obj.on_clear_completion_selection) cmd.hide_completion.connect(completion_obj.hide) + def _set_decoration(self, hidden): + """ Set the visibility of the window decoration via Qt.""" + window_flags = Qt.Window + if hidden: + window_flags |= Qt.CustomizeWindowHint | Qt.NoDropShadowWindowHint + self.setWindowFlags(window_flags) + self.hide() + self.show() + @pyqtSlot(bool) def _on_fullscreen_requested(self, on): if not config.val.content.windowed_fullscreen: From ff299c87a84723864ddea4c11e766a47b0cc671e Mon Sep 17 00:00:00 2001 From: rien333 Date: Thu, 22 Mar 2018 23:32:37 +0100 Subject: [PATCH 135/223] Reinsert wayland specific code for toggling decoration visibility --- qutebrowser/config/configinit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutebrowser/config/configinit.py b/qutebrowser/config/configinit.py index 5c06a4a3d..1f9f0f4b6 100644 --- a/qutebrowser/config/configinit.py +++ b/qutebrowser/config/configinit.py @@ -90,7 +90,7 @@ def _init_envvars(): if config.val.qt.force_platform is not None: os.environ['QT_QPA_PLATFORM'] = config.val.qt.force_platform - if config.val.window.hide_wayland_decoration: + if config.val.window.hide_decoration: os.environ['QT_WAYLAND_DISABLE_WINDOWDECORATION'] = '1' if config.val.qt.highdpi: From bb0c79b5a29806c4c041fe34a99c7ae014f09d5d Mon Sep 17 00:00:00 2001 From: rien333 Date: Fri, 23 Mar 2018 01:38:45 +0100 Subject: [PATCH 136/223] Fix wayland test --- tests/unit/config/test_configinit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/config/test_configinit.py b/tests/unit/config/test_configinit.py index 2ee572860..6f3f0e1e5 100644 --- a/tests/unit/config/test_configinit.py +++ b/tests/unit/config/test_configinit.py @@ -292,7 +292,7 @@ class TestEarlyInit: 'QT_XCB_FORCE_SOFTWARE_OPENGL', '1'), ('qt.force_platform', 'toaster', 'QT_QPA_PLATFORM', 'toaster'), ('qt.highdpi', True, 'QT_AUTO_SCREEN_SCALE_FACTOR', '1'), - ('window.hide_wayland_decoration', True, + ('window.hide_decoration', True, 'QT_WAYLAND_DISABLE_WINDOWDECORATION', '1') ]) def test_env_vars(self, monkeypatch, config_stub, From 1fc0abb06417fb7f2785bae8a29cea0821209a6e Mon Sep 17 00:00:00 2001 From: rien333 Date: Fri, 23 Mar 2018 02:50:36 +0100 Subject: [PATCH 137/223] Delete .#configfiles.py --- qutebrowser/config/.#configfiles.py | 1 - 1 file changed, 1 deletion(-) delete mode 120000 qutebrowser/config/.#configfiles.py diff --git a/qutebrowser/config/.#configfiles.py b/qutebrowser/config/.#configfiles.py deleted file mode 120000 index 3c80a2a2a..000000000 --- a/qutebrowser/config/.#configfiles.py +++ /dev/null @@ -1 +0,0 @@ -rw@domper.local.3624 \ No newline at end of file From 00bdb6062796d47fdc11219028b2abca9c5c6f98 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 23 Mar 2018 07:53:12 +0100 Subject: [PATCH 138/223] Ignore "Dropping message on closed channel." message This seems to happen with this test in tabs.feature with Qt 5.11: Scenario: :buffer with wrong argument (-1) It only happens ~1/50 times though, and seems like some Qt bug. See #3661 --- 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 41dee1275..e30dc03fa 100644 --- a/tests/end2end/fixtures/quteprocess.py +++ b/tests/end2end/fixtures/quteprocess.py @@ -200,6 +200,10 @@ def is_ignored_chromium_message(line): # [23359:23359:0319/115812.168578:WARNING: # render_frame_host_impl.cc(2744)] OnDidStopLoading was called twice. 'OnDidStopLoading was called twice.', + + # [30412:30412:0323/074933.387250:ERROR:node_channel.cc(899)] Dropping + # message on closed channel. + 'Dropping message on closed channel.', ] return any(testutils.pattern_match(pattern=pattern, value=message) for pattern in ignored_messages) From f1789effdc0307a1bc9f64144863fd747381cf81 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 23 Mar 2018 10:29:25 +0100 Subject: [PATCH 139/223] Stabilize navigate.feature on Qt 5.11 Looks like we get qute://help as URL from the previous test otherwise? See #3661 --- tests/end2end/features/navigate.feature | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/end2end/features/navigate.feature b/tests/end2end/features/navigate.feature index 307a6c53a..b40b4d9cc 100644 --- a/tests/end2end/features/navigate.feature +++ b/tests/end2end/features/navigate.feature @@ -26,7 +26,7 @@ Feature: Using :navigate # prev/next Scenario: Navigating to previous page - When I open data/navigate + When I open data/navigate in a new tab And I run :navigate prev Then data/navigate/prev.html should be loaded From 880b33fff599a8339207881ca871e178d3634106 Mon Sep 17 00:00:00 2001 From: rien333 Date: Fri, 23 Mar 2018 15:19:37 +0100 Subject: [PATCH 140/223] Restore correct window visibility after decoration config change --- qutebrowser/mainwindow/mainwindow.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/qutebrowser/mainwindow/mainwindow.py b/qutebrowser/mainwindow/mainwindow.py index 796c1f1b9..db96d7c73 100644 --- a/qutebrowser/mainwindow/mainwindow.py +++ b/qutebrowser/mainwindow/mainwindow.py @@ -500,11 +500,13 @@ class MainWindow(QWidget): def _set_decoration(self, hidden): """ Set the visibility of the window decoration via Qt.""" window_flags = Qt.Window + refresh_window = self.isVisible() if hidden: window_flags |= Qt.CustomizeWindowHint | Qt.NoDropShadowWindowHint self.setWindowFlags(window_flags) - self.hide() - self.show() + if refresh_window: + self.hide() + self.show() @pyqtSlot(bool) def _on_fullscreen_requested(self, on): From 6db1ab0a58f5930b32b8304faf84e9df33dd2bbd Mon Sep 17 00:00:00 2001 From: rien333 Date: Fri, 23 Mar 2018 15:21:02 +0100 Subject: [PATCH 141/223] Cosmetic changes --- qutebrowser/mainwindow/mainwindow.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/qutebrowser/mainwindow/mainwindow.py b/qutebrowser/mainwindow/mainwindow.py index db96d7c73..f0594d90e 100644 --- a/qutebrowser/mainwindow/mainwindow.py +++ b/qutebrowser/mainwindow/mainwindow.py @@ -232,7 +232,6 @@ class MainWindow(QWidget): objreg.get("app").new_window.emit(self) self._set_decoration(config.val.window.hide_decoration) - def _init_geometry(self, geometry): """Initialize the window geometry or load it from disk.""" if geometry is not None: @@ -498,7 +497,7 @@ class MainWindow(QWidget): cmd.hide_completion.connect(completion_obj.hide) def _set_decoration(self, hidden): - """ Set the visibility of the window decoration via Qt.""" + """Set the visibility of the window decoration via Qt.""" window_flags = Qt.Window refresh_window = self.isVisible() if hidden: From e211801e16b5ecdf8e2f3353bb4f411a233104c1 Mon Sep 17 00:00:00 2001 From: rien333 Date: Fri, 23 Mar 2018 15:24:18 +0100 Subject: [PATCH 142/223] Handle wayland decoration option rename through configdata.yml --- qutebrowser/config/configdata.yml | 3 +++ qutebrowser/config/configfiles.py | 11 ----------- tests/unit/config/test_configfiles.py | 12 ------------ 3 files changed, 3 insertions(+), 23 deletions(-) diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml index 0122bbb0d..bbc49b374 100644 --- a/qutebrowser/config/configdata.yml +++ b/qutebrowser/config/configdata.yml @@ -1533,6 +1533,9 @@ url.yank_ignored_parameters: ## window +window.hide_wayland_decoration: + renamed: window.hide_decoration + window.hide_decoration: type: Bool default: false diff --git a/qutebrowser/config/configfiles.py b/qutebrowser/config/configfiles.py index a0fa165bf..fdb1583e0 100644 --- a/qutebrowser/config/configfiles.py +++ b/qutebrowser/config/configfiles.py @@ -262,17 +262,6 @@ class YamlConfig(QObject): del settings[old] self._mark_changed() - # window.hide_wayland_decoration was replaced by a more system agnostic - # Qt based equivalent - old = 'window.hide_wayland_decoration' - new = 'window.hide_decoration' - if old in settings: - settings[new] = {} - for scope, val in settings[old].items(): - settings[new][scope] = val - del settings[old] - self._mark_changed() - # bindings.default can't be set in autoconfig.yml anymore, so ignore # old values. if 'bindings.default' in settings: diff --git a/tests/unit/config/test_configfiles.py b/tests/unit/config/test_configfiles.py index 4364da814..96f5d4976 100644 --- a/tests/unit/config/test_configfiles.py +++ b/tests/unit/config/test_configfiles.py @@ -223,18 +223,6 @@ class TestYaml: mode = 'persist' if persist else 'normal' assert data['tabs.mode_on_change']['global'] == mode - @pytest.mark.parametrize('hide_decoration', [True, False]) - def test_decoration_hiding(self, yaml, autoconfig, hide_decoration): - """Tests for migration of window.hide_wayland_decoration""" - old = {'window.hide_wayland_decoration': {'global': hide_decoration}} - autoconfig.write(old) - yaml.load() - yaml._save() - - data = autoconfig.read() - assert 'window.hide_wayland_decoration' not in data - assert data['window.hide_decoration']['global'] == hide_decoration - def test_bindings_default(self, yaml, autoconfig): """Make sure bindings.default gets removed from autoconfig.yml.""" autoconfig.write({'bindings.default': {'global': '{}'}}) From fa21d280fabd33ca865ccce747e5283e689daf50 Mon Sep 17 00:00:00 2001 From: rien333 Date: Sat, 24 Mar 2018 05:09:03 +0100 Subject: [PATCH 143/223] Remove unnecessary hide operation --- qutebrowser/mainwindow/mainwindow.py | 1 - 1 file changed, 1 deletion(-) diff --git a/qutebrowser/mainwindow/mainwindow.py b/qutebrowser/mainwindow/mainwindow.py index f0594d90e..596280e81 100644 --- a/qutebrowser/mainwindow/mainwindow.py +++ b/qutebrowser/mainwindow/mainwindow.py @@ -504,7 +504,6 @@ class MainWindow(QWidget): window_flags |= Qt.CustomizeWindowHint | Qt.NoDropShadowWindowHint self.setWindowFlags(window_flags) if refresh_window: - self.hide() self.show() @pyqtSlot(bool) From 01d8314dd865203947254840072f0f9f6cea034e Mon Sep 17 00:00:00 2001 From: George Edward Bulmer Date: Sat, 24 Mar 2018 18:24:36 +0000 Subject: [PATCH 144/223] Change default blocklist to StevenBlack combined --- qutebrowser/config/configdata.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml index 35b514880..f8cbbd9c7 100644 --- a/qutebrowser/config/configdata.yml +++ b/qutebrowser/config/configdata.yml @@ -425,11 +425,7 @@ content.host_blocking.enabled: content.host_blocking.lists: default: - - "https://www.malwaredomainlist.com/hostslist/hosts.txt" - - "http://someonewhocares.org/hosts/hosts" - - "http://winhelp2002.mvps.org/hosts.zip" - - "http://malwaredomains.lehigh.edu/files/justdomains.zip" - - "https://pgl.yoyo.org/adservers/serverlist.php?hostformat=hosts&mimetype=plaintext" + - "https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts" type: name: List valtype: Url From c8db9e1c762c5d526cea327448c774b8b235891f Mon Sep 17 00:00:00 2001 From: George Edward Bulmer Date: Sat, 24 Mar 2018 19:42:34 +0000 Subject: [PATCH 145/223] Remove WHITELISTED, making file parsing satisfy: 1) 'dotless' hosts, e.g. localhost, cannot be blocked by a file 2) hosts ending in '.localdomain' cannot be blocked by a file --- qutebrowser/browser/adblock.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/qutebrowser/browser/adblock.py b/qutebrowser/browser/adblock.py index f0462a778..961994a9a 100644 --- a/qutebrowser/browser/adblock.py +++ b/qutebrowser/browser/adblock.py @@ -94,14 +94,8 @@ class HostBlocker: _done_count: How many files have been read successfully. _local_hosts_file: The path to the blocked-hosts file. _config_hosts_file: The path to a blocked-hosts in ~/.config - - Class attributes: - WHITELISTED: Hosts which never should be blocked. """ - WHITELISTED = ('localhost', 'localhost.localdomain', 'broadcasthost', - 'local') - def __init__(self): self._blocked_hosts = set() self._config_blocked_hosts = set() @@ -242,7 +236,7 @@ class HostBlocker: log.misc.error("Failed to parse: {!r}".format(line)) return False - if host not in self.WHITELISTED: + if '.' not in host and not host.endswith('.localdomain'): self._blocked_hosts.add(host) return True From 3f37fcf8fadb96658cf58fea64f371735a5bf144 Mon Sep 17 00:00:00 2001 From: George Edward Bulmer Date: Sat, 24 Mar 2018 20:15:34 +0000 Subject: [PATCH 146/223] Modify tests, localhost should never be blocked --- qutebrowser/browser/adblock.py | 2 +- tests/unit/browser/test_adblock.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/qutebrowser/browser/adblock.py b/qutebrowser/browser/adblock.py index 961994a9a..8328a8a76 100644 --- a/qutebrowser/browser/adblock.py +++ b/qutebrowser/browser/adblock.py @@ -236,7 +236,7 @@ class HostBlocker: log.misc.error("Failed to parse: {!r}".format(line)) return False - if '.' not in host and not host.endswith('.localdomain'): + if '.' in host and not host.endswith('.localdomain'): self._blocked_hosts.add(host) return True diff --git a/tests/unit/browser/test_adblock.py b/tests/unit/browser/test_adblock.py index 5b353efb9..d6ebb3877 100644 --- a/tests/unit/browser/test_adblock.py +++ b/tests/unit/browser/test_adblock.py @@ -114,14 +114,14 @@ def create_blocklist(directory, blocked_hosts=BLOCKLIST_HOSTS, return name -def assert_urls(host_blocker, blocked=BLOCKLIST_HOSTS, +def assert_urls(host_blocker, blocked=BLOCKLIST_HOSTS[1:], whitelisted=WHITELISTED_HOSTS, urls_to_check=URLS_TO_CHECK): """Test if Urls to check are blocked or not by HostBlocker. Ensure URLs in 'blocked' and not in 'whitelisted' are blocked. All other URLs must not be blocked. """ - whitelisted = list(whitelisted) + list(host_blocker.WHITELISTED) + whitelisted = list(whitelisted) for str_url in urls_to_check: url = QUrl(str_url) host = url.host() @@ -341,7 +341,7 @@ def test_blocking_with_whitelist(config_stub, basedir, download_stub, """Ensure hosts in content.host_blocking.whitelist are never blocked.""" # Simulate adblock_update has already been run # by creating a file named blocked-hosts, - # Exclude localhost from it, since localhost is in HostBlocker.WHITELISTED + # Exclude localhost from it, since localhost is never blocked by list filtered_blocked_hosts = BLOCKLIST_HOSTS[1:] blocklist = create_blocklist(data_tmpdir, blocked_hosts=filtered_blocked_hosts, From 8809ef02a109e3dc590020301cf7858a5414679a Mon Sep 17 00:00:00 2001 From: George Edward Bulmer Date: Sat, 24 Mar 2018 20:20:16 +0000 Subject: [PATCH 147/223] Add support for more than 1 host on a given line --- qutebrowser/browser/adblock.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/qutebrowser/browser/adblock.py b/qutebrowser/browser/adblock.py index 8328a8a76..1cee34f25 100644 --- a/qutebrowser/browser/adblock.py +++ b/qutebrowser/browser/adblock.py @@ -228,16 +228,13 @@ class HostBlocker: parts = line.split() if len(parts) == 1: # "one host per line" format - host = parts[0] - elif len(parts) == 2: - # /etc/hosts format - host = parts[1] + hosts = [parts[0]] else: - log.misc.error("Failed to parse: {!r}".format(line)) - return False + hosts = parts[1:] - if '.' in host and not host.endswith('.localdomain'): - self._blocked_hosts.add(host) + for host in hosts: + if '.' in host and not host.endswith('.localdomain'): + self._blocked_hosts.add(host) return True From 1380fef600fd70f35677567e4bec7c6e42f99013 Mon Sep 17 00:00:00 2001 From: George Edward Bulmer Date: Sat, 24 Mar 2018 21:08:55 +0000 Subject: [PATCH 148/223] Add test for parsing multiple lines --- tests/unit/browser/test_adblock.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/unit/browser/test_adblock.py b/tests/unit/browser/test_adblock.py index d6ebb3877..573ac3ad8 100644 --- a/tests/unit/browser/test_adblock.py +++ b/tests/unit/browser/test_adblock.py @@ -94,6 +94,7 @@ def create_blocklist(directory, blocked_hosts=BLOCKLIST_HOSTS, name: name to give to the blocklist file line_format: 'etc_hosts' --> /etc/hosts format 'one_per_line' --> one host per line format + 'all_on_one_line' --> pathological example with one line 'not_correct' --> Not a correct hosts file format. """ blocklist_file = directory / name @@ -106,6 +107,8 @@ def create_blocklist(directory, blocked_hosts=BLOCKLIST_HOSTS, elif line_format == 'one_per_line': for host in blocked_hosts: blocklist.write(host + '\n') + elif line_format == 'all_on_one_line': + blocklist.write('127.0.0.1 ' + ' '.join(blocked_hosts) + '\n') elif line_format == 'not_correct': for host in blocked_hosts: blocklist.write(host + ' This is not a correct hosts file\n') @@ -247,6 +250,15 @@ def test_successful_update(config_stub, basedir, download_stub, assert_urls(host_blocker, whitelisted=[]) +def test_parsing_multiple_hosts_on_line(config_stub, basedir, download_stub, + data_tmpdir, tmpdir, win_registry, caplog): + """Ensure multiple hosts on a line get parsed correctly""" + host_blocker = adblock.HostBlocker() + bytes_host_line = ' '.join(BLOCKLIST_HOSTS).encode('utf-8') + host_blocker._parse_line(bytes_host_line) + assert_urls(host_blocker, whitelisted=[]) + + def test_failed_dl_update(config_stub, basedir, download_stub, data_tmpdir, tmpdir, win_registry, caplog): """One blocklist fails to download. From 64b01cc07674773b32d96f30a84cb39ef6121edf Mon Sep 17 00:00:00 2001 From: George Edward Bulmer Date: Sat, 24 Mar 2018 21:10:23 +0000 Subject: [PATCH 149/223] Remove extraneous part --- tests/unit/browser/test_adblock.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/unit/browser/test_adblock.py b/tests/unit/browser/test_adblock.py index 573ac3ad8..464ae87bd 100644 --- a/tests/unit/browser/test_adblock.py +++ b/tests/unit/browser/test_adblock.py @@ -94,7 +94,6 @@ def create_blocklist(directory, blocked_hosts=BLOCKLIST_HOSTS, name: name to give to the blocklist file line_format: 'etc_hosts' --> /etc/hosts format 'one_per_line' --> one host per line format - 'all_on_one_line' --> pathological example with one line 'not_correct' --> Not a correct hosts file format. """ blocklist_file = directory / name @@ -107,8 +106,6 @@ def create_blocklist(directory, blocked_hosts=BLOCKLIST_HOSTS, elif line_format == 'one_per_line': for host in blocked_hosts: blocklist.write(host + '\n') - elif line_format == 'all_on_one_line': - blocklist.write('127.0.0.1 ' + ' '.join(blocked_hosts) + '\n') elif line_format == 'not_correct': for host in blocked_hosts: blocklist.write(host + ' This is not a correct hosts file\n') From b9bcad9c147a19535fe45f4e46d541ccea82e7ac Mon Sep 17 00:00:00 2001 From: George Edward Bulmer Date: Sat, 24 Mar 2018 21:13:22 +0000 Subject: [PATCH 150/223] Grammar change --- tests/unit/browser/test_adblock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/browser/test_adblock.py b/tests/unit/browser/test_adblock.py index 464ae87bd..ab40fa543 100644 --- a/tests/unit/browser/test_adblock.py +++ b/tests/unit/browser/test_adblock.py @@ -350,7 +350,7 @@ def test_blocking_with_whitelist(config_stub, basedir, download_stub, """Ensure hosts in content.host_blocking.whitelist are never blocked.""" # Simulate adblock_update has already been run # by creating a file named blocked-hosts, - # Exclude localhost from it, since localhost is never blocked by list + # Exclude localhost from it as localhost is never blocked via list filtered_blocked_hosts = BLOCKLIST_HOSTS[1:] blocklist = create_blocklist(data_tmpdir, blocked_hosts=filtered_blocked_hosts, From eb5684e5f7aa0ec1d49077ef390220b9fb491573 Mon Sep 17 00:00:00 2001 From: George Edward Bulmer Date: Sat, 24 Mar 2018 21:52:26 +0000 Subject: [PATCH 151/223] Pylint fix --- tests/unit/browser/test_adblock.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/unit/browser/test_adblock.py b/tests/unit/browser/test_adblock.py index ab40fa543..39c83d698 100644 --- a/tests/unit/browser/test_adblock.py +++ b/tests/unit/browser/test_adblock.py @@ -248,7 +248,8 @@ def test_successful_update(config_stub, basedir, download_stub, def test_parsing_multiple_hosts_on_line(config_stub, basedir, download_stub, - data_tmpdir, tmpdir, win_registry, caplog): + data_tmpdir, tmpdir, win_registry, + caplog): """Ensure multiple hosts on a line get parsed correctly""" host_blocker = adblock.HostBlocker() bytes_host_line = ' '.join(BLOCKLIST_HOSTS).encode('utf-8') From a85ac1725faaa8186c27c76976e1fc9ce64490de Mon Sep 17 00:00:00 2001 From: George Edward Bulmer Date: Sat, 24 Mar 2018 22:56:47 +0000 Subject: [PATCH 152/223] Missing fullstop in a docstring --- tests/unit/browser/test_adblock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/browser/test_adblock.py b/tests/unit/browser/test_adblock.py index 39c83d698..69392fc7b 100644 --- a/tests/unit/browser/test_adblock.py +++ b/tests/unit/browser/test_adblock.py @@ -250,7 +250,7 @@ def test_successful_update(config_stub, basedir, download_stub, def test_parsing_multiple_hosts_on_line(config_stub, basedir, download_stub, data_tmpdir, tmpdir, win_registry, caplog): - """Ensure multiple hosts on a line get parsed correctly""" + """Ensure multiple hosts on a line get parsed correctly.""" host_blocker = adblock.HostBlocker() bytes_host_line = ' '.join(BLOCKLIST_HOSTS).encode('utf-8') host_blocker._parse_line(bytes_host_line) From 12a405965aed0e190c02730c15763b71920c990f Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 25 Mar 2018 14:47:00 +0200 Subject: [PATCH 153/223] Make QtWebEngine inspector work with JS disabled --- doc/changelog.asciidoc | 2 ++ qutebrowser/browser/webengine/webengineinspector.py | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index ba1259d42..10109518b 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -55,6 +55,8 @@ Fixed loaded correctly in some situations. - The statusbar now more closely reflects the caret mode state. - The icon on Windows should now be displayed in a higher resolution. +- The QtWebEngine development tools (inspector) now also work when JavaScript is + disabled globally. v1.2.1 ------ diff --git a/qutebrowser/browser/webengine/webengineinspector.py b/qutebrowser/browser/webengine/webengineinspector.py index 9200e3eb3..0145ad634 100644 --- a/qutebrowser/browser/webengine/webengineinspector.py +++ b/qutebrowser/browser/webengine/webengineinspector.py @@ -22,7 +22,7 @@ import os from PyQt5.QtCore import QUrl -from PyQt5.QtWebEngineWidgets import QWebEngineView +from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEngineSettings from qutebrowser.browser import inspector @@ -35,6 +35,8 @@ class WebEngineInspector(inspector.AbstractWebInspector): super().__init__(parent) self.port = None view = QWebEngineView() + settings = view.settings() + settings.setAttribute(QWebEngineSettings.JavascriptEnabled, True) self._set_widget(view) def inspect(self, _page): From 68e3ad6cba98eb2da76e029900a36ee16d350bb3 Mon Sep 17 00:00:00 2001 From: bitraid Date: Sun, 25 Mar 2018 19:43:59 +0300 Subject: [PATCH 154/223] Pyinstaller: don't use upx --- misc/qutebrowser.spec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/misc/qutebrowser.spec b/misc/qutebrowser.spec index c3da992af..0d4645dee 100644 --- a/misc/qutebrowser.spec +++ b/misc/qutebrowser.spec @@ -58,14 +58,14 @@ exe = EXE(pyz, icon=icon, debug=False, strip=False, - upx=True, + upx=False, console=False ) coll = COLLECT(exe, a.binaries, a.zipfiles, a.datas, strip=False, - upx=True, + upx=False, name='qutebrowser') app = BUNDLE(coll, From 91ca7d0911b24f09be947b52cef706dc2dba3092 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 25 Mar 2018 19:39:34 +0200 Subject: [PATCH 155/223] tests: Rename close function in window_open.html Naming it close() conflicts with the global JS close() --- tests/end2end/data/javascript/window_open.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/end2end/data/javascript/window_open.html b/tests/end2end/data/javascript/window_open.html index f842380d6..774acafc6 100644 --- a/tests/end2end/data/javascript/window_open.html +++ b/tests/end2end/data/javascript/window_open.html @@ -17,7 +17,7 @@ window.open('', 'my_window'); } - function close() { + function close_normal() { my_window.close(); } @@ -33,7 +33,7 @@ - + From d4ea1df2325c932343ff8c3f278512065fa057fb Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 25 Mar 2018 19:56:48 +0200 Subject: [PATCH 156/223] Improve window_open.html tests --- tests/end2end/data/javascript/window_open.html | 6 ++++-- tests/end2end/features/javascript.feature | 8 ++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/tests/end2end/data/javascript/window_open.html b/tests/end2end/data/javascript/window_open.html index 774acafc6..f5eafa0bc 100644 --- a/tests/end2end/data/javascript/window_open.html +++ b/tests/end2end/data/javascript/window_open.html @@ -3,10 +3,10 @@ diff --git a/tests/end2end/features/javascript.feature b/tests/end2end/features/javascript.feature index fb48a5932..8f715397f 100644 --- a/tests/end2end/features/javascript.feature +++ b/tests/end2end/features/javascript.feature @@ -15,7 +15,10 @@ Feature: Javascript stuff And I wait for "Changing title for idx 1 to 'about:blank'" in the log And I run :tab-focus 1 And I run :click-element id close-normal + And I wait for "[*] window closed" in the log Then "Focus object changed: *" should be logged + And the following tabs should be open: + - data/javascript/window_open.html (active) @qtwebkit_skip Scenario: Opening/closing a modal window via JS @@ -25,8 +28,11 @@ Feature: Javascript stuff And I wait for "Changing title for idx 1 to 'about:blank'" in the log And I run :tab-focus 1 And I run :click-element id close-normal + And I wait for "[*] window closed" in the log Then "Focus object changed: *" should be logged And "Web*Dialog requested, but we don't support that!" should be logged + And the following tabs should be open: + - data/javascript/window_open.html (active) # https://github.com/qutebrowser/qutebrowser/issues/906 @@ -39,6 +45,7 @@ Feature: Javascript stuff And I wait for "Changing title for idx 2 to 'about:blank'" in the log And I run :tab-focus 2 And I run :click-element id close-twice + And I wait for "[*] window closed" in the log Then "Requested to close * which does not exist!" should be logged @qtwebkit_skip @flaky @@ -51,6 +58,7 @@ Feature: Javascript stuff And I run :buffer window_open.html And I run :click-element id close-twice And I wait for "Focus object changed: *" in the log + And I wait for "[*] window closed" in the log Then no crash should happen @flaky From cecb79cf050dc77c6663a570aca6c90eb74ac3a8 Mon Sep 17 00:00:00 2001 From: Philip Lewis Date: Sun, 25 Mar 2018 15:18:02 -0400 Subject: [PATCH 157/223] Fix keyhints for special characters `prefix` is a string and `seq` is a key sequence, so removing `len(prefix)` items from `seq` will remove too many if `prefix` contains a special character (ex ""). Remove the number of characters from `str(seq)` instead. --- qutebrowser/misc/keyhintwidget.py | 2 +- tests/unit/misc/test_keyhints.py | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/qutebrowser/misc/keyhintwidget.py b/qutebrowser/misc/keyhintwidget.py index 11446aa40..e1de9a6cc 100644 --- a/qutebrowser/misc/keyhintwidget.py +++ b/qutebrowser/misc/keyhintwidget.py @@ -130,7 +130,7 @@ class KeyHintView(QLabel): ).format( html.escape(prefix), suffix_color, - html.escape(str(seq[len(prefix):])), + html.escape(str(seq)[len(prefix):]), html.escape(cmd) ) text = '{}
'.format(text) diff --git a/tests/unit/misc/test_keyhints.py b/tests/unit/misc/test_keyhints.py index c45913a2a..9ac6e6221 100644 --- a/tests/unit/misc/test_keyhints.py +++ b/tests/unit/misc/test_keyhints.py @@ -92,6 +92,30 @@ def test_suggestions(keyhint, config_stub): ('a', 'yellow', 'c', 'message-info cmd-ac')) +def test_suggestions_special(keyhint, config_stub): + """Test that special characters work properly as prefix.""" + bindings = {'normal': { + 'a': 'message-info cmd-Cca', + '': 'message-info cmd-CcCc', + '': 'message-info cmd-CcCx', + 'cbb': 'message-info cmd-cbb', + 'xd': 'message-info cmd-xd', + 'xe': 'message-info cmd-xe', + }} + default_bindings = {'normal': { + 'c': 'message-info cmd-Ccc', + }} + config_stub.val.bindings.default = default_bindings + config_stub.val.bindings.commands = bindings + + keyhint.update_keyhint('normal', '') + assert keyhint.text() == expected_text( + ('<Ctrl+c>', 'yellow', 'a', 'message-info cmd-Cca'), + ('<Ctrl+c>', 'yellow', 'c', 'message-info cmd-Ccc'), + ('<Ctrl+c>', 'yellow', '<Ctrl+c>', 'message-info cmd-CcCc'), + ('<Ctrl+c>', 'yellow', '<Ctrl+x>', 'message-info cmd-CcCx')) + + def test_suggestions_with_count(keyhint, config_stub, monkeypatch, stubs): """Test that a count prefix filters out commands that take no count.""" monkeypatch.setattr('qutebrowser.commands.cmdutils.cmd_dict', { From d4899240de0c7966ebcfa08659821b62e20d45a8 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 26 Mar 2018 10:51:04 +0200 Subject: [PATCH 158/223] Break long lines --- tests/unit/misc/test_keyhints.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/unit/misc/test_keyhints.py b/tests/unit/misc/test_keyhints.py index 9ac6e6221..7c9727b65 100644 --- a/tests/unit/misc/test_keyhints.py +++ b/tests/unit/misc/test_keyhints.py @@ -112,8 +112,10 @@ def test_suggestions_special(keyhint, config_stub): assert keyhint.text() == expected_text( ('<Ctrl+c>', 'yellow', 'a', 'message-info cmd-Cca'), ('<Ctrl+c>', 'yellow', 'c', 'message-info cmd-Ccc'), - ('<Ctrl+c>', 'yellow', '<Ctrl+c>', 'message-info cmd-CcCc'), - ('<Ctrl+c>', 'yellow', '<Ctrl+x>', 'message-info cmd-CcCx')) + ('<Ctrl+c>', 'yellow', '<Ctrl+c>', + 'message-info cmd-CcCc'), + ('<Ctrl+c>', 'yellow', '<Ctrl+x>', + 'message-info cmd-CcCx')) def test_suggestions_with_count(keyhint, config_stub, monkeypatch, stubs): From 6755e176303b66bcb789f0cc3bbb6090b54855bb Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 26 Mar 2018 10:54:15 +0200 Subject: [PATCH 159/223] Update changelog --- doc/changelog.asciidoc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index 10109518b..7de3fde84 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -57,6 +57,8 @@ Fixed - The icon on Windows should now be displayed in a higher resolution. - The QtWebEngine development tools (inspector) now also work when JavaScript is disabled globally. +- Building `.exe` files now works when `upx` is installed on the system. +- The keyhint widget now shows the correct text for chained modifiers. v1.2.1 ------ From edf2652431080286e36cef549c29ff6fc0cfd013 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 26 Mar 2018 18:17:13 +0200 Subject: [PATCH 160/223] Update flake8-builtins from 1.0.post0 to 1.1.1 --- misc/requirements/requirements-flake8.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-flake8.txt b/misc/requirements/requirements-flake8.txt index 5e8f557c1..04169779a 100644 --- a/misc/requirements/requirements-flake8.txt +++ b/misc/requirements/requirements-flake8.txt @@ -3,7 +3,7 @@ attrs==17.4.0 flake8==3.5.0 flake8-bugbear==18.2.0 -flake8-builtins==1.0.post0 # rq.filter: != 1.1.0 +flake8-builtins==1.1.1 # rq.filter: != 1.1.0 flake8-comprehensions==1.4.1 flake8-copyright==0.2.0 flake8-debugger==3.1.0 From ba9b166962504ac7fe907ed25d1cfb883629a826 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 26 Mar 2018 18:17:15 +0200 Subject: [PATCH 161/223] Update python-dateutil from 2.7.0 to 2.7.2 --- 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 c5e1b20ed..b04d43b90 100644 --- a/misc/requirements/requirements-pylint-master.txt +++ b/misc/requirements/requirements-pylint-master.txt @@ -9,7 +9,7 @@ isort==4.3.4 lazy-object-proxy==1.3.1 mccabe==0.6.1 -e git+https://github.com/PyCQA/pylint.git#egg=pylint -python-dateutil==2.7.0 +python-dateutil==2.7.2 ./scripts/dev/pylint_checkers requests==2.18.4 six==1.11.0 From 056a901da0b75551ef70862d24bbd45d5c501d72 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 26 Mar 2018 18:17:16 +0200 Subject: [PATCH 162/223] Update python-dateutil from 2.7.0 to 2.7.2 --- 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 1827150cb..861735ec9 100644 --- a/misc/requirements/requirements-pylint.txt +++ b/misc/requirements/requirements-pylint.txt @@ -9,7 +9,7 @@ isort==4.3.4 lazy-object-proxy==1.3.1 mccabe==0.6.1 pylint==1.8.3 -python-dateutil==2.7.0 +python-dateutil==2.7.2 ./scripts/dev/pylint_checkers requests==2.18.4 six==1.11.0 From b7b4cc7f31e69cfd343e1a300137ac9810a77c39 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 26 Mar 2018 18:17:17 +0200 Subject: [PATCH 163/223] Update hypothesis from 3.50.0 to 3.52.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 a485b54a7..72a51bbc7 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -11,7 +11,7 @@ fields==5.0.0 Flask==0.12.2 glob2==0.6 hunter==2.0.2 -hypothesis==3.50.0 +hypothesis==3.52.0 itsdangerous==0.24 # Jinja2==2.10 Mako==1.0.7 From 85be0f28012f91b1406ec195e93929958fdb6dc7 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 26 Mar 2018 18:17:19 +0200 Subject: [PATCH 164/223] Update py from 1.5.2 to 1.5.3 --- misc/requirements/requirements-tests.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index 72a51bbc7..0d415b7bd 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -20,7 +20,7 @@ more-itertools==4.1.0 parse==1.8.2 parse-type==0.4.2 pluggy==0.6.0 -py==1.5.2 +py==1.5.3 py-cpuinfo==3.3.0 pytest==3.4.2 pytest-bdd==2.20.0 From 55a818b156b787a753cb9f88324fdd49e4b82668 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 26 Mar 2018 18:17:20 +0200 Subject: [PATCH 165/223] Update py from 1.5.2 to 1.5.3 --- 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 d2b3a719b..ff6ae137f 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.2 +py==1.5.3 six==1.11.0 tox==2.9.1 virtualenv==15.1.0 From 496e6fc624d9754ea634a7c145455d8d6559eb08 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 26 Mar 2018 18:17:22 +0200 Subject: [PATCH 166/223] Update pytest from 3.4.2 to 3.5.0 --- misc/requirements/requirements-tests.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index 0d415b7bd..a154f8d30 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==3.3.0 -pytest==3.4.2 +pytest==3.5.0 pytest-bdd==2.20.0 pytest-benchmark==3.1.1 pytest-cov==2.5.1 From d98590d7128b927af72fa618c92c86e9ac67440b Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 26 Mar 2018 18:17:23 +0200 Subject: [PATCH 167/223] Update pytest-faulthandler from 1.4.1 to 1.5.0 --- misc/requirements/requirements-tests.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index a154f8d30..3a41dcb6d 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -26,7 +26,7 @@ pytest==3.5.0 pytest-bdd==2.20.0 pytest-benchmark==3.1.1 pytest-cov==2.5.1 -pytest-faulthandler==1.4.1 +pytest-faulthandler==1.5.0 pytest-instafail==0.3.0 pytest-mock==1.7.1 pytest-qt==2.3.1 From 4896765fccc1b226461ecf6569945e59f955d4a9 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 26 Mar 2018 18:17:25 +0200 Subject: [PATCH 168/223] Update virtualenv from 15.1.0 to 15.2.0 --- 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 ff6ae137f..adacf389a 100644 --- a/misc/requirements/requirements-tox.txt +++ b/misc/requirements/requirements-tox.txt @@ -4,4 +4,4 @@ pluggy==0.6.0 py==1.5.3 six==1.11.0 tox==2.9.1 -virtualenv==15.1.0 +virtualenv==15.2.0 From 22e887045b7ea81c6526ed5d73258fd8c1723d64 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 26 Mar 2018 18:27:15 +0200 Subject: [PATCH 169/223] Remove flake8-builtins filter --- misc/requirements/requirements-flake8.txt | 2 +- misc/requirements/requirements-flake8.txt-raw | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/misc/requirements/requirements-flake8.txt b/misc/requirements/requirements-flake8.txt index 04169779a..5eab7629b 100644 --- a/misc/requirements/requirements-flake8.txt +++ b/misc/requirements/requirements-flake8.txt @@ -3,7 +3,7 @@ attrs==17.4.0 flake8==3.5.0 flake8-bugbear==18.2.0 -flake8-builtins==1.1.1 # rq.filter: != 1.1.0 +flake8-builtins==1.1.1 flake8-comprehensions==1.4.1 flake8-copyright==0.2.0 flake8-debugger==3.1.0 diff --git a/misc/requirements/requirements-flake8.txt-raw b/misc/requirements/requirements-flake8.txt-raw index 291313930..1f30b83ae 100644 --- a/misc/requirements/requirements-flake8.txt-raw +++ b/misc/requirements/requirements-flake8.txt-raw @@ -1,6 +1,6 @@ flake8 flake8-bugbear -flake8-builtins!=1.1.0 +flake8-builtins flake8-comprehensions flake8-copyright flake8-debugger @@ -15,5 +15,3 @@ flake8-tuple pep8-naming pydocstyle pyflakes - -#@ filter: flake8-builtins != 1.1.0 \ No newline at end of file From 021bb256227574351f5683c8c17f157f643c4eb7 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 26 Mar 2018 22:53:13 +0200 Subject: [PATCH 170/223] Add regenerating website to contributing.asciidoc [ci skip] (cherry picked from commit 9f95736bbe4a00c9cc4a8b222ab3dc55d6bdf96c) --- doc/contributing.asciidoc | 4 +++- tests/unit/browser/webkit/test_qt_javascript.py | 8 ++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/doc/contributing.asciidoc b/doc/contributing.asciidoc index 2c95c125e..ad48c1324 100644 --- a/doc/contributing.asciidoc +++ b/doc/contributing.asciidoc @@ -687,6 +687,8 @@ 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). -* On server: Run `python3 scripts/dev/download_release.py 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` * Update `qutebrowser-git` PKGBUILD if dependencies/install changed. * Announce to qutebrowser and qutebrowser-announce mailinglist. diff --git a/tests/unit/browser/webkit/test_qt_javascript.py b/tests/unit/browser/webkit/test_qt_javascript.py index 901c373c4..1b1b1e331 100644 --- a/tests/unit/browser/webkit/test_qt_javascript.py +++ b/tests/unit/browser/webkit/test_qt_javascript.py @@ -22,6 +22,8 @@ import pytest +from PyQt5.QtCore import QUrl + @pytest.mark.parametrize('js_enabled, expected', [(True, 2.0), (False, None)]) def test_simple_js_webkit(webview, js_enabled, expected): @@ -49,13 +51,15 @@ def test_element_js_webkit(webview, js_enabled, expected): @pytest.mark.usefixtures('redirect_webengine_data') @pytest.mark.parametrize('js_enabled, expected', [(True, 2.0), (False, 2.0)]) def test_simple_js_webengine(callback_checker, webengineview, js_enabled, - expected): + expected, qapp): """With QtWebEngine, runJavaScript works even when JS is off.""" # If we get there (because of the webengineview fixture) we can be certain # QtWebEngine is available from PyQt5.QtWebEngineWidgets import QWebEngineSettings webengineview.settings().setAttribute(QWebEngineSettings.JavascriptEnabled, js_enabled) + qapp.processEvents() - webengineview.page().runJavaScript('1 + 1', callback_checker.callback) + webengineview.setHtml('') + webengineview.page().runJavaScript('1 + 1', 1, callback_checker.callback) callback_checker.check(expected) From 6ecea8ef17679bacd876d7fe65471e4b59f8ad8a Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 27 Mar 2018 07:13:17 +0200 Subject: [PATCH 171/223] Revert accidental changes to test_qt_javascript.py --- tests/unit/browser/webkit/test_qt_javascript.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/tests/unit/browser/webkit/test_qt_javascript.py b/tests/unit/browser/webkit/test_qt_javascript.py index 1b1b1e331..901c373c4 100644 --- a/tests/unit/browser/webkit/test_qt_javascript.py +++ b/tests/unit/browser/webkit/test_qt_javascript.py @@ -22,8 +22,6 @@ import pytest -from PyQt5.QtCore import QUrl - @pytest.mark.parametrize('js_enabled, expected', [(True, 2.0), (False, None)]) def test_simple_js_webkit(webview, js_enabled, expected): @@ -51,15 +49,13 @@ def test_element_js_webkit(webview, js_enabled, expected): @pytest.mark.usefixtures('redirect_webengine_data') @pytest.mark.parametrize('js_enabled, expected', [(True, 2.0), (False, 2.0)]) def test_simple_js_webengine(callback_checker, webengineview, js_enabled, - expected, qapp): + expected): """With QtWebEngine, runJavaScript works even when JS is off.""" # If we get there (because of the webengineview fixture) we can be certain # QtWebEngine is available from PyQt5.QtWebEngineWidgets import QWebEngineSettings webengineview.settings().setAttribute(QWebEngineSettings.JavascriptEnabled, js_enabled) - qapp.processEvents() - webengineview.setHtml('') - webengineview.page().runJavaScript('1 + 1', 1, callback_checker.callback) + webengineview.page().runJavaScript('1 + 1', callback_checker.callback) callback_checker.check(expected) From 6dbd6d1ddf54f039f9d9f33eb544c32cddc22414 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 27 Mar 2018 07:14:05 +0200 Subject: [PATCH 172/223] Move test_qt_javascript.py --- .../test_qt_javascript.py => javascript/test_js_execution.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/unit/{browser/webkit/test_qt_javascript.py => javascript/test_js_execution.py} (100%) diff --git a/tests/unit/browser/webkit/test_qt_javascript.py b/tests/unit/javascript/test_js_execution.py similarity index 100% rename from tests/unit/browser/webkit/test_qt_javascript.py rename to tests/unit/javascript/test_js_execution.py From 2249a88e3a290886a6362e877bf951a0916e938e Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 27 Mar 2018 07:29:43 +0200 Subject: [PATCH 173/223] Make test_simple_js_webengine work correctly --- tests/unit/javascript/test_js_execution.py | 31 +++++++++++++++++----- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/tests/unit/javascript/test_js_execution.py b/tests/unit/javascript/test_js_execution.py index 901c373c4..c5b5018d2 100644 --- a/tests/unit/javascript/test_js_execution.py +++ b/tests/unit/javascript/test_js_execution.py @@ -47,15 +47,32 @@ def test_element_js_webkit(webview, js_enabled, expected): @pytest.mark.usefixtures('redirect_webengine_data') -@pytest.mark.parametrize('js_enabled, expected', [(True, 2.0), (False, 2.0)]) -def test_simple_js_webengine(callback_checker, webengineview, js_enabled, - expected): +@pytest.mark.parametrize('js_enabled, world, expected', [ + # main world + (True, 0, 2.0), + (False, 0, None), + # application world + (True, 1, 2.0), + (False, 1, 2.0), + # user world + (True, 2, 2.0), + (False, 2, 2.0), +]) +def test_simple_js_webengine(callback_checker, webengineview, qapp, + js_enabled, world, expected): """With QtWebEngine, runJavaScript works even when JS is off.""" # If we get there (because of the webengineview fixture) we can be certain # QtWebEngine is available - from PyQt5.QtWebEngineWidgets import QWebEngineSettings - webengineview.settings().setAttribute(QWebEngineSettings.JavascriptEnabled, - js_enabled) + from PyQt5.QtWebEngineWidgets import QWebEngineSettings, QWebEngineScript - webengineview.page().runJavaScript('1 + 1', callback_checker.callback) + assert world in [QWebEngineScript.MainWorld, + QWebEngineScript.ApplicationWorld, + QWebEngineScript.UserWorld] + + settings = webengineview.settings() + settings.setAttribute(QWebEngineSettings.JavascriptEnabled, js_enabled) + qapp.processEvents() + + page = webengineview.page() + page.runJavaScript('1 + 1', world, callback_checker.callback) callback_checker.check(expected) From a6f6fdf19b0665d81261642384bd67e2edd3b6f8 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 27 Mar 2018 11:09:18 +0200 Subject: [PATCH 174/223] Update docs --- doc/changelog.asciidoc | 2 ++ doc/help/settings.asciidoc | 4 +++- qutebrowser/config/configdata.yml | 6 +++--- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index 7de3fde84..1eaf473e9 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -39,6 +39,8 @@ Changed - `:version` now shows OS information for POSIX OS other than Linux/macOS. - When there's an error inserting the text from an external editor, a backup file is now saved. +- The `window.hide_wayland_decoration` setting got renamed to + `window.hide_decoration` and now also works outside of wayland. Fixed ~~~~~ diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc index dbee21b8f..621d04c6b 100644 --- a/doc/help/settings.asciidoc +++ b/doc/help/settings.asciidoc @@ -3160,7 +3160,9 @@ Default: [[window.hide_decoration]] === window.hide_decoration Hide the window decoration. -This setting requires a restart. + +This setting requires a restart on Wayland. + Type: <> diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml index bbc49b374..1de220938 100644 --- a/qutebrowser/config/configdata.yml +++ b/qutebrowser/config/configdata.yml @@ -1539,10 +1539,10 @@ window.hide_wayland_decoration: window.hide_decoration: type: Bool default: false - restart: false desc: | - Hide the window decoration. Does require a restart for - Wayland users. + Hide the window decoration. + + This setting requires a restart on Wayland. window.title_format: type: From 7e66c3cf464af7ef2ed3a6550de5aac0f0289692 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 27 Mar 2018 11:11:34 +0200 Subject: [PATCH 175/223] Update changelog --- doc/changelog.asciidoc | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index 1eaf473e9..d980c1296 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -41,6 +41,7 @@ Changed file is now saved. - The `window.hide_wayland_decoration` setting got renamed to `window.hide_decoration` and now also works outside of wayland. +- Hover tooltips on tabs now always show the webpage's title. Fixed ~~~~~ From 68b707c749ffbf79222d7a2242f3340daf331543 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 27 Mar 2018 12:01:42 +0200 Subject: [PATCH 176/223] Update changelog --- doc/changelog.asciidoc | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index d980c1296..dae1da098 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -47,6 +47,7 @@ Fixed ~~~~~ - Using hints before a page is fully loaded is now possible again. +- Selecting hints with the number keypad now works again. - Tab titles for tabs loaded from sessions should now really be correct instead of showing the URL. - Loading URLs with customized settings from a session now avoids an additional From 046a3dc159a04362b31f9411c82a1c8e75a70328 Mon Sep 17 00:00:00 2001 From: Jussi Timperi Date: Tue, 6 Mar 2018 23:46:54 +0200 Subject: [PATCH 177/223] Add option to only show favicons for pinned tabs Closes #3440 --- qutebrowser/browser/browsertab.py | 4 ++++ qutebrowser/browser/commands.py | 2 +- qutebrowser/config/configdata.yml | 11 ++++++++--- qutebrowser/config/configfiles.py | 9 +++++++++ qutebrowser/mainwindow/tabbedbrowser.py | 16 ++++------------ qutebrowser/mainwindow/tabwidget.py | 16 +++++++++++++++- tests/helpers/stubs.py | 3 +++ tests/unit/config/test_configfiles.py | 16 ++++++++++++++++ 8 files changed, 60 insertions(+), 17 deletions(-) diff --git a/qutebrowser/browser/browsertab.py b/qutebrowser/browser/browsertab.py index dd3673aa6..aea15a257 100644 --- a/qutebrowser/browser/browsertab.py +++ b/qutebrowser/browser/browsertab.py @@ -114,6 +114,10 @@ class TabData: netrc_used = attr.ib(False) input_mode = attr.ib(usertypes.KeyMode.normal) + def should_show_icon(self): + return (config.val.tabs.favicons.show == 'always' or + config.val.tabs.favicons.show == 'pinned' and self.pinned) + class AbstractAction: diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index d492eb513..f2438cebe 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -502,7 +502,7 @@ class CommandDispatcher: idx = new_tabbed_browser.widget.indexOf(newtab) new_tabbed_browser.widget.set_page_title(idx, cur_title) - if config.val.tabs.favicons.show: + if curtab.data.should_show_icon(): new_tabbed_browser.widget.setTabIcon(idx, curtab.icon()) if config.val.tabs.tabs_are_windows: new_tabbed_browser.widget.window().setWindowIcon(curtab.icon()) diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml index 1de220938..556e4af33 100644 --- a/qutebrowser/config/configdata.yml +++ b/qutebrowser/config/configdata.yml @@ -1252,9 +1252,14 @@ tabs.favicons.scale: `tabs.padding`. tabs.favicons.show: - default: true - type: Bool - desc: Show favicons in the tab bar. + default: always + type: + name: String + valid_values: + - always: Always show favicons. + - never: Always hide favicons. + - pinned: Show favicons only on pinned tabs. + desc: When to show favicons in the tab bar. tabs.last_close: default: ignore diff --git a/qutebrowser/config/configfiles.py b/qutebrowser/config/configfiles.py index fdb1583e0..986ca3f56 100644 --- a/qutebrowser/config/configfiles.py +++ b/qutebrowser/config/configfiles.py @@ -268,6 +268,15 @@ class YamlConfig(QObject): del settings['bindings.default'] self._mark_changed() + # Option to show favicons only for pinned tabs changed the type of + # tabs.favicons.show from Bool to String + name = 'tabs.favicons.show' + if name in settings: + for scope, val in settings[name].items(): + if isinstance(val, bool): + settings[name][scope] = 'always' if val else 'never' + self._mark_changed() + return settings def _validate(self, settings): diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py index cedb0cf33..ce36f0038 100644 --- a/qutebrowser/mainwindow/tabbedbrowser.py +++ b/qutebrowser/mainwindow/tabbedbrowser.py @@ -531,16 +531,8 @@ class TabbedBrowser(QWidget): def _update_favicons(self): """Update favicons when config was changed.""" - for i, tab in enumerate(self.widgets()): - if config.val.tabs.favicons.show: - self.widget.setTabIcon(i, tab.icon()) - if config.val.tabs.tabs_are_windows: - self.widget.window().setWindowIcon(tab.icon()) - else: - self.widget.setTabIcon(i, QIcon()) - if config.val.tabs.tabs_are_windows: - window = self.widget.window() - window.setWindowIcon(self.default_window_icon) + for tab in self.widgets(): + self.widget.update_tab_favicon(tab) @pyqtSlot() def on_load_started(self, tab): @@ -559,7 +551,7 @@ class TabbedBrowser(QWidget): tab.data.keep_icon = False else: if (config.val.tabs.tabs_are_windows and - config.val.tabs.favicons.show): + tab.data.should_show_icon()): self.widget.window().setWindowIcon(self.default_window_icon) if idx == self.widget.currentIndex(): self._update_window_title() @@ -623,7 +615,7 @@ class TabbedBrowser(QWidget): tab: The WebView where the title was changed. icon: The new icon """ - if not config.val.tabs.favicons.show: + if not tab.data.should_show_icon(): return try: idx = self._tab_index(tab) diff --git a/qutebrowser/mainwindow/tabwidget.py b/qutebrowser/mainwindow/tabwidget.py index a8cdcf54e..6a6eac901 100644 --- a/qutebrowser/mainwindow/tabwidget.py +++ b/qutebrowser/mainwindow/tabwidget.py @@ -108,6 +108,7 @@ class TabWidget(QTabWidget): bar.set_tab_data(idx, 'pinned', pinned) tab.data.pinned = pinned + self.update_tab_favicon(tab) self.update_tab_title(idx) def tab_indicator_color(self, idx): @@ -300,6 +301,19 @@ class TabWidget(QTabWidget): qtutils.ensure_valid(url) return url + def update_tab_favicon(self, tab: QWidget): + """Update favicon of the given tab.""" + idx = self.indexOf(tab) + + if tab.data.should_show_icon(): + self.setTabIcon(idx, tab.icon()) + if config.val.tabs.tabs_are_windows: + self.window().setWindowIcon(tab.icon()) + else: + self.setTabIcon(idx, QIcon()) + if config.val.tabs.tabs_are_windows: + self.window().setWindowIcon(self.window().windowIcon()) + class TabBar(QTabBar): @@ -906,7 +920,7 @@ class TabBarStyle(QCommonStyle): # reserve space for favicon when tab bar is vertical (issue #1968) position = config.val.tabs.position if (position in [QTabWidget.East, QTabWidget.West] and - config.val.tabs.favicons.show): + config.val.tabs.favicons.show != 'never'): tab_icon_size = icon_size else: actual_size = opt.icon.actualSize(icon_size, icon_mode, icon_state) diff --git a/tests/helpers/stubs.py b/tests/helpers/stubs.py index ea7cbf268..edc81f139 100644 --- a/tests/helpers/stubs.py +++ b/tests/helpers/stubs.py @@ -266,6 +266,9 @@ class FakeWebTab(browsertab.AbstractTab): def shutdown(self): pass + def icon(self): + return self.windowIcon() + class FakeSignal: diff --git a/tests/unit/config/test_configfiles.py b/tests/unit/config/test_configfiles.py index 96f5d4976..13873d98e 100644 --- a/tests/unit/config/test_configfiles.py +++ b/tests/unit/config/test_configfiles.py @@ -233,6 +233,22 @@ class TestYaml: data = autoconfig.read() assert 'bindings.default' not in data + @pytest.mark.parametrize('show', + [True, False, 'always', 'never', 'pinned']) + def test_tabs_favicons_show(self, yaml, autoconfig, show): + """Tests for migration of tabs.favicons.show.""" + autoconfig.write({'tabs.favicons.show': {'global': show}}) + + yaml.load() + yaml._save() + + data = autoconfig.read() + if isinstance(show, bool): + when = 'always' if show else 'never' + else: + when = show + assert data['tabs.favicons.show']['global'] == when + def test_renamed_key_unknown_target(self, monkeypatch, yaml, autoconfig): """A key marked as renamed with invalid name should raise an error.""" From a828851640865f1b9879b1cfb969af4b710a292c Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 28 Mar 2018 08:48:18 +0200 Subject: [PATCH 178/223] Update recommended Qt version in readme --- README.asciidoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.asciidoc b/README.asciidoc index 8dfc11c9f..6656c2bbd 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -99,7 +99,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 with the following modules: +* 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) - QtSQL (part of qtbase in some distributions) @@ -109,7 +109,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.9.2 recommended) for Python 3 + (5.10 recommended) for Python 3 * https://pypi.python.org/pypi/setuptools/[pkg_resources/setuptools] * http://fdik.org/pyPEG/[pyPEG2] * http://jinja.pocoo.org/[jinja2] From c7e5033eaa3580d5044a3393dad39e3fb53dc418 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 28 Mar 2018 08:58:07 +0200 Subject: [PATCH 179/223] Set MainWindow as parent of TabbedBrowser If we close the MainWindow (and it gets deleted), we need to make sure to delete the TabbedBrowser as well. Fixes #3781 --- qutebrowser/mainwindow/mainwindow.py | 3 ++- tests/end2end/features/misc.feature | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/qutebrowser/mainwindow/mainwindow.py b/qutebrowser/mainwindow/mainwindow.py index 596280e81..0f66d6797 100644 --- a/qutebrowser/mainwindow/mainwindow.py +++ b/qutebrowser/mainwindow/mainwindow.py @@ -184,7 +184,8 @@ class MainWindow(QWidget): private = bool(private) self._private = private self.tabbed_browser = tabbedbrowser.TabbedBrowser(win_id=self.win_id, - private=private) + private=private, + parent=self) objreg.register('tabbed-browser', self.tabbed_browser, scope='window', window=self.win_id) self._init_command_dispatcher() diff --git a/tests/end2end/features/misc.feature b/tests/end2end/features/misc.feature index 743cffd84..27f68e1bb 100644 --- a/tests/end2end/features/misc.feature +++ b/tests/end2end/features/misc.feature @@ -568,4 +568,4 @@ Feature: Various utility commands. Scenario: Simple adblock update When I set up "simple" as block lists And I run :adblock-update - Then the message "adblock: Read 1 hosts from 1 sources." should be shown + Then the message "adblock: Read 1 hosts from 1 sources." should be shown \ No newline at end of file From 005fa8b6754529d8b34485dfb5c5dc6a658bea76 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 28 Mar 2018 09:14:26 +0200 Subject: [PATCH 180/223] Fix newline --- tests/end2end/features/misc.feature | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/end2end/features/misc.feature b/tests/end2end/features/misc.feature index 27f68e1bb..743cffd84 100644 --- a/tests/end2end/features/misc.feature +++ b/tests/end2end/features/misc.feature @@ -568,4 +568,4 @@ Feature: Various utility commands. Scenario: Simple adblock update When I set up "simple" as block lists And I run :adblock-update - Then the message "adblock: Read 1 hosts from 1 sources." should be shown \ No newline at end of file + Then the message "adblock: Read 1 hosts from 1 sources." should be shown From 32568a6da44468a03a2e08b1a0cbe8c4a913c3ca Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 28 Mar 2018 09:33:27 +0200 Subject: [PATCH 181/223] Simplify tests --- tests/helpers/stubs.py | 3 ++- tests/unit/config/test_configfiles.py | 27 +++++++++++++++------------ 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/tests/helpers/stubs.py b/tests/helpers/stubs.py index edc81f139..545b32fb4 100644 --- a/tests/helpers/stubs.py +++ b/tests/helpers/stubs.py @@ -27,6 +27,7 @@ import shutil import attr from PyQt5.QtCore import pyqtSignal, QPoint, QProcess, QObject, QUrl +from PyQt5.QtGui import QIcon from PyQt5.QtNetwork import (QNetworkRequest, QAbstractNetworkCache, QNetworkCacheMetaData) from PyQt5.QtWidgets import QCommonStyle, QLineEdit, QWidget, QTabBar @@ -267,7 +268,7 @@ class FakeWebTab(browsertab.AbstractTab): pass def icon(self): - return self.windowIcon() + return QIcon() class FakeSignal: diff --git a/tests/unit/config/test_configfiles.py b/tests/unit/config/test_configfiles.py index 13873d98e..5a1fea72c 100644 --- a/tests/unit/config/test_configfiles.py +++ b/tests/unit/config/test_configfiles.py @@ -211,8 +211,11 @@ class TestYaml: data = autoconfig.read() assert data == {'tabs.show': {'global': 'value'}} - @pytest.mark.parametrize('persist', [True, False]) - def test_merge_persist(self, yaml, autoconfig, persist): + @pytest.mark.parametrize('persist, expected', [ + (True, 'persist'), + (False, 'normal'), + ]) + def test_merge_persist(self, yaml, autoconfig, persist, expected): """Tests for migration of tabs.persist_mode_on_change.""" autoconfig.write({'tabs.persist_mode_on_change': {'global': persist}}) yaml.load() @@ -220,8 +223,7 @@ class TestYaml: data = autoconfig.read() assert 'tabs.persist_mode_on_change' not in data - mode = 'persist' if persist else 'normal' - assert data['tabs.mode_on_change']['global'] == mode + assert data['tabs.mode_on_change']['global'] == expected def test_bindings_default(self, yaml, autoconfig): """Make sure bindings.default gets removed from autoconfig.yml.""" @@ -233,9 +235,14 @@ class TestYaml: data = autoconfig.read() assert 'bindings.default' not in data - @pytest.mark.parametrize('show', - [True, False, 'always', 'never', 'pinned']) - def test_tabs_favicons_show(self, yaml, autoconfig, show): + @pytest.mark.parametrize('show, expected', [ + (True, 'always'), + (False, 'never'), + ('always', 'always'), + ('never', 'never'), + ('pinned', 'pinned'), + ]) + def test_tabs_favicons_show(self, yaml, autoconfig, show, expected): """Tests for migration of tabs.favicons.show.""" autoconfig.write({'tabs.favicons.show': {'global': show}}) @@ -243,11 +250,7 @@ class TestYaml: yaml._save() data = autoconfig.read() - if isinstance(show, bool): - when = 'always' if show else 'never' - else: - when = show - assert data['tabs.favicons.show']['global'] == when + assert data['tabs.favicons.show']['global'] == expected def test_renamed_key_unknown_target(self, monkeypatch, yaml, autoconfig): From 758ea8b17108f7159bef057c0fe0ba5626115556 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 28 Mar 2018 09:36:32 +0200 Subject: [PATCH 182/223] Update changelog --- doc/changelog.asciidoc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index dae1da098..da41f6069 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -41,6 +41,9 @@ Changed file is now saved. - The `window.hide_wayland_decoration` setting got renamed to `window.hide_decoration` and now also works outside of wayland. +- The `tabs.favicons.show` setting now can take three values: `'always'` (was + `True`), `'never'` (was `False`) and `'pinned'` (to only show favicons for + pinned tabs). - Hover tooltips on tabs now always show the webpage's title. Fixed From 1ccb464d1cb9d713915cc054f6a45075aac5950a Mon Sep 17 00:00:00 2001 From: George Edward Bulmer Date: Wed, 28 Mar 2018 14:17:13 +0100 Subject: [PATCH 183/223] Return removed comment about hosts format --- qutebrowser/browser/adblock.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qutebrowser/browser/adblock.py b/qutebrowser/browser/adblock.py index 1cee34f25..f42d1a1db 100644 --- a/qutebrowser/browser/adblock.py +++ b/qutebrowser/browser/adblock.py @@ -230,6 +230,7 @@ class HostBlocker: # "one host per line" format hosts = [parts[0]] else: + # /etc/hosts format hosts = parts[1:] for host in hosts: From 2789bec1e79621d78ba2716e4a28dd8e52301367 Mon Sep 17 00:00:00 2001 From: George Edward Bulmer Date: Wed, 28 Mar 2018 14:27:17 +0100 Subject: [PATCH 184/223] Modify assert_url to treat localhost differently --- tests/unit/browser/test_adblock.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/unit/browser/test_adblock.py b/tests/unit/browser/test_adblock.py index 69392fc7b..8ab3b930d 100644 --- a/tests/unit/browser/test_adblock.py +++ b/tests/unit/browser/test_adblock.py @@ -114,14 +114,16 @@ def create_blocklist(directory, blocked_hosts=BLOCKLIST_HOSTS, return name -def assert_urls(host_blocker, blocked=BLOCKLIST_HOSTS[1:], +def assert_urls(host_blocker, blocked=BLOCKLIST_HOSTS, whitelisted=WHITELISTED_HOSTS, urls_to_check=URLS_TO_CHECK): """Test if Urls to check are blocked or not by HostBlocker. Ensure URLs in 'blocked' and not in 'whitelisted' are blocked. All other URLs must not be blocked. + + localhost is an example of a special case that shouldn't be blocked. """ - whitelisted = list(whitelisted) + whitelisted = list(whitelisted) + ['localhost'] for str_url in urls_to_check: url = QUrl(str_url) host = url.host() From 5bc794f85a87234a282ca88d11e849a02ba9b985 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 28 Mar 2018 21:11:20 +0200 Subject: [PATCH 185/223] Update changelog --- doc/changelog.asciidoc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index da41f6069..0e7b72a50 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -45,6 +45,10 @@ Changed `True`), `'never'` (was `False`) and `'pinned'` (to only show favicons for pinned tabs). - Hover tooltips on tabs now always show the webpage's title. +- The default value for `content.host_blocking.lists` was changed to only + include https://github.com/StevenBlack/hosts[Steven Black's hosts-list] which + combines various sources. + Fixed ~~~~~ From 7f5a79cdfd25621cbad7d1b67c47dfae1ec86281 Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Fri, 30 Mar 2018 00:49:36 -0400 Subject: [PATCH 186/223] Escape strings with string_escape rather than tojson --- qutebrowser/browser/greasemonkey.py | 8 +++++--- qutebrowser/javascript/greasemonkey_wrapper.js | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/qutebrowser/browser/greasemonkey.py b/qutebrowser/browser/greasemonkey.py index aed4e04cf..41e4a1ed4 100644 --- a/qutebrowser/browser/greasemonkey.py +++ b/qutebrowser/browser/greasemonkey.py @@ -30,7 +30,8 @@ import textwrap import attr from PyQt5.QtCore import pyqtSignal, QObject, QUrl -from qutebrowser.utils import log, standarddir, jinja, objreg, utils +from qutebrowser.utils import (log, standarddir, jinja, objreg, utils, + javascript) from qutebrowser.commands import cmdutils from qutebrowser.browser import downloads @@ -106,9 +107,10 @@ class GreasemonkeyScript: """ template = jinja.js_environment.get_template('greasemonkey_wrapper.js') return template.render( - scriptName="/".join([self.namespace or '', self.name]), + scriptName=javascript.string_escape( + "/".join([self.namespace or '', self.name])), scriptInfo=self._meta_json(), - scriptMeta=self.script_meta, + scriptMeta=javascript.string_escape(self.script_meta), scriptSource=self._code) def _meta_json(self): diff --git a/qutebrowser/javascript/greasemonkey_wrapper.js b/qutebrowser/javascript/greasemonkey_wrapper.js index 71266755a..387856e5c 100644 --- a/qutebrowser/javascript/greasemonkey_wrapper.js +++ b/qutebrowser/javascript/greasemonkey_wrapper.js @@ -1,5 +1,5 @@ (function() { - const _qute_script_id = "__gm_" + {{ scriptName | tojson }}; + const _qute_script_id = "__gm_" + "{{ scriptName }}"; function GM_log(text) { console.log(text); @@ -7,7 +7,7 @@ const GM_info = { 'script': {{ scriptInfo }}, - 'scriptMetaStr': {{ scriptMeta | tojson }}, + 'scriptMetaStr': "{{ scriptMeta }}", 'scriptWillUpdate': false, 'version': "0.0.1", // so scripts don't expect exportFunction From d438aa15faac0f55f61628dc62bdcf2ee2150d4e Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 30 Mar 2018 11:48:06 +0200 Subject: [PATCH 187/223] Simplify setting _qute_script_id --- qutebrowser/javascript/greasemonkey_wrapper.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutebrowser/javascript/greasemonkey_wrapper.js b/qutebrowser/javascript/greasemonkey_wrapper.js index 387856e5c..2f269f180 100644 --- a/qutebrowser/javascript/greasemonkey_wrapper.js +++ b/qutebrowser/javascript/greasemonkey_wrapper.js @@ -1,5 +1,5 @@ (function() { - const _qute_script_id = "__gm_" + "{{ scriptName }}"; + const _qute_script_id = "__gm_{{ scriptName }}"; function GM_log(text) { console.log(text); From 6151d077e51f45460fa5c8685fea3ddef62c9886 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 30 Mar 2018 11:49:39 +0200 Subject: [PATCH 188/223] Update changelog --- doc/changelog.asciidoc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index 0e7b72a50..e3ab6e9c8 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -70,6 +70,8 @@ Fixed disabled globally. - Building `.exe` files now works when `upx` is installed on the system. - The keyhint widget now shows the correct text for chained modifiers. +- Loading GreaseMonkey scripts now also works with Jinja2 2.8 (e.g. on Debian + Stable). v1.2.1 ------ From 599a3d75a4dcb843e94bd5cd43eceee1d7a3136a Mon Sep 17 00:00:00 2001 From: pylipp Date: Sat, 31 Mar 2018 21:43:20 +0200 Subject: [PATCH 189/223] Add userscript to download bibtex for DOI scraped from current tab --- misc/userscripts/getbib | 69 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100755 misc/userscripts/getbib diff --git a/misc/userscripts/getbib b/misc/userscripts/getbib new file mode 100755 index 000000000..22af7a8f9 --- /dev/null +++ b/misc/userscripts/getbib @@ -0,0 +1,69 @@ +#!/usr/bin/env python3 +"""Qutebrowser userscript scraping the current web page for DOIs and downloading +corresponding bibtex information. + +Set the environment variable 'QUTE_BIB_FILEPATH' to indicate the path to +download to. Otherwise, bibtex information is downloaded to '/tmp' and hence +deleted at reboot. + +Installation: see qute://help/userscripts.html + +Inspired by +https://ocefpaf.github.io/python4oceanographers/blog/2014/05/19/doi2bibtex/ +""" + +import os +import sys +import shutil +import re +from collections import Counter +from urllib import parse as url_parse +from urllib import request as url_request + + +FIFO_PATH = os.getenv("QUTE_FIFO") + +def message_fifo(message, level="warning"): + """Send message to qutebrowser FIFO. The level must be one of 'info', + 'warning' (default) or 'error'.""" + with open(FIFO_PATH, "w") as fifo: + fifo.write("message-{} '{}'".format(level, message)) + + +source = os.getenv("QUTE_TEXT") +with open(source) as f: + text = f.read() + +# find DOIs on page using regex +dval = re.compile(r'(10\.(\d)+/([^(\s\>\"\<)])+)') +# https://stackoverflow.com/a/10324802/3865876, too strict +# dval = re.compile(r'\b(10[.][0-9]{4,}(?:[.][0-9]+)*/(?:(?!["&\'<>])\S)+)\b') +dois = dval.findall(text) +dois = Counter(e[0] for e in dois) +try: + doi = dois.most_common(1)[0][0] +except IndexError: + message_fifo("No DOIs found on page") + sys.exit() +message_fifo("Found {} DOIs on page, selecting {}".format(len(dois), doi), + level="info") + +# get bibtex data corresponding to DOI +url = "http://dx.doi.org/" + url_parse.quote(doi) +headers = dict(Accept='text/bibliography; style=bibtex') +request = url_request.Request(url, headers=headers) +response = url_request.urlopen(request) +status_code = response.getcode() +if status_code >= 400: + message_fifo("Request returned {}".format(status_code)) + sys.exit() + +# obtain content and format it +bibtex = response.read().decode("utf-8").strip() +bibtex = bibtex.replace(" ", "\n ", 1).\ + replace("}, ", "},\n ").replace("}}", "}\n}") + +# append to file +bib_filepath = os.getenv("QUTE_BIB_FILEPATH", "/tmp/qute.bib") +with open(bib_filepath, "a") as f: + f.write(bibtex + "\n\n") From 1c0616f3ec6f394dbaa7be1f2700d7353bc70a0d Mon Sep 17 00:00:00 2001 From: Jimmy Date: Sun, 1 Apr 2018 12:38:48 +1200 Subject: [PATCH 190/223] Greasemonkey: fix early addstyle with fast sites. nemanjan00 reported this script not having any effect https://gist.github.com/nemanjan00/01537ec65cd672d1f125170c7807f400/raw/da850e49cc28bc9c94e1a3fb564939eaa2e0aa77/duckduckgo-deepdark.user.js It turns out that the previous implementation of GM_addStyle was relying on `document.onreadystatechange` when the script ran early enough that there was no `head` element. That event wasn't getting fired for the main frame of duckduckgo.com for whatever reason. Maybe using `DOMContentLoaded` or something would have worked but I just copied the fallback in the above linked script which seems to work just fine. --- qutebrowser/javascript/greasemonkey_wrapper.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/qutebrowser/javascript/greasemonkey_wrapper.js b/qutebrowser/javascript/greasemonkey_wrapper.js index 2f269f180..0731e93ac 100644 --- a/qutebrowser/javascript/greasemonkey_wrapper.js +++ b/qutebrowser/javascript/greasemonkey_wrapper.js @@ -100,11 +100,8 @@ const head = document.getElementsByTagName("head")[0]; if (head === undefined) { - document.onreadystatechange = function() { - if (document.readyState === "interactive") { - document.getElementsByTagName("head")[0].appendChild(oStyle); - } - }; + // no head yet, stick it whereever + document.documentElement.appendChild(oStyle); } else { head.appendChild(oStyle); } From 79823a9a0bb934d92226a8a47ce8830b231ce167 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 1 Apr 2018 13:22:01 +0200 Subject: [PATCH 191/223] Regenerate docs --- doc/help/settings.asciidoc | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc index 621d04c6b..1e58ffa87 100644 --- a/doc/help/settings.asciidoc +++ b/doc/help/settings.asciidoc @@ -236,7 +236,7 @@ |<>|Mouse button with which to close tabs. |<>|How to behave when the close mouse button is pressed on the tab bar. |<>|Scaling factor for favicons in the tab bar. -|<>|Show favicons in the tab bar. +|<>|When to show favicons in the tab bar. |<>|Padding (in pixels) for tab indicators. |<>|Width (in pixels) of the progress indicator (0 to disable). |<>|How to behave when the last tab is closed. @@ -1655,11 +1655,7 @@ Type: <> Default: -- +pass:[https://www.malwaredomainlist.com/hostslist/hosts.txt]+ -- +pass:[http://someonewhocares.org/hosts/hosts]+ -- +pass:[http://winhelp2002.mvps.org/hosts.zip]+ -- +pass:[http://malwaredomains.lehigh.edu/files/justdomains.zip]+ -- +pass:[https://pgl.yoyo.org/adservers/serverlist.php?hostformat=hosts&mimetype=plaintext]+ +- +pass:[https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts]+ [[content.host_blocking.whitelist]] === content.host_blocking.whitelist @@ -2825,11 +2821,17 @@ Default: +pass:[1.0]+ [[tabs.favicons.show]] === tabs.favicons.show -Show favicons in the tab bar. +When to show favicons in the tab bar. -Type: <> +Type: <> -Default: +pass:[true]+ +Valid values: + + * +always+: Always show favicons. + * +never+: Always hide favicons. + * +pinned+: Show favicons only on pinned tabs. + +Default: +pass:[always]+ [[tabs.indicator.padding]] === tabs.indicator.padding From 3b2c0823afc742bdaab0afa44e681d500a40810d Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Mon, 2 Apr 2018 20:34:34 -0400 Subject: [PATCH 192/223] Fix win_id 0 always being included in :tab-take completion --- qutebrowser/completion/models/miscmodels.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutebrowser/completion/models/miscmodels.py b/qutebrowser/completion/models/miscmodels.py index 8606bbf75..35beb24de 100644 --- a/qutebrowser/completion/models/miscmodels.py +++ b/qutebrowser/completion/models/miscmodels.py @@ -110,7 +110,7 @@ def _buffer(skip_win_id=None): model = completionmodel.CompletionModel(column_widths=(6, 40, 54)) for win_id in objreg.window_registry: - if skip_win_id and win_id == skip_win_id: + if skip_win_id is not None and win_id == skip_win_id: continue tabbed_browser = objreg.get('tabbed-browser', scope='window', window=win_id) From 164ea98a5b51a58f86780a4d232978b116405696 Mon Sep 17 00:00:00 2001 From: Jimmy Date: Tue, 3 Apr 2018 20:11:15 +1200 Subject: [PATCH 193/223] Greasemonkey: fix default include value Greasemonkey scripts are supposed to default to running on all pages. @jgkamat and @nemanjan00 repurted some script not running on all pages unless they either removed (or broke) the metadata block or added an include directive. Indeed I had a logic error when it only defaulted to being included on all pages when no metadata block at all was included. Whoops. --- qutebrowser/browser/greasemonkey.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutebrowser/browser/greasemonkey.py b/qutebrowser/browser/greasemonkey.py index 41e4a1ed4..a43644bf6 100644 --- a/qutebrowser/browser/greasemonkey.py +++ b/qutebrowser/browser/greasemonkey.py @@ -92,7 +92,7 @@ class GreasemonkeyScript: props = "" script = cls(re.findall(cls.PROPS_REGEX, props), source) script.script_meta = props - if not props: + if not script.includes: script.includes = ['*'] return script From 39c08cb582c208c995e845cab840fcbe6a756702 Mon Sep 17 00:00:00 2001 From: Slackhead Date: Tue, 3 Apr 2018 16:27:04 +0100 Subject: [PATCH 194/223] Add option to mute the Last Tab/First Tab messages when tabs.wrap is false --- doc/help/settings.asciidoc | 10 ++++++++++ qutebrowser/browser/commands.py | 6 ++++-- qutebrowser/config/configdata.yml | 5 +++++ 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc index 1e58ffa87..4c0362aa7 100644 --- a/doc/help/settings.asciidoc +++ b/doc/help/settings.asciidoc @@ -257,6 +257,8 @@ |<>|Format to use for the tab title for pinned tabs. The same placeholders like for `tabs.title.format` are defined. |<>|Width (in pixels or as percentage of the window) of the tab bar if it's vertical. |<>|Wrap when changing tabs. +|<>|Mute the First Tab and Last Tab +messages when wrap is set to False. |<>|What search to start when something else than a URL is entered. |<>|Page to open if :open -t/-b/-w is used without URL. |<>|URL segments where `:navigate increment/decrement` will search for a number. @@ -3074,6 +3076,14 @@ Type: <> Default: +pass:[true]+ +[[tabs.mute_messages]] +=== tabs.mute_messages +Mute First Tab and Last Tab messages when tabs.wrap is set to False. + +Type: <> + +Default: +pass:[false]+ + [[url.auto_search]] === url.auto_search What search to start when something else than a URL is entered. diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index f2438cebe..63b13339e 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -1022,7 +1022,8 @@ class CommandDispatcher: elif config.val.tabs.wrap: self._set_current_index(newidx % self._count()) else: - raise cmdexc.CommandError("First tab") + if not config.val.tabs.mute_messages: + raise cmdexc.CommandError("First tab") @cmdutils.register(instance='command-dispatcher', scope='window') @cmdutils.argument('count', count=True) @@ -1042,7 +1043,8 @@ class CommandDispatcher: elif config.val.tabs.wrap: self._set_current_index(newidx % self._count()) else: - raise cmdexc.CommandError("Last tab") + if not config.val.tabs.mute_messages: + raise cmdexc.CommandError("Last tab") def _resolve_buffer_index(self, index): """Resolve a buffer index to the tabbedbrowser and tab. diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml index 7034d030c..8ce9566ac 100644 --- a/qutebrowser/config/configdata.yml +++ b/qutebrowser/config/configdata.yml @@ -1458,6 +1458,11 @@ tabs.wrap: type: Bool desc: Wrap when changing tabs. +tabs.mute_messages: + default: false + type: Bool + desc: Mute the First Tab and Last Tab messages when wrap is set to False. + ## url url.auto_search: From eb18f0a2ac38cd8122dbb8d8728b8e960d808238 Mon Sep 17 00:00:00 2001 From: Slackhead Date: Tue, 3 Apr 2018 17:49:51 +0100 Subject: [PATCH 195/223] Adjust the help docs entry --- doc/help/settings.asciidoc | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc index 4c0362aa7..111fc5a2b 100644 --- a/doc/help/settings.asciidoc +++ b/doc/help/settings.asciidoc @@ -240,6 +240,8 @@ |<>|Padding (in pixels) for tab indicators. |<>|Width (in pixels) of the progress indicator (0 to disable). |<>|How to behave when the last tab is closed. +|<>|Mute the First Tab and Last Tab +messages when wrap is False. |<>|Minimum width (in pixels) of tabs (-1 for the default minimum size behavior). |<>|When switching tabs, what input mode is applied. |<>|Switch between tabs using the mouse wheel. @@ -257,8 +259,6 @@ |<>|Format to use for the tab title for pinned tabs. The same placeholders like for `tabs.title.format` are defined. |<>|Width (in pixels or as percentage of the window) of the tab bar if it's vertical. |<>|Wrap when changing tabs. -|<>|Mute the First Tab and Last Tab -messages when wrap is set to False. |<>|What search to start when something else than a URL is entered. |<>|Page to open if :open -t/-b/-w is used without URL. |<>|URL segments where `:navigate increment/decrement` will search for a number. @@ -2872,6 +2872,14 @@ Valid values: Default: +pass:[ignore]+ +[[tabs.mute_messages]] +=== tabs.mute_messages +Mute First Tab and Last Tab messages when `tabs.wrap` is False. + +Type: <> + +Default: +pass:[false]+ + [[tabs.min_width]] === tabs.min_width Minimum width (in pixels) of tabs (-1 for the default minimum size behavior). @@ -3076,14 +3084,6 @@ Type: <> Default: +pass:[true]+ -[[tabs.mute_messages]] -=== tabs.mute_messages -Mute First Tab and Last Tab messages when tabs.wrap is set to False. - -Type: <> - -Default: +pass:[false]+ - [[url.auto_search]] === url.auto_search What search to start when something else than a URL is entered. From d0d5ad2eda93bd39a01ba9b3369d995abdbe3958 Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Wed, 4 Apr 2018 01:17:37 -0400 Subject: [PATCH 196/223] Stop read timer when download is cancelled --- qutebrowser/browser/qtnetworkdownloads.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qutebrowser/browser/qtnetworkdownloads.py b/qutebrowser/browser/qtnetworkdownloads.py index 82996a803..4e992b172 100644 --- a/qutebrowser/browser/qtnetworkdownloads.py +++ b/qutebrowser/browser/qtnetworkdownloads.py @@ -162,6 +162,7 @@ class DownloadItem(downloads.AbstractDownloadItem): QTimer.singleShot(0, lambda: self._die(reply.errorString())) def _do_cancel(self): + self._read_timer.stop() if self._reply is not None: self._reply.finished.disconnect(self._on_reply_finished) self._reply.abort() From feb2f99ea9c73d51a275dbaa8adfab4b72c7a24d Mon Sep 17 00:00:00 2001 From: AlvaroLuken Date: Thu, 5 Apr 2018 11:22:38 -0400 Subject: [PATCH 197/223] Added surrounding gray box to launch command for Ubuntu + Tox --- doc/install.asciidoc | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/doc/install.asciidoc b/doc/install.asciidoc index 572839208..411675bfa 100644 --- a/doc/install.asciidoc +++ b/doc/install.asciidoc @@ -426,7 +426,11 @@ Creating a wrapper script ~~~~~~~~~~~~~~~~~~~~~~~~~ Running `tox` does not install a system-wide `qutebrowser` script. You can -launch qutebrowser by doing `.venv/bin/python3 -m qutebrowser`. +launch qutebrowser by doing: + +---- +.venv/bin/python3 -m qutebrowser +---- You can create a simple wrapper script to start qutebrowser somewhere in your `$PATH` (e.g. `/usr/local/bin/qutebrowser` or `~/bin/qutebrowser`): From b7964d9baf28e9ad1ed018c7e57111b779ea150f Mon Sep 17 00:00:00 2001 From: Slackhead Date: Sun, 8 Apr 2018 10:31:12 +0100 Subject: [PATCH 198/223] Remove first/last tab messages --- doc/help/settings.asciidoc | 10 ---------- qutebrowser/browser/commands.py | 6 ------ qutebrowser/config/configdata.yml | 5 ----- 3 files changed, 21 deletions(-) diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc index 111fc5a2b..1e58ffa87 100644 --- a/doc/help/settings.asciidoc +++ b/doc/help/settings.asciidoc @@ -240,8 +240,6 @@ |<>|Padding (in pixels) for tab indicators. |<>|Width (in pixels) of the progress indicator (0 to disable). |<>|How to behave when the last tab is closed. -|<>|Mute the First Tab and Last Tab -messages when wrap is False. |<>|Minimum width (in pixels) of tabs (-1 for the default minimum size behavior). |<>|When switching tabs, what input mode is applied. |<>|Switch between tabs using the mouse wheel. @@ -2872,14 +2870,6 @@ Valid values: Default: +pass:[ignore]+ -[[tabs.mute_messages]] -=== tabs.mute_messages -Mute First Tab and Last Tab messages when `tabs.wrap` is False. - -Type: <> - -Default: +pass:[false]+ - [[tabs.min_width]] === tabs.min_width Minimum width (in pixels) of tabs (-1 for the default minimum size behavior). diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 63b13339e..e2a95215d 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -1021,9 +1021,6 @@ class CommandDispatcher: self._set_current_index(newidx) elif config.val.tabs.wrap: self._set_current_index(newidx % self._count()) - else: - if not config.val.tabs.mute_messages: - raise cmdexc.CommandError("First tab") @cmdutils.register(instance='command-dispatcher', scope='window') @cmdutils.argument('count', count=True) @@ -1042,9 +1039,6 @@ class CommandDispatcher: self._set_current_index(newidx) elif config.val.tabs.wrap: self._set_current_index(newidx % self._count()) - else: - if not config.val.tabs.mute_messages: - raise cmdexc.CommandError("Last tab") def _resolve_buffer_index(self, index): """Resolve a buffer index to the tabbedbrowser and tab. diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml index 8ce9566ac..7034d030c 100644 --- a/qutebrowser/config/configdata.yml +++ b/qutebrowser/config/configdata.yml @@ -1458,11 +1458,6 @@ tabs.wrap: type: Bool desc: Wrap when changing tabs. -tabs.mute_messages: - default: false - type: Bool - desc: Mute the First Tab and Last Tab messages when wrap is set to False. - ## url url.auto_search: From fac546e9b42405e4bee801f68fcd04709b0559d9 Mon Sep 17 00:00:00 2001 From: Slackhead Date: Sun, 8 Apr 2018 18:56:16 +0100 Subject: [PATCH 199/223] Remove test scenarios for last/first tab when wrap is off --- tests/end2end/features/tabs.feature | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/tests/end2end/features/tabs.feature b/tests/end2end/features/tabs.feature index 7a36b60cb..6e089e13c 100644 --- a/tests/end2end/features/tabs.feature +++ b/tests/end2end/features/tabs.feature @@ -332,18 +332,6 @@ Feature: Tab management - data/numbers/2.txt - data/numbers/3.txt (active) - Scenario: :tab-prev on first tab without wrap - When I set tabs.wrap to false - And I open data/numbers/1.txt - And I run :tab-prev - Then the error "First tab" should be shown - - Scenario: :tab-next with last tab without wrap - When I set tabs.wrap to false - And I open data/numbers/1.txt - And I run :tab-next - Then the error "Last tab" should be shown - Scenario: :tab-prev on first tab with wrap When I set tabs.wrap to true And I open data/numbers/1.txt From e35c91043eff646320ff4f3bcf29d4480528621b Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 8 Apr 2018 20:46:43 +0200 Subject: [PATCH 200/223] pyinstaller: Use '.' as path for git-commit-id See #384 --- misc/qutebrowser.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/qutebrowser.spec b/misc/qutebrowser.spec index 0d4645dee..8029e2a44 100644 --- a/misc/qutebrowser.spec +++ b/misc/qutebrowser.spec @@ -15,7 +15,7 @@ def get_data_files(): ('../qutebrowser/img', 'img'), ('../qutebrowser/javascript', 'javascript'), ('../qutebrowser/html/doc', 'html/doc'), - ('../qutebrowser/git-commit-id', ''), + ('../qutebrowser/git-commit-id', '.'), ('../qutebrowser/config/configdata.yml', 'config'), ] From 62aa9bdbb35b30ac89a5356c0971ddb49bad3474 Mon Sep 17 00:00:00 2001 From: Slackhead Date: Mon, 9 Apr 2018 02:03:02 +0100 Subject: [PATCH 201/223] Added debug() logging for next/prev-tab and test scenarios --- qutebrowser/browser/commands.py | 4 ++++ tests/end2end/features/tabs.feature | 12 ++++++++++++ 2 files changed, 16 insertions(+) diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index e2a95215d..60844029f 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -1021,6 +1021,8 @@ class CommandDispatcher: self._set_current_index(newidx) elif config.val.tabs.wrap: self._set_current_index(newidx % self._count()) + else: + log.webview.debug("First tab") @cmdutils.register(instance='command-dispatcher', scope='window') @cmdutils.argument('count', count=True) @@ -1039,6 +1041,8 @@ class CommandDispatcher: self._set_current_index(newidx) elif config.val.tabs.wrap: self._set_current_index(newidx % self._count()) + else: + log.webview.debug("Last tab") def _resolve_buffer_index(self, index): """Resolve a buffer index to the tabbedbrowser and tab. diff --git a/tests/end2end/features/tabs.feature b/tests/end2end/features/tabs.feature index 6e089e13c..1841ef9c9 100644 --- a/tests/end2end/features/tabs.feature +++ b/tests/end2end/features/tabs.feature @@ -332,6 +332,18 @@ Feature: Tab management - data/numbers/2.txt - data/numbers/3.txt (active) + Scenario: :tab-prev on first tab without wrap + When I set tabs.wrap to false + And I open data/numbers/1.txt + And I run :tab-prev + Then "First tab" should be logged + + Scenario: :tab-next with last tab without wrap + When I set tabs.wrap to false + And I open data/numbers/1.txt + And I run :tab-next + Then "Last tab" should be logged + Scenario: :tab-prev on first tab with wrap When I set tabs.wrap to true And I open data/numbers/1.txt From 6374b6dd4c7294734c9600a8edc7276f6d74a694 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 9 Apr 2018 18:13:14 +0200 Subject: [PATCH 202/223] Update flake8-builtins from 1.1.1 to 1.2.2 --- misc/requirements/requirements-flake8.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-flake8.txt b/misc/requirements/requirements-flake8.txt index 5eab7629b..82aaf8411 100644 --- a/misc/requirements/requirements-flake8.txt +++ b/misc/requirements/requirements-flake8.txt @@ -3,7 +3,7 @@ attrs==17.4.0 flake8==3.5.0 flake8-bugbear==18.2.0 -flake8-builtins==1.1.1 +flake8-builtins==1.2.2 flake8-comprehensions==1.4.1 flake8-copyright==0.2.0 flake8-debugger==3.1.0 From ef2a2702f5a4cd9e66c4ce5cbebeaf569ddfb3bb Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 9 Apr 2018 18:13:15 +0200 Subject: [PATCH 203/223] Update wheel from 0.30.0 to 0.31.0 --- misc/requirements/requirements-pip.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-pip.txt b/misc/requirements/requirements-pip.txt index 31f1c27bf..b1c6271f0 100644 --- a/misc/requirements/requirements-pip.txt +++ b/misc/requirements/requirements-pip.txt @@ -5,4 +5,4 @@ packaging==17.1 pyparsing==2.2.0 setuptools==39.0.1 six==1.11.0 -wheel==0.30.0 +wheel==0.31.0 From f964bf1b67343e7f7d884207b65ebb1d759d8890 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 9 Apr 2018 18:13:17 +0200 Subject: [PATCH 204/223] Update github3.py from 1.0.1 to 1.0.2 --- 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 b04d43b90..82a632c10 100644 --- a/misc/requirements/requirements-pylint-master.txt +++ b/misc/requirements/requirements-pylint-master.txt @@ -3,7 +3,7 @@ -e git+https://github.com/PyCQA/astroid.git#egg=astroid certifi==2018.1.18 chardet==3.0.4 -github3.py==1.0.1 +github3.py==1.0.2 idna==2.6 isort==4.3.4 lazy-object-proxy==1.3.1 From 03b7459b00d038183911ece27487d72119da3dba Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 9 Apr 2018 18:13:18 +0200 Subject: [PATCH 205/223] Update github3.py from 1.0.1 to 1.0.2 --- 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 861735ec9..9e99a0f9c 100644 --- a/misc/requirements/requirements-pylint.txt +++ b/misc/requirements/requirements-pylint.txt @@ -3,7 +3,7 @@ astroid==1.6.2 certifi==2018.1.18 chardet==3.0.4 -github3.py==1.0.1 +github3.py==1.0.2 idna==2.6 isort==4.3.4 lazy-object-proxy==1.3.1 From fc33b065c2b6e024d8cafb95b51243421728e8e7 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 9 Apr 2018 18:13:20 +0200 Subject: [PATCH 206/223] Update astroid from 1.6.2 to 1.6.3 --- 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 9e99a0f9c..6faa84acb 100644 --- a/misc/requirements/requirements-pylint.txt +++ b/misc/requirements/requirements-pylint.txt @@ -1,6 +1,6 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -astroid==1.6.2 +astroid==1.6.3 certifi==2018.1.18 chardet==3.0.4 github3.py==1.0.2 From 780ced8a52ad6928eb76d8f03abf90144e5b5bb7 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 9 Apr 2018 18:13:21 +0200 Subject: [PATCH 207/223] Update pylint from 1.8.3 to 1.8.4 --- 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 6faa84acb..7f0fa6da3 100644 --- a/misc/requirements/requirements-pylint.txt +++ b/misc/requirements/requirements-pylint.txt @@ -8,7 +8,7 @@ idna==2.6 isort==4.3.4 lazy-object-proxy==1.3.1 mccabe==0.6.1 -pylint==1.8.3 +pylint==1.8.4 python-dateutil==2.7.2 ./scripts/dev/pylint_checkers requests==2.18.4 From 03ea07e99fddbd8c0e3ced0f683422aa9720227b Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 9 Apr 2018 18:13:23 +0200 Subject: [PATCH 208/223] Update cheroot from 6.0.0 to 6.1.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 3a41dcb6d..975a8d857 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -2,7 +2,7 @@ attrs==17.4.0 beautifulsoup4==4.6.0 -cheroot==6.0.0 +cheroot==6.1.2 click==6.7 # colorama==0.3.9 coverage==4.5.1 From 3d75d86123763b21f890da6df1e977bd61c4a25e Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 9 Apr 2018 18:13:24 +0200 Subject: [PATCH 209/223] Update hypothesis from 3.52.0 to 3.55.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 975a8d857..d83e180ba 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -11,7 +11,7 @@ fields==5.0.0 Flask==0.12.2 glob2==0.6 hunter==2.0.2 -hypothesis==3.52.0 +hypothesis==3.55.1 itsdangerous==0.24 # Jinja2==2.10 Mako==1.0.7 From 28126055dafdfbc8517a87e3a9eee1dcd10e042a Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 9 Apr 2018 18:13:26 +0200 Subject: [PATCH 210/223] Update py-cpuinfo from 3.3.0 to 4.0.0 --- misc/requirements/requirements-tests.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index d83e180ba..22fd283b0 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -21,7 +21,7 @@ parse==1.8.2 parse-type==0.4.2 pluggy==0.6.0 py==1.5.3 -py-cpuinfo==3.3.0 +py-cpuinfo==4.0.0 pytest==3.5.0 pytest-bdd==2.20.0 pytest-benchmark==3.1.1 From 9e628901e91a59951cf981c3f9230a0725430a87 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 9 Apr 2018 18:13:27 +0200 Subject: [PATCH 211/223] Update pytest-bdd from 2.20.0 to 2.21.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 22fd283b0..7df06b219 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -23,7 +23,7 @@ pluggy==0.6.0 py==1.5.3 py-cpuinfo==4.0.0 pytest==3.5.0 -pytest-bdd==2.20.0 +pytest-bdd==2.21.0 pytest-benchmark==3.1.1 pytest-cov==2.5.1 pytest-faulthandler==1.5.0 From 849e427231e97482228b8f9ddc4180a274250647 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 9 Apr 2018 18:13:29 +0200 Subject: [PATCH 212/223] Update pytest-mock from 1.7.1 to 1.8.0 --- misc/requirements/requirements-tests.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index 7df06b219..1597a9688 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -28,7 +28,7 @@ pytest-benchmark==3.1.1 pytest-cov==2.5.1 pytest-faulthandler==1.5.0 pytest-instafail==0.3.0 -pytest-mock==1.7.1 +pytest-mock==1.8.0 pytest-qt==2.3.1 pytest-repeat==0.4.1 pytest-rerunfailures==4.0 From ca311f8588c977719b04041f42d2cc6eec93706b Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 9 Apr 2018 18:13:31 +0200 Subject: [PATCH 213/223] Update tox from 2.9.1 to 3.0.0 --- 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 adacf389a..f5344f57e 100644 --- a/misc/requirements/requirements-tox.txt +++ b/misc/requirements/requirements-tox.txt @@ -3,5 +3,5 @@ pluggy==0.6.0 py==1.5.3 six==1.11.0 -tox==2.9.1 +tox==3.0.0 virtualenv==15.2.0 From ed76d689b00ec886a0898dbd8b4c724d1f6cb2c3 Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Tue, 10 Apr 2018 02:43:35 -0400 Subject: [PATCH 214/223] Add test for completing other buffer excluding id0 --- tests/unit/completion/test_models.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/unit/completion/test_models.py b/tests/unit/completion/test_models.py index efc30dd1c..af0a1ca62 100644 --- a/tests/unit/completion/test_models.py +++ b/tests/unit/completion/test_models.py @@ -618,6 +618,29 @@ def test_other_buffer_completion(qtmodeltester, fake_web_tab, app_stub, }) +def test_other_buffer_completion_id0(qtmodeltester, fake_web_tab, app_stub, + win_registry, tabbed_browser_stubs, info): + tabbed_browser_stubs[0].widget.tabs = [ + fake_web_tab(QUrl('https://github.com'), 'GitHub', 0), + fake_web_tab(QUrl('https://wikipedia.org'), 'Wikipedia', 1), + fake_web_tab(QUrl('https://duckduckgo.com'), 'DuckDuckGo', 2), + ] + tabbed_browser_stubs[1].widget.tabs = [ + fake_web_tab(QUrl('https://wiki.archlinux.org'), 'ArchWiki', 0), + ] + info.win_id = 0 + model = miscmodels.other_buffer(info=info) + model.set_pattern('') + qtmodeltester.data_display_may_return_none = True + qtmodeltester.check(model) + + _check_completions(model, { + '1': [ + ('1/1', 'https://wiki.archlinux.org', 'ArchWiki'), + ], + }) + + def test_window_completion(qtmodeltester, fake_web_tab, tabbed_browser_stubs, info): tabbed_browser_stubs[0].widget.tabs = [ From 48b865073ced1bb21de2e5e3c55a6de143720192 Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Fri, 13 Apr 2018 12:21:12 -0400 Subject: [PATCH 215/223] Update changelog --- doc/changelog.asciidoc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index e3ab6e9c8..8433bf7a8 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -48,6 +48,8 @@ Changed - The default value for `content.host_blocking.lists` was changed to only include https://github.com/StevenBlack/hosts[Steven Black's hosts-list] which combines various sources. +- Error messages when trying to wrap when `tabs.wrap` is `False` are now logged + to debug instead of messages. Fixed @@ -72,6 +74,8 @@ Fixed - The keyhint widget now shows the correct text for chained modifiers. - Loading GreaseMonkey scripts now also works with Jinja2 2.8 (e.g. on Debian Stable). +- Adding styles with GreaseMonkey on fast sites now works properly. +- Window ID 0 is now excluded properly from `:tab-take` completion. v1.2.1 ------ From b89b38fd3ceafd8ed0f998637fa8cfbb2001e6da Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 16 Apr 2018 08:27:37 +0200 Subject: [PATCH 216/223] Use latest release of PyInstaller This is as a stop-gap solution for #3842. --- misc/requirements/requirements-pyinstaller.txt | 2 +- misc/requirements/requirements-pyinstaller.txt-raw | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/misc/requirements/requirements-pyinstaller.txt b/misc/requirements/requirements-pyinstaller.txt index f65e8c62a..e9163933b 100644 --- a/misc/requirements/requirements-pyinstaller.txt +++ b/misc/requirements/requirements-pyinstaller.txt @@ -4,4 +4,4 @@ altgraph==0.15 future==0.16.0 macholib==1.9 pefile==2017.11.5 --e git+https://github.com/pyinstaller/pyinstaller.git@develop#egg=PyInstaller +PyInstaller==3.3.1 diff --git a/misc/requirements/requirements-pyinstaller.txt-raw b/misc/requirements/requirements-pyinstaller.txt-raw index f6cb8ce72..c313980b0 100644 --- a/misc/requirements/requirements-pyinstaller.txt-raw +++ b/misc/requirements/requirements-pyinstaller.txt-raw @@ -1,4 +1 @@ --e git+https://github.com/pyinstaller/pyinstaller.git@develop#egg=PyInstaller - -# remove @commit-id for scm installs -#@ replace: @.*# @develop# +PyInstaller From 643bf4bc20cc0fc4b46b03d0a88beed1ab79de64 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 16 Apr 2018 08:33:01 +0200 Subject: [PATCH 217/223] Remove 32-bit check in build_release.py This is probably not needed anymore, see #3842 --- scripts/dev/build_release.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/scripts/dev/build_release.py b/scripts/dev/build_release.py index 94f02dbf0..84e897eee 100755 --- a/scripts/dev/build_release.py +++ b/scripts/dev/build_release.py @@ -401,14 +401,6 @@ def main(): run_asciidoc2html(args) if os.name == 'nt': - if sys.maxsize > 2**32: - # WORKAROUND - print("Due to a python/Windows bug, this script needs to be run ") - print("with a 32bit Python.") - print() - print("See http://bugs.python.org/issue24493 and ") - print("https://github.com/pypa/virtualenv/issues/774") - sys.exit(1) artifacts = build_windows() elif sys.platform == 'darwin': artifacts = build_mac() From 3704e3ddd5d474a4976a823d597fc4c2e0c230b7 Mon Sep 17 00:00:00 2001 From: Sebastian Heinlein Date: Mon, 16 Apr 2018 13:44:22 +0200 Subject: [PATCH 218/223] Fix DESTDIR and PREFIX in makefile --- misc/Makefile | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/misc/Makefile b/misc/Makefile index 714223d10..6a6762d6a 100644 --- a/misc/Makefile +++ b/misc/Makefile @@ -1,25 +1,31 @@ PYTHON = python3 -DESTDIR = / +PREFIX = /usr/local +DESTDIR = ICONSIZES = 16 24 32 48 64 128 256 512 +SETUPTOOLSOPTIONS = +ifdef DESTDIR +SETUPTOOLSOPTS = --root="$(DESTDIR)" +endif + .PHONY: install doc/qutebrowser.1.html: a2x -f manpage doc/qutebrowser.1.asciidoc install: doc/qutebrowser.1.html - $(PYTHON) setup.py install --root="$(DESTDIR)" --optimize=1 + $(PYTHON) setup.py install --prefix="$(PREFIX)" --optimize=1 $(SETUPTOOLSOPTS) install -Dm644 doc/qutebrowser.1 \ - "$(DESTDIR)/usr/share/man/man1/qutebrowser.1" + "$(DESTDIR)$(PREFIX)/share/man/man1/qutebrowser.1" install -Dm644 misc/qutebrowser.desktop \ - "$(DESTDIR)/usr/share/applications/qutebrowser.desktop" + "$(DESTDIR)$(PREFIX)/share/applications/qutebrowser.desktop" $(foreach i,$(ICONSIZES),install -Dm644 "icons/qutebrowser-$(i)x$(i).png" \ - "$(DESTDIR)/usr/share/icons/hicolor/$(i)x$(i)/apps/qutebrowser.png";) + "$(DESTDIR)$(PREFIX)/share/icons/hicolor/$(i)x$(i)/apps/qutebrowser.png";) install -Dm644 icons/qutebrowser.svg \ - "$(DESTDIR)/usr/share/icons/hicolor/scalable/apps/qutebrowser.svg" - install -Dm755 -t "$(DESTDIR)/usr/share/qutebrowser/userscripts/" \ + "$(DESTDIR)$(PREFIX)/share/icons/hicolor/scalable/apps/qutebrowser.svg" + install -Dm755 -t "$(DESTDIR)$(PREFIX)/share/qutebrowser/userscripts/" \ $(wildcard misc/userscripts/*) - install -Dm755 -t "$(DESTDIR)/usr/share/qutebrowser/scripts/" \ + install -Dm755 -t "$(DESTDIR)$(PREFIX)/share/qutebrowser/scripts/" \ $(filter-out scripts/__init__.py scripts/__pycache__ scripts/dev \ scripts/testbrowser scripts/asciidoc2html.py scripts/setupcommon.py \ scripts/link_pyqt.py,$(wildcard scripts/*)) From 23d4d72f3bca19395e269519d15b9bb5cd2ad818 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 16 Apr 2018 17:05:10 +0200 Subject: [PATCH 219/223] Update changelog --- doc/changelog.asciidoc | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index 8433bf7a8..bed255efa 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -76,6 +76,7 @@ Fixed Stable). - Adding styles with GreaseMonkey on fast sites now works properly. - Window ID 0 is now excluded properly from `:tab-take` completion. +- A rare crash when cancelling a download has been fixed. v1.2.1 ------ From d2207f66f1049d3c2de3ee5c136afe945b130af3 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 16 Apr 2018 17:14:14 +0200 Subject: [PATCH 220/223] Skip test_set_error entirely See #3771 --- tests/unit/javascript/stylesheet/test_stylesheet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/javascript/stylesheet/test_stylesheet.py b/tests/unit/javascript/stylesheet/test_stylesheet.py index 1553d4f14..591d2cd73 100644 --- a/tests/unit/javascript/stylesheet/test_stylesheet.py +++ b/tests/unit/javascript/stylesheet/test_stylesheet.py @@ -118,7 +118,7 @@ def test_set_svg(stylesheet_tester): stylesheet_tester.check_eq('"svg"', "document.documentElement.nodeName") -@pytest.mark.flaky +@pytest.mark.skip(reason="Too flaky, see #3771") def test_set_error(stylesheet_tester, config_stub): """Test stylesheet modifies file not found error pages.""" config_stub.changed.disconnect() # This test is flaky otherwise... From 4a78519b63774661a05ed0aa2f4a4a9675bdc425 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 16 Apr 2018 17:14:47 +0200 Subject: [PATCH 221/223] Mark opening/closing window via JS test as flaky --- tests/end2end/features/javascript.feature | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/end2end/features/javascript.feature b/tests/end2end/features/javascript.feature index 8f715397f..208aeab88 100644 --- a/tests/end2end/features/javascript.feature +++ b/tests/end2end/features/javascript.feature @@ -8,6 +8,7 @@ Feature: Javascript stuff When I open data/javascript/consolelog.html Then the javascript message "console.log works!" should be logged + @flaky Scenario: Opening/Closing a window via JS When I open data/javascript/window_open.html And I run :tab-only From ec57e585306bf4134b92d9a5fcc54b81f5e3b9c2 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 16 Apr 2018 17:21:42 +0200 Subject: [PATCH 222/223] Update changelog --- doc/changelog.asciidoc | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index bed255efa..6c8b9a0c3 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -77,6 +77,7 @@ Fixed - Adding styles with GreaseMonkey on fast sites now works properly. - Window ID 0 is now excluded properly from `:tab-take` completion. - A rare crash when cancelling a download has been fixed. +- The Makefile (intended for packagers) now supports `PREFIX` properly. v1.2.1 ------ From 178eeaed0dc3ebc8766676efae997c9d3c857136 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 17 Apr 2018 07:49:10 +0200 Subject: [PATCH 223/223] Update changelog --- doc/changelog.asciidoc | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index 6c8b9a0c3..7569af8ef 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -25,6 +25,7 @@ Added - New `url.open_base_url` option to open the base URL of a searchengine when no search term is given. - New `tabs.min_width` setting to configure the minimal width for tabs. +- New `getbib` userscript to download bibtex information for DOIs on a page. Changed ~~~~~~~