From b30d37e3e0b1d488f348efeb6001512ec0700fba Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 14 Nov 2016 23:03:36 +0100 Subject: [PATCH 01/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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 c5cacbc439e275543c8d1ac0d05a0327446e949b Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 21 Nov 2016 06:22:38 +0100 Subject: [PATCH 36/36] 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