From cef49864d90db46c44a6a2dd55db539418920c4d Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 13 Mar 2015 07:58:04 +0100 Subject: [PATCH] Refactor websettings and save/restore defaults. This makes qutebrowser.config.websettings much easier to understand, and saves all defaults so it can restore them properly when a setting is set to an empty string. Before, when we set the fonts to empty strings instead of the true default, in some cases anti-aliasing was broken. Fixes #549. --- qutebrowser/config/websettings.py | 378 +++++++++++++++++++++--------- 1 file changed, 273 insertions(+), 105 deletions(-) diff --git a/qutebrowser/config/websettings.py b/qutebrowser/config/websettings.py index 2b3ceed53..1f0fadb5e 100644 --- a/qutebrowser/config/websettings.py +++ b/qutebrowser/config/websettings.py @@ -22,176 +22,345 @@ Module attributes: ATTRIBUTES: A mapping from internal setting names to QWebSetting enum constants. - SETTERS: A mapping from setting names to QWebSetting setter method names. - settings: The global QWebSettings singleton instance. """ import os.path from PyQt5.QtWebKit import QWebSettings -from PyQt5.QtCore import QUrl from qutebrowser.config import config -from qutebrowser.utils import usertypes, standarddir, objreg, log +from qutebrowser.utils import standarddir, objreg, log, utils, debug -MapType = usertypes.enum('MapType', ['attribute', 'setter', 'static_setter']) +UNSET = object() + + +class Base: + + """Base class for QWebSetting wrappers. + + Attributes: + _default: The default value of this setting. + """ + + def __init__(self): + self._default = UNSET + + def _get_qws(self, qws): + """Get the QWebSettings object to use. + + Args: + qws: The QWebSettings instance to use, or None to use the global + instance. + """ + if qws is None: + return QWebSettings.globalSettings() + else: + return qws + + def save_default(self, qws=None): + """Save the default value based on the currently set one. + + This does nothing if no getter is configured for this setting. + + Args: + qws: The QWebSettings instance to use, or None to use the global + instance. + """ + try: + self._default = self.get(qws) + except AttributeError: + pass + + def restore_default(self, qws=None): + """Restore the default value from the saved one. + + This does nothing if the default has never been set. + + Args: + qws: The QWebSettings instance to use, or None to use the global + instance. + """ + if self._default is not UNSET: + self._set(self._default, qws=qws) + + def get(self, qws=None): + """Get the value of this setting. + + Must be overridden by subclasses. + + Args: + qws: The QWebSettings instance to use, or None to use the global + instance. + """ + raise NotImplementedError + + def set(self, value, qws=None): + """Set the value of this setting. + + Args: + value: The value to set. + qws: The QWebSettings instance to use, or None to use the global + instance. + """ + if value is None: + self.restore_default(qws) + else: + self._set(value, qws=qws) + + def _set(self, value, qws): + """Inner function to set the value of this setting. + + Must be overridden by subclasses. + + Args: + value: The value to set. + qws: The QWebSettings instance to use, or None to use the global + instance. + """ + raise NotImplementedError + + +class Attribute(Base): + + """A setting set via QWebSettings::setAttribute. + + Attributes: + self._attribute: A QWebSettings::WebAttribute instance. + """ + + def __init__(self, attribute): + super().__init__() + self._attribute = attribute + + def __repr__(self): + return utils.get_repr( + self, attribute=debug.qenum_key(QWebSettings, self._attribute), + constructor=True) + + def get(self, qws=None): + return self._get_qws(qws).attribute(self._attribute) + + def _set(self, value, qws=None): + self._get_qws(qws).setAttribute(self._attribute, value) + + +class Setter(Base): + + """A setting set via QWebSettings getter/setter methods. + + This will pass the QWebSettings instance ("self") as first argument to the + methods, so self._getter/self._setter are the *unbound* methods. + + Attributes: + _getter: The unbound QWebSettings method to get this value, or None. + _setter: The unbound QWebSettings method to set this value. + _args: An iterable of the arguments to pass to the setter/getter + (before the value, for the setter). + _unpack: Whether to unpack args (True) or pass them directly (False). + """ + + def __init__(self, getter, setter, args=(), unpack=False): + super().__init__() + self._getter = getter + self._setter = setter + self._args = args + self._unpack = unpack + + def __repr__(self): + return utils.get_repr(self, getter=self._getter, setter=self._setter, + args=self._args, unpack=self._unpack, + constructor=True) + + def get(self, qws=None): + if self._getter is None: + raise AttributeError("No getter set!") + return self._getter(self._get_qws(qws), *self._args) + + def _set(self, value, qws=None): + args = [self._get_qws(qws)] + args.extend(self._args) + if self._unpack: + args.extend(value) + else: + args.append(value) + self._setter(*args) + + +class NullStringSetter(Setter): + + """A setter for settings requiring a null QString as default. + + This overrides save_default so None is saved for an empty string. This is + needed for the CSS media type, because it returns an empty Python string + when getting the value, but setting it to the default requires passing None + (a null QString) instead of an empty string. + """ + + def save_default(self, qws=None): + try: + val = self.get(qws) + except AttributeError: + pass + if val == '': + self._set(None, qws=qws) + else: + self._set(val, qws=qws) + + +class GlobalSetter(Setter): + + """A setting set via static QWebSettings getter/setter methods. + + self._getter/self._setter are the *bound* methods. + """ + + def get(self, qws=None): + if qws is not None: + raise ValueError("qws may not be set with GlobalSetters!") + if self._getter is None: + raise AttributeError("No getter set!") + return self._getter(*self._args) + + def _set(self, value, qws=None): + if qws is not None: + raise ValueError("qws may not be set with GlobalSetters!") + args = list(self._args) + if self._unpack: + args.extend(value) + else: + args.append(value) + self._setter(*args) MAPPINGS = { 'content': { 'allow-images': - (MapType.attribute, QWebSettings.AutoLoadImages), + Attribute(QWebSettings.AutoLoadImages), 'allow-javascript': - (MapType.attribute, QWebSettings.JavascriptEnabled), + Attribute(QWebSettings.JavascriptEnabled), 'javascript-can-open-windows': - (MapType.attribute, QWebSettings.JavascriptCanOpenWindows), + Attribute(QWebSettings.JavascriptCanOpenWindows), 'javascript-can-close-windows': - (MapType.attribute, QWebSettings.JavascriptCanCloseWindows), + Attribute(QWebSettings.JavascriptCanCloseWindows), 'javascript-can-access-clipboard': - (MapType.attribute, QWebSettings.JavascriptCanAccessClipboard), + Attribute(QWebSettings.JavascriptCanAccessClipboard), #'allow-java': - # (MapType.attribute, QWebSettings.JavaEnabled), + # Attribute(QWebSettings.JavaEnabled), 'allow-plugins': - (MapType.attribute, QWebSettings.PluginsEnabled), + Attribute(QWebSettings.PluginsEnabled), 'local-content-can-access-remote-urls': - (MapType.attribute, QWebSettings.LocalContentCanAccessRemoteUrls), + Attribute(QWebSettings.LocalContentCanAccessRemoteUrls), 'local-content-can-access-file-urls': - (MapType.attribute, QWebSettings.LocalContentCanAccessFileUrls), + Attribute(QWebSettings.LocalContentCanAccessFileUrls), }, 'network': { 'dns-prefetch': - (MapType.attribute, QWebSettings.DnsPrefetchEnabled), + Attribute(QWebSettings.DnsPrefetchEnabled), }, 'input': { 'spatial-navigation': - (MapType.attribute, QWebSettings.SpatialNavigationEnabled), + Attribute(QWebSettings.SpatialNavigationEnabled), 'links-included-in-focus-chain': - (MapType.attribute, QWebSettings.LinksIncludedInFocusChain), + Attribute(QWebSettings.LinksIncludedInFocusChain), }, 'fonts': { 'web-family-standard': - (MapType.setter, lambda qws, v: - qws.setFontFamily(QWebSettings.StandardFont, v), - ""), + Setter(getter=QWebSettings.fontFamily, + setter=QWebSettings.setFontFamily, + args=[QWebSettings.StandardFont]), 'web-family-fixed': - (MapType.setter, lambda qws, v: - qws.setFontFamily(QWebSettings.FixedFont, v), - ""), + Setter(getter=QWebSettings.fontFamily, + setter=QWebSettings.setFontFamily, + args=[QWebSettings.FixedFont]), 'web-family-serif': - (MapType.setter, lambda qws, v: - qws.setFontFamily(QWebSettings.SerifFont, v), - ""), + Setter(getter=QWebSettings.fontFamily, + setter=QWebSettings.setFontFamily, + args=[QWebSettings.SerifFont]), 'web-family-sans-serif': - (MapType.setter, lambda qws, v: - qws.setFontFamily(QWebSettings.SansSerifFont, v), - ""), + Setter(getter=QWebSettings.fontFamily, + setter=QWebSettings.setFontFamily, + args=[QWebSettings.SansSerifFont]), 'web-family-cursive': - (MapType.setter, lambda qws, v: - qws.setFontFamily(QWebSettings.CursiveFont, v), - ""), + Setter(getter=QWebSettings.fontFamily, + setter=QWebSettings.setFontFamily, + args=[QWebSettings.CursiveFont]), 'web-family-fantasy': - (MapType.setter, lambda qws, v: - qws.setFontFamily(QWebSettings.FantasyFont, v), - ""), + Setter(getter=QWebSettings.fontFamily, + setter=QWebSettings.setFontFamily, + args=[QWebSettings.FantasyFont]), 'web-size-minimum': - (MapType.setter, lambda qws, v: - qws.setFontSize(QWebSettings.MinimumFontSize, v)), + Setter(getter=QWebSettings.fontSize, + setter=QWebSettings.setFontSize, + args=[QWebSettings.MinimumFontSize]), 'web-size-minimum-logical': - (MapType.setter, lambda qws, v: - qws.setFontSize(QWebSettings.MinimumLogicalFontSize, v)), + Setter(getter=QWebSettings.fontSize, + setter=QWebSettings.setFontSize, + args=[QWebSettings.MinimumLogicalFontSize]), 'web-size-default': - (MapType.setter, lambda qws, v: - qws.setFontSize(QWebSettings.DefaultFontSize, v)), + Setter(getter=QWebSettings.fontSize, + setter=QWebSettings.setFontSize, + args=[QWebSettings.DefaultFontSize]), 'web-size-default-fixed': - (MapType.setter, lambda qws, v: - qws.setFontSize(QWebSettings.DefaultFixedFontSize, v)), + Setter(getter=QWebSettings.fontSize, + setter=QWebSettings.setFontSize, + args=[QWebSettings.DefaultFixedFontSize]), }, 'ui': { 'zoom-text-only': - (MapType.attribute, QWebSettings.ZoomTextOnly), + Attribute(QWebSettings.ZoomTextOnly), 'frame-flattening': - (MapType.attribute, QWebSettings.FrameFlatteningEnabled), + Attribute(QWebSettings.FrameFlatteningEnabled), 'user-stylesheet': - (MapType.setter, lambda qws, v: - qws.setUserStyleSheetUrl(v), - QUrl()), + Setter(getter=QWebSettings.userStyleSheetUrl, + setter=QWebSettings.setUserStyleSheetUrl), 'css-media-type': - (MapType.setter, lambda qws, v: - qws.setCSSMediaType(v)), + NullStringSetter(getter=QWebSettings.cssMediaType, + setter=QWebSettings.setCSSMediaType), #'accelerated-compositing': - # (MapType.attribute, QWebSettings.AcceleratedCompositingEnabled), + # Attribute(QWebSettings.AcceleratedCompositingEnabled), #'tiled-backing-store': - # (MapType.attribute, QWebSettings.TiledBackingStoreEnabled), + # Attribute(QWebSettings.TiledBackingStoreEnabled), }, 'storage': { 'offline-storage-database': - (MapType.attribute, QWebSettings.OfflineStorageDatabaseEnabled), + Attribute(QWebSettings.OfflineStorageDatabaseEnabled), 'offline-web-application-storage': - (MapType.attribute, - QWebSettings.OfflineWebApplicationCacheEnabled), + Attribute(QWebSettings.OfflineWebApplicationCacheEnabled), 'local-storage': - (MapType.attribute, QWebSettings.LocalStorageEnabled), + Attribute(QWebSettings.LocalStorageEnabled), 'maximum-pages-in-cache': - (MapType.static_setter, lambda v: - QWebSettings.setMaximumPagesInCache(v)), + GlobalSetter(getter=QWebSettings.maximumPagesInCache, + setter=QWebSettings.setMaximumPagesInCache), 'object-cache-capacities': - (MapType.static_setter, lambda v: - QWebSettings.setObjectCacheCapacities(*v)), + GlobalSetter(getter=None, + setter=QWebSettings.setObjectCacheCapacities, + unpack=True), 'offline-storage-default-quota': - (MapType.static_setter, lambda v: - QWebSettings.setOfflineStorageDefaultQuota(v)), + GlobalSetter(getter=QWebSettings.offlineStorageDefaultQuota, + setter=QWebSettings.setOfflineStorageDefaultQuota), 'offline-web-application-cache-quota': - (MapType.static_setter, lambda v: - QWebSettings.setOfflineWebApplicationCacheQuota(v)), + GlobalSetter( + getter=QWebSettings.offlineWebApplicationCacheQuota, + setter=QWebSettings.setOfflineWebApplicationCacheQuota), }, 'general': { 'private-browsing': - (MapType.attribute, QWebSettings.PrivateBrowsingEnabled), + Attribute(QWebSettings.PrivateBrowsingEnabled), 'developer-extras': - (MapType.attribute, QWebSettings.DeveloperExtrasEnabled), + Attribute(QWebSettings.DeveloperExtrasEnabled), 'print-element-backgrounds': - (MapType.attribute, QWebSettings.PrintElementBackgrounds), + Attribute(QWebSettings.PrintElementBackgrounds), 'xss-auditing': - (MapType.attribute, QWebSettings.XSSAuditingEnabled), + Attribute(QWebSettings.XSSAuditingEnabled), 'site-specific-quirks': - (MapType.attribute, QWebSettings.SiteSpecificQuirksEnabled), + Attribute(QWebSettings.SiteSpecificQuirksEnabled), 'default-encoding': - (MapType.setter, lambda qws, v: qws.setDefaultTextEncoding(v), ""), + Setter(getter=QWebSettings.defaultTextEncoding, + setter=QWebSettings.setDefaultTextEncoding), } } -settings = None -UNSET = object() - - -def _set_setting(typ, arg, default=UNSET, value=UNSET): - """Set a QWebSettings setting. - - Args: - typ: The type of the item. - arg: The argument (attribute/handler) - default: The value to use if the user set an empty string. - value: The value to set. - """ - if not isinstance(typ, MapType): - raise TypeError("Type {} is no MapType member!".format(typ)) - if value is UNSET: - raise TypeError("No value given!") - if value is None: - if default is UNSET: - return - else: - value = default - - if typ == MapType.attribute: - settings.setAttribute(arg, value) - elif typ == MapType.setter: - arg(settings, value) - elif typ == MapType.static_setter: - arg(value) - - def init(): """Initialize the global QWebSettings.""" QWebSettings.setIconDatabasePath(standarddir.cache()) @@ -202,14 +371,13 @@ def init(): QWebSettings.setOfflineStoragePath( os.path.join(standarddir.data(), 'offline-storage')) - global settings - settings = QWebSettings.globalSettings() for sectname, section in MAPPINGS.items(): for optname, mapping in section.items(): + mapping.save_default() value = config.get(sectname, optname) log.misc.debug("Setting {} -> {} to {!r}".format(sectname, optname, value)) - _set_setting(*mapping, value=value) + mapping.set(value) objreg.get('config').changed.connect(update_settings) @@ -220,4 +388,4 @@ def update_settings(section, option): except KeyError: return value = config.get(section, option) - _set_setting(*mapping, value=value) + mapping.set(value)