From b30d37e3e0b1d488f348efeb6001512ec0700fba Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 14 Nov 2016 23:03:36 +0100 Subject: [PATCH 01/48] WebEngine: Fake keypresses with modifiers instead of override_target With QtWebEngine, handling a click (e.g. createWindow being called) happens some time after a click event has been handled. When setting override_target, that means we don't know when to set the override target back, and we can't simply only unset it in createWindow as elements not causing a new tab to be opened (like an input field) can be hinted too. Instead, we now only use override_target with QtWebKit, and simply generate fake key events with the right modifiers to do the right thing in createWindow for QtWebEngine. Fixes #2119. --- qutebrowser/browser/browsertab.py | 1 + qutebrowser/browser/webelem.py | 20 +++++++++++++------- qutebrowser/browser/webengine/webview.py | 12 +++--------- 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/qutebrowser/browser/browsertab.py b/qutebrowser/browser/browsertab.py index cbf566847..efc6d6f4b 100644 --- a/qutebrowser/browser/browsertab.py +++ b/qutebrowser/browser/browsertab.py @@ -84,6 +84,7 @@ class TabData: inspector: The QWebInspector used for this webview. viewing_source: Set if we're currently showing a source view. override_target: Override for open_target for fake clicks (like hints). + Only used for QtWebKit. """ def __init__(self): diff --git a/qutebrowser/browser/webelem.py b/qutebrowser/browser/webelem.py index cd56a9127..97c5695c4 100644 --- a/qutebrowser/browser/webelem.py +++ b/qutebrowser/browser/webelem.py @@ -338,6 +338,8 @@ class AbstractWebElement(collections.abc.MutableMapping): """Simulate a click on the element.""" # FIXME:qtwebengine do we need this? # self._widget.setFocus() + + # For QtWebKit self._tab.data.override_target = click_target pos = self._mouse_pos() @@ -345,20 +347,24 @@ class AbstractWebElement(collections.abc.MutableMapping): log.webelem.debug("Sending fake click to {!r} at position {} with " "target {}".format(self, pos, click_target)) - if click_target in [usertypes.ClickTarget.tab, - usertypes.ClickTarget.tab_bg, - usertypes.ClickTarget.window]: - modifiers = Qt.ControlModifier + modifiers = { + usertypes.ClickTarget.normal: Qt.NoModifier, + usertypes.ClickTarget.window: Qt.AltModifier | Qt.ShiftModifier, + usertypes.ClickTarget.tab: Qt.ControlModifier, + usertypes.ClickTarget.tab_bg: Qt.ControlModifier, + } + if config.get('tabs', 'background-tabs'): + modifiers[usertypes.ClickTarget.tab] |= Qt.ShiftModifier else: - modifiers = Qt.NoModifier + modifiers[usertypes.ClickTarget.tab_bg] |= Qt.ShiftModifier events = [ QMouseEvent(QEvent.MouseMove, pos, Qt.NoButton, Qt.NoButton, Qt.NoModifier), QMouseEvent(QEvent.MouseButtonPress, pos, Qt.LeftButton, - Qt.LeftButton, modifiers), + Qt.LeftButton, modifiers[click_target]), QMouseEvent(QEvent.MouseButtonRelease, pos, Qt.LeftButton, - Qt.NoButton, modifiers), + Qt.NoButton, modifiers[click_target]), ] for evt in events: diff --git a/qutebrowser/browser/webengine/webview.py b/qutebrowser/browser/webengine/webview.py index 882e2379f..ffc9555f3 100644 --- a/qutebrowser/browser/webengine/webview.py +++ b/qutebrowser/browser/webengine/webview.py @@ -74,18 +74,12 @@ class WebEngineView(QWebEngineView): """ debug_type = debug.qenum_key(QWebEnginePage, wintype) background_tabs = config.get('tabs', 'background-tabs') - override_target = self._tabdata.override_target log.webview.debug("createWindow with type {}, background_tabs " - "{}, override_target {}".format( - debug_type, background_tabs, override_target)) + "{}".format(debug_type, background_tabs)) - if override_target is not None: - target = override_target - self._tabdata.override_target = None - elif wintype == QWebEnginePage.WebBrowserWindow: - log.webview.debug("createWindow with WebBrowserWindow - when does " - "this happen?!") + if wintype == QWebEnginePage.WebBrowserWindow: + # Shift-Alt-Click target = usertypes.ClickTarget.window elif wintype == QWebEnginePage.WebDialog: log.webview.warning("{} requested, but we don't support " From 81d67f8a2c618a3cf84ec70703c5379f20a91217 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 14 Nov 2016 08:31:06 +0100 Subject: [PATCH 02/48] Remove support for an empty data/config/cachedir See #2115 --- doc/qutebrowser.1.asciidoc | 6 +- qutebrowser/browser/adblock.py | 21 ++----- qutebrowser/browser/history.py | 8 --- qutebrowser/browser/urlmarks.py | 13 +--- qutebrowser/browser/webkit/cache.py | 16 +---- qutebrowser/browser/webkit/webkitsettings.py | 17 +++--- qutebrowser/commands/userscripts.py | 14 ++--- qutebrowser/config/config.py | 59 ++++++++---------- qutebrowser/config/configtypes.py | 20 +----- qutebrowser/config/parsers/ini.py | 7 +-- qutebrowser/config/parsers/keyconf.py | 10 +-- qutebrowser/misc/crashsignal.py | 10 +-- qutebrowser/misc/lineparser.py | 9 +-- qutebrowser/misc/sessions.py | 23 ++----- qutebrowser/qutebrowser.py | 16 +++-- qutebrowser/utils/standarddir.py | 32 +++++----- tests/end2end/test_invocations.py | 18 ------ tests/unit/browser/test_adblock.py | 27 --------- tests/unit/browser/webkit/test_cache.py | 10 --- tests/unit/browser/webkit/test_history.py | 7 --- tests/unit/config/test_config.py | 64 +++----------------- tests/unit/config/test_configtypes.py | 16 +---- tests/unit/misc/test_lineparser.py | 26 -------- tests/unit/misc/test_sessions.py | 31 +--------- tests/unit/utils/test_standarddir.py | 22 ++----- 25 files changed, 110 insertions(+), 392 deletions(-) diff --git a/doc/qutebrowser.1.asciidoc b/doc/qutebrowser.1.asciidoc index c0d2b577a..8b856ccb3 100644 --- a/doc/qutebrowser.1.asciidoc +++ b/doc/qutebrowser.1.asciidoc @@ -39,13 +39,13 @@ show it. show this help message and exit *-c* 'CONFDIR', *--confdir* 'CONFDIR':: - Set config directory (empty for no config storage). + Set config directory *--datadir* 'DATADIR':: - Set data directory (empty for no data storage). + Set data directory *--cachedir* 'CACHEDIR':: - Set cache directory (empty for no cache storage). + Set cache directory *--basedir* 'BASEDIR':: Base directory for all storage. Other --*dir arguments are ignored if this is given. diff --git a/qutebrowser/browser/adblock.py b/qutebrowser/browser/adblock.py index 84d630ab6..06d0cabe8 100644 --- a/qutebrowser/browser/adblock.py +++ b/qutebrowser/browser/adblock.py @@ -29,7 +29,7 @@ import fnmatch from qutebrowser.browser import downloads from qutebrowser.config import config from qutebrowser.utils import objreg, standarddir, log, message -from qutebrowser.commands import cmdutils, cmdexc +from qutebrowser.commands import cmdutils def guess_zip_filename(zf): @@ -113,17 +113,11 @@ class HostBlocker: self._done_count = 0 data_dir = standarddir.data() - if data_dir is None: - self._local_hosts_file = None - else: - self._local_hosts_file = os.path.join(data_dir, 'blocked-hosts') + self._local_hosts_file = os.path.join(data_dir, 'blocked-hosts') self.on_config_changed() config_dir = standarddir.config() - if config_dir is None: - self._config_hosts_file = None - else: - self._config_hosts_file = os.path.join(config_dir, 'blocked-hosts') + self._config_hosts_file = os.path.join(config_dir, 'blocked-hosts') objreg.get('config').changed.connect(self.on_config_changed) @@ -146,7 +140,7 @@ class HostBlocker: Return: True if a read was attempted, False otherwise """ - if filename is None or not os.path.exists(filename): + if not os.path.exists(filename): return False try: @@ -162,9 +156,6 @@ class HostBlocker: """Read hosts from the existing blocked-hosts file.""" self._blocked_hosts = set() - if self._local_hosts_file is None: - return - self._read_hosts_file(self._config_hosts_file, self._config_blocked_hosts) @@ -186,8 +177,6 @@ class HostBlocker: """ self._read_hosts_file(self._config_hosts_file, self._config_blocked_hosts) - if self._local_hosts_file is None: - raise cmdexc.CommandError("No data storage is configured!") self._blocked_hosts = set() self._done_count = 0 urls = config.get('content', 'host-block-lists') @@ -275,7 +264,7 @@ class HostBlocker: def on_config_changed(self): """Update files when the config changed.""" urls = config.get('content', 'host-block-lists') - if urls is None and self._local_hosts_file is not None: + if urls is None: try: os.remove(self._local_hosts_file) except FileNotFoundError: diff --git a/qutebrowser/browser/history.py b/qutebrowser/browser/history.py index 4e2e126f6..c9008548a 100644 --- a/qutebrowser/browser/history.py +++ b/qutebrowser/browser/history.py @@ -129,7 +129,6 @@ class WebHistory(QObject): Attributes: history_dict: An OrderedDict of URLs read from the on-disk history. - _hist_dir: The directory to store the history in _lineparser: The AppendLineParser used to save the history. _new_history: A list of Entry items of the current session. _saved_count: How many HistoryEntries have been written to disk. @@ -157,7 +156,6 @@ class WebHistory(QObject): super().__init__(parent) self._initial_read_started = False self._initial_read_done = False - self._hist_dir = hist_dir self._lineparser = lineparser.AppendLineParser(hist_dir, hist_name, parent=self) self.history_dict = collections.OrderedDict() @@ -183,12 +181,6 @@ class WebHistory(QObject): return self._initial_read_started = True - if self._hist_dir is None: - self._initial_read_done = True - self.async_read_done.emit() - assert not self._temp_history - return - with self._lineparser.open(): for line in self._lineparser: yield diff --git a/qutebrowser/browser/urlmarks.py b/qutebrowser/browser/urlmarks.py index fc727a284..991ba7fc5 100644 --- a/qutebrowser/browser/urlmarks.py +++ b/qutebrowser/browser/urlmarks.py @@ -73,8 +73,7 @@ class UrlMarkManager(QObject): Attributes: marks: An OrderedDict of all quickmarks/bookmarks. - _lineparser: The LineParser used for the marks, or None - (when qutebrowser is started with -c ''). + _lineparser: The LineParser used for the marks Signals: changed: Emitted when anything changed. @@ -91,10 +90,6 @@ class UrlMarkManager(QObject): super().__init__(parent) self.marks = collections.OrderedDict() - self._lineparser = None - - if standarddir.config() is None: - return self._init_lineparser() for line in self._lineparser: @@ -115,10 +110,8 @@ class UrlMarkManager(QObject): def save(self): """Save the marks to disk.""" - if self._lineparser is not None: - self._lineparser.data = [' '.join(tpl) - for tpl in self.marks.items()] - self._lineparser.save() + self._lineparser.data = [' '.join(tpl) for tpl in self.marks.items()] + self._lineparser.save() def delete(self, key): """Delete a quickmark/bookmark. diff --git a/qutebrowser/browser/webkit/cache.py b/qutebrowser/browser/webkit/cache.py index 1270498e4..bc774c250 100644 --- a/qutebrowser/browser/webkit/cache.py +++ b/qutebrowser/browser/webkit/cache.py @@ -32,23 +32,14 @@ class DiskCache(QNetworkDiskCache): """Disk cache which sets correct cache dir and size. - If the cache is deactivated via the command line argument --cachedir="", - both attributes _cache_dir and _http_cache_dir are set to None. - Attributes: _activated: Whether the cache should be used. - _cache_dir: The base directory for cache files (standarddir.cache()) or - None. - _http_cache_dir: the HTTP subfolder in _cache_dir or None. + _cache_dir: The base directory for cache files (standarddir.cache()) """ def __init__(self, cache_dir, parent=None): super().__init__(parent) self._cache_dir = cache_dir - if cache_dir is None: - self._http_cache_dir = None - else: - self._http_cache_dir = os.path.join(cache_dir, 'http') self._maybe_activate() objreg.get('config').changed.connect(self.on_config_changed) @@ -59,12 +50,11 @@ class DiskCache(QNetworkDiskCache): def _maybe_activate(self): """Activate/deactivate the cache based on the config.""" - if (config.get('general', 'private-browsing') or - self._cache_dir is None): + if config.get('general', 'private-browsing'): self._activated = False else: self._activated = True - self.setCacheDirectory(self._http_cache_dir) + self.setCacheDirectory(os.path.join(self._cache_dir, 'http')) self.setMaximumCacheSize(config.get('storage', 'cache-size')) @pyqtSlot(str, str) diff --git a/qutebrowser/browser/webkit/webkitsettings.py b/qutebrowser/browser/webkit/webkitsettings.py index ffb394872..86505162f 100644 --- a/qutebrowser/browser/webkit/webkitsettings.py +++ b/qutebrowser/browser/webkit/webkitsettings.py @@ -95,18 +95,17 @@ def init(): """Initialize the global QWebSettings.""" cache_path = standarddir.cache() data_path = standarddir.data() - if config.get('general', 'private-browsing') or cache_path is None: + if config.get('general', 'private-browsing'): QWebSettings.setIconDatabasePath('') else: QWebSettings.setIconDatabasePath(cache_path) - if cache_path is not None: - QWebSettings.setOfflineWebApplicationCachePath( - os.path.join(cache_path, 'application-cache')) - if data_path is not None: - QWebSettings.globalSettings().setLocalStoragePath( - os.path.join(data_path, 'local-storage')) - QWebSettings.setOfflineStoragePath( - os.path.join(data_path, 'offline-storage')) + + QWebSettings.setOfflineWebApplicationCachePath( + os.path.join(cache_path, 'application-cache')) + QWebSettings.globalSettings().setLocalStoragePath( + os.path.join(data_path, 'local-storage')) + QWebSettings.setOfflineStoragePath( + os.path.join(data_path, 'offline-storage')) websettings.init_mappings(MAPPINGS) objreg.get('config').changed.connect(update_settings) diff --git a/qutebrowser/commands/userscripts.py b/qutebrowser/commands/userscripts.py index d939d6928..3c1d4d89e 100644 --- a/qutebrowser/commands/userscripts.py +++ b/qutebrowser/commands/userscripts.py @@ -408,15 +408,11 @@ def run_async(tab, cmd, *args, win_id, env, verbose=False): user_agent = config.get('network', 'user-agent') if user_agent is not None: env['QUTE_USER_AGENT'] = user_agent - config_dir = standarddir.config() - if config_dir is not None: - env['QUTE_CONFIG_DIR'] = config_dir - data_dir = standarddir.data() - if data_dir is not None: - env['QUTE_DATA_DIR'] = data_dir - download_dir = downloads.download_dir() - if download_dir is not None: - env['QUTE_DOWNLOAD_DIR'] = download_dir + + env['QUTE_CONFIG_DIR'] = standarddir.config() + env['QUTE_DATA_DIR'] = standarddir.data() + env['QUTE_DOWNLOAD_DIR'] = downloads.download_dir() + cmd_path = os.path.expanduser(cmd) # if cmd is not given as an absolute path, look it up diff --git a/qutebrowser/config/config.py b/qutebrowser/config/config.py index 2cfea1510..38d8aa6eb 100644 --- a/qutebrowser/config/config.py +++ b/qutebrowser/config/config.py @@ -160,19 +160,18 @@ def _init_main_config(parent=None): sys.exit(usertypes.Exit.err_config) else: objreg.register('config', config_obj) - if standarddir.config() is not None: - filename = os.path.join(standarddir.config(), 'qutebrowser.conf') - save_manager = objreg.get('save-manager') - save_manager.add_saveable( - 'config', config_obj.save, config_obj.changed, - config_opt=('general', 'auto-save-config'), filename=filename) - for sect in config_obj.sections.values(): - for opt in sect.values.values(): - if opt.values['conf'] is None: - # Option added to built-in defaults but not in user's - # config yet - save_manager.save('config', explicit=True, force=True) - return + filename = os.path.join(standarddir.config(), 'qutebrowser.conf') + save_manager = objreg.get('save-manager') + save_manager.add_saveable( + 'config', config_obj.save, config_obj.changed, + config_opt=('general', 'auto-save-config'), filename=filename) + for sect in config_obj.sections.values(): + for opt in sect.values.values(): + if opt.values['conf'] is None: + # Option added to built-in defaults but not in user's + # config yet + save_manager.save('config', explicit=True, force=True) + return def _init_key_config(parent): @@ -197,13 +196,12 @@ def _init_key_config(parent): sys.exit(usertypes.Exit.err_key_config) else: objreg.register('key-config', key_config) - if standarddir.config() is not None: - save_manager = objreg.get('save-manager') - filename = os.path.join(standarddir.config(), 'keys.conf') - save_manager.add_saveable( - 'key-config', key_config.save, key_config.config_dirty, - config_opt=('general', 'auto-save-config'), filename=filename, - dirty=key_config.is_dirty) + save_manager = objreg.get('save-manager') + filename = os.path.join(standarddir.config(), 'keys.conf') + save_manager.add_saveable( + 'key-config', key_config.save, key_config.config_dirty, + config_opt=('general', 'auto-save-config'), filename=filename, + dirty=key_config.is_dirty) def _init_misc(): @@ -237,10 +235,7 @@ def _init_misc(): # This fixes one of the corruption issues here: # https://github.com/The-Compiler/qutebrowser/issues/515 - if standarddir.config() is None: - path = os.devnull - else: - path = os.path.join(standarddir.config(), 'qsettings') + path = os.path.join(standarddir.config(), 'qsettings') for fmt in [QSettings.NativeFormat, QSettings.IniFormat]: QSettings.setPath(fmt, QSettings.UserScope, path) @@ -659,15 +654,11 @@ class ConfigManager(QObject): def read(self, configdir, fname, relaxed=False): """Read the config from the given directory/file.""" self._fname = fname - if configdir is None: - self._configdir = None - self._initialized = True - else: - self._configdir = configdir - parser = ini.ReadConfigParser(configdir, fname) - self._from_cp(parser, relaxed) - self._initialized = True - self._validate_all() + self._configdir = configdir + parser = ini.ReadConfigParser(configdir, fname) + self._from_cp(parser, relaxed) + self._initialized = True + self._validate_all() def items(self, sectname, raw=True): """Get a list of (optname, value) tuples for a section. @@ -884,8 +875,6 @@ class ConfigManager(QObject): def save(self): """Save the config file.""" - if self._configdir is None: - return configfile = os.path.join(self._configdir, self._fname) log.destroy.debug("Saving config to {}".format(configfile)) with qtutils.savefile_open(configfile) as f: diff --git a/qutebrowser/config/configtypes.py b/qutebrowser/config/configtypes.py index 346b5a8d7..0642b79e8 100644 --- a/qutebrowser/config/configtypes.py +++ b/qutebrowser/config/configtypes.py @@ -859,9 +859,7 @@ class File(BaseType): value = os.path.expanduser(value) value = os.path.expandvars(value) if not os.path.isabs(value): - cfgdir = standarddir.config() - assert cfgdir is not None - value = os.path.join(cfgdir, value) + value = os.path.join(standarddir.config(), value) return value def validate(self, value): @@ -872,12 +870,7 @@ class File(BaseType): value = os.path.expandvars(value) try: if not os.path.isabs(value): - cfgdir = standarddir.config() - if cfgdir is None: - raise configexc.ValidationError( - value, "must be an absolute path when not using a " - "config directory!") - value = os.path.join(cfgdir, value) + value = os.path.join(standarddir.config(), value) not_isfile_message = ("must be a valid path relative to the " "config directory!") else: @@ -1180,14 +1173,7 @@ class UserStyleSheet(File): if not value: return None - if standarddir.config() is None: - # We can't call super().transform() here as this counts on the - # validation previously ensuring that we don't have a relative path - # when starting with -c "". - path = None - else: - path = super().transform(value) - + path = super().transform(value) if path is not None and os.path.exists(path): return QUrl.fromLocalFile(path) else: diff --git a/qutebrowser/config/parsers/ini.py b/qutebrowser/config/parsers/ini.py index 90ca27c50..56640e299 100644 --- a/qutebrowser/config/parsers/ini.py +++ b/qutebrowser/config/parsers/ini.py @@ -47,15 +47,12 @@ class ReadConfigParser(configparser.ConfigParser): self.optionxform = lambda opt: opt # be case-insensitive self._configdir = configdir self._fname = fname - if self._configdir is None: - self._configfile = None - return self._configfile = os.path.join(self._configdir, fname) + if not os.path.isfile(self._configfile): return log.init.debug("Reading config from {}".format(self._configfile)) - if self._configfile is not None: - self.read(self._configfile, encoding='utf-8') + self.read(self._configfile, encoding='utf-8') def __repr__(self): return utils.get_repr(self, constructor=True, diff --git a/qutebrowser/config/parsers/keyconf.py b/qutebrowser/config/parsers/keyconf.py index aa1600ae1..e602c33d5 100644 --- a/qutebrowser/config/parsers/keyconf.py +++ b/qutebrowser/config/parsers/keyconf.py @@ -89,11 +89,9 @@ class KeyConfigParser(QObject): self._cur_command = None # Mapping of section name(s) to key binding -> command dicts. self.keybindings = collections.OrderedDict() - if configdir is None: - self._configfile = None - else: - self._configfile = os.path.join(configdir, fname) - if self._configfile is None or not os.path.exists(self._configfile): + self._configfile = os.path.join(configdir, fname) + + if not os.path.exists(self._configfile): self._load_default() else: self._read(relaxed) @@ -143,8 +141,6 @@ class KeyConfigParser(QObject): def save(self): """Save the key config file.""" - if self._configfile is None: - return log.destroy.debug("Saving key config to {}".format(self._configfile)) with qtutils.savefile_open(self._configfile, encoding='utf-8') as f: data = str(self) diff --git a/qutebrowser/misc/crashsignal.py b/qutebrowser/misc/crashsignal.py index b970e467b..35c39bf2e 100644 --- a/qutebrowser/misc/crashsignal.py +++ b/qutebrowser/misc/crashsignal.py @@ -72,10 +72,7 @@ class CrashHandler(QObject): def handle_segfault(self): """Handle a segfault from a previous run.""" - data_dir = standarddir.data() - if data_dir is None: - return - logname = os.path.join(data_dir, 'crash.log') + logname = os.path.join(standarddir.data(), 'crash.log') try: # First check if an old logfile exists. if os.path.exists(logname): @@ -131,10 +128,7 @@ class CrashHandler(QObject): def _init_crashlogfile(self): """Start a new logfile and redirect faulthandler to it.""" assert not self._args.no_err_windows - data_dir = standarddir.data() - if data_dir is None: - return - logname = os.path.join(data_dir, 'crash.log') + logname = os.path.join(standarddir.data(), 'crash.log') try: self._crash_log_file = open(logname, 'w', encoding='ascii') except OSError: diff --git a/qutebrowser/misc/lineparser.py b/qutebrowser/misc/lineparser.py index 4999c2e22..55bae7142 100644 --- a/qutebrowser/misc/lineparser.py +++ b/qutebrowser/misc/lineparser.py @@ -57,10 +57,7 @@ class BaseLineParser(QObject): """ super().__init__(parent) self._configdir = configdir - if self._configdir is None: - self._configfile = None - else: - self._configfile = os.path.join(self._configdir, fname) + self._configfile = os.path.join(self._configdir, fname) self._fname = fname self._binary = binary self._opened = False @@ -76,8 +73,6 @@ class BaseLineParser(QObject): Return: True if the file should be saved, False otherwise. """ - if self._configdir is None: - return False if not os.path.exists(self._configdir): os.makedirs(self._configdir, 0o755) return True @@ -222,7 +217,7 @@ class LineParser(BaseLineParser): binary: Whether to open the file in binary mode. """ super().__init__(configdir, fname, binary=binary, parent=parent) - if configdir is None or not os.path.isfile(self._configfile): + if not os.path.isfile(self._configfile): self.data = [] else: log.init.debug("Reading {}".format(self._configfile)) diff --git a/qutebrowser/misc/sessions.py b/qutebrowser/misc/sessions.py index c3a3a8e34..9a544c738 100644 --- a/qutebrowser/misc/sessions.py +++ b/qutebrowser/misc/sessions.py @@ -47,15 +47,11 @@ def init(parent=None): Args: parent: The parent to use for the SessionManager. """ - data_dir = standarddir.data() - if data_dir is None: - base_path = None - else: - base_path = os.path.join(standarddir.data(), 'sessions') - try: - os.mkdir(base_path) - except FileExistsError: - pass + base_path = os.path.join(standarddir.data(), 'sessions') + try: + os.mkdir(base_path) + except FileExistsError: + pass session_manager = SessionManager(base_path, parent) objreg.register('session-manager', session_manager) @@ -137,11 +133,6 @@ class SessionManager(QObject): if os.path.isabs(path) and ((not check_exists) or os.path.exists(path)): return path - elif self._base_path is None: - if check_exists: - raise SessionNotFoundError(name) - else: - return None else: path = os.path.join(self._base_path, name + '.yml') if check_exists and not os.path.exists(path): @@ -279,8 +270,6 @@ class SessionManager(QObject): """ name = self._get_session_name(name) path = self._get_session_path(name) - if path is None: - raise SessionError("No data storage configured.") log.sessions.debug("Saving session {} to {}...".format(name, path)) if last_window: @@ -392,8 +381,6 @@ class SessionManager(QObject): def list_sessions(self): """Get a list of all session names.""" sessions = [] - if self._base_path is None: - return sessions for filename in os.listdir(self._base_path): base, ext = os.path.splitext(filename) if ext == '.yml': diff --git a/qutebrowser/qutebrowser.py b/qutebrowser/qutebrowser.py index 5a6d7e259..de80b972b 100644 --- a/qutebrowser/qutebrowser.py +++ b/qutebrowser/qutebrowser.py @@ -47,12 +47,11 @@ def get_argparser(): """Get the argparse parser.""" parser = argparse.ArgumentParser(prog='qutebrowser', description=qutebrowser.__description__) - parser.add_argument('-c', '--confdir', help="Set config directory (empty " - "for no config storage).") - parser.add_argument('--datadir', help="Set data directory (empty for " - "no data storage).") - parser.add_argument('--cachedir', help="Set cache directory (empty for " - "no cache storage).") + parser.add_argument('-c', '--confdir', help="Set config directory", + type=directory) + parser.add_argument('--datadir', help="Set data directory", type=directory) + parser.add_argument('--cachedir', help="Set cache directory", + type=directory) parser.add_argument('--basedir', help="Base directory for all storage. " "Other --*dir arguments are ignored if this is given.") parser.add_argument('-V', '--version', help="Show version and quit.", @@ -124,6 +123,11 @@ def get_argparser(): return parser +def directory(arg): + if not arg: + raise argparse.ArgumentTypeError("Invalid empty value") + + def logfilter_error(logfilter: str): """Validate logger names passed to --logfilter. diff --git a/qutebrowser/utils/standarddir.py b/qutebrowser/utils/standarddir.py index 10929f251..cbfa2555c 100644 --- a/qutebrowser/utils/standarddir.py +++ b/qutebrowser/utils/standarddir.py @@ -43,7 +43,7 @@ def config(): # WORKAROUND - see # https://bugreports.qt.io/browse/QTBUG-38872 path = os.path.join(path, appname) - _maybe_create(path) + _create(path) return path @@ -61,7 +61,7 @@ def data(): QStandardPaths.ConfigLocation) if data_path == config_path: path = os.path.join(path, 'data') - _maybe_create(path) + _create(path) return path @@ -82,7 +82,7 @@ def cache(): overridden, path = _from_args(typ, _args) if not overridden: path = _writable_location(typ) - _maybe_create(path) + _create(path) return path @@ -92,7 +92,7 @@ def download(): overridden, path = _from_args(typ, _args) if not overridden: path = _writable_location(typ) - _maybe_create(path) + _create(path) return path @@ -116,7 +116,7 @@ def runtime(): # maximum length of 104 chars), so we don't add the username here... appname = QCoreApplication.instance().applicationName() path = os.path.join(path, appname) - _maybe_create(path) + _create(path) return path @@ -175,16 +175,16 @@ def _from_args(typ, args): except KeyError: return (False, None) arg_value = getattr(args, argname) + + assert arg_value != '', argname if arg_value is None: return (False, None) - elif arg_value == '': - return (True, None) else: return (True, arg_value) -def _maybe_create(path): - """Create the `path` directory if path is not None. +def _create(path): + """Create the `path` directory. From the XDG basedir spec: If, when attempting to write a file, the destination directory is @@ -192,11 +192,10 @@ def _maybe_create(path): 0700. If the destination directory exists already the permissions should not be changed. """ - if path is not None: - try: - os.makedirs(path, 0o700) - except FileExistsError: - pass + try: + os.makedirs(path, 0o700) + except FileExistsError: + pass def init(args): @@ -214,10 +213,7 @@ def _init_cachedir_tag(): See http://www.brynosaurus.com/cachedir/spec.html """ - cache_dir = cache() - if cache_dir is None: - return - cachedir_tag = os.path.join(cache_dir, 'CACHEDIR.TAG') + cachedir_tag = os.path.join(cache(), 'CACHEDIR.TAG') if not os.path.exists(cachedir_tag): try: with open(cachedir_tag, 'w', encoding='utf-8') as f: diff --git a/tests/end2end/test_invocations.py b/tests/end2end/test_invocations.py index fd414db12..d85e2a467 100644 --- a/tests/end2end/test_invocations.py +++ b/tests/end2end/test_invocations.py @@ -68,24 +68,6 @@ def temp_basedir_env(tmpdir, short_tmpdir): return env -@pytest.mark.linux -def test_no_config(request, temp_basedir_env, quteproc_new): - """Test starting with -c "".""" - args = ['-c', ''] + _base_args(request.config) - quteproc_new.start(args, env=temp_basedir_env) - quteproc_new.send_cmd(':quit') - quteproc_new.wait_for_quit() - - -@pytest.mark.linux -def test_no_cache(request, temp_basedir_env, quteproc_new): - """Test starting with --cachedir="".""" - args = ['--cachedir='] + _base_args(request.config) - quteproc_new.start(args, env=temp_basedir_env) - quteproc_new.send_cmd(':quit') - quteproc_new.wait_for_quit() - - @pytest.mark.linux def test_ascii_locale(request, httpbin, tmpdir, quteproc_new): """Test downloads with LC_ALL=C set. diff --git a/tests/unit/browser/test_adblock.py b/tests/unit/browser/test_adblock.py index b0285a8f5..3caf6e526 100644 --- a/tests/unit/browser/test_adblock.py +++ b/tests/unit/browser/test_adblock.py @@ -29,7 +29,6 @@ from PyQt5.QtCore import pyqtSignal, QUrl, QObject from qutebrowser.browser import adblock from qutebrowser.utils import objreg -from qutebrowser.commands import cmdexc pytestmark = pytest.mark.usefixtures('qapp', 'config_tmpdir') @@ -225,32 +224,6 @@ def generic_blocklists(directory): return [blocklist1, blocklist2, blocklist3, blocklist4, blocklist5] -def test_without_datadir(config_stub, tmpdir, monkeypatch, win_registry): - """No directory for data configured so no hosts file exists. - - Ensure CommandError is raised and no URL is blocked. - """ - config_stub.data = { - 'content': { - 'host-block-lists': generic_blocklists(tmpdir), - 'host-blocking-enabled': True, - } - } - monkeypatch.setattr('qutebrowser.utils.standarddir.data', lambda: None) - host_blocker = adblock.HostBlocker() - - with pytest.raises(cmdexc.CommandError) as excinfo: - host_blocker.adblock_update() - assert str(excinfo.value) == "No data storage is configured!" - - host_blocker.read_hosts() - for str_url in URLS_TO_CHECK: - assert not host_blocker.is_blocked(QUrl(str_url)) - - # To test on_config_changed - config_stub.set('content', 'host-block-lists', None) - - def test_disabled_blocking_update(basedir, config_stub, download_stub, data_tmpdir, tmpdir, win_registry, caplog): """Ensure no URL is blocked when host blocking is disabled.""" diff --git a/tests/unit/browser/webkit/test_cache.py b/tests/unit/browser/webkit/test_cache.py index 1d9b8a684..c716b9b82 100644 --- a/tests/unit/browser/webkit/test_cache.py +++ b/tests/unit/browser/webkit/test_cache.py @@ -111,16 +111,6 @@ def test_cache_size_deactivated(config_stub, tmpdir): assert disk_cache.cacheSize() == 0 -def test_cache_no_cache_dir(config_stub): - """Confirm that the cache is deactivated when cache_dir is None.""" - config_stub.data = { - 'storage': {'cache-size': 1024}, - 'general': {'private-browsing': False}, - } - disk_cache = cache.DiskCache(None) - assert disk_cache.cacheSize() == 0 - - def test_cache_existing_metadata_file(config_stub, tmpdir): """Test querying existing meta data file from activated cache.""" config_stub.data = { diff --git a/tests/unit/browser/webkit/test_history.py b/tests/unit/browser/webkit/test_history.py index c99adbb5a..7b2b6f53a 100644 --- a/tests/unit/browser/webkit/test_history.py +++ b/tests/unit/browser/webkit/test_history.py @@ -65,13 +65,6 @@ def test_async_read_twice(monkeypatch, qtbot, tmpdir, caplog): assert caplog.records[0].msg == expected -def test_async_read_no_datadir(qtbot, config_stub, fake_save_manager): - config_stub.data = {'general': {'private-browsing': False}} - hist = history.WebHistory(hist_dir=None, hist_name='history') - with qtbot.waitSignal(hist.async_read_done): - list(hist.async_read()) - - @pytest.mark.parametrize('redirect', [True, False]) def test_adding_item_during_async_read(qtbot, hist, redirect): """Check what happens when adding URL while reading the history.""" diff --git a/tests/unit/config/test_config.py b/tests/unit/config/test_config.py index 1253b6a0a..3b6f21f7d 100644 --- a/tests/unit/config/test_config.py +++ b/tests/unit/config/test_config.py @@ -23,9 +23,7 @@ import os.path import configparser import collections import shutil -from unittest import mock -from PyQt5.QtCore import QObject from PyQt5.QtGui import QColor import pytest @@ -33,7 +31,6 @@ import qutebrowser from qutebrowser.config import config, configexc, configdata from qutebrowser.config.parsers import keyconf from qutebrowser.commands import runners -from qutebrowser.utils import objreg, standarddir class TestConfigParser: @@ -43,12 +40,12 @@ class TestConfigParser: Objects = collections.namedtuple('Objects', ['cp', 'cfg']) @pytest.fixture - def objects(self): + def objects(self, tmpdir): cp = configparser.ConfigParser(interpolation=None, comment_prefixes='#') cp.optionxform = lambda opt: opt # be case-insensitive cfg = config.ConfigManager() - cfg.read(None, None) + cfg.read(str(tmpdir), 'qutebrowser.conf') return self.Objects(cp=cp, cfg=cfg) @pytest.mark.parametrize('config, section, option, value', [ @@ -247,7 +244,7 @@ class TestKeyConfigParser: """Test config.parsers.keyconf.KeyConfigParser.""" - def test_cmd_binding(self, cmdline_test, config_stub): + def test_cmd_binding(self, cmdline_test, config_stub, tmpdir): """Test various command bindings. See https://github.com/The-Compiler/qutebrowser/issues/615 @@ -256,7 +253,7 @@ class TestKeyConfigParser: cmdline_test: A pytest fixture which provides testcases. """ config_stub.data = {'aliases': []} - kcp = keyconf.KeyConfigParser(None, None) + kcp = keyconf.KeyConfigParser(str(tmpdir), 'keys.conf') kcp._cur_section = 'normal' if cmdline_test.valid: kcp._read_command(cmdline_test.cmd) @@ -379,17 +376,17 @@ class TestDefaultConfig: """Test validating of the default config.""" @pytest.mark.usefixtures('qapp') - def test_default_config(self): + def test_default_config(self, tmpdir): """Test validating of the default config.""" conf = config.ConfigManager() - conf.read(None, None) + conf.read(str(tmpdir), 'qutebrowser.conf') conf._validate_all() - def test_default_key_config(self): + def test_default_key_config(self, tmpdir): """Test validating of the default key config.""" # We import qutebrowser.app so the cmdutils.register decorators run. import qutebrowser.app # pylint: disable=unused-variable - conf = keyconf.KeyConfigParser(None, None) + conf = keyconf.KeyConfigParser(str(tmpdir), 'keys.conf') runner = runners.CommandRunner(win_id=0) for sectname in configdata.KEY_DATA: for cmd in conf.get_bindings_for(sectname).values(): @@ -418,48 +415,3 @@ class TestDefaultConfig: shutil.copy(full_path, str(tmpdir / 'qutebrowser.conf')) conf = config.ConfigManager() conf.read(str(tmpdir), 'qutebrowser.conf') - - -@pytest.mark.integration -class TestConfigInit: - - """Test initializing of the config.""" - - @pytest.fixture(autouse=True) - def patch(self, fake_args): - objreg.register('app', QObject()) - objreg.register('save-manager', mock.MagicMock()) - fake_args.relaxed_config = False - old_standarddir_args = standarddir._args - yield - objreg.delete('app') - objreg.delete('save-manager') - # registered by config.init() - objreg.delete('config') - objreg.delete('key-config') - objreg.delete('state-config') - standarddir._args = old_standarddir_args - - @pytest.fixture - def env(self, tmpdir): - conf_path = (tmpdir / 'config').ensure(dir=1) - data_path = (tmpdir / 'data').ensure(dir=1) - cache_path = (tmpdir / 'cache').ensure(dir=1) - env = { - 'XDG_CONFIG_HOME': str(conf_path), - 'XDG_DATA_HOME': str(data_path), - 'XDG_CACHE_HOME': str(cache_path), - } - return env - - def test_config_none(self, monkeypatch, env, fake_args): - """Test initializing with config path set to None.""" - fake_args.confdir = '' - fake_args.datadir = '' - fake_args.cachedir = '' - fake_args.basedir = None - for k, v in env.items(): - monkeypatch.setenv(k, v) - standarddir.init(fake_args) - config.init() - assert not os.listdir(env['XDG_CONFIG_HOME']) diff --git a/tests/unit/config/test_configtypes.py b/tests/unit/config/test_configtypes.py index 3a87ac999..2133cedd0 100644 --- a/tests/unit/config/test_configtypes.py +++ b/tests/unit/config/test_configtypes.py @@ -1284,14 +1284,6 @@ class TestFileAndUserStyleSheet: os_mock.path.join.assert_called_once_with( '/home/foo/.config/', 'foobar') - def test_validate_rel_config_none_file(self, os_mock, monkeypatch): - """Test with a relative path and standarddir.config returning None.""" - monkeypatch.setattr( - 'qutebrowser.config.configtypes.standarddir.config', lambda: None) - os_mock.path.isabs.return_value = False - with pytest.raises(configexc.ValidationError): - configtypes.File().validate('foobar') - @pytest.mark.parametrize('configtype, value, raises', [ (configtypes.File(), 'foobar', True), (configtypes.UserStyleSheet(), 'foobar', False), @@ -1355,14 +1347,8 @@ class TestFileAndUserStyleSheet: expected = self._expected(klass, '/configdir/foo') assert klass().transform('foo') == expected - @pytest.mark.parametrize('no_config', [False, True]) - def test_transform_userstylesheet_base64(self, monkeypatch, no_config): + def test_transform_userstylesheet_base64(self, monkeypatch): """Test transform with a data string.""" - if no_config: - monkeypatch.setattr( - 'qutebrowser.config.configtypes.standarddir.config', - lambda: None) - b64 = base64.b64encode(b"test").decode('ascii') url = QUrl("data:text/css;charset=utf-8;base64,{}".format(b64)) assert configtypes.UserStyleSheet().transform("test") == url diff --git a/tests/unit/misc/test_lineparser.py b/tests/unit/misc/test_lineparser.py index f5fbb2f4d..546d53cf9 100644 --- a/tests/unit/misc/test_lineparser.py +++ b/tests/unit/misc/test_lineparser.py @@ -52,15 +52,6 @@ class TestBaseLineParser: lineparser._prepare_save() os_mock.makedirs.assert_called_with(self.CONFDIR, 0o755) - def test_prepare_save_no_config(self, mocker): - """Test if _prepare_save doesn't create a None config dir.""" - os_mock = mocker.patch('qutebrowser.misc.lineparser.os') - os_mock.path.exists.return_value = True - - lineparser = lineparsermod.BaseLineParser(None, self.FILENAME) - assert not lineparser._prepare_save() - assert not os_mock.makedirs.called - def test_double_open(self, mocker, lineparser): """Test if _open refuses reentry.""" mocker.patch('builtins.open', mock.mock_open()) @@ -158,15 +149,6 @@ class TestAppendLineParser: lineparser.save() assert (tmpdir / 'file').read() == self._get_expected(new_data) - def test_save_without_configdir(self, tmpdir, lineparser): - """Test save() failing because no configdir was set.""" - new_data = ['new data 1', 'new data 2'] - lineparser.new_data = new_data - lineparser._configdir = None - assert not lineparser.save() - # make sure new data is still there - assert lineparser.new_data == new_data - def test_clear(self, tmpdir, lineparser): """Check if calling clear() empties both pending and persisted data.""" lineparser.new_data = ['one', 'two'] @@ -179,14 +161,6 @@ class TestAppendLineParser: assert not lineparser.new_data assert (tmpdir / 'file').read() == "" - def test_clear_without_configdir(self, tmpdir, lineparser): - """Test clear() failing because no configdir was set.""" - new_data = ['new data 1', 'new data 2'] - lineparser.new_data = new_data - lineparser._configdir = None - assert not lineparser.clear() - assert lineparser.new_data == new_data - def test_iter_without_open(self, lineparser): """Test __iter__ without having called open().""" with pytest.raises(ValueError): diff --git a/tests/unit/misc/test_sessions.py b/tests/unit/misc/test_sessions.py index 557ed7b2f..f1810c203 100644 --- a/tests/unit/misc/test_sessions.py +++ b/tests/unit/misc/test_sessions.py @@ -40,9 +40,9 @@ webengine_refactoring_xfail = pytest.mark.xfail( @pytest.fixture -def sess_man(): - """Fixture providing a SessionManager with no session dir.""" - return sessions.SessionManager(base_path=None) +def sess_man(tmpdir): + """Fixture providing a SessionManager.""" + return sessions.SessionManager(base_path=str(tmpdir)) class TestInit: @@ -52,13 +52,6 @@ class TestInit: yield objreg.delete('session-manager') - def test_no_standarddir(self, monkeypatch): - monkeypatch.setattr('qutebrowser.misc.sessions.standarddir.data', - lambda: None) - sessions.init() - manager = objreg.get('session-manager') - assert manager._base_path is None - @pytest.mark.parametrize('create_dir', [True, False]) def test_with_standarddir(self, tmpdir, monkeypatch, create_dir): monkeypatch.setattr('qutebrowser.misc.sessions.standarddir.data', @@ -110,16 +103,6 @@ class TestExists: assert not man.exists(name) - @pytest.mark.parametrize('absolute', [True, False]) - def test_no_datadir(self, sess_man, tmpdir, absolute): - abs_session = tmpdir / 'foo.yml' - abs_session.ensure() - - if absolute: - assert sess_man.exists(str(abs_session)) - else: - assert not sess_man.exists('foo') - @webengine_refactoring_xfail class TestSaveTab: @@ -247,11 +230,6 @@ class TestSave: objreg.delete('main-window', scope='window', window=0) objreg.delete('tabbed-browser', scope='window', window=0) - def test_no_config_storage(self, sess_man): - with pytest.raises(sessions.SessionError) as excinfo: - sess_man.save('foo') - assert str(excinfo.value) == "No data storage configured." - def test_update_completion_signal(self, sess_man, tmpdir, qtbot): session_path = tmpdir / 'foo.yml' with qtbot.waitSignal(sess_man.update_completion): @@ -415,9 +393,6 @@ def test_delete_update_completion_signal(sess_man, qtbot, tmpdir): class TestListSessions: - def test_no_base_path(self, sess_man): - assert not sess_man.list_sessions() - def test_no_sessions(self, tmpdir): sess_man = sessions.SessionManager(str(tmpdir)) assert not sess_man.list_sessions() diff --git a/tests/unit/utils/test_standarddir.py b/tests/unit/utils/test_standarddir.py index a40adb748..96a121b3f 100644 --- a/tests/unit/utils/test_standarddir.py +++ b/tests/unit/utils/test_standarddir.py @@ -165,15 +165,12 @@ class TestArguments: """Tests with confdir/cachedir/datadir arguments.""" - @pytest.fixture(params=[DirArgTest('', None), DirArgTest('foo', 'foo')]) + @pytest.fixture(params=[DirArgTest('foo', 'foo')]) def testcase(self, request, tmpdir): """Fixture providing testcases.""" - if request.param.expected is None: - return request.param - else: - # prepend tmpdir to both - arg = str(tmpdir / request.param.arg) - return DirArgTest(arg, arg) + # prepend tmpdir to both + arg = str(tmpdir / request.param.arg) + return DirArgTest(arg, arg) def test_confdir(self, testcase): """Test --confdir.""" @@ -210,8 +207,8 @@ class TestArguments: monkeypatch.setattr( 'qutebrowser.utils.standarddir.QStandardPaths.writableLocation', lambda _typ: str(tmpdir)) - args = types.SimpleNamespace(confdir=None, cachedir=None, datadir=None, - basedir=None) + args = types.SimpleNamespace(confdir=None, cachedir=None, + datadir=None, basedir=None) standarddir.init(args) assert standarddir.runtime() == str(tmpdir / 'qute_test') @@ -239,13 +236,6 @@ class TestInitCacheDirTag: """Tests for _init_cachedir_tag.""" - def test_no_cache_dir(self, mocker, monkeypatch): - """Smoke test with cache() returning None.""" - monkeypatch.setattr('qutebrowser.utils.standarddir.cache', - lambda: None) - mocker.patch('builtins.open', side_effect=AssertionError) - standarddir._init_cachedir_tag() - def test_existent_cache_dir_tag(self, tmpdir, mocker, monkeypatch): """Test with an existent CACHEDIR.TAG.""" monkeypatch.setattr('qutebrowser.utils.standarddir.cache', From d04534dc33564c6bcb91e990dcd2cc145a4d9c2b Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 15 Nov 2016 07:02:32 +0100 Subject: [PATCH 03/48] Reenable @qtwebengine_createWindow tests Closes #2121 --- pytest.ini | 1 - qutebrowser/browser/webengine/webview.py | 1 + tests/end2end/conftest.py | 7 ------- tests/end2end/features/hints.feature | 7 ++----- tests/end2end/features/history.feature | 1 - tests/end2end/features/javascript.feature | 8 +------- tests/end2end/features/utilcmds.feature | 3 +-- 7 files changed, 5 insertions(+), 23 deletions(-) diff --git a/pytest.ini b/pytest.ini index 109e30fde..2285053a9 100644 --- a/pytest.ini +++ b/pytest.ini @@ -17,7 +17,6 @@ markers = qtwebengine_todo: Features still missing with QtWebEngine qtwebengine_skip: Tests not applicable with QtWebEngine qtwebkit_skip: Tests not applicable with QtWebKit - qtwebengine_createWindow: Tests using createWindow with QtWebEngine (QTBUG-54419) qtwebengine_flaky: Tests which are flaky (and currently skipped) with QtWebEngine qtwebengine_osx_xfail: Tests which fail on OS X with QtWebEngine js_prompt: Tests needing to display a javascript prompt diff --git a/qutebrowser/browser/webengine/webview.py b/qutebrowser/browser/webengine/webview.py index ffc9555f3..1c3a989b1 100644 --- a/qutebrowser/browser/webengine/webview.py +++ b/qutebrowser/browser/webengine/webview.py @@ -87,6 +87,7 @@ class WebEngineView(QWebEngineView): target = usertypes.ClickTarget.tab elif wintype == QWebEnginePage.WebBrowserTab: # Middle-click / Ctrl-Click with Shift + # FIXME:qtwebengine this also affects target=_blank links... if background_tabs: target = usertypes.ClickTarget.tab else: diff --git a/tests/end2end/conftest.py b/tests/end2end/conftest.py index 4914422fb..d2512457e 100644 --- a/tests/end2end/conftest.py +++ b/tests/end2end/conftest.py @@ -132,14 +132,7 @@ if not getattr(sys, 'frozen', False): def pytest_collection_modifyitems(config, items): """Apply @qtwebengine_* markers; skip unittests with QUTE_BDD_WEBENGINE.""" - vercheck = qtutils.version_check - qtbug_54419_fixed = ((vercheck('5.6.2') and not vercheck('5.7.0')) or - qtutils.version_check('5.7.1') or - os.environ.get('QUTE_QTBUG54419_PATCHED', '')) - markers = [ - ('qtwebengine_createWindow', 'Skipped because of QTBUG-54419', - pytest.mark.skipif, not qtbug_54419_fixed and config.webengine), ('qtwebengine_todo', 'QtWebEngine TODO', pytest.mark.xfail, config.webengine), ('qtwebengine_skip', 'Skipped with QtWebEngine', pytest.mark.skipif, diff --git a/tests/end2end/features/hints.feature b/tests/end2end/features/hints.feature index 2a8f6082e..b6db8b47a 100644 --- a/tests/end2end/features/hints.feature +++ b/tests/end2end/features/hints.feature @@ -22,7 +22,6 @@ Feature: Using hints ### Opening in current or new tab - @qtwebengine_createWindow Scenario: Following a hint and force to open in current tab. When I open data/hints/link_blank.html And I hint with args "links current" and follow a @@ -30,7 +29,7 @@ Feature: Using hints Then the following tabs should be open: - data/hello.txt (active) - @qtwebengine_createWindow + @qtwebengine_skip: Opens in background Scenario: Following a hint and allow to open in new tab. When I open data/hints/link_blank.html And I hint with args "links normal" and follow a @@ -39,7 +38,6 @@ Feature: Using hints - data/hints/link_blank.html - data/hello.txt (active) - @qtwebengine_createWindow Scenario: Following a hint to link with sub-element and force to open in current tab. When I open data/hints/link_span.html And I run :tab-close @@ -186,13 +184,12 @@ Feature: Using hints And I hint wht args "links normal" and follow a Then "navigation request: url http://localhost:*/data/hello2.txt, type NavigationTypeLinkClicked, *" should be logged - @qtwebengine_createWindow + @qtwebengine_skip: Opens in new tab due to Chromium bug Scenario: Opening a link inside a specific iframe When I open data/hints/iframe_target.html And I hint with args "links normal" and follow a Then "navigation request: url http://localhost:*/data/hello.txt, type NavigationTypeLinkClicked, *" should be logged - @qtwebengine_createWindow Scenario: Opening a link with specific target frame in a new tab When I open data/hints/iframe_target.html And I run :tab-only diff --git a/tests/end2end/features/history.feature b/tests/end2end/features/history.feature index 529cced46..05a896be2 100644 --- a/tests/end2end/features/history.feature +++ b/tests/end2end/features/history.feature @@ -48,7 +48,6 @@ Feature: Page history Then the history file should contain: http://localhost:(port)/status/404 Error loading page: http://localhost:(port)/status/404 - @qtwebengine_createWindow Scenario: History with invalid URL When I open data/javascript/window_open.html And I run :click-element id open-invalid diff --git a/tests/end2end/features/javascript.feature b/tests/end2end/features/javascript.feature index dda097f00..c6e71f4de 100644 --- a/tests/end2end/features/javascript.feature +++ b/tests/end2end/features/javascript.feature @@ -7,8 +7,6 @@ Feature: Javascript stuff And I open data/javascript/consolelog.html Then the javascript message "console.log works!" should be logged - # Causes segfaults... - @qtwebengine_createWindow Scenario: Opening/Closing a window via JS When I open data/javascript/window_open.html And I run :tab-only @@ -18,8 +16,6 @@ Feature: Javascript stuff And I run :click-element id close-normal Then "Focus object changed: *" should be logged - # Causes segfaults... - @qtwebengine_createWindow Scenario: Opening/closing a modal window via JS When I open data/javascript/window_open.html And I run :tab-only @@ -44,7 +40,7 @@ Feature: Javascript stuff And I run :click-element id close-twice Then "Requested to close * which does not exist!" should be logged - @qtwebengine_createWindow @qtwebkit_skip + @qtwebkit_skip Scenario: Closing a JS window twice (issue 906) - qtwebengine When I open about:blank And I run :tab-only @@ -56,7 +52,6 @@ Feature: Javascript stuff And I wait for "Focus object changed: *" in the log Then no crash should happen - @qtwebengine_createWindow Scenario: Opening window without user interaction with javascript-can-open-windows-automatically set to true When I open data/hello.txt And I set content -> javascript-can-open-windows-automatically to true @@ -64,7 +59,6 @@ Feature: Javascript stuff And I run :jseval if (window.open('about:blank')) { console.log('window opened'); } else { console.log('error while opening window'); } Then the javascript message "window opened" should be logged - @qtwebengine_createWindow Scenario: Opening window without user interaction with javascript-can-open-windows-automatically set to false When I open data/hello.txt And I set content -> javascript-can-open-windows-automatically to false diff --git a/tests/end2end/features/utilcmds.feature b/tests/end2end/features/utilcmds.feature index 5f58ced3c..22acabc0b 100644 --- a/tests/end2end/features/utilcmds.feature +++ b/tests/end2end/features/utilcmds.feature @@ -124,11 +124,10 @@ Feature: Miscellaneous utility commands exposed to the user. Then the page should not be scrolled And the error "prompt-accept: This command is only allowed in prompt/yesno mode, not normal." should be shown - @qtwebengine_createWindow Scenario: :repeat-command with mode-switching command When I open data/hints/link_blank.html And I run :tab-only - And I hint with args "all" + And I hint with args "all tab-fg" And I run :leave-mode And I run :repeat-command And I run :follow-hint a From 671ce67be5bb2df6a13b408c19d8c79c76eaade4 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 15 Nov 2016 07:09:46 +0100 Subject: [PATCH 04/48] Remove --datadir/--confdir/--cachedir Closes #2115. --- CHANGELOG.asciidoc | 2 ++ doc/qutebrowser.1.asciidoc | 11 +------ qutebrowser/app.py | 4 +-- qutebrowser/qutebrowser.py | 8 +---- qutebrowser/utils/standarddir.py | 17 ---------- tests/unit/utils/test_standarddir.py | 49 +--------------------------- 6 files changed, 7 insertions(+), 84 deletions(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 8e7b898e5..e15136487 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -168,6 +168,8 @@ Removed thus removed. - All `--qt-*` arguments got replaced by `--qt-arg` and `--qt-flag` and thus removed. +- The `-c`/`--confdir`, `--datadir` and `--cachedir` arguments got removed, as + `--basedir` should be sufficient. Fixed ~~~~~ diff --git a/doc/qutebrowser.1.asciidoc b/doc/qutebrowser.1.asciidoc index 8b856ccb3..989fbd3cd 100644 --- a/doc/qutebrowser.1.asciidoc +++ b/doc/qutebrowser.1.asciidoc @@ -38,17 +38,8 @@ show it. *-h*, *--help*:: show this help message and exit -*-c* 'CONFDIR', *--confdir* 'CONFDIR':: - Set config directory - -*--datadir* 'DATADIR':: - Set data directory - -*--cachedir* 'CACHEDIR':: - Set cache directory - *--basedir* 'BASEDIR':: - Base directory for all storage. Other --*dir arguments are ignored if this is given. + Base directory for all storage. *-V*, *--version*:: Show version and quit. diff --git a/qutebrowser/app.py b/qutebrowser/app.py index 7a9260bc7..edc19931c 100644 --- a/qutebrowser/app.py +++ b/qutebrowser/app.py @@ -320,8 +320,8 @@ def _open_quickstart(args): Args: args: The argparse namespace. """ - if args.datadir is not None or args.basedir is not None: - # With --datadir or --basedir given, don't open quickstart. + if args.basedir is not None: + # With --basedir given, don't open quickstart. return state_config = objreg.get('state-config') try: diff --git a/qutebrowser/qutebrowser.py b/qutebrowser/qutebrowser.py index de80b972b..420a07d0c 100644 --- a/qutebrowser/qutebrowser.py +++ b/qutebrowser/qutebrowser.py @@ -47,13 +47,7 @@ def get_argparser(): """Get the argparse parser.""" parser = argparse.ArgumentParser(prog='qutebrowser', description=qutebrowser.__description__) - parser.add_argument('-c', '--confdir', help="Set config directory", - type=directory) - parser.add_argument('--datadir', help="Set data directory", type=directory) - parser.add_argument('--cachedir', help="Set cache directory", - type=directory) - parser.add_argument('--basedir', help="Base directory for all storage. " - "Other --*dir arguments are ignored if this is given.") + parser.add_argument('--basedir', help="Base directory for all storage.") parser.add_argument('-V', '--version', help="Show version and quit.", action='store_true') parser.add_argument('-s', '--set', help="Set a temporary setting for " diff --git a/qutebrowser/utils/standarddir.py b/qutebrowser/utils/standarddir.py index cbfa2555c..2903ad6bd 100644 --- a/qutebrowser/utils/standarddir.py +++ b/qutebrowser/utils/standarddir.py @@ -145,11 +145,6 @@ def _from_args(typ, args): override: boolean, if the user did override the path path: The overridden path, or None to turn off storage. """ - typ_to_argparse_arg = { - QStandardPaths.ConfigLocation: 'confdir', - QStandardPaths.DataLocation: 'datadir', - QStandardPaths.CacheLocation: 'cachedir', - } basedir_suffix = { QStandardPaths.ConfigLocation: 'config', QStandardPaths.DataLocation: 'data', @@ -170,18 +165,6 @@ def _from_args(typ, args): return (False, None) return (True, os.path.abspath(os.path.join(basedir, suffix))) - try: - argname = typ_to_argparse_arg[typ] - except KeyError: - return (False, None) - arg_value = getattr(args, argname) - - assert arg_value != '', argname - if arg_value is None: - return (False, None) - else: - return (True, arg_value) - def _create(path): """Create the `path` directory. diff --git a/tests/unit/utils/test_standarddir.py b/tests/unit/utils/test_standarddir.py index 96a121b3f..2d92fa1fc 100644 --- a/tests/unit/utils/test_standarddir.py +++ b/tests/unit/utils/test_standarddir.py @@ -163,54 +163,7 @@ DirArgTest = collections.namedtuple('DirArgTest', 'arg, expected') @pytest.mark.usefixtures('reset_standarddir') class TestArguments: - """Tests with confdir/cachedir/datadir arguments.""" - - @pytest.fixture(params=[DirArgTest('foo', 'foo')]) - def testcase(self, request, tmpdir): - """Fixture providing testcases.""" - # prepend tmpdir to both - arg = str(tmpdir / request.param.arg) - return DirArgTest(arg, arg) - - def test_confdir(self, testcase): - """Test --confdir.""" - args = types.SimpleNamespace(confdir=testcase.arg, cachedir=None, - datadir=None, basedir=None) - standarddir.init(args) - assert standarddir.config() == testcase.expected - - def test_cachedir(self, testcase): - """Test --cachedir.""" - args = types.SimpleNamespace(confdir=None, cachedir=testcase.arg, - datadir=None, basedir=None) - standarddir.init(args) - assert standarddir.cache() == testcase.expected - - def test_datadir(self, testcase): - """Test --datadir.""" - args = types.SimpleNamespace(confdir=None, cachedir=None, - datadir=testcase.arg, basedir=None) - standarddir.init(args) - assert standarddir.data() == testcase.expected - - def test_confdir_none(self, mocker): - """Test --confdir with None given.""" - # patch makedirs to a noop so we don't really create a directory - mocker.patch('qutebrowser.utils.standarddir.os.makedirs') - args = types.SimpleNamespace(confdir=None, cachedir=None, datadir=None, - basedir=None) - standarddir.init(args) - assert standarddir.config().split(os.sep)[-1] == 'qute_test' - - def test_runtimedir(self, tmpdir, monkeypatch): - """Test runtime dir (which has no args).""" - monkeypatch.setattr( - 'qutebrowser.utils.standarddir.QStandardPaths.writableLocation', - lambda _typ: str(tmpdir)) - args = types.SimpleNamespace(confdir=None, cachedir=None, - datadir=None, basedir=None) - standarddir.init(args) - assert standarddir.runtime() == str(tmpdir / 'qute_test') + """Tests the --basedir argument.""" @pytest.mark.parametrize('typ', ['config', 'data', 'cache', 'download', pytest.mark.linux('runtime')]) From a38e6be52ae1f2630641c991a0356116adf6d134 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 15 Nov 2016 07:26:10 +0100 Subject: [PATCH 05/48] Fix standarddir._from_args --- qutebrowser/utils/standarddir.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/qutebrowser/utils/standarddir.py b/qutebrowser/utils/standarddir.py index 2903ad6bd..bd80cd9af 100644 --- a/qutebrowser/utils/standarddir.py +++ b/qutebrowser/utils/standarddir.py @@ -153,9 +153,6 @@ def _from_args(typ, args): QStandardPaths.RuntimeLocation: 'runtime', } - if args is None: - return (False, None) - if getattr(args, 'basedir', None) is not None: basedir = args.basedir @@ -164,6 +161,8 @@ def _from_args(typ, args): except KeyError: # pragma: no cover return (False, None) return (True, os.path.abspath(os.path.join(basedir, suffix))) + else: + return (False, None) def _create(path): From ed10cd14d64546d516680e0df4eb010446cba47c Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 15 Nov 2016 07:28:09 +0100 Subject: [PATCH 06/48] Remove history NUL byte logging It just produces a lot of logging noise, and I still have no idea what to do about it... --- qutebrowser/browser/history.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/qutebrowser/browser/history.py b/qutebrowser/browser/history.py index c9008548a..40c497823 100644 --- a/qutebrowser/browser/history.py +++ b/qutebrowser/browser/history.py @@ -88,12 +88,8 @@ class Entry: if not url.isValid(): raise ValueError("Invalid URL: {}".format(url.errorString())) - if atime.startswith('\0'): - log.init.debug( - "Removing NUL bytes from entry {!r} - see " - "https://github.com/The-Compiler/qutebrowser/issues/" - "670".format(data)) - atime = atime.lstrip('\0') + # https://github.com/The-Compiler/qutebrowser/issues/670 + atime = atime.lstrip('\0') if '-' in atime: atime, flags = atime.split('-') From 96e16d6fe88059f54bd50980148d685d2d5e07f9 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 15 Nov 2016 09:12:52 +0100 Subject: [PATCH 07/48] Add a test for standarddir.runtime() --- tests/unit/utils/test_standarddir.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/unit/utils/test_standarddir.py b/tests/unit/utils/test_standarddir.py index 2d92fa1fc..de87509ba 100644 --- a/tests/unit/utils/test_standarddir.py +++ b/tests/unit/utils/test_standarddir.py @@ -110,6 +110,7 @@ class TestStandardDir: (standarddir.data, 'XDG_DATA_HOME'), (standarddir.config, 'XDG_CONFIG_HOME'), (standarddir.cache, 'XDG_CACHE_HOME'), + (standarddir.runtime, 'XDG_RUNTIME_DIR'), ]) @pytest.mark.linux def test_linux_explicit(self, monkeypatch, tmpdir, func, varname): From fc7961ae2247cdca28995fe45d33022237c2f7d8 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 15 Nov 2016 10:33:49 +0100 Subject: [PATCH 08/48] Set correct paths for QtWebEngine --- .../browser/webengine/webenginesettings.py | 13 +-- qutebrowser/utils/standarddir.py | 49 +++++++++ tests/unit/utils/test_standarddir.py | 99 +++++++++++++++++++ 3 files changed, 155 insertions(+), 6 deletions(-) diff --git a/qutebrowser/browser/webengine/webenginesettings.py b/qutebrowser/browser/webengine/webenginesettings.py index 3eca8cd2c..7b38f47fc 100644 --- a/qutebrowser/browser/webengine/webenginesettings.py +++ b/qutebrowser/browser/webengine/webenginesettings.py @@ -27,11 +27,11 @@ Module attributes: import os # pylint: disable=no-name-in-module,import-error,useless-suppression -from PyQt5.QtWebEngineWidgets import QWebEngineSettings +from PyQt5.QtWebEngineWidgets import QWebEngineSettings, QWebEngineProfile # pylint: enable=no-name-in-module,import-error,useless-suppression from qutebrowser.config import websettings, config -from qutebrowser.utils import objreg, utils +from qutebrowser.utils import objreg, utils, standarddir class Attribute(websettings.Attribute): @@ -70,12 +70,15 @@ def update_settings(section, option): def init(): """Initialize the global QWebSettings.""" - # FIXME:qtwebengine set paths in profile - if config.get('general', 'developer-extras'): # FIXME:qtwebengine Make sure we call globalSettings *after* this... os.environ['QTWEBENGINE_REMOTE_DEBUGGING'] = str(utils.random_port()) + profile = QWebEngineProfile.defaultProfile() + profile.setCachePath(os.path.join(standarddir.cache(), 'webengine')) + profile.setPersistentStoragePath( + os.path.join(standarddir.data(), 'webengine')) + websettings.init_mappings(MAPPINGS) objreg.get('config').changed.connect(update_settings) @@ -98,13 +101,11 @@ def shutdown(): # - PictographFont # # TODO settings on profile: -# - cachePath # - httpAcceptLanguage # - httpCacheMaximumSize # - httpUserAgent # - persistentCookiesPolicy # - offTheRecord -# - persistentStoragePath # # TODO settings elsewhere: # - proxy diff --git a/qutebrowser/utils/standarddir.py b/qutebrowser/utils/standarddir.py index bd80cd9af..05fe7ce27 100644 --- a/qutebrowser/utils/standarddir.py +++ b/qutebrowser/utils/standarddir.py @@ -21,6 +21,7 @@ import os import sys +import shutil import os.path from PyQt5.QtCore import QCoreApplication, QStandardPaths @@ -188,6 +189,8 @@ def init(args): log.init.debug("Base directory: {}".format(args.basedir)) _args = args _init_cachedir_tag() + if args is not None: + _move_webengine_data() def _init_cachedir_tag(): @@ -207,3 +210,49 @@ def _init_cachedir_tag(): "cachedir/\n") except OSError: log.init.exception("Failed to create CACHEDIR.TAG") + + +def _move_webengine_data(): + """Move QtWebEngine data from an older location to the new one.""" + # Do NOT use _writable_location here as that'd give us a wrong path + old_data_dir = QStandardPaths.writableLocation(QStandardPaths.DataLocation) + old_cache_dir = QStandardPaths.writableLocation( + QStandardPaths.CacheLocation) + new_data_dir = os.path.join(data(), 'webengine') + new_cache_dir = os.path.join(cache(), 'webengine') + + if (not os.path.exists(os.path.join(old_data_dir, 'QtWebEngine')) and + not os.path.exists(os.path.join(old_cache_dir, 'QtWebEngine'))): + return + + log.init.debug("Moving QtWebEngine data from {} to {}".format( + old_data_dir, new_data_dir)) + log.init.debug("Moving QtWebEngine cache from {} to {}".format( + old_cache_dir, new_cache_dir)) + + if os.path.exists(new_data_dir): + log.init.warning("Failed to move old QtWebEngine data as {} already " + "exists!".format(new_data_dir)) + return + if os.path.exists(new_cache_dir): + log.init.warning("Failed to move old QtWebEngine cache as {} already " + "exists!".format(new_cache_dir)) + return + + try: + shutil.move(os.path.join(old_data_dir, 'QtWebEngine', 'Default'), + new_data_dir) + shutil.move(os.path.join(old_cache_dir, 'QtWebEngine', 'Default'), + new_cache_dir) + + # Remove e.g. + # ~/.local/share/qutebrowser/qutebrowser/QtWebEngine/Default + if old_data_dir.split(os.sep)[-2:] == ['qutebrowser', 'qutebrowser']: + log.init.debug("Removing {} / {}".format( + old_data_dir, old_cache_dir)) + for old_dir in old_data_dir, old_cache_dir: + os.rmdir(os.path.join(old_dir, 'QtWebEngine')) + os.rmdir(old_dir) + except OSError as e: + log.init.exception("Failed to move old QtWebEngine data/cache: " + "{}".format(e)) diff --git a/tests/unit/utils/test_standarddir.py b/tests/unit/utils/test_standarddir.py index de87509ba..eb348132b 100644 --- a/tests/unit/utils/test_standarddir.py +++ b/tests/unit/utils/test_standarddir.py @@ -300,3 +300,102 @@ class TestSystemData: standarddir.init(fake_args) monkeypatch.setattr('sys.platform', "potato") assert standarddir.system_data() == standarddir.data() + + +class TestMoveWebEngineData: + + """Test moving QtWebEngine data from an old location.""" + + @pytest.fixture(autouse=True) + def patch_standardpaths(self, tmpdir, monkeypatch): + locations = { + QStandardPaths.DataLocation: str(tmpdir / 'data'), + QStandardPaths.CacheLocation: str(tmpdir / 'cache'), + } + monkeypatch.setattr(standarddir.QStandardPaths, 'writableLocation', + locations.get) + monkeypatch.setattr(standarddir, 'data', + lambda: str(tmpdir / 'new_data')) + monkeypatch.setattr(standarddir, 'cache', + lambda: str(tmpdir / 'new_cache')) + + @pytest.fixture + def files(self, tmpdir): + files = collections.namedtuple('Files', ['old_data', 'new_data', + 'old_cache', 'new_cache']) + return files( + old_data=tmpdir / 'data' / 'QtWebEngine' / 'Default' / 'datafile', + new_data=tmpdir / 'new_data' / 'webengine' / 'datafile', + old_cache=(tmpdir / 'cache' / 'QtWebEngine' / 'Default' / + 'cachefile'), + new_cache=(tmpdir / 'new_cache' / 'webengine' / 'cachefile'), + ) + + def test_no_webengine_dir(self, caplog): + """Nothing should happen without any QtWebEngine directory.""" + standarddir._move_webengine_data() + assert not any(rec.message.startswith('Moving QtWebEngine') + for rec in caplog.records) + + def test_moving_data(self, files): + files.old_data.ensure() + files.old_cache.ensure() + + standarddir._move_webengine_data() + + assert not files.old_data.exists() + assert not files.old_cache.exists() + assert files.new_data.exists() + assert files.new_cache.exists() + + @pytest.mark.parametrize('what', ['data', 'cache']) + def test_already_existing(self, files, caplog, what): + files.old_data.ensure() + files.old_cache.ensure() + + if what == 'data': + files.new_data.ensure() + else: + files.new_cache.ensure() + + with caplog.at_level(logging.WARNING): + standarddir._move_webengine_data() + + record = caplog.records[-1] + expected = "Failed to move old QtWebEngine {}".format(what) + assert record.message.startswith(expected) + + def test_deleting_empty_dirs(self, monkeypatch, tmpdir): + """When we have a qutebrowser/qutebrowser subfolder, clean it up.""" + old_data = tmpdir / 'data' / 'qutebrowser' / 'qutebrowser' + old_cache = tmpdir / 'cache' / 'qutebrowser' / 'qutebrowser' + locations = { + QStandardPaths.DataLocation: str(old_data), + QStandardPaths.CacheLocation: str(old_cache), + } + monkeypatch.setattr(standarddir.QStandardPaths, 'writableLocation', + locations.get) + + old_data_file = old_data / 'QtWebEngine' / 'Default' / 'datafile' + old_cache_file = old_cache / 'QtWebEngine' / 'Default' / 'cachefile' + old_data_file.ensure() + old_cache_file.ensure() + + standarddir._move_webengine_data() + + assert not (tmpdir / 'data' / 'qutebrowser' / 'qutebrowser').exists() + assert not (tmpdir / 'cache' / 'qutebrowser' / 'qutebrowser').exists() + + def test_deleting_error(self, files, monkeypatch, mocker, caplog): + """When there was an error it should be logged.""" + mock = mocker.Mock(side_effect=OSError('error')) + monkeypatch.setattr(standarddir.shutil, 'move', mock) + files.old_data.ensure() + files.old_cache.ensure() + + with caplog.at_level(logging.ERROR): + standarddir._move_webengine_data() + + record = caplog.records[-1] + expected = "Failed to move old QtWebEngine data/cache: error" + assert record.message == expected From bc3f96ce9ae82052f6392a57781af9bad0a99bd6 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 15 Nov 2016 10:51:21 +0100 Subject: [PATCH 09/48] flake8 requirement: Update pycodestyle to 2.2.0 --- misc/requirements/requirements-flake8.txt | 2 +- misc/requirements/requirements-flake8.txt-raw | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/misc/requirements/requirements-flake8.txt b/misc/requirements/requirements-flake8.txt index fcea7b719..7fa392d0d 100644 --- a/misc/requirements/requirements-flake8.txt +++ b/misc/requirements/requirements-flake8.txt @@ -15,7 +15,7 @@ flake8-tuple==0.2.12 mccabe==0.5.2 packaging==16.8 pep8-naming==0.4.1 -pycodestyle==2.1.0 +pycodestyle==2.2.0 pydocstyle==1.1.1 pyflakes==1.3.0 pyparsing==2.1.10 diff --git a/misc/requirements/requirements-flake8.txt-raw b/misc/requirements/requirements-flake8.txt-raw index 984ec50ca..73c4eafb4 100644 --- a/misc/requirements/requirements-flake8.txt-raw +++ b/misc/requirements/requirements-flake8.txt-raw @@ -15,7 +15,7 @@ pydocstyle pyflakes # Pinned to 2.0.0 otherwise -pycodestyle==2.1.0 +pycodestyle==2.2.0 # Waiting until flake8-putty updated #@ filter: flake8 < 3.0.0 From 8d173e1718e1a99754381b7f2ac49e03f0029870 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 15 Nov 2016 10:55:12 +0100 Subject: [PATCH 10/48] Update comment --- qutebrowser/browser/webengine/webenginesettings.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/qutebrowser/browser/webengine/webenginesettings.py b/qutebrowser/browser/webengine/webenginesettings.py index 7b38f47fc..ae18243a6 100644 --- a/qutebrowser/browser/webengine/webenginesettings.py +++ b/qutebrowser/browser/webengine/webenginesettings.py @@ -109,8 +109,6 @@ def shutdown(): # # TODO settings elsewhere: # - proxy -# - custom headers -# - ssl-strict MAPPINGS = { 'content': { From d05918ac0b77c607bed32b361444ef8c032c8406 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 15 Nov 2016 11:32:05 +0100 Subject: [PATCH 11/48] Add custom user-agent support for QtWebEngine --- qutebrowser/browser/webengine/interceptor.py | 5 +++++ qutebrowser/browser/webengine/webenginesettings.py | 2 -- qutebrowser/config/configdata.py | 3 +-- qutebrowser/config/configtypes.py | 5 +++++ tests/end2end/features/conftest.py | 6 +++++- tests/end2end/features/misc.feature | 10 ++++++++++ tests/unit/config/test_configtypes.py | 5 +++-- 7 files changed, 29 insertions(+), 7 deletions(-) diff --git a/qutebrowser/browser/webengine/interceptor.py b/qutebrowser/browser/webengine/interceptor.py index 918d10fe3..350378147 100644 --- a/qutebrowser/browser/webengine/interceptor.py +++ b/qutebrowser/browser/webengine/interceptor.py @@ -23,6 +23,7 @@ from PyQt5.QtWebEngineCore import QWebEngineUrlRequestInterceptor # pylint: enable=no-name-in-module,import-error,useless-suppression +from qutebrowser.config import config from qutebrowser.browser import shared from qutebrowser.utils import utils, log @@ -64,3 +65,7 @@ class RequestInterceptor(QWebEngineUrlRequestInterceptor): for header, value in shared.custom_headers(): info.setHttpHeader(header, value) + + user_agent = config.get('network', 'user-agent') + if user_agent is not None: + info.setHttpHeader(b'User-Agent', user_agent.encode('ascii')) diff --git a/qutebrowser/browser/webengine/webenginesettings.py b/qutebrowser/browser/webengine/webenginesettings.py index ae18243a6..ec54e0044 100644 --- a/qutebrowser/browser/webengine/webenginesettings.py +++ b/qutebrowser/browser/webengine/webenginesettings.py @@ -101,9 +101,7 @@ def shutdown(): # - PictographFont # # TODO settings on profile: -# - httpAcceptLanguage # - httpCacheMaximumSize -# - httpUserAgent # - persistentCookiesPolicy # - offTheRecord # diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index e90ac15b3..abcb5e226 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -413,8 +413,7 @@ def data(readonly=False): "Send the Referer header"), ('user-agent', - SettingValue(typ.UserAgent(none_ok=True), '', - backends=[usertypes.Backend.QtWebKit]), + SettingValue(typ.UserAgent(none_ok=True), ''), "User agent to send. Empty to send the default."), ('proxy', diff --git a/qutebrowser/config/configtypes.py b/qutebrowser/config/configtypes.py index 0642b79e8..4525e58c2 100644 --- a/qutebrowser/config/configtypes.py +++ b/qutebrowser/config/configtypes.py @@ -1462,6 +1462,11 @@ class UserAgent(BaseType): def validate(self, value): self._basic_validation(value) + try: + value.encode('ascii') + except UnicodeEncodeError as e: + msg = "User-Agent contains non-ascii characters: {}".format(e) + raise configexc.ValidationError(value, msg) # To update the following list of user agents, run the script 'ua_fetch.py' # Vim-protip: Place your cursor below this comment and run diff --git a/tests/end2end/features/conftest.py b/tests/end2end/features/conftest.py index dea0e9a83..7dd42c2d0 100644 --- a/tests/end2end/features/conftest.py +++ b/tests/end2end/features/conftest.py @@ -121,6 +121,8 @@ def set_setting_given(quteproc, httpbin, sect, opt, value): This is available as "Given:" step so it can be used as "Background:". """ + if value == '': + value = '' value = value.replace('(port)', str(httpbin.port)) quteproc.set_setting(sect, opt, value) @@ -211,6 +213,8 @@ def open_path(quteproc, path): @bdd.when(bdd.parsers.parse("I set {sect} -> {opt} to {value}")) def set_setting(quteproc, httpbin, sect, opt, value): """Set a qutebrowser setting.""" + if value == '': + value = '' value = value.replace('(port)', str(httpbin.port)) quteproc.set_setting(sect, opt, value) @@ -479,7 +483,7 @@ def check_header(quteproc, header, value): content = quteproc.get_content() data = json.loads(content) print(data) - assert data['headers'][header] == value + assert utils.pattern_match(pattern=value, value=data['headers'][header]) @bdd.then(bdd.parsers.parse('the page should contain the html "{text}"')) diff --git a/tests/end2end/features/misc.feature b/tests/end2end/features/misc.feature index 40f17d1a0..b4ba55fc3 100644 --- a/tests/end2end/features/misc.feature +++ b/tests/end2end/features/misc.feature @@ -415,6 +415,16 @@ Feature: Various utility commands. And I open headers Then the header Accept-Language should be set to en,de + Scenario: Setting a custom user-agent header + When I set network -> user-agent to toaster + And I open headers + Then the header User-Agent should be set to toaster + + Scenario: Setting the default user-agent header + When I set network -> user-agent to + And I open headers + Then the header User-Agent should be set to Mozilla/5.0 * + ## :messages Scenario: Showing error messages diff --git a/tests/unit/config/test_configtypes.py b/tests/unit/config/test_configtypes.py index 2133cedd0..f9d86f0ce 100644 --- a/tests/unit/config/test_configtypes.py +++ b/tests/unit/config/test_configtypes.py @@ -1976,10 +1976,11 @@ class TestUserAgent: def test_validate_valid(self, klass, val): klass(none_ok=True).validate(val) - def test_validate_invalid(self, klass): + @pytest.mark.parametrize('val', ['', 'überbrowser']) + def test_validate_invalid(self, klass, val): """Test validate with empty string and none_ok = False.""" with pytest.raises(configexc.ValidationError): - klass().validate('') + klass().validate(val) def test_transform(self, klass): assert klass().transform('foobar') == 'foobar' From 456ab18f249a7a62a3bb3adc601c190b4ffdb0a6 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 15 Nov 2016 19:36:32 +0100 Subject: [PATCH 12/48] Fix double-When in BDD test --- tests/end2end/features/javascript.feature | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/end2end/features/javascript.feature b/tests/end2end/features/javascript.feature index c6e71f4de..c4662006b 100644 --- a/tests/end2end/features/javascript.feature +++ b/tests/end2end/features/javascript.feature @@ -33,7 +33,7 @@ Feature: Javascript stuff Scenario: Closing a JS window twice (issue 906) - qtwebkit When I open about:blank And I run :tab-only - When I open data/javascript/window_open.html in a new tab + And I open data/javascript/window_open.html in a new tab And I run :click-element id open-normal And I wait for "Changing title for idx 2 to 'about:blank'" in the log And I run :tab-focus 2 From a09a565aead2f691ef2928a135e3a83c9020c6c7 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 15 Nov 2016 19:43:03 +0100 Subject: [PATCH 13/48] Improve :click-element error message --- qutebrowser/browser/commands.py | 2 +- tests/end2end/features/misc.feature | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 6820cb3dc..f4b90b8e5 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -1572,7 +1572,7 @@ class CommandDispatcher: def single_cb(elem): """Click a single element.""" if elem is None: - message.error("No element found!") + message.error("No element found with id {}!".format(value)) return try: elem.click(target) diff --git a/tests/end2end/features/misc.feature b/tests/end2end/features/misc.feature index b4ba55fc3..6d3862713 100644 --- a/tests/end2end/features/misc.feature +++ b/tests/end2end/features/misc.feature @@ -566,7 +566,7 @@ Feature: Various utility commands. Scenario: Clicking an element with unknown ID When I open data/click_element.html And I run :click-element id blah - Then the error "No element found!" should be shown + Then the error "No element found with id blah!" should be shown Scenario: Clicking an element by ID When I open data/click_element.html From e1928ad99150725c1b4e64cab4de70256425a501 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 15 Nov 2016 20:17:51 +0100 Subject: [PATCH 14/48] Mark javascript BDD test as flaky For some reason it sometimes fails with an error message coming out of nowhere... --- tests/end2end/features/javascript.feature | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/end2end/features/javascript.feature b/tests/end2end/features/javascript.feature index c4662006b..338101f73 100644 --- a/tests/end2end/features/javascript.feature +++ b/tests/end2end/features/javascript.feature @@ -40,7 +40,7 @@ Feature: Javascript stuff And I run :click-element id close-twice Then "Requested to close * which does not exist!" should be logged - @qtwebkit_skip + @qtwebkit_skip @flaky Scenario: Closing a JS window twice (issue 906) - qtwebengine When I open about:blank And I run :tab-only From 964ddb472bb57516110e8a8f4c50ab2b95513e6d Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 15 Nov 2016 22:25:51 +0100 Subject: [PATCH 15/48] Add urlutils.data_url --- qutebrowser/config/configtypes.py | 5 ++--- qutebrowser/utils/jinja.py | 3 +-- qutebrowser/utils/urlutils.py | 9 +++++++++ tests/unit/browser/webkit/test_history.py | 8 +++----- tests/unit/config/test_configtypes.py | 2 +- tests/unit/utils/test_jinja.py | 2 +- tests/unit/utils/test_urlutils.py | 5 +++++ 7 files changed, 22 insertions(+), 12 deletions(-) diff --git a/qutebrowser/config/configtypes.py b/qutebrowser/config/configtypes.py index 4525e58c2..d4e8a3514 100644 --- a/qutebrowser/config/configtypes.py +++ b/qutebrowser/config/configtypes.py @@ -37,7 +37,7 @@ from PyQt5.QtWidgets import QTabWidget, QTabBar from qutebrowser.commands import cmdutils from qutebrowser.config import configexc -from qutebrowser.utils import standarddir, utils +from qutebrowser.utils import standarddir, utils, urlutils SYSTEM_PROXY = object() # Return value for Proxy type @@ -1177,8 +1177,7 @@ class UserStyleSheet(File): if path is not None and os.path.exists(path): return QUrl.fromLocalFile(path) else: - data = base64.b64encode(value.encode('utf-8')).decode('ascii') - return QUrl("data:text/css;charset=utf-8;base64,{}".format(data)) + return urlutils.data_url('text/css', value.encode('utf-8')) def validate(self, value): self._basic_validation(value) diff --git a/qutebrowser/utils/jinja.py b/qutebrowser/utils/jinja.py index a8d746b58..0487f5ddb 100644 --- a/qutebrowser/utils/jinja.py +++ b/qutebrowser/utils/jinja.py @@ -82,8 +82,7 @@ def data_url(path): filename = utils.resource_filename(path) mimetype = mimetypes.guess_type(filename) assert mimetype is not None, path - b64 = base64.b64encode(data).decode('ascii') - return 'data:{};charset=utf-8;base64,{}'.format(mimetype[0], b64) + return urlutils.data_url(mimetype[0], data).toString() def render(template, **kwargs): diff --git a/qutebrowser/utils/urlutils.py b/qutebrowser/utils/urlutils.py index 5417d9a7a..6a24ceb82 100644 --- a/qutebrowser/utils/urlutils.py +++ b/qutebrowser/utils/urlutils.py @@ -20,6 +20,7 @@ """Utils regarding URL handling.""" import re +import base64 import os.path import ipaddress import posixpath @@ -580,3 +581,11 @@ def file_url(path): path: The absolute path to the local file """ return QUrl.fromLocalFile(path).toString(QUrl.FullyEncoded) + + +def data_url(mimetype, data): + """Get a data: QUrl for the given data.""" + b64 = base64.b64encode(data).decode('ascii') + url = QUrl('data:{};base64,{}'.format(mimetype, b64)) + qtutils.ensure_valid(url) + return url diff --git a/tests/unit/browser/webkit/test_history.py b/tests/unit/browser/webkit/test_history.py index 7b2b6f53a..94169e40e 100644 --- a/tests/unit/browser/webkit/test_history.py +++ b/tests/unit/browser/webkit/test_history.py @@ -19,7 +19,6 @@ """Tests for the global page history.""" -import base64 import logging import pytest @@ -28,7 +27,7 @@ from hypothesis import strategies from PyQt5.QtCore import QUrl from qutebrowser.browser import history -from qutebrowser.utils import objreg +from qutebrowser.utils import objreg, urlutils class FakeWebHistory: @@ -375,9 +374,8 @@ def hist_interface(): def test_history_interface(qtbot, webview, hist_interface): - html = "foo" - data = base64.b64encode(html.encode('utf-8')).decode('ascii') - url = QUrl("data:text/html;charset=utf-8;base64,{}".format(data)) + html = b"foo" + url = urlutils.data_url('text/html', html) with qtbot.waitSignal(webview.loadFinished): webview.load(url) diff --git a/tests/unit/config/test_configtypes.py b/tests/unit/config/test_configtypes.py index f9d86f0ce..2f0c3ce7a 100644 --- a/tests/unit/config/test_configtypes.py +++ b/tests/unit/config/test_configtypes.py @@ -1350,7 +1350,7 @@ class TestFileAndUserStyleSheet: def test_transform_userstylesheet_base64(self, monkeypatch): """Test transform with a data string.""" b64 = base64.b64encode(b"test").decode('ascii') - url = QUrl("data:text/css;charset=utf-8;base64,{}".format(b64)) + url = QUrl("data:text/css;base64,{}".format(b64)) assert configtypes.UserStyleSheet().transform("test") == url diff --git a/tests/unit/utils/test_jinja.py b/tests/unit/utils/test_jinja.py index eb7a621fc..e951e3994 100644 --- a/tests/unit/utils/test_jinja.py +++ b/tests/unit/utils/test_jinja.py @@ -102,7 +102,7 @@ def test_data_url(): print(data) url = QUrl(data) assert url.isValid() - assert data == 'data:text/plain;charset=utf-8;base64,Zm9v' # 'foo' + assert data == 'data:text/plain;base64,Zm9v' # 'foo' def test_not_found(): diff --git a/tests/unit/utils/test_urlutils.py b/tests/unit/utils/test_urlutils.py index f98ba65d6..984971b3c 100644 --- a/tests/unit/utils/test_urlutils.py +++ b/tests/unit/utils/test_urlutils.py @@ -731,3 +731,8 @@ class TestIncDecNumber: def test_file_url(): assert urlutils.file_url('/foo/bar') == 'file:///foo/bar' + + +def test_data_url(): + url = urlutils.data_url('text/plain', b'foo') + assert url == QUrl('data:text/plain;base64,Zm9v') From fcb955458c1e2b596dea1db2c8ebe44a3f813896 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 15 Nov 2016 22:58:56 +0100 Subject: [PATCH 16/48] Require a filename for user-stylesheet; add hide-scrollbar setting --- CHANGELOG.asciidoc | 3 ++ qutebrowser/browser/shared.py | 16 +++++++++ qutebrowser/browser/webkit/webkitsettings.py | 21 +++++++++--- qutebrowser/config/config.py | 5 +++ qutebrowser/config/configdata.py | 14 ++++---- qutebrowser/config/configtypes.py | 34 ------------------- tests/unit/config/test_configtypes.py | 33 ++---------------- .../config/test_configtypes_hypothesis.py | 3 +- 8 files changed, 52 insertions(+), 77 deletions(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index e15136487..8dc7caa3a 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -50,6 +50,8 @@ Added - New `cast` userscript to show a video on a Google Chromecast - New `:run-with-count` command which replaces the (undocumented) `:count:command` syntax. - New `:record-macro` (`q`) and `:run-macro` (`@`) commands for keyboard macros. +- New `ui -> hide-scrollbar` setting to hide the scrollbar independently of the + `user-stylesheet` setting. Changed ~~~~~~~ @@ -143,6 +145,7 @@ Changed - Various functionality now works when javascript is disabled with QtWebKit - Various commands/settings taking `left`/`right`/`previous` arguments now take `prev`/`next`/`last-used` to remove ambiguity. +- The `ui -> user-stylesheet` setting now only takes filenames, not CSS snippets Deprecated ~~~~~~~~~~ diff --git a/qutebrowser/browser/shared.py b/qutebrowser/browser/shared.py index ea1597ee5..5db2d9fa0 100644 --- a/qutebrowser/browser/shared.py +++ b/qutebrowser/browser/shared.py @@ -222,3 +222,19 @@ def get_tab(win_id, target): tabbed_browser = objreg.get('tabbed-browser', scope='window', window=win_id) return tabbed_browser.tabopen(url=None, background=bg_tab) + + +def get_user_stylesheet(): + """Get the combined user-stylesheet.""" + filename = config.get('ui', 'user-stylesheet') + + if filename is None: + css = '' + else: + with open(filename, 'r', encoding='utf-8') as f: + css = f.read() + + if config.get('ui', 'hide-scrollbar'): + css += '\nhtml > ::-webkit-scrollbar { width: 0px; height: 0px; }' + + return css diff --git a/qutebrowser/browser/webkit/webkitsettings.py b/qutebrowser/browser/webkit/webkitsettings.py index 86505162f..800206c5a 100644 --- a/qutebrowser/browser/webkit/webkitsettings.py +++ b/qutebrowser/browser/webkit/webkitsettings.py @@ -26,10 +26,12 @@ Module attributes: import os.path +from PyQt5.QtCore import QUrl from PyQt5.QtWebKit import QWebSettings from qutebrowser.config import config, websettings -from qutebrowser.utils import standarddir, objreg +from qutebrowser.utils import standarddir, objreg, urlutils +from qutebrowser.browser import shared class Attribute(websettings.Attribute): @@ -80,14 +82,24 @@ class CookiePolicy(websettings.Base): self.MAPPING[value]) +def _set_user_stylesheet(): + """Set the generated user-stylesheet.""" + stylesheet = shared.get_user_stylesheet().encode('utf-8') + url = urlutils.data_url('text/css;charset=utf-8', stylesheet) + QWebSettings.globalSettings().setUserStyleSheetUrl(url) + + def update_settings(section, option): """Update global settings when qwebsettings changed.""" - cache_path = standarddir.cache() if (section, option) == ('general', 'private-browsing'): + cache_path = standarddir.cache() if config.get('general', 'private-browsing') or cache_path is None: QWebSettings.setIconDatabasePath('') else: QWebSettings.setIconDatabasePath(cache_path) + elif section == 'ui' and option in ['hide-scrollbar', 'user-stylesheet']: + _set_user_stylesheet() + websettings.update_mappings(MAPPINGS, section, option) @@ -108,6 +120,7 @@ def init(): os.path.join(data_path, 'offline-storage')) websettings.init_mappings(MAPPINGS) + _set_user_stylesheet() objreg.get('config').changed.connect(update_settings) @@ -204,9 +217,7 @@ MAPPINGS = { Attribute(QWebSettings.ZoomTextOnly), 'frame-flattening': Attribute(QWebSettings.FrameFlatteningEnabled), - 'user-stylesheet': - Setter(getter=QWebSettings.userStyleSheetUrl, - setter=QWebSettings.setUserStyleSheetUrl), + # user-stylesheet is handled separately 'css-media-type': NullStringSetter(getter=QWebSettings.cssMediaType, setter=QWebSettings.setCSSMediaType), diff --git a/qutebrowser/config/config.py b/qutebrowser/config/config.py index 38d8aa6eb..57c498400 100644 --- a/qutebrowser/config/config.py +++ b/qutebrowser/config/config.py @@ -437,6 +437,11 @@ class ConfigManager(QObject): ('fonts', 'hints'): _transform_hint_font, ('completion', 'show'): _get_value_transformer({'false': 'never', 'true': 'always'}), + ('ui', 'user-stylesheet'): + _get_value_transformer({ + 'html > ::-webkit-scrollbar { width: 0px; height: 0px; }': '', + '::-webkit-scrollbar { width: 0px; height: 0px; }': '', + }), } changed = pyqtSignal(str, str) diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index abcb5e226..ebc89145c 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -323,13 +323,15 @@ def data(readonly=False): "page."), ('user-stylesheet', - SettingValue(typ.UserStyleSheet(none_ok=True), - 'html > ::-webkit-scrollbar { width: 0px; ' - 'height: 0px; }', + SettingValue(typ.File(none_ok=True), '', backends=[usertypes.Backend.QtWebKit]), - "User stylesheet to use (absolute filename, filename relative to " - "the config directory or CSS string). Will expand environment " - "variables."), + "User stylesheet to use (absolute filename or filename relative to " + "the config directory). Will expand environment variables."), + + ('hide-scrollbar', + SettingValue(typ.Bool(), 'true', + backends=[usertypes.Backend.QtWebKit]), + "Hide the main scrollbar."), ('css-media-type', SettingValue(typ.String(none_ok=True), '', diff --git a/qutebrowser/config/configtypes.py b/qutebrowser/config/configtypes.py index d4e8a3514..b5aa8798a 100644 --- a/qutebrowser/config/configtypes.py +++ b/qutebrowser/config/configtypes.py @@ -1165,40 +1165,6 @@ class Encoding(BaseType): raise configexc.ValidationError(value, "is not a valid encoding!") -class UserStyleSheet(File): - - """QWebSettings UserStyleSheet.""" - - def transform(self, value): - if not value: - return None - - path = super().transform(value) - if path is not None and os.path.exists(path): - return QUrl.fromLocalFile(path) - else: - return urlutils.data_url('text/css', value.encode('utf-8')) - - def validate(self, value): - self._basic_validation(value) - if not value: - return - value = os.path.expandvars(value) - value = os.path.expanduser(value) - try: - super().validate(value) - except configexc.ValidationError: - try: - if not os.path.isabs(value): - # probably a CSS, so we don't handle it as filename. - # FIXME We just try if it is encodable, maybe we should - # validate CSS? - # https://github.com/The-Compiler/qutebrowser/issues/115 - value.encode('utf-8') - except UnicodeEncodeError as e: - raise configexc.ValidationError(value, str(e)) - - class AutoSearch(BaseType): """Whether to start a search when something else than a URL is entered.""" diff --git a/tests/unit/config/test_configtypes.py b/tests/unit/config/test_configtypes.py index 2f0c3ce7a..ec52570e6 100644 --- a/tests/unit/config/test_configtypes.py +++ b/tests/unit/config/test_configtypes.py @@ -1211,15 +1211,9 @@ def unrequired_class(**kwargs): @pytest.mark.usefixtures('qapp') @pytest.mark.usefixtures('config_tmpdir') -class TestFileAndUserStyleSheet: +class TestFile: - """Test File/UserStyleSheet.""" - - @pytest.fixture(params=[ - configtypes.File, - configtypes.UserStyleSheet, - unrequired_class, - ]) + @pytest.fixture(params=[configtypes.File, unrequired_class]) def klass(self, request): return request.param @@ -1227,18 +1221,12 @@ class TestFileAndUserStyleSheet: def file_class(self): return configtypes.File - @pytest.fixture - def userstylesheet_class(self): - return configtypes.UserStyleSheet - def _expected(self, klass, arg): """Get the expected value.""" if not arg: return None elif klass is configtypes.File: return arg - elif klass is configtypes.UserStyleSheet: - return QUrl.fromLocalFile(arg) elif klass is unrequired_class: return arg else: @@ -1262,11 +1250,6 @@ class TestFileAndUserStyleSheet: os_mock.path.isfile.return_value = False configtypes.File(required=False).validate('foobar') - def test_validate_does_not_exist_userstylesheet(self, os_mock): - """Test validate with a file which does not exist (UserStyleSheet).""" - os_mock.path.isfile.return_value = False - configtypes.UserStyleSheet().validate('foobar') - def test_validate_exists_abs(self, klass, os_mock): """Test validate with a file which does exist.""" os_mock.path.isfile.return_value = True @@ -1286,11 +1269,8 @@ class TestFileAndUserStyleSheet: @pytest.mark.parametrize('configtype, value, raises', [ (configtypes.File(), 'foobar', True), - (configtypes.UserStyleSheet(), 'foobar', False), - (configtypes.UserStyleSheet(), '\ud800', True), (configtypes.File(required=False), 'foobar', False), - ], ids=['file-foobar', 'userstylesheet-foobar', 'userstylesheet-unicode', - 'file-optional-foobar']) + ], ids=['file-foobar', 'file-optional-foobar']) def test_validate_rel_inexistent(self, os_mock, monkeypatch, configtype, value, raises): """Test with a relative path and standarddir.config returning None.""" @@ -1339,7 +1319,6 @@ class TestFileAndUserStyleSheet: def test_transform_relative(self, klass, os_mock, monkeypatch): """Test transform() with relative dir and an available configdir.""" - os_mock.path.exists.return_value = True # for TestUserStyleSheet os_mock.path.isabs.return_value = False monkeypatch.setattr( 'qutebrowser.config.configtypes.standarddir.config', @@ -1347,12 +1326,6 @@ class TestFileAndUserStyleSheet: expected = self._expected(klass, '/configdir/foo') assert klass().transform('foo') == expected - def test_transform_userstylesheet_base64(self, monkeypatch): - """Test transform with a data string.""" - b64 = base64.b64encode(b"test").decode('ascii') - url = QUrl("data:text/css;base64,{}".format(b64)) - assert configtypes.UserStyleSheet().transform("test") == url - class TestDirectory: diff --git a/tests/unit/config/test_configtypes_hypothesis.py b/tests/unit/config/test_configtypes_hypothesis.py index 329d7357c..ed45ccc0b 100644 --- a/tests/unit/config/test_configtypes_hypothesis.py +++ b/tests/unit/config/test_configtypes_hypothesis.py @@ -50,8 +50,7 @@ def gen_classes(): @hypothesis.given(strategies.text()) @hypothesis.example('\x00') def test_configtypes_hypothesis(klass, s): - if (klass in [configtypes.File, configtypes.UserStyleSheet] and - sys.platform == 'linux' and + if (klass == configtypes.File and sys.platform == 'linux' and not os.environ.get('DISPLAY', '')): pytest.skip("No DISPLAY available") From d7d270ea284c66cf27e816df6a5907d09879ad63 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 15 Nov 2016 23:17:27 +0100 Subject: [PATCH 17/48] Implement user stylesheets for QtWebEngine --- .../browser/webengine/webenginesettings.py | 43 ++++++++++++++++++- qutebrowser/browser/webengine/webenginetab.py | 1 - qutebrowser/config/configdata.py | 6 +-- 3 files changed, 43 insertions(+), 7 deletions(-) diff --git a/qutebrowser/browser/webengine/webenginesettings.py b/qutebrowser/browser/webengine/webenginesettings.py index ec54e0044..1568abad5 100644 --- a/qutebrowser/browser/webengine/webenginesettings.py +++ b/qutebrowser/browser/webengine/webenginesettings.py @@ -27,11 +27,13 @@ Module attributes: import os # pylint: disable=no-name-in-module,import-error,useless-suppression -from PyQt5.QtWebEngineWidgets import QWebEngineSettings, QWebEngineProfile +from PyQt5.QtWebEngineWidgets import (QWebEngineSettings, QWebEngineProfile, + QWebEngineScript) # pylint: enable=no-name-in-module,import-error,useless-suppression +from qutebrowser.browser import shared from qutebrowser.config import websettings, config -from qutebrowser.utils import objreg, utils, standarddir +from qutebrowser.utils import objreg, utils, standarddir, javascript class Attribute(websettings.Attribute): @@ -63,9 +65,45 @@ class StaticSetter(websettings.StaticSetter): GLOBAL_SETTINGS = QWebEngineSettings.globalSettings +def _init_stylesheet(profile): + """Initialize custom stylesheets. + + Mostly inspired by QupZilla: + https://github.com/QupZilla/qupzilla/blob/v2.0/src/lib/app/mainapplication.cpp#L1063-L1101 + https://github.com/QupZilla/qupzilla/blob/v2.0/src/lib/tools/scripts.cpp#L119-L132 + + FIXME:qtwebengine Use QWebEngineStyleSheet once that's available + https://codereview.qt-project.org/#/c/148671/ + """ + old_script = profile.scripts().findScript('_qute_stylesheet') + if not old_script.isNull(): + profile.scripts().remove(old_script) + + css = shared.get_user_stylesheet() + source = """ + (function() {{ + var css = document.createElement('style'); + css.setAttribute('type', 'text/css'); + css.appendChild(document.createTextNode('{}')); + document.getElementsByTagName('head')[0].appendChild(css); + }})() + """.format(javascript.string_escape(css)) + + script = QWebEngineScript() + script.setName('_qute_stylesheet') + script.setInjectionPoint(QWebEngineScript.DocumentReady) + script.setWorldId(QWebEngineScript.ApplicationWorld) + script.setRunsOnSubFrames(True) + script.setSourceCode(source) + profile.scripts().insert(script) + + def update_settings(section, option): """Update global settings when qwebsettings changed.""" websettings.update_mappings(MAPPINGS, section, option) + profile = QWebEngineProfile.defaultProfile() + if section == 'ui' and option in ['hide-scrollbar', 'user-stylesheet']: + _init_stylesheet(profile) def init(): @@ -78,6 +116,7 @@ def init(): profile.setCachePath(os.path.join(standarddir.cache(), 'webengine')) profile.setPersistentStoragePath( os.path.join(standarddir.data(), 'webengine')) + _init_stylesheet(profile) websettings.init_mappings(MAPPINGS) objreg.get('config').changed.connect(update_settings) diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index 66aaf733d..52a22f9d0 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -469,7 +469,6 @@ class WebEngineTab(browsertab.AbstractTab): self._set_widget(widget) self._connect_signals() self.backend = usertypes.Backend.QtWebEngine - # init js stuff self._init_js() self._child_event_filter = None self.needs_qtbug54419_workaround = False diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index ebc89145c..74480bdfd 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -323,14 +323,12 @@ def data(readonly=False): "page."), ('user-stylesheet', - SettingValue(typ.File(none_ok=True), '', - backends=[usertypes.Backend.QtWebKit]), + SettingValue(typ.File(none_ok=True), ''), "User stylesheet to use (absolute filename or filename relative to " "the config directory). Will expand environment variables."), ('hide-scrollbar', - SettingValue(typ.Bool(), 'true', - backends=[usertypes.Backend.QtWebKit]), + SettingValue(typ.Bool(), 'true'), "Hide the main scrollbar."), ('css-media-type', From 8e0565b79ab19505c86eadd6063e3d0adef992b6 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 15 Nov 2016 23:21:12 +0100 Subject: [PATCH 18/48] Add {backend} window/tab title field --- qutebrowser/config/configdata.py | 9 ++++++--- qutebrowser/mainwindow/tabwidget.py | 1 + 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index 74480bdfd..a939eb2f8 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -356,7 +356,8 @@ def data(readonly=False): ('window-title-format', SettingValue(typ.FormatString(fields=['perc', 'perc_raw', 'title', 'title_sep', 'id', - 'scroll_pos', 'host']), + 'scroll_pos', 'host', + 'backend']), '{perc}{title}{title_sep}qutebrowser'), "The format to use for the window title. The following " "placeholders are defined:\n\n" @@ -367,7 +368,8 @@ def data(readonly=False): "otherwise.\n" "* `{id}`: The internal window ID of this window.\n" "* `{scroll_pos}`: The page scroll position.\n" - "* `{host}`: The host of the current web page."), + "* `{host}`: The host of the current web page.\n" + "* `{backend}`: Either 'webkit' or 'webengine'"), ('modal-js-dialog', SettingValue(typ.Bool(), 'false'), @@ -675,7 +677,8 @@ def data(readonly=False): "* `{index}`: The index of this tab.\n" "* `{id}`: The internal tab ID of this tab.\n" "* `{scroll_pos}`: The page scroll position.\n" - "* `{host}`: The host of the current web page."), + "* `{host}`: The host of the current web page.\n" + "* `{backend}`: Either 'webkit' or 'webengine'"), ('title-alignment', SettingValue(typ.TextAlignment(), 'left'), diff --git a/qutebrowser/mainwindow/tabwidget.py b/qutebrowser/mainwindow/tabwidget.py index 3ad449b9c..c43b805c6 100644 --- a/qutebrowser/mainwindow/tabwidget.py +++ b/qutebrowser/mainwindow/tabwidget.py @@ -124,6 +124,7 @@ class TabWidget(QTabWidget): fields['title'] = page_title fields['title_sep'] = ' - ' if page_title else '' fields['perc_raw'] = tab.progress() + fields['backend'] = objreg.get('args').backend if tab.load_status() == usertypes.LoadStatus.loading: fields['perc'] = '[{}%] '.format(tab.progress()) From b5f9135f98da3cea01babbfcd646bd4a085bf375 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 15 Nov 2016 23:23:34 +0100 Subject: [PATCH 19/48] Update docs --- CHANGELOG.asciidoc | 1 + doc/help/settings.asciidoc | 22 ++++++++++++++++------ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 8dc7caa3a..efe83cd4c 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -146,6 +146,7 @@ Changed - Various commands/settings taking `left`/`right`/`previous` arguments now take `prev`/`next`/`last-used` to remove ambiguity. - The `ui -> user-stylesheet` setting now only takes filenames, not CSS snippets +- `ui -> window-title-format` now has a new `{backend} ` replacement Deprecated ~~~~~~~~~~ diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc index c7da3e82e..6e2d3d67c 100644 --- a/doc/help/settings.asciidoc +++ b/doc/help/settings.asciidoc @@ -44,7 +44,8 @@ |<>|Whether to confirm quitting the application. |<>|Whether the zoom factor on a frame applies only to the text or to all content. |<>|Whether to expand each subframe to its contents. -|<>|User stylesheet to use (absolute filename, filename relative to the config directory or CSS string). Will expand environment variables. +|<>|User stylesheet to use (absolute filename or filename relative to the config directory). Will expand environment variables. +|<>|Hide the main scrollbar. |<>|Set the CSS media type. |<>|Whether to enable smooth scrolling for webpages. |<>|Number of milliseconds to wait before removing finished downloads. Will not be removed if value is -1. @@ -618,11 +619,20 @@ This setting is only available with the QtWebKit backend. [[ui-user-stylesheet]] === user-stylesheet -User stylesheet to use (absolute filename, filename relative to the config directory or CSS string). Will expand environment variables. +User stylesheet to use (absolute filename or filename relative to the config directory). Will expand environment variables. -Default: +pass:[html > ::-webkit-scrollbar { width: 0px; height: 0px; }]+ +Default: empty -This setting is only available with the QtWebKit backend. +[[ui-hide-scrollbar]] +=== hide-scrollbar +Hide the main scrollbar. + +Valid values: + + * +true+ + * +false+ + +Default: +pass:[true]+ [[ui-css-media-type]] === css-media-type @@ -677,6 +687,7 @@ The format to use for the window title. The following placeholders are defined: * `{id}`: The internal window ID of this window. * `{scroll_pos}`: The page scroll position. * `{host}`: The host of the current web page. +* `{backend}`: Either 'webkit' or 'webengine' Default: +pass:[{perc}{title}{title_sep}qutebrowser]+ @@ -756,8 +767,6 @@ User agent to send. Empty to send the default. Default: empty -This setting is only available with the QtWebKit backend. - [[network-proxy]] === proxy The proxy to use. @@ -1195,6 +1204,7 @@ The format to use for the tab title. The following placeholders are defined: * `{id}`: The internal tab ID of this tab. * `{scroll_pos}`: The page scroll position. * `{host}`: The host of the current web page. +* `{backend}`: Either 'webkit' or 'webengine' Default: +pass:[{index}: {title}]+ From 7d8ef9fccf96f405e855b89906f5cd03cb727f82 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 15 Nov 2016 23:25:14 +0100 Subject: [PATCH 20/48] Remove urlutils import --- qutebrowser/config/configtypes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutebrowser/config/configtypes.py b/qutebrowser/config/configtypes.py index b5aa8798a..74d65c89a 100644 --- a/qutebrowser/config/configtypes.py +++ b/qutebrowser/config/configtypes.py @@ -37,7 +37,7 @@ from PyQt5.QtWidgets import QTabWidget, QTabBar from qutebrowser.commands import cmdutils from qutebrowser.config import configexc -from qutebrowser.utils import standarddir, utils, urlutils +from qutebrowser.utils import standarddir, utils SYSTEM_PROXY = object() # Return value for Proxy type From d64efa6b9bd50b9da598d0019326270017affb9f Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 15 Nov 2016 23:40:24 +0100 Subject: [PATCH 21/48] Fix test_tabwidget --- tests/helpers/fixtures.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/helpers/fixtures.py b/tests/helpers/fixtures.py index b1a6966e8..f494943a7 100644 --- a/tests/helpers/fixtures.py +++ b/tests/helpers/fixtures.py @@ -155,10 +155,11 @@ def tab_registry(win_registry): @pytest.fixture -def fake_web_tab(stubs, tab_registry, mode_manager, qapp): +def fake_web_tab(stubs, tab_registry, mode_manager, qapp, fake_args): """Fixture providing the FakeWebTab *class*.""" if PYQT_VERSION < 0x050600: pytest.skip('Causes segfaults, see #1638') + fake_args.backend = 'webengine' return stubs.FakeWebTab From 3638849257d484e8c821e6db0c87a8510f241b1a Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 15 Nov 2016 23:41:53 +0100 Subject: [PATCH 22/48] Fix lint --- qutebrowser/browser/webkit/webkitsettings.py | 1 - qutebrowser/config/configdata.py | 4 ++-- qutebrowser/config/configtypes.py | 1 - qutebrowser/utils/jinja.py | 1 - tests/unit/config/test_configtypes.py | 1 - 5 files changed, 2 insertions(+), 6 deletions(-) diff --git a/qutebrowser/browser/webkit/webkitsettings.py b/qutebrowser/browser/webkit/webkitsettings.py index 800206c5a..5fe27c48b 100644 --- a/qutebrowser/browser/webkit/webkitsettings.py +++ b/qutebrowser/browser/webkit/webkitsettings.py @@ -26,7 +26,6 @@ Module attributes: import os.path -from PyQt5.QtCore import QUrl from PyQt5.QtWebKit import QWebSettings from qutebrowser.config import config, websettings diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index a939eb2f8..a99205902 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -324,8 +324,8 @@ def data(readonly=False): ('user-stylesheet', SettingValue(typ.File(none_ok=True), ''), - "User stylesheet to use (absolute filename or filename relative to " - "the config directory). Will expand environment variables."), + "User stylesheet to use (absolute filename or filename relative " + "to the config directory). Will expand environment variables."), ('hide-scrollbar', SettingValue(typ.Bool(), 'true'), diff --git a/qutebrowser/config/configtypes.py b/qutebrowser/config/configtypes.py index 74d65c89a..457781e77 100644 --- a/qutebrowser/config/configtypes.py +++ b/qutebrowser/config/configtypes.py @@ -22,7 +22,6 @@ import re import json import shlex -import base64 import codecs import os.path import itertools diff --git a/qutebrowser/utils/jinja.py b/qutebrowser/utils/jinja.py index 0487f5ddb..e60032ff4 100644 --- a/qutebrowser/utils/jinja.py +++ b/qutebrowser/utils/jinja.py @@ -23,7 +23,6 @@ import os import os.path import traceback import mimetypes -import base64 import jinja2 import jinja2.exceptions diff --git a/tests/unit/config/test_configtypes.py b/tests/unit/config/test_configtypes.py index ec52570e6..77cfdfdee 100644 --- a/tests/unit/config/test_configtypes.py +++ b/tests/unit/config/test_configtypes.py @@ -22,7 +22,6 @@ import re import collections import itertools import os.path -import base64 import warnings import pytest From 2f5f17e121ea8074f9163f207ead7c5b3dfc5f88 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 15 Nov 2016 23:52:53 +0100 Subject: [PATCH 23/48] pylint/codecov requirements: Update requests to 2.12.0 --- misc/requirements/requirements-codecov.txt | 2 +- misc/requirements/requirements-pylint-master.txt | 2 +- misc/requirements/requirements-pylint.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/misc/requirements/requirements-codecov.txt b/misc/requirements/requirements-codecov.txt index b058b64d6..d2c1877a2 100644 --- a/misc/requirements/requirements-codecov.txt +++ b/misc/requirements/requirements-codecov.txt @@ -2,4 +2,4 @@ codecov==2.0.5 coverage==4.2 -requests==2.11.1 +requests==2.12.0 diff --git a/misc/requirements/requirements-pylint-master.txt b/misc/requirements/requirements-pylint-master.txt index d42317ac9..d1a4c6ccb 100644 --- a/misc/requirements/requirements-pylint-master.txt +++ b/misc/requirements/requirements-pylint-master.txt @@ -7,6 +7,6 @@ lazy-object-proxy==1.2.2 mccabe==0.5.2 -e git+https://github.com/PyCQA/pylint.git#egg=pylint ./scripts/dev/pylint_checkers -requests==2.11.1 +requests==2.12.0 six==1.10.0 wrapt==1.10.8 diff --git a/misc/requirements/requirements-pylint.txt b/misc/requirements/requirements-pylint.txt index 63b9b52b3..ddd94f9be 100644 --- a/misc/requirements/requirements-pylint.txt +++ b/misc/requirements/requirements-pylint.txt @@ -7,7 +7,7 @@ lazy-object-proxy==1.2.2 mccabe==0.5.2 pylint==1.6.4 ./scripts/dev/pylint_checkers -requests==2.11.1 +requests==2.12.0 six==1.10.0 uritemplate==3.0.0 uritemplate.py==3.0.2 From f274e198e4b44ed1aa3f1040179b3fb7e550c441 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 16 Nov 2016 07:16:37 +0100 Subject: [PATCH 24/48] tox requirements: Update virtualenv to 15.1.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 acb3c0012..ca50e90d5 100644 --- a/misc/requirements/requirements-tox.txt +++ b/misc/requirements/requirements-tox.txt @@ -3,4 +3,4 @@ pluggy==0.4.0 py==1.4.31 tox==2.4.1 -virtualenv==15.0.3 +virtualenv==15.1.0 From e9a8201aa1ec324c571bfcbe4426ad96d76db1d3 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 16 Nov 2016 07:48:37 +0100 Subject: [PATCH 25/48] cxfreeze requirements: Blacklist >= 5.0.0 See #1004 --- misc/requirements/requirements-cxfreeze.txt | 2 +- misc/requirements/requirements-cxfreeze.txt-raw | 6 +++++- qutebrowser/mainwindow/prompt.py | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/misc/requirements/requirements-cxfreeze.txt b/misc/requirements/requirements-cxfreeze.txt index 17b849d65..58f14266e 100644 --- a/misc/requirements/requirements-cxfreeze.txt +++ b/misc/requirements/requirements-cxfreeze.txt @@ -1,3 +1,3 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -cx-Freeze==4.3.4 +cx-Freeze==4.3.4 # rq.filter: < 5.0.0 diff --git a/misc/requirements/requirements-cxfreeze.txt-raw b/misc/requirements/requirements-cxfreeze.txt-raw index c95cfcbb6..2ae8920ca 100644 --- a/misc/requirements/requirements-cxfreeze.txt-raw +++ b/misc/requirements/requirements-cxfreeze.txt-raw @@ -1 +1,5 @@ -cx_Freeze +cx-Freeze < 5.0.0 + +# We'll probably switch to PyInstaller soon, and 5.x doesn't install without a +# compiler? +#@ filter: cx-Freeze < 5.0.0 diff --git a/qutebrowser/mainwindow/prompt.py b/qutebrowser/mainwindow/prompt.py index 6e2ab68d6..4897e207c 100644 --- a/qutebrowser/mainwindow/prompt.py +++ b/qutebrowser/mainwindow/prompt.py @@ -24,7 +24,7 @@ import html import collections import sip -from PyQt5.QtCore import (pyqtSlot, pyqtSignal, Qt, QTimer, QDir, QModelIndex, +from PyQt5.QtCore import (pyqtSlot, pyqtSignal, Qt, QTimer, QDir, QModelInhttp://cx-freeze.readthedocs.io/en/latest/releasenotes.html#version-5-0-november-2016dex, QItemSelectionModel, QObject) from PyQt5.QtWidgets import (QWidget, QGridLayout, QVBoxLayout, QLineEdit, QLabel, QFileSystemModel, QTreeView, QSizePolicy) From 35d95144560b3f9384bf42278c84716e1124ea1e Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 16 Nov 2016 07:50:47 +0100 Subject: [PATCH 26/48] Whoops... Dang, spacemacs! --- qutebrowser/mainwindow/prompt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutebrowser/mainwindow/prompt.py b/qutebrowser/mainwindow/prompt.py index 4897e207c..6e2ab68d6 100644 --- a/qutebrowser/mainwindow/prompt.py +++ b/qutebrowser/mainwindow/prompt.py @@ -24,7 +24,7 @@ import html import collections import sip -from PyQt5.QtCore import (pyqtSlot, pyqtSignal, Qt, QTimer, QDir, QModelInhttp://cx-freeze.readthedocs.io/en/latest/releasenotes.html#version-5-0-november-2016dex, +from PyQt5.QtCore import (pyqtSlot, pyqtSignal, Qt, QTimer, QDir, QModelIndex, QItemSelectionModel, QObject) from PyQt5.QtWidgets import (QWidget, QGridLayout, QVBoxLayout, QLineEdit, QLabel, QFileSystemModel, QTreeView, QSizePolicy) From 5e53f230eed78c5f21a3723e3a12e2ecbade601c Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 16 Nov 2016 08:28:36 +0100 Subject: [PATCH 27/48] travis: Set matrix.fast_finish --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 8c7175f51..58e7f09d5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -45,6 +45,7 @@ matrix: - os: osx env: TESTENV=py35 OSX=elcapitan osx_image: xcode7.3 + fast_finish: true cache: directories: From 25358bb5fc500a7e75bf33a95171f9efd23ee7ae Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 16 Nov 2016 12:15:43 +0100 Subject: [PATCH 28/48] Clean up webelem.text/set_text webelem.text() was only used without use_js=True from webelem.__str__. Now we instead default to the use_js=True behavior and renamed the method from text() to value(). The old behavior is instead directly implemented in __str__. This changes how webelem.value (aka text) handles elements for which is_content_editable() returns True, but I haven't found any cases where this makes a difference. This also fixes getting existing text from elements with QtWebEngine, which closes #1957. --- qutebrowser/browser/commands.py | 4 +-- qutebrowser/browser/webelem.py | 22 ++++----------- .../browser/webengine/webengineelem.py | 27 +++++-------------- qutebrowser/browser/webkit/webkitelem.py | 21 ++++++++------- qutebrowser/javascript/webelem.js | 5 ++-- tests/end2end/features/editor.feature | 12 +++++++++ 6 files changed, 40 insertions(+), 51 deletions(-) diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index f4b90b8e5..45320f011 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -1486,7 +1486,7 @@ class CommandDispatcher: message.error("Focused element is not editable!") return - text = elem.text(use_js=True) + text = elem.value() ed = editor.ExternalEditor(self._tabbed_browser) ed.editing_finished.connect(functools.partial( self.on_editing_finished, elem)) @@ -1513,7 +1513,7 @@ class CommandDispatcher: text: The new text to insert. """ try: - elem.set_text(text, use_js=True) + elem.set_value(text) except webelem.Error as e: raise cmdexc.CommandError(str(e)) diff --git a/qutebrowser/browser/webelem.py b/qutebrowser/browser/webelem.py index 97c5695c4..74304040c 100644 --- a/qutebrowser/browser/webelem.py +++ b/qutebrowser/browser/webelem.py @@ -87,7 +87,7 @@ class AbstractWebElement(collections.abc.MutableMapping): raise NotImplementedError def __str__(self): - return self.text() + raise NotImplementedError def __getitem__(self, key): raise NotImplementedError @@ -138,24 +138,12 @@ class AbstractWebElement(collections.abc.MutableMapping): """Get the full HTML representation of this element.""" raise NotImplementedError - def text(self, *, use_js=False): - """Get the plain text content for this element. - - Args: - use_js: Whether to use javascript if the element isn't - content-editable. - """ - # FIXME:qtwebengine what to do about use_js with WebEngine? + def value(self): + """Get the value attribute for this element.""" raise NotImplementedError - def set_text(self, text, *, use_js=False): - """Set the given plain text. - - Args: - use_js: Whether to use javascript if the element isn't - content-editable. - """ - # FIXME:qtwebengine what to do about use_js with WebEngine? + def set_value(self, value): + """Set the element value.""" raise NotImplementedError def insert_text(self, text): diff --git a/qutebrowser/browser/webengine/webengineelem.py b/qutebrowser/browser/webengine/webengineelem.py index 1874389e0..e045d22ed 100644 --- a/qutebrowser/browser/webengine/webengineelem.py +++ b/qutebrowser/browser/webengine/webengineelem.py @@ -37,6 +37,9 @@ class WebEngineElement(webelem.AbstractWebElement): self._id = js_dict['id'] self._js_dict = js_dict + def __str__(self): + return self._js_dict.get('text', '') + def __eq__(self, other): if not isinstance(other, WebEngineElement): return NotImplemented @@ -87,27 +90,11 @@ class WebEngineElement(webelem.AbstractWebElement): """Get the full HTML representation of this element.""" return self._js_dict['outer_xml'] - def text(self, *, use_js=False): - """Get the plain text content for this element. + def value(self): + return self._js_dict['value'] - Args: - use_js: Whether to use javascript if the element isn't - content-editable. - """ - if use_js: - # FIXME:qtwebengine what to do about use_js with WebEngine? - log.stub('with use_js=True') - return self._js_dict.get('text', '') - - def set_text(self, text, *, use_js=False): - """Set the given plain text. - - Args: - use_js: Whether to use javascript if the element isn't - content-editable. - """ - # FIXME:qtwebengine what to do about use_js with WebEngine? - js_code = javascript.assemble('webelem', 'set_text', self._id, text) + def set_value(self, value): + js_code = javascript.assemble('webelem', 'set_value', self._id, value) self._tab.run_js_async(js_code) def insert_text(self, text): diff --git a/qutebrowser/browser/webkit/webkitelem.py b/qutebrowser/browser/webkit/webkitelem.py index 93b58bd34..6de1842bc 100644 --- a/qutebrowser/browser/webkit/webkitelem.py +++ b/qutebrowser/browser/webkit/webkitelem.py @@ -46,6 +46,10 @@ class WebKitElement(webelem.AbstractWebElement): raise IsNullError('{} is a null element!'.format(elem)) self._elem = elem + def __str__(self): + self._check_vanished() + return self._elem.toPlainText() + def __eq__(self, other): if not isinstance(other, WebKitElement): return NotImplemented @@ -116,22 +120,19 @@ class WebKitElement(webelem.AbstractWebElement): self._check_vanished() return self._elem.toOuterXml() - def text(self, *, use_js=False): + def value(self): self._check_vanished() - if self.is_content_editable() or not use_js: - return self._elem.toPlainText() - else: - return self._elem.evaluateJavaScript('this.value') + return self._elem.evaluateJavaScript('this.value') - def set_text(self, text, *, use_js=False): + def set_value(self, value): self._check_vanished() - if self.is_content_editable() or not use_js: + if self.is_content_editable(): log.webelem.debug("Filling {!r} via set_text.".format(self)) - self._elem.setPlainText(text) + self._elem.setPlainText(value) else: log.webelem.debug("Filling {!r} via javascript.".format(self)) - text = javascript.string_escape(text) - self._elem.evaluateJavaScript("this.value='{}'".format(text)) + value = javascript.string_escape(value) + self._elem.evaluateJavaScript("this.value='{}'".format(value)) def insert_text(self, text): self._check_vanished() diff --git a/qutebrowser/javascript/webelem.js b/qutebrowser/javascript/webelem.js index 602e6b261..84d38b348 100644 --- a/qutebrowser/javascript/webelem.js +++ b/qutebrowser/javascript/webelem.js @@ -34,6 +34,7 @@ window._qutebrowser.webelem = (function() { var out = { "id": id, "text": elem.text, + "value": elem.value, "tag_name": elem.tagName, "outer_xml": elem.outerHTML, "class_name": elem.className, @@ -129,8 +130,8 @@ window._qutebrowser.webelem = (function() { return serialize_elem(elem); }; - funcs.set_text = function(id, text) { - elements[id].value = text; + funcs.set_value = function(id, value) { + elements[id].value = value; }; funcs.insert_text = function(id, text) { diff --git a/tests/end2end/features/editor.feature b/tests/end2end/features/editor.feature index 5160ebf69..861c71710 100644 --- a/tests/end2end/features/editor.feature +++ b/tests/end2end/features/editor.feature @@ -93,3 +93,15 @@ Feature: Opening external editors And I wait for "Read back: foobar" in the log And I run :click-element id qute-button Then the javascript message "text: foobar" should be logged + + Scenario: Spawning an editor with existing text + When I set up a fake editor replacing "foo" by "bar" + And I open data/editor.html + And I run :click-element id qute-textarea + And I wait for "Clicked editable element!" in the log + And I run :insert-text foo + And I wait for "Inserting text into element *" in the log + And I run :open-editor + And I wait for "Read back: bar" in the log + And I run :click-element id qute-button + Then the javascript message "text: bar" should be logged From 52df867030ad02f1337198eadd9856e864c0d0cb Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 16 Nov 2016 13:09:16 +0100 Subject: [PATCH 29/48] Fix test_webkitelem --- tests/unit/browser/webkit/test_webkitelem.py | 32 +++++++------------- 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/tests/unit/browser/webkit/test_webkitelem.py b/tests/unit/browser/webkit/test_webkitelem.py index aab685faf..eaff07f9e 100644 --- a/tests/unit/browser/webkit/test_webkitelem.py +++ b/tests/unit/browser/webkit/test_webkitelem.py @@ -258,8 +258,8 @@ class TestWebKitElement: lambda e: e.has_frame(), lambda e: e.geometry(), lambda e: e.style_property('visibility', strategy='computed'), - lambda e: e.text(), - lambda e: e.set_text('foo'), + lambda e: e.value(), + lambda e: e.set_value('foo'), lambda e: e.insert_text('foo'), lambda e: e.is_writable(), lambda e: e.is_content_editable(), @@ -271,7 +271,7 @@ class TestWebKitElement: lambda e: e.rect_on_view(), lambda e: e._is_visible(None), ], ids=['str', 'getitem', 'setitem', 'delitem', 'contains', 'iter', 'len', - 'frame', 'geometry', 'style_property', 'text', 'set_text', + 'frame', 'geometry', 'style_property', 'value', 'set_value', 'insert_text', 'is_writable', 'is_content_editable', 'is_editable', 'is_text_input', 'remove_blank_target', 'outer_xml', 'tag_name', 'rect_on_view', 'is_visible']) @@ -411,28 +411,18 @@ class TestWebKitElement: def test_style_property(self, elem): assert elem.style_property('foo', strategy='computed') == 'bar' - @pytest.mark.parametrize('use_js, editable, expected', [ - (True, 'false', 'js'), - (True, 'true', 'nojs'), - (False, 'false', 'nojs'), - (False, 'true', 'nojs'), - ]) - def test_text(self, use_js, editable, expected): - elem = get_webelem(attributes={'contenteditable': editable}) - elem._elem.toPlainText.return_value = 'nojs' + def test_value(self, elem): elem._elem.evaluateJavaScript.return_value = 'js' - assert elem.text(use_js=use_js) == expected + assert elem.value() == 'js' - @pytest.mark.parametrize('use_js, editable, text, uses_js, arg', [ - (True, 'false', 'foo', True, "this.value='foo'"), - (True, 'false', "foo'bar", True, r"this.value='foo\'bar'"), - (True, 'true', 'foo', False, 'foo'), - (False, 'false', 'foo', False, 'foo'), - (False, 'true', 'foo', False, 'foo'), + @pytest.mark.parametrize('editable, value, uses_js, arg', [ + ('false', 'foo', True, "this.value='foo'"), + ('false', "foo'bar", True, r"this.value='foo\'bar'"), + ('true', 'foo', False, 'foo'), ]) - def test_set_text(self, use_js, editable, text, uses_js, arg): + def test_set_value(self, editable, value, uses_js, arg): elem = get_webelem(attributes={'contenteditable': editable}) - elem.set_text(text, use_js=use_js) + elem.set_value(value) attr = 'evaluateJavaScript' if uses_js else 'setPlainText' called_mock = getattr(elem._elem, attr) called_mock.assert_called_with(arg) From 7c88fe318f7b881b79e397bcf8c1f7047e367423 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 16 Nov 2016 22:12:57 +0100 Subject: [PATCH 30/48] Handle cancelled SSL prompts correctly When the user pressed esc during an SSL prompt, the message.ask call returned None, which was handled fine by QtWebKit (which simply used an 'if') but failed with QtWebEngine (which returned the value to Qt). Fixes #2123. --- qutebrowser/browser/shared.py | 10 +++++++--- tests/end2end/features/prompts.feature | 8 ++++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/qutebrowser/browser/shared.py b/qutebrowser/browser/shared.py index 5db2d9fa0..8536ea837 100644 --- a/qutebrowser/browser/shared.py +++ b/qutebrowser/browser/shared.py @@ -146,9 +146,13 @@ def ignore_certificate_errors(url, errors, abort_on): """.strip()) msg = err_template.render(url=url, errors=errors) - return message.ask(title="Certificate errors - continue?", text=msg, - mode=usertypes.PromptMode.yesno, default=False, - abort_on=abort_on) + ignore = message.ask(title="Certificate errors - continue?", text=msg, + mode=usertypes.PromptMode.yesno, default=False, + abort_on=abort_on) + if ignore is None: + # prompt aborted + ignore = False + return ignore elif ssl_strict is False: log.webview.debug("ssl-strict is False, only warning about errors") for err in errors: diff --git a/tests/end2end/features/prompts.feature b/tests/end2end/features/prompts.feature index 978e79406..00cb93dd9 100644 --- a/tests/end2end/features/prompts.feature +++ b/tests/end2end/features/prompts.feature @@ -197,6 +197,14 @@ Feature: Prompts And I run :prompt-accept no Then a SSL error page should be shown + Scenario: SSL error with ssl-strict = ask -> abort + When I clear SSL errors + And I set network -> ssl-strict to ask + And I load an SSL page + And I wait for a prompt + And I run :leave-mode + Then a SSL error page should be shown + # Geolocation Scenario: Always rejecting geolocation From 4fc2f93b7a716af9f8ccf87688c10766b0987369 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 16 Nov 2016 22:15:37 +0100 Subject: [PATCH 31/48] codecov/pylint requirements: Update requests to 2.12.1 --- misc/requirements/requirements-codecov.txt | 2 +- misc/requirements/requirements-pylint-master.txt | 2 +- misc/requirements/requirements-pylint.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/misc/requirements/requirements-codecov.txt b/misc/requirements/requirements-codecov.txt index d2c1877a2..4f9fc5b12 100644 --- a/misc/requirements/requirements-codecov.txt +++ b/misc/requirements/requirements-codecov.txt @@ -2,4 +2,4 @@ codecov==2.0.5 coverage==4.2 -requests==2.12.0 +requests==2.12.1 diff --git a/misc/requirements/requirements-pylint-master.txt b/misc/requirements/requirements-pylint-master.txt index d1a4c6ccb..862e0abca 100644 --- a/misc/requirements/requirements-pylint-master.txt +++ b/misc/requirements/requirements-pylint-master.txt @@ -7,6 +7,6 @@ lazy-object-proxy==1.2.2 mccabe==0.5.2 -e git+https://github.com/PyCQA/pylint.git#egg=pylint ./scripts/dev/pylint_checkers -requests==2.12.0 +requests==2.12.1 six==1.10.0 wrapt==1.10.8 diff --git a/misc/requirements/requirements-pylint.txt b/misc/requirements/requirements-pylint.txt index ddd94f9be..0aafe2388 100644 --- a/misc/requirements/requirements-pylint.txt +++ b/misc/requirements/requirements-pylint.txt @@ -7,7 +7,7 @@ lazy-object-proxy==1.2.2 mccabe==0.5.2 pylint==1.6.4 ./scripts/dev/pylint_checkers -requests==2.12.0 +requests==2.12.1 six==1.10.0 uritemplate==3.0.0 uritemplate.py==3.0.2 From acd13eed49e0601755f2b99e5854794d598c7524 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 16 Nov 2016 22:16:13 +0100 Subject: [PATCH 32/48] pytest requirements: Update pytest-faulthandler to 1.3.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 9905e09c5..ce6f7ce1f 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -20,7 +20,7 @@ pytest==3.0.4 pytest-bdd==2.18.1 pytest-catchlog==1.2.2 pytest-cov==2.4.0 -pytest-faulthandler==1.3.0 +pytest-faulthandler==1.3.1 pytest-instafail==0.3.0 pytest-mock==1.4.0 pytest-qt==2.1.0 From 9649884add7bafa51c6a6713c43667b8c36b0223 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 16 Nov 2016 22:16:40 +0100 Subject: [PATCH 33/48] tox requirements: Update tox to 2.5.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 ca50e90d5..4768ddc09 100644 --- a/misc/requirements/requirements-tox.txt +++ b/misc/requirements/requirements-tox.txt @@ -2,5 +2,5 @@ pluggy==0.4.0 py==1.4.31 -tox==2.4.1 +tox==2.5.0 virtualenv==15.1.0 From 8d7fcf41da756cd1918bd8fa827e85cc5ae80e29 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 16 Nov 2016 22:26:16 +0100 Subject: [PATCH 34/48] Set a maximum width for prompts Fixes #2124 --- qutebrowser/mainwindow/mainwindow.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qutebrowser/mainwindow/mainwindow.py b/qutebrowser/mainwindow/mainwindow.py index 9e8f5a191..f7d326a09 100644 --- a/qutebrowser/mainwindow/mainwindow.py +++ b/qutebrowser/mainwindow/mainwindow.py @@ -232,8 +232,8 @@ class MainWindow(QWidget): width = self.width() - 2 * padding left = padding else: - width = size_hint.width() - left = (self.width() - size_hint.width()) / 2 if centered else 0 + width = min(size_hint.width(), self.width() - 2 * padding) + left = (self.width() - width) / 2 if centered else 0 height_padding = 20 status_position = config.get('ui', 'status-position') From d1154759d14b2c2b3c50b9845bff6fd6536f6217 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 16 Nov 2016 22:32:44 +0100 Subject: [PATCH 35/48] Improve --qt-arg docs --- doc/qutebrowser.1.asciidoc | 4 ++-- qutebrowser/qutebrowser.py | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/doc/qutebrowser.1.asciidoc b/doc/qutebrowser.1.asciidoc index 989fbd3cd..d76c4480c 100644 --- a/doc/qutebrowser.1.asciidoc +++ b/doc/qutebrowser.1.asciidoc @@ -102,8 +102,8 @@ show it. *--no-err-windows*:: Don't show any error windows (used for tests/smoke.py). -*--qt-arg* 'QT_ARG':: - Pass an argument with a value to Qt. +*--qt-arg* 'NAME' 'VALUE':: + Pass an argument with a value to Qt. For example, you can do `--qt-arg geometry 650x555+200+300` to set the window geometry. *--qt-flag* 'QT_FLAG':: Pass an argument to Qt as flag. diff --git a/qutebrowser/qutebrowser.py b/qutebrowser/qutebrowser.py index 420a07d0c..8665d4592 100644 --- a/qutebrowser/qutebrowser.py +++ b/qutebrowser/qutebrowser.py @@ -105,8 +105,10 @@ def get_argparser(): "temporary basedir.") debug.add_argument('--no-err-windows', action='store_true', help="Don't " "show any error windows (used for tests/smoke.py).") - debug.add_argument('--qt-arg', help="Pass an argument with a value to Qt.", - nargs=2) + debug.add_argument('--qt-arg', help="Pass an argument with a value to Qt. " + "For example, you can do " + "`--qt-arg geometry 650x555+200+300` to set the window " + "geometry.", nargs=2, metavar=('NAME', 'VALUE')) debug.add_argument('--qt-flag', help="Pass an argument to Qt as flag.", nargs=1) parser.add_argument('command', nargs='*', help="Commands to execute on " From 554b9b2bda94f4381b19292b19f50d292f558217 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Wo=C5=82czyk?= Date: Fri, 18 Nov 2016 20:16:43 +0100 Subject: [PATCH 36/48] Added chronicle flag --- qutebrowser/browser/hints.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/qutebrowser/browser/hints.py b/qutebrowser/browser/hints.py index af1827b2f..7ef136ed7 100644 --- a/qutebrowser/browser/hints.py +++ b/qutebrowser/browser/hints.py @@ -602,7 +602,7 @@ class HintManager(QObject): star_args_optional=True, maxsplit=2) @cmdutils.argument('win_id', win_id=True) def start(self, rapid=False, group=webelem.Group.all, target=Target.normal, - *args, win_id, mode=None): + *args, win_id, mode=None, chronicle=False): """Start hinting. Args: @@ -691,6 +691,7 @@ class HintManager(QObject): self._context.target = target self._context.rapid = rapid self._context.hint_mode = mode + self._context.chronicle = chronicle try: self._context.baseurl = tabbed_browser.current_url() except qtutils.QtValueError: @@ -860,6 +861,8 @@ class HintManager(QObject): return handler = functools.partial(url_handlers[self._context.target], url, self._context) + if self._context.chronicle: + objreg.get('web-history').add_from_tab(url, url, "") else: raise ValueError("No suitable handler found!") From bf4113584631be919b2a6f79a861f65e4c6997b7 Mon Sep 17 00:00:00 2001 From: Rahid Date: Fri, 18 Nov 2016 23:10:50 +0100 Subject: [PATCH 37/48] Added chronicle flag to init function, docs --- qutebrowser/browser/hints.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/qutebrowser/browser/hints.py b/qutebrowser/browser/hints.py index 7ef136ed7..ad852ac76 100644 --- a/qutebrowser/browser/hints.py +++ b/qutebrowser/browser/hints.py @@ -152,6 +152,7 @@ class HintContext: to_follow: The link to follow when enter is pressed. args: Custom arguments for userscript/spawn rapid: Whether to do rapid hinting. + chronicle: Whether to add yanked or spawned link to the history. filterstr: Used to save the filter string for restoring in rapid mode. tab: The WebTab object we started hinting in. group: The group of web elements to hint. @@ -164,6 +165,7 @@ class HintContext: self.baseurl = None self.to_follow = None self.rapid = False + self.chronicle = False self.filterstr = None self.args = [] self.tab = None @@ -609,6 +611,8 @@ class HintManager(QObject): rapid: Whether to do rapid hinting. This is only possible with targets `tab` (with background-tabs=true), `tab-bg`, `window`, `run`, `hover`, `userscript` and `spawn`. + chronicle: Whether to add spawned or yanked linked to the + browsing history. group: The element types to hint. - `all`: All clickable elements. From 058c3e6541be297533102f9bc3897296330ea15e Mon Sep 17 00:00:00 2001 From: Rahid Date: Fri, 18 Nov 2016 23:45:12 +0100 Subject: [PATCH 38/48] Trailing spaces --- qutebrowser/browser/hints.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qutebrowser/browser/hints.py b/qutebrowser/browser/hints.py index ad852ac76..f1d84daa3 100644 --- a/qutebrowser/browser/hints.py +++ b/qutebrowser/browser/hints.py @@ -611,7 +611,7 @@ class HintManager(QObject): rapid: Whether to do rapid hinting. This is only possible with targets `tab` (with background-tabs=true), `tab-bg`, `window`, `run`, `hover`, `userscript` and `spawn`. - chronicle: Whether to add spawned or yanked linked to the + chronicle: Whether to add spawned or yanked linked to the browsing history. group: The element types to hint. @@ -866,7 +866,7 @@ class HintManager(QObject): handler = functools.partial(url_handlers[self._context.target], url, self._context) if self._context.chronicle: - objreg.get('web-history').add_from_tab(url, url, "") + objreg.get('web-history').add_from_tab(url, url, "") else: raise ValueError("No suitable handler found!") From c5cacbc439e275543c8d1ac0d05a0327446e949b Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 21 Nov 2016 06:22:38 +0100 Subject: [PATCH 39/48] Handle background tabs in QtWebEngine createWindow with older Qt --- qutebrowser/browser/webengine/webview.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/qutebrowser/browser/webengine/webview.py b/qutebrowser/browser/webengine/webview.py index 1c3a989b1..f3e9d6a94 100644 --- a/qutebrowser/browser/webengine/webview.py +++ b/qutebrowser/browser/webengine/webview.py @@ -78,6 +78,13 @@ class WebEngineView(QWebEngineView): log.webview.debug("createWindow with type {}, background_tabs " "{}".format(debug_type, background_tabs)) + try: + background_tab_wintype = QWebEnginePage.WebBrowserBackgroundTab + except AttributeError: + # This is unavailable with an older PyQt, but we still might get + # this with a newer Qt... + background_tab_wintype = 0x0003 + if wintype == QWebEnginePage.WebBrowserWindow: # Shift-Alt-Click target = usertypes.ClickTarget.window @@ -92,8 +99,7 @@ class WebEngineView(QWebEngineView): target = usertypes.ClickTarget.tab else: target = usertypes.ClickTarget.tab_bg - elif (hasattr(QWebEnginePage, 'WebBrowserBackgroundTab') and - wintype == QWebEnginePage.WebBrowserBackgroundTab): + elif wintype == background_tab_wintype: # Middle-click / Ctrl-Click if background_tabs: target = usertypes.ClickTarget.tab_bg From 918b3e2d122aac2b81b71d5fc48b93b5ec2d79fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Wo=C5=82czyk?= Date: Mon, 21 Nov 2016 15:33:38 +0100 Subject: [PATCH 40/48] History flag: test added, short version changed, minor fixes --- qutebrowser/browser/hints.py | 15 ++++++++------- tests/end2end/features/history.feature | 7 +++++++ 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/qutebrowser/browser/hints.py b/qutebrowser/browser/hints.py index f1d84daa3..f32389025 100644 --- a/qutebrowser/browser/hints.py +++ b/qutebrowser/browser/hints.py @@ -152,7 +152,7 @@ class HintContext: to_follow: The link to follow when enter is pressed. args: Custom arguments for userscript/spawn rapid: Whether to do rapid hinting. - chronicle: Whether to add yanked or spawned link to the history. + history: Whether to add yanked or spawned link to the history. filterstr: Used to save the filter string for restoring in rapid mode. tab: The WebTab object we started hinting in. group: The group of web elements to hint. @@ -165,7 +165,7 @@ class HintContext: self.baseurl = None self.to_follow = None self.rapid = False - self.chronicle = False + self.history = False self.filterstr = None self.args = [] self.tab = None @@ -603,15 +603,16 @@ class HintManager(QObject): @cmdutils.register(instance='hintmanager', scope='tab', name='hint', star_args_optional=True, maxsplit=2) @cmdutils.argument('win_id', win_id=True) + @cmdutils.argument('history', flag="s") def start(self, rapid=False, group=webelem.Group.all, target=Target.normal, - *args, win_id, mode=None, chronicle=False): + *args, win_id, mode=None, history=False): """Start hinting. Args: rapid: Whether to do rapid hinting. This is only possible with targets `tab` (with background-tabs=true), `tab-bg`, `window`, `run`, `hover`, `userscript` and `spawn`. - chronicle: Whether to add spawned or yanked linked to the + history: Whether to add spawned or yanked link to the browsing history. group: The element types to hint. @@ -695,7 +696,7 @@ class HintManager(QObject): self._context.target = target self._context.rapid = rapid self._context.hint_mode = mode - self._context.chronicle = chronicle + self._context.history = history try: self._context.baseurl = tabbed_browser.current_url() except qtutils.QtValueError: @@ -865,8 +866,8 @@ class HintManager(QObject): return handler = functools.partial(url_handlers[self._context.target], url, self._context) - if self._context.chronicle: - objreg.get('web-history').add_from_tab(url, url, "") + if self._context.history: + objreg.get('web-history').add_url(url, "") else: raise ValueError("No suitable handler found!") diff --git a/tests/end2end/features/history.feature b/tests/end2end/features/history.feature index 05a896be2..0ea1a5352 100644 --- a/tests/end2end/features/history.feature +++ b/tests/end2end/features/history.feature @@ -58,6 +58,13 @@ Feature: Page history And I run :history-clear Then the history file should be empty + Scenario: History with yanked URL and 'add to history' flag + When I open data/hints/html/simple.html + And I hint with args "--history links yank" and follow a + Then the history file should contain: + http://localhost:(port)/data/hints/html/simple.html Simple link + http://localhost:(port)/data/hello.txt + ## Bugs @qtwebengine_skip From c363982d057a82ef23edb4a506f06eedda732913 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 22 Nov 2016 11:10:37 +0100 Subject: [PATCH 41/48] Use per-tab QNAM for QtNetwork downloads again When starting a download due to unsupportedContent being emitted, we need to use (and later adopt) the page's QNetworkAccessManager. Since we need the whole adopting logic for that case anyways, let's keep things as they were and always run downloads in per-tab QNAMs. This reverts 53e360ec4b156c1e43ecceb6ef0787f64281c345 and fixes #2134. --- qutebrowser/browser/commands.py | 13 +++++- qutebrowser/browser/hints.py | 11 ++++- qutebrowser/browser/qtnetworkdownloads.py | 42 +++++++++++++++++-- .../browser/webkit/network/networkmanager.py | 28 +++++++++++++ qutebrowser/browser/webkit/webpage.py | 10 ++++- tests/end2end/features/downloads.feature | 13 ++++++ 6 files changed, 109 insertions(+), 8 deletions(-) diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 45320f011..220ef614f 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -1332,11 +1332,22 @@ class CommandDispatcher: elif mhtml_: self._download_mhtml(dest) else: + tab = self._current_widget() + + # FIXME:qtwebengine have a proper API for this + # pylint: disable=protected-access + try: + qnam = tab._widget.page().networkAccessManager() + except AttributeError: + # QtWebEngine + qnam = None + # pylint: enable=protected-access + if dest is None: target = None else: target = downloads.FileDownloadTarget(dest) - download_manager.get(self._current_url(), target=target) + download_manager.get(self._current_url(), qnam=qnam, target=target) def _download_mhtml(self, dest=None): """Download the current page as an MHTML file, including all assets. diff --git a/qutebrowser/browser/hints.py b/qutebrowser/browser/hints.py index af1827b2f..40fbcd8d4 100644 --- a/qutebrowser/browser/hints.py +++ b/qutebrowser/browser/hints.py @@ -284,10 +284,19 @@ class HintActions: else: prompt = None + # FIXME:qtwebengine get a proper API for this + # pylint: disable=protected-access + try: + qnam = elem._elem.webFrame().page().networkAccessManager() + except AttributeError: + # QtWebEngine + qnam = None + # pylint: enable=protected-access + # FIXME:qtwebengine do this with QtWebEngine downloads? download_manager = objreg.get('qtnetwork-download-manager', scope='window', window=self._win_id) - download_manager.get(url, prompt_download_directory=prompt) + download_manager.get(url, qnam=qnam, prompt_download_directory=prompt) def call_userscript(self, elem, context): """Call a userscript from a hint. diff --git a/qutebrowser/browser/qtnetworkdownloads.py b/qutebrowser/browser/qtnetworkdownloads.py index b75fee821..eeda5d9be 100644 --- a/qutebrowser/browser/qtnetworkdownloads.py +++ b/qutebrowser/browser/qtnetworkdownloads.py @@ -24,7 +24,7 @@ import shutil import functools import collections -from PyQt5.QtCore import pyqtSlot, QTimer +from PyQt5.QtCore import pyqtSlot, pyqtSignal, QTimer from PyQt5.QtNetwork import QNetworkRequest, QNetworkReply from qutebrowser.utils import message, usertypes, log, urlutils @@ -67,9 +67,15 @@ class DownloadItem(downloads.AbstractDownloadItem): _reply: The QNetworkReply associated with this download. _autoclose: Whether to close the associated file when the download is done. + + Signals: + adopt_download: Emitted when a download is retried and should be + adopted by the QNAM if needed. + arg 0: The new DownloadItem """ _MAX_REDIRECTS = 10 + adopt_download = pyqtSignal(object) # DownloadItem def __init__(self, reply, manager): """Constructor. @@ -162,7 +168,9 @@ class DownloadItem(downloads.AbstractDownloadItem): assert self.done assert not self.successful new_reply = self._retry_info.manager.get(self._retry_info.request) - self._manager.fetch(new_reply, suggested_filename=self.basename) + new_download = self._manager.fetch(new_reply, + suggested_filename=self.basename) + self.adopt_download.emit(new_download) self.cancel() def _get_open_filename(self): @@ -335,6 +343,14 @@ class DownloadItem(downloads.AbstractDownloadItem): old_reply.deleteLater() return True + def _uses_nam(self, nam): + """Check if this download uses the given QNetworkAccessManager.""" + running_nam = self._reply is not None and self._reply.manager() is nam + # user could request retry after tab is closed. + retry_nam = (self.done and (not self.successful) and + self._retry_info.manager is nam) + return running_nam or retry_nam + class DownloadManager(downloads.AbstractDownloadManager): @@ -409,17 +425,20 @@ class DownloadManager(downloads.AbstractDownloadManager): suggested_filename=suggested_fn, **kwargs) - def _fetch_request(self, request, **kwargs): + def _fetch_request(self, request, *, qnam=None, **kwargs): """Download a QNetworkRequest to disk. Args: request: The QNetworkRequest to download. + qnam: The QNetworkAccessManager to use. **kwargs: passed to fetch(). Return: The created DownloadItem. """ - reply = self._networkmanager.get(request) + if qnam is None: + qnam = self._networkmanager + reply = qnam.get(request) return self.fetch(reply, **kwargs) @pyqtSlot('QNetworkReply') @@ -467,3 +486,18 @@ class DownloadManager(downloads.AbstractDownloadManager): message.global_bridge.ask(question, blocking=False) return download + + def has_downloads_with_nam(self, nam): + """Check if the DownloadManager has any downloads with the given QNAM. + + Args: + nam: The QNetworkAccessManager to check. + + Return: + A boolean. + """ + assert nam.adopted_downloads == 0 + for download in self.downloads: + if download._uses_nam(nam): # pylint: disable=protected-access + nam.adopt_download(download) + return nam.adopted_downloads diff --git a/qutebrowser/browser/webkit/network/networkmanager.py b/qutebrowser/browser/webkit/network/networkmanager.py index 100db9ab0..477a9958a 100644 --- a/qutebrowser/browser/webkit/network/networkmanager.py +++ b/qutebrowser/browser/webkit/network/networkmanager.py @@ -116,6 +116,11 @@ class NetworkManager(QNetworkAccessManager): """Our own QNetworkAccessManager. Attributes: + adopted_downloads: If downloads are running with this QNAM but the + associated tab gets closed already, the NAM gets + reparented to the DownloadManager. This counts the + still running downloads, so the QNAM can clean + itself up when this reaches zero again. _requests: Pending requests. _scheme_handlers: A dictionary (scheme -> handler) of supported custom schemes. @@ -137,6 +142,7 @@ class NetworkManager(QNetworkAccessManager): # http://www.riverbankcomputing.com/pipermail/pyqt/2014-November/035045.html super().__init__(parent) log.init.debug("NetworkManager init done") + self.adopted_downloads = 0 self._win_id = win_id self._tab_id = tab_id self._requests = [] @@ -328,6 +334,28 @@ class NetworkManager(QNetworkAccessManager): # switched from private mode to normal mode self._set_cookiejar() + @pyqtSlot() + def on_adopted_download_destroyed(self): + """Check if we can clean up if an adopted download was destroyed. + + See the description for adopted_downloads for details. + """ + self.adopted_downloads -= 1 + log.downloads.debug("Adopted download destroyed, {} left.".format( + self.adopted_downloads)) + assert self.adopted_downloads >= 0 + if self.adopted_downloads == 0: + self.deleteLater() + + @pyqtSlot(object) # DownloadItem + def adopt_download(self, download): + """Adopt a new DownloadItem.""" + self.adopted_downloads += 1 + log.downloads.debug("Adopted download, {} adopted.".format( + self.adopted_downloads)) + download.destroyed.connect(self.on_adopted_download_destroyed) + download.adopt_download.connect(self.adopt_download) + def set_referer(self, req, current_url): """Set the referer header.""" referer_header_conf = config.get('network', 'referer-header') diff --git a/qutebrowser/browser/webkit/webpage.py b/qutebrowser/browser/webkit/webpage.py index 464fc3785..7fc891c4c 100644 --- a/qutebrowser/browser/webkit/webpage.py +++ b/qutebrowser/browser/webkit/webpage.py @@ -210,7 +210,13 @@ class BrowserPage(QWebPage): """Prepare the web page for being deleted.""" self._is_shutting_down = True self.shutting_down.emit() - self.networkAccessManager().shutdown() + download_manager = objreg.get('qtnetwork-download-manager', + scope='window', window=self._win_id) + nam = self.networkAccessManager() + if download_manager.has_downloads_with_nam(nam): + nam.setParent(download_manager) + else: + nam.shutdown() def display_content(self, reply, mimetype): """Display a QNetworkReply with an explicitly set mimetype.""" @@ -239,7 +245,7 @@ class BrowserPage(QWebPage): req = QNetworkRequest(request) download_manager = objreg.get('qtnetwork-download-manager', scope='window', window=self._win_id) - download_manager.get_request(req) + download_manager.get_request(req, qnam=self.networkAccessManager()) @pyqtSlot('QNetworkReply*') def on_unsupported_content(self, reply): diff --git a/tests/end2end/features/downloads.feature b/tests/end2end/features/downloads.feature index 1e1e59b34..14757c87a 100644 --- a/tests/end2end/features/downloads.feature +++ b/tests/end2end/features/downloads.feature @@ -100,6 +100,19 @@ Feature: Downloading things from a website. And I run :close Then qutebrowser should quit + # https://github.com/The-Compiler/qutebrowser/issues/2134 + @qtwebengine_skip + Scenario: Downloading, then closing a tab + When I set storage -> prompt-download-directory to false + And I open about:blank + And I open data/downloads/issue2134.html in a new tab + # This needs to be a download connected to the tabs QNAM + And I hint with args "links normal" and follow a + And I wait for "fetch: * -> drip" in the log + And I run :tab-close + And I wait for "Download drip finished" in the log + Then the downloaded file drip should contain 128 bytes + ## :download-retry Scenario: Retrying a failed download From 97d23144f7d98f87a346df035eeb9040e05368dc Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 22 Nov 2016 11:23:45 +0100 Subject: [PATCH 42/48] Add an API to get the QNAM of a tab --- qutebrowser/browser/browsertab.py | 8 ++++++++ qutebrowser/browser/commands.py | 11 +---------- qutebrowser/browser/hints.py | 10 +--------- qutebrowser/browser/webengine/webenginetab.py | 3 +++ qutebrowser/browser/webkit/webkittab.py | 6 ++++-- 5 files changed, 17 insertions(+), 21 deletions(-) diff --git a/qutebrowser/browser/browsertab.py b/qutebrowser/browser/browsertab.py index efc6d6f4b..484e2a39e 100644 --- a/qutebrowser/browser/browsertab.py +++ b/qutebrowser/browser/browsertab.py @@ -727,6 +727,14 @@ class AbstractTab(QWidget): def set_html(self, html, base_url): raise NotImplementedError + def networkaccessmanager(self): + """Get the QNetworkAccessManager for this tab. + + This is only implemented for QtWebKit. + For QtWebEngine, always returns None. + """ + raise NotImplementedError + def __repr__(self): try: url = utils.elide(self.url().toDisplayString(QUrl.EncodeUnicode), diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 220ef614f..0458e18ed 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -1332,16 +1332,7 @@ class CommandDispatcher: elif mhtml_: self._download_mhtml(dest) else: - tab = self._current_widget() - - # FIXME:qtwebengine have a proper API for this - # pylint: disable=protected-access - try: - qnam = tab._widget.page().networkAccessManager() - except AttributeError: - # QtWebEngine - qnam = None - # pylint: enable=protected-access + qnam = self._current_widget().networkaccessmanager() if dest is None: target = None diff --git a/qutebrowser/browser/hints.py b/qutebrowser/browser/hints.py index 40fbcd8d4..4b2ba6c57 100644 --- a/qutebrowser/browser/hints.py +++ b/qutebrowser/browser/hints.py @@ -284,15 +284,7 @@ class HintActions: else: prompt = None - # FIXME:qtwebengine get a proper API for this - # pylint: disable=protected-access - try: - qnam = elem._elem.webFrame().page().networkAccessManager() - except AttributeError: - # QtWebEngine - qnam = None - # pylint: enable=protected-access - + qnam = context.tab.networkaccessmanager() # FIXME:qtwebengine do this with QtWebEngine downloads? download_manager = objreg.get('qtnetwork-download-manager', scope='window', window=self._win_id) diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index 52a22f9d0..ce84a10ba 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -574,6 +574,9 @@ class WebEngineTab(browsertab.AbstractTab): # percent encoded content is 2 megabytes minus 30 bytes. self._widget.setHtml(html, base_url) + def networkaccessmanager(self): + return None + def clear_ssl_errors(self): raise browsertab.UnsupportedOperationError diff --git a/qutebrowser/browser/webkit/webkittab.py b/qutebrowser/browser/webkit/webkittab.py index 9362e5d75..407ff2591 100644 --- a/qutebrowser/browser/webkit/webkittab.py +++ b/qutebrowser/browser/webkit/webkittab.py @@ -657,8 +657,7 @@ class WebKitTab(browsertab.AbstractTab): return self._widget.title() def clear_ssl_errors(self): - nam = self._widget.page().networkAccessManager() - nam.clear_all_ssl_errors() + self.networkaccessmanager().clear_all_ssl_errors() @pyqtSlot() def _on_history_trigger(self): @@ -669,6 +668,9 @@ class WebKitTab(browsertab.AbstractTab): def set_html(self, html, base_url): self._widget.setHtml(html, base_url) + def networkaccessmanager(self): + return self._widget.page().networkAccessManager() + @pyqtSlot() def _on_frame_load_finished(self): """Make sure we emit an appropriate status when loading finished. From 20af3133ebf2ced1f163d4d047692a2db856e7b2 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 22 Nov 2016 11:24:34 +0100 Subject: [PATCH 43/48] Simplify if-condition --- qutebrowser/browser/hints.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/qutebrowser/browser/hints.py b/qutebrowser/browser/hints.py index 4b2ba6c57..c01207e56 100644 --- a/qutebrowser/browser/hints.py +++ b/qutebrowser/browser/hints.py @@ -279,12 +279,10 @@ class HintActions: url = elem.resolve_url(context.baseurl) if url is None: raise HintingError("No suitable link found for this element.") - if context.rapid: - prompt = False - else: - prompt = None + prompt = False if context.rapid else None qnam = context.tab.networkaccessmanager() + # FIXME:qtwebengine do this with QtWebEngine downloads? download_manager = objreg.get('qtnetwork-download-manager', scope='window', window=self._win_id) From caf6b74954820e6c2998efa735b6cd4c305713f3 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 22 Nov 2016 11:42:01 +0100 Subject: [PATCH 44/48] Add missing file --- tests/end2end/data/downloads/issue2134.html | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 tests/end2end/data/downloads/issue2134.html diff --git a/tests/end2end/data/downloads/issue2134.html b/tests/end2end/data/downloads/issue2134.html new file mode 100644 index 000000000..d79c7fe8c --- /dev/null +++ b/tests/end2end/data/downloads/issue2134.html @@ -0,0 +1,10 @@ + + + + + Closing tab during download + + + download + + From 9d19c3aee6fb15e62f7fcc7c7e594cc95c147c37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Wo=C5=82czyk?= Date: Tue, 22 Nov 2016 17:27:34 +0100 Subject: [PATCH 45/48] Changed add to history option name --- qutebrowser/browser/hints.py | 13 ++++++------- tests/end2end/features/history.feature | 2 +- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/qutebrowser/browser/hints.py b/qutebrowser/browser/hints.py index f32389025..3c2b3e50e 100644 --- a/qutebrowser/browser/hints.py +++ b/qutebrowser/browser/hints.py @@ -152,7 +152,7 @@ class HintContext: to_follow: The link to follow when enter is pressed. args: Custom arguments for userscript/spawn rapid: Whether to do rapid hinting. - history: Whether to add yanked or spawned link to the history. + addhistory: Whether to add yanked or spawned link to the history. filterstr: Used to save the filter string for restoring in rapid mode. tab: The WebTab object we started hinting in. group: The group of web elements to hint. @@ -165,7 +165,7 @@ class HintContext: self.baseurl = None self.to_follow = None self.rapid = False - self.history = False + self.addhistory = False self.filterstr = None self.args = [] self.tab = None @@ -603,16 +603,15 @@ class HintManager(QObject): @cmdutils.register(instance='hintmanager', scope='tab', name='hint', star_args_optional=True, maxsplit=2) @cmdutils.argument('win_id', win_id=True) - @cmdutils.argument('history', flag="s") def start(self, rapid=False, group=webelem.Group.all, target=Target.normal, - *args, win_id, mode=None, history=False): + *args, win_id, mode=None, addhistory=False): """Start hinting. Args: rapid: Whether to do rapid hinting. This is only possible with targets `tab` (with background-tabs=true), `tab-bg`, `window`, `run`, `hover`, `userscript` and `spawn`. - history: Whether to add spawned or yanked link to the + addhistory: Whether to add spawned or yanked link to the browsing history. group: The element types to hint. @@ -696,7 +695,7 @@ class HintManager(QObject): self._context.target = target self._context.rapid = rapid self._context.hint_mode = mode - self._context.history = history + self._context.addhistory = addhistory try: self._context.baseurl = tabbed_browser.current_url() except qtutils.QtValueError: @@ -866,7 +865,7 @@ class HintManager(QObject): return handler = functools.partial(url_handlers[self._context.target], url, self._context) - if self._context.history: + if self._context.addhistory: objreg.get('web-history').add_url(url, "") else: raise ValueError("No suitable handler found!") diff --git a/tests/end2end/features/history.feature b/tests/end2end/features/history.feature index 0ea1a5352..fa1fa8fe5 100644 --- a/tests/end2end/features/history.feature +++ b/tests/end2end/features/history.feature @@ -60,7 +60,7 @@ Feature: Page history Scenario: History with yanked URL and 'add to history' flag When I open data/hints/html/simple.html - And I hint with args "--history links yank" and follow a + And I hint with args "--addhistory links yank" and follow a Then the history file should contain: http://localhost:(port)/data/hints/html/simple.html Simple link http://localhost:(port)/data/hello.txt From 81cbd4c8a0470c2cd16b53e2cb293fd73806369b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Wo=C5=82czyk?= Date: Tue, 22 Nov 2016 17:39:13 +0100 Subject: [PATCH 46/48] Hyphen in variable name fixed --- qutebrowser/browser/hints.py | 12 ++++++------ tests/end2end/features/history.feature | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/qutebrowser/browser/hints.py b/qutebrowser/browser/hints.py index 3c2b3e50e..778609fb7 100644 --- a/qutebrowser/browser/hints.py +++ b/qutebrowser/browser/hints.py @@ -152,7 +152,7 @@ class HintContext: to_follow: The link to follow when enter is pressed. args: Custom arguments for userscript/spawn rapid: Whether to do rapid hinting. - addhistory: Whether to add yanked or spawned link to the history. + add_history: Whether to add yanked or spawned link to the history. filterstr: Used to save the filter string for restoring in rapid mode. tab: The WebTab object we started hinting in. group: The group of web elements to hint. @@ -165,7 +165,7 @@ class HintContext: self.baseurl = None self.to_follow = None self.rapid = False - self.addhistory = False + self.add_history = False self.filterstr = None self.args = [] self.tab = None @@ -604,14 +604,14 @@ class HintManager(QObject): star_args_optional=True, maxsplit=2) @cmdutils.argument('win_id', win_id=True) def start(self, rapid=False, group=webelem.Group.all, target=Target.normal, - *args, win_id, mode=None, addhistory=False): + *args, win_id, mode=None, add_history=False): """Start hinting. Args: rapid: Whether to do rapid hinting. This is only possible with targets `tab` (with background-tabs=true), `tab-bg`, `window`, `run`, `hover`, `userscript` and `spawn`. - addhistory: Whether to add spawned or yanked link to the + add_history: Whether to add spawned or yanked link to the browsing history. group: The element types to hint. @@ -695,7 +695,7 @@ class HintManager(QObject): self._context.target = target self._context.rapid = rapid self._context.hint_mode = mode - self._context.addhistory = addhistory + self._context.add_history = add_history try: self._context.baseurl = tabbed_browser.current_url() except qtutils.QtValueError: @@ -865,7 +865,7 @@ class HintManager(QObject): return handler = functools.partial(url_handlers[self._context.target], url, self._context) - if self._context.addhistory: + if self._context.add_history: objreg.get('web-history').add_url(url, "") else: raise ValueError("No suitable handler found!") diff --git a/tests/end2end/features/history.feature b/tests/end2end/features/history.feature index fa1fa8fe5..f4f57dcb7 100644 --- a/tests/end2end/features/history.feature +++ b/tests/end2end/features/history.feature @@ -60,7 +60,7 @@ Feature: Page history Scenario: History with yanked URL and 'add to history' flag When I open data/hints/html/simple.html - And I hint with args "--addhistory links yank" and follow a + And I hint with args "--add-history links yank" and follow a Then the history file should contain: http://localhost:(port)/data/hints/html/simple.html Simple link http://localhost:(port)/data/hello.txt From 7ffabb2b9232bfc7c885343a7c22a19b231863dc Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 22 Nov 2016 20:37:00 +0100 Subject: [PATCH 47/48] Update docs --- CHANGELOG.asciidoc | 2 ++ README.asciidoc | 1 + doc/help/commands.asciidoc | 5 ++++- qutebrowser/browser/hints.py | 4 ++-- scripts/dev/src2asciidoc.py | 1 + 5 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index efe83cd4c..d0e412ced 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -147,6 +147,8 @@ Changed `prev`/`next`/`last-used` to remove ambiguity. - The `ui -> user-stylesheet` setting now only takes filenames, not CSS snippets - `ui -> window-title-format` now has a new `{backend} ` replacement +- `:hint` has a new `--add-history` argument to add the URL to the history for + yank/spawn targets. Deprecated ~~~~~~~~~~ diff --git a/README.asciidoc b/README.asciidoc index 28614d3ae..fea1101b6 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -188,6 +188,7 @@ Contributors, sorted by the number of commits in descending order: * Jonas Schürmann * error800 * Michael Hoang +* Maciej Wołczyk * Liam BEGUIN * Julie Engel * skinnay diff --git a/doc/help/commands.asciidoc b/doc/help/commands.asciidoc index 478e82258..4151eb892 100644 --- a/doc/help/commands.asciidoc +++ b/doc/help/commands.asciidoc @@ -341,7 +341,8 @@ Show help about a command or setting. [[hint]] === hint -Syntax: +:hint [*--rapid*] [*--mode* 'mode'] ['group'] ['target'] ['args' ['args' ...]]+ +Syntax: +:hint [*--rapid*] [*--mode* 'mode'] [*--add-history*] + ['group'] ['target'] ['args' ['args' ...]]+ Start hinting. @@ -406,6 +407,8 @@ Start hinting. +* +*-a*+, +*--add-history*+: Whether to add the spawned or yanked link to the browsing history. + ==== note * This command does not split arguments after the last argument and handles quotes literally. diff --git a/qutebrowser/browser/hints.py b/qutebrowser/browser/hints.py index 4355df12e..b895c7db1 100644 --- a/qutebrowser/browser/hints.py +++ b/qutebrowser/browser/hints.py @@ -610,8 +610,8 @@ class HintManager(QObject): rapid: Whether to do rapid hinting. This is only possible with targets `tab` (with background-tabs=true), `tab-bg`, `window`, `run`, `hover`, `userscript` and `spawn`. - add_history: Whether to add spawned or yanked link to the - browsing history. + add_history: Whether to add the spawned or yanked link to the + browsing history. group: The element types to hint. - `all`: All clickable elements. diff --git a/scripts/dev/src2asciidoc.py b/scripts/dev/src2asciidoc.py index 6a29c3410..2628ba282 100755 --- a/scripts/dev/src2asciidoc.py +++ b/scripts/dev/src2asciidoc.py @@ -432,6 +432,7 @@ def _get_authors(): 'Alexey Glushko': 'haitaka', 'Corentin Jule': 'Corentin Julé', 'Claire C.C': 'Claire Cavanaugh', + 'Rahid': 'Maciej Wołczyk', } commits = subprocess.check_output(['git', 'log', '--format=%aN']) authors = [corrections.get(author, author) From df5fdb986439b379a11db1148ae9db21bda4d47b Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 22 Nov 2016 22:50:58 +0100 Subject: [PATCH 48/48] test requirements: Update pytest-mock 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 ce6f7ce1f..9a6588eb9 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -22,7 +22,7 @@ pytest-catchlog==1.2.2 pytest-cov==2.4.0 pytest-faulthandler==1.3.1 pytest-instafail==0.3.0 -pytest-mock==1.4.0 +pytest-mock==1.5.0 pytest-qt==2.1.0 pytest-repeat==0.4.1 pytest-rerunfailures==2.1.0