diff --git a/qutebrowser/browser/browsertab.py b/qutebrowser/browser/browsertab.py index 4fa65eee0..3fb700420 100644 --- a/qutebrowser/browser/browsertab.py +++ b/qutebrowser/browser/browsertab.py @@ -675,6 +675,7 @@ class AbstractTab(QWidget): self.printing._widget = widget self.action._widget = widget self.elements._widget = widget + self.settings._settings = widget.settings() self._install_event_filter() self.zoom.set_default() diff --git a/qutebrowser/browser/webengine/webenginesettings.py b/qutebrowser/browser/webengine/webenginesettings.py index 18516c719..7d8d14dc6 100644 --- a/qutebrowser/browser/webengine/webenginesettings.py +++ b/qutebrowser/browser/webengine/webenginesettings.py @@ -17,9 +17,6 @@ # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see . -# We get various "abstract but not overridden" warnings -# pylint: disable=abstract-method - """Bridge from QWebEngineSettings to our own settings. Module attributes: @@ -36,7 +33,7 @@ from PyQt5.QtWebEngineWidgets import (QWebEngineSettings, QWebEngineProfile, from qutebrowser.browser import shared from qutebrowser.browser.webengine import spell -from qutebrowser.config import config, websettings +from qutebrowser.config import config, websettings, configutils from qutebrowser.utils import (utils, standarddir, javascript, qtutils, message, log, objreg) @@ -44,116 +41,135 @@ from qutebrowser.utils import (utils, standarddir, javascript, qtutils, default_profile = None # The QWebEngineProfile used for private (off-the-record) windows private_profile = None +# The global WebEngineSettings object +global_settings = None -class Base(websettings.Base): +class _SettingsWrapper: - """Base settings class with appropriate _get_global_settings.""" + """Expose a QWebEngineSettings interface which acts on all profiles.""" - def _get_global_settings(self): - return [default_profile.settings(), private_profile.settings()] + def __init__(self): + self._settings = [default_profile.settings(), + private_profile.settings()] + + def setAttribute(self, *args, **kwargs): + for settings in self._settings: + settings.setAttribute(*args, **kwargs) + + def setFontFamily(self, *args, **kwargs): + for settings in self._settings: + settings.setFontFamily(*args, **kwargs) + + def setFontSize(self, *args, **kwargs): + for settings in self._settings: + settings.setFontSize(*args, **kwargs) + + def setDefaultTextEncoding(self, *args, **kwargs): + for settings in self._settings: + settings.setDefaultTextEncoding(*args, **kwargs) -class Attribute(Base, websettings.Attribute): +class WebEngineSettings(websettings.AbstractSettings): - """A setting set via QWebEngineSettings::setAttribute.""" + """A wrapper for the config for QWebEngineSettings.""" - ENUM_BASE = QWebEngineSettings + _ATTRIBUTES = { + 'content.xss_auditing': + QWebEngineSettings.XSSAuditingEnabled, + 'content.images': + QWebEngineSettings.AutoLoadImages, + 'content.javascript.enabled': + QWebEngineSettings.JavascriptEnabled, + 'content.javascript.can_open_tabs_automatically': + QWebEngineSettings.JavascriptCanOpenWindows, + 'content.javascript.can_access_clipboard': + QWebEngineSettings.JavascriptCanAccessClipboard, + 'content.plugins': + QWebEngineSettings.PluginsEnabled, + 'content.hyperlink_auditing': + QWebEngineSettings.HyperlinkAuditingEnabled, + 'content.local_content_can_access_remote_urls': + QWebEngineSettings.LocalContentCanAccessRemoteUrls, + 'content.local_content_can_access_file_urls': + QWebEngineSettings.LocalContentCanAccessFileUrls, + 'content.webgl': + QWebEngineSettings.WebGLEnabled, + 'content.local_storage': + QWebEngineSettings.LocalStorageEnabled, + 'input.spatial_navigation': + QWebEngineSettings.SpatialNavigationEnabled, + 'input.links_included_in_focus_chain': + QWebEngineSettings.LinksIncludedInFocusChain, -class Setter(Base, websettings.Setter): + 'scrolling.smooth': + QWebEngineSettings.ScrollAnimatorEnabled, - """A setting set via a QWebEngineSettings setter method.""" + # Missing QtWebEngine attributes: + # - ScreenCaptureEnabled + # - Accelerated2dCanvasEnabled + # - AutoLoadIconsForPage + # - TouchIconsEnabled + # - FocusOnNavigationEnabled (5.8) + # - AllowRunningInsecureContent (5.8) + } - pass + _FONT_SIZES = { + 'fonts.web.size.minimum': + QWebEngineSettings.MinimumFontSize, + 'fonts.web.size.minimum_logical': + QWebEngineSettings.MinimumLogicalFontSize, + 'fonts.web.size.default': + QWebEngineSettings.DefaultFontSize, + 'fonts.web.size.default_fixed': + QWebEngineSettings.DefaultFixedFontSize, + } + _FONT_FAMILIES = { + 'fonts.web.family.standard': QWebEngineSettings.StandardFont, + 'fonts.web.family.fixed': QWebEngineSettings.FixedFont, + 'fonts.web.family.serif': QWebEngineSettings.SerifFont, + 'fonts.web.family.sans_serif': QWebEngineSettings.SansSerifFont, + 'fonts.web.family.cursive': QWebEngineSettings.CursiveFont, + 'fonts.web.family.fantasy': QWebEngineSettings.FantasyFont, -class FontFamilySetter(Base, websettings.FontFamilySetter): + # Missing QtWebEngine fonts: + # - PictographFont + } - """A setter for a font family. + # Mapping from WebEngineSettings::initDefaults in + # qtwebengine/src/core/web_engine_settings.cpp + _FONT_TO_QFONT = { + QWebEngineSettings.StandardFont: QFont.Serif, + QWebEngineSettings.FixedFont: QFont.Monospace, + QWebEngineSettings.SerifFont: QFont.Serif, + QWebEngineSettings.SansSerifFont: QFont.SansSerif, + QWebEngineSettings.CursiveFont: QFont.Cursive, + QWebEngineSettings.FantasyFont: QFont.Fantasy, + } - Gets the default value from QFont. - """ - - def __init__(self, font): - # Mapping from WebEngineSettings::initDefaults in - # qtwebengine/src/core/web_engine_settings.cpp - font_to_qfont = { - QWebEngineSettings.StandardFont: QFont.Serif, - QWebEngineSettings.FixedFont: QFont.Monospace, - QWebEngineSettings.SerifFont: QFont.Serif, - QWebEngineSettings.SansSerifFont: QFont.SansSerif, - QWebEngineSettings.CursiveFont: QFont.Cursive, - QWebEngineSettings.FantasyFont: QFont.Fantasy, + def __init__(self, settings): + super().__init__(settings) + # Attributes which don't exist in all Qt versions. + new_attributes = { + # Qt 5.8 + 'content.print_element_backgrounds': 'PrintElementBackgrounds', } - super().__init__(setter=QWebEngineSettings.setFontFamily, font=font, - qfont=font_to_qfont[font]) + for name, attribute in new_attributes.items(): + try: + value = getattr(QWebEngineSettings, attribute) + except AttributeError: + continue + self._ATTRIBUTES[name] = value -class DefaultProfileSetter(websettings.Base): - - """A setting set on the QWebEngineProfile.""" - - def __init__(self, setter, converter=None, default=websettings.UNSET): - super().__init__(default) - self._setter = setter - self._converter = converter - - def __repr__(self): - return utils.get_repr(self, setter=self._setter, constructor=True) - - def _set(self, value, settings=None): - if settings is not None: - raise ValueError("'settings' may not be set with " - "DefaultProfileSetters!") - - setter = getattr(default_profile, self._setter) - if self._converter is not None: - value = self._converter(value) - - setter(value) - - -class PersistentCookiePolicy(DefaultProfileSetter): - - """The content.cookies.store setting is different from other settings.""" - - def __init__(self): - super().__init__('setPersistentCookiesPolicy') - - def _set(self, value, settings=None): - if settings is not None: - raise ValueError("'settings' may not be set with " - "PersistentCookiePolicy!") - setter = getattr(QWebEngineProfile.defaultProfile(), self._setter) - setter( - QWebEngineProfile.AllowPersistentCookies if value else - QWebEngineProfile.NoPersistentCookies - ) - - -class DictionaryLanguageSetter(DefaultProfileSetter): - - """Sets paths to dictionary files based on language codes.""" - - def __init__(self): - super().__init__('setSpellCheckLanguages', default=[]) - - def _find_installed(self, code): - local_filename = spell.local_filename(code) - if not local_filename: - message.warning( - "Language {} is not installed - see scripts/dictcli.py " - "in qutebrowser's sources".format(code)) - return local_filename - - def _set(self, value, settings=None): - if settings is not None: - raise ValueError("'settings' may not be set with " - "DictionaryLanguageSetter!") - filenames = [self._find_installed(code) for code in value] - log.config.debug("Found dicts: {}".format(filenames)) - super()._set([f for f in filenames if f], settings) + def set_attribute(self, name, value): + attribute = self._ATTRIBUTES[name] + if value is configutils.UNSET: + self._settings.resetAttribute(attribute) + else: + self._settings.setAttribute(attribute, value) def _init_stylesheet(profile): @@ -210,9 +226,47 @@ def _set_http_headers(profile): profile.setHttpAcceptLanguage(accept_language) +def _set_http_cache_size(profile): + """Initialize the HTTP cache size for the given profile.""" + size = config.val.content.cache.size + if size is None: + size = 0 + else: + size = qtutils.check_overflow(size, 'int', fatal=False) + + # 0: automatically managed by QtWebEngine + profile.setHttpCacheMaximumSize(size) + + +def _set_persistent_cookie_policy(profile): + """Set the HTTP Cookie size for the given profile.""" + if config.val.content.cookies.store: + value = QWebEngineProfile.AllowPersistentCookies + else: + value = QWebEngineProfile.NoPersistentCookies + profile.setPersistentCookiesPolicy(value) + + +def _set_dictionary_language(profile): + filenames = [] + for code in config.val.spellcheck.languages or []: + local_filename = spell.local_filename(code) + if not local_filename: + message.warning( + "Language {} is not installed - see scripts/dictcli.py " + "in qutebrowser's sources".format(code)) + continue + + filenames.append(local_filename) + + log.config.debug("Found dicts: {}".format(filenames)) + profile.setSpellCheckLanguages(filenames) + + def _update_settings(option): """Update global settings when qwebsettings changed.""" - websettings.update_mappings(MAPPINGS, option) + global_settings.update_setting(option) + if option in ['scrolling.bar', 'content.user_stylesheets']: _init_stylesheet(default_profile) _init_stylesheet(private_profile) @@ -221,27 +275,46 @@ def _update_settings(option): 'content.headers.accept_language']: _set_http_headers(default_profile) _set_http_headers(private_profile) + elif option == 'content.cache.size': + _set_http_cache_size(default_profile) + _set_http_cache_size(private_profile) + elif (option == 'content.cookies.store' and + # https://bugreports.qt.io/browse/QTBUG-58650 + qtutils.version_check('5.9', compiled=False)): + _set_persistent_cookie_policy(default_profile) + # We're not touching the private profile's cookie policy. + elif option == 'spellcheck.languages' and qtutils.version_check('5.8'): + _set_dictionary_language(default_profile) + _set_dictionary_language(private_profile) + + +def _init_profile(profile): + """Init the given profile.""" + _init_stylesheet(profile) + _set_http_headers(profile) + _set_http_cache_size(profile) + profile.settings().setAttribute( + QWebEngineSettings.FullScreenSupportEnabled, True) + if qtutils.version_check('5.8'): + profile.setSpellCheckEnabled(True) + _set_dictionary_language(profile) def _init_profiles(): """Init the two used QWebEngineProfiles.""" global default_profile, private_profile + default_profile = QWebEngineProfile.defaultProfile() default_profile.setCachePath( os.path.join(standarddir.cache(), 'webengine')) default_profile.setPersistentStoragePath( os.path.join(standarddir.data(), 'webengine')) - _init_stylesheet(default_profile) - _set_http_headers(default_profile) + _init_profile(default_profile) + _set_persistent_cookie_policy(default_profile) private_profile = QWebEngineProfile() assert private_profile.isOffTheRecord() - _init_stylesheet(private_profile) - _set_http_headers(private_profile) - - if qtutils.version_check('5.8'): - default_profile.setSpellCheckEnabled(True) - private_profile.setSpellCheckEnabled(True) + _init_profile(private_profile) def inject_userscripts(): @@ -287,115 +360,13 @@ def init(args): os.environ['QTWEBENGINE_REMOTE_DEBUGGING'] = str(utils.random_port()) _init_profiles() - - # We need to do this here as a WORKAROUND for - # https://bugreports.qt.io/browse/QTBUG-58650 - if not qtutils.version_check('5.9', compiled=False): - PersistentCookiePolicy().set(config.val.content.cookies.store) - Attribute(QWebEngineSettings.FullScreenSupportEnabled).set(True) - - websettings.init_mappings(MAPPINGS) config.instance.changed.connect(_update_settings) - -def update_for_tab(tab, url): - websettings.update_for_tab(MAPPINGS, tab, url) + global global_settings + global_settings = WebEngineSettings(_SettingsWrapper()) + global_settings.init_settings() def shutdown(): # FIXME:qtwebengine do we need to do something for a clean shutdown here? pass - - -# Missing QtWebEngine attributes: -# - ScreenCaptureEnabled -# - Accelerated2dCanvasEnabled -# - AutoLoadIconsForPage -# - TouchIconsEnabled -# - FocusOnNavigationEnabled (5.8) -# - AllowRunningInsecureContent (5.8) -# -# Missing QtWebEngine fonts: -# - PictographFont - - -MAPPINGS = { - 'content.images': - Attribute(QWebEngineSettings.AutoLoadImages), - 'content.javascript.enabled': - Attribute(QWebEngineSettings.JavascriptEnabled), - 'content.javascript.can_open_tabs_automatically': - Attribute(QWebEngineSettings.JavascriptCanOpenWindows), - 'content.javascript.can_access_clipboard': - Attribute(QWebEngineSettings.JavascriptCanAccessClipboard), - 'content.plugins': - Attribute(QWebEngineSettings.PluginsEnabled), - 'content.hyperlink_auditing': - Attribute(QWebEngineSettings.HyperlinkAuditingEnabled), - 'content.local_content_can_access_remote_urls': - Attribute(QWebEngineSettings.LocalContentCanAccessRemoteUrls), - 'content.local_content_can_access_file_urls': - Attribute(QWebEngineSettings.LocalContentCanAccessFileUrls), - 'content.webgl': - Attribute(QWebEngineSettings.WebGLEnabled), - 'content.local_storage': - Attribute(QWebEngineSettings.LocalStorageEnabled), - 'content.cache.size': - # 0: automatically managed by QtWebEngine - DefaultProfileSetter('setHttpCacheMaximumSize', default=0, - converter=lambda val: - qtutils.check_overflow(val, 'int', fatal=False)), - 'content.xss_auditing': - Attribute(QWebEngineSettings.XSSAuditingEnabled), - 'content.default_encoding': - Setter(QWebEngineSettings.setDefaultTextEncoding), - - 'input.spatial_navigation': - Attribute(QWebEngineSettings.SpatialNavigationEnabled), - 'input.links_included_in_focus_chain': - Attribute(QWebEngineSettings.LinksIncludedInFocusChain), - - 'fonts.web.family.standard': - FontFamilySetter(QWebEngineSettings.StandardFont), - 'fonts.web.family.fixed': - FontFamilySetter(QWebEngineSettings.FixedFont), - 'fonts.web.family.serif': - FontFamilySetter(QWebEngineSettings.SerifFont), - 'fonts.web.family.sans_serif': - FontFamilySetter(QWebEngineSettings.SansSerifFont), - 'fonts.web.family.cursive': - FontFamilySetter(QWebEngineSettings.CursiveFont), - 'fonts.web.family.fantasy': - FontFamilySetter(QWebEngineSettings.FantasyFont), - 'fonts.web.size.minimum': - Setter(QWebEngineSettings.setFontSize, - args=[QWebEngineSettings.MinimumFontSize]), - 'fonts.web.size.minimum_logical': - Setter(QWebEngineSettings.setFontSize, - args=[QWebEngineSettings.MinimumLogicalFontSize]), - 'fonts.web.size.default': - Setter(QWebEngineSettings.setFontSize, - args=[QWebEngineSettings.DefaultFontSize]), - 'fonts.web.size.default_fixed': - Setter(QWebEngineSettings.setFontSize, - args=[QWebEngineSettings.DefaultFixedFontSize]), - - 'scrolling.smooth': - Attribute(QWebEngineSettings.ScrollAnimatorEnabled), -} - -try: - MAPPINGS['content.print_element_backgrounds'] = Attribute( - QWebEngineSettings.PrintElementBackgrounds) -except AttributeError: - # Added in Qt 5.8 - pass - - -if qtutils.version_check('5.8'): - MAPPINGS['spellcheck.languages'] = DictionaryLanguageSetter() - - -if qtutils.version_check('5.9', compiled=False): - # https://bugreports.qt.io/browse/QTBUG-58650 - MAPPINGS['content.cookies.store'] = PersistentCookiePolicy() diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index e047c9d1e..828fe9e56 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -604,6 +604,8 @@ class WebEngineTab(browsertab.AbstractTab): self.printing = WebEnginePrinting() self.elements = WebEngineElements(tab=self) self.action = WebEngineAction(tab=self) + # We're assigning settings in _set_widget + self.settings = webenginesettings.WebEngineSettings(settings=None) self._set_widget(widget) self._connect_signals() self.backend = usertypes.Backend.QtWebEngine @@ -876,7 +878,7 @@ class WebEngineTab(browsertab.AbstractTab): def _on_navigation_request(self, navigation): super()._on_navigation_request(navigation) if navigation.accepted and navigation.is_main_frame: - webenginesettings.update_for_tab(self, navigation.url) + self.settings.update_for_url(navigation.url) def _connect_signals(self): view = self._widget diff --git a/qutebrowser/browser/webkit/webkitsettings.py b/qutebrowser/browser/webkit/webkitsettings.py index 976432418..6ba15f62a 100644 --- a/qutebrowser/browser/webkit/webkitsettings.py +++ b/qutebrowser/browser/webkit/webkitsettings.py @@ -17,9 +17,6 @@ # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see . -# We get various "abstract but not overridden" warnings -# pylint: disable=abstract-method - """Bridge from QWebSettings to our own settings. Module attributes: @@ -32,94 +29,142 @@ import os.path from PyQt5.QtGui import QFont from PyQt5.QtWebKit import QWebSettings -from qutebrowser.config import config, websettings +from qutebrowser.config import config, websettings, configutils from qutebrowser.utils import standarddir, urlutils from qutebrowser.browser import shared -class Base(websettings.Base): - - """Base settings class with appropriate _get_global_settings.""" - - def _get_global_settings(self): - return [QWebSettings.globalSettings()] +# The global WebKitSettings object +global_settings = None -class Attribute(Base, websettings.Attribute): +class WebKitSettings(websettings.AbstractSettings): - """A setting set via QWebSettings::setAttribute.""" + """A wrapper for the config for QWebSettings.""" - ENUM_BASE = QWebSettings + _ATTRIBUTES = { + 'content.images': + [QWebSettings.AutoLoadImages], + 'content.javascript.enabled': + [QWebSettings.JavascriptEnabled], + 'content.javascript.can_open_tabs_automatically': + [QWebSettings.JavascriptCanOpenWindows], + 'content.javascript.can_close_tabs': + [QWebSettings.JavascriptCanCloseWindows], + 'content.javascript.can_access_clipboard': + [QWebSettings.JavascriptCanAccessClipboard], + 'content.plugins': + [QWebSettings.PluginsEnabled], + 'content.webgl': + [QWebSettings.WebGLEnabled], + 'content.hyperlink_auditing': + [QWebSettings.HyperlinkAuditingEnabled], + 'content.local_content_can_access_remote_urls': + [QWebSettings.LocalContentCanAccessRemoteUrls], + 'content.local_content_can_access_file_urls': + [QWebSettings.LocalContentCanAccessFileUrls], + 'content.dns_prefetch': + [QWebSettings.DnsPrefetchEnabled], + 'content.frame_flattening': + [QWebSettings.FrameFlatteningEnabled], + 'content.cache.appcache': + [QWebSettings.OfflineWebApplicationCacheEnabled], + 'content.local_storage': + [QWebSettings.LocalStorageEnabled, + QWebSettings.OfflineStorageDatabaseEnabled], + 'content.developer_extras': + [QWebSettings.DeveloperExtrasEnabled], + 'content.print_element_backgrounds': + [QWebSettings.PrintElementBackgrounds], + 'content.xss_auditing': + [QWebSettings.XSSAuditingEnabled], + + 'input.spatial_navigation': + [QWebSettings.SpatialNavigationEnabled], + 'input.links_included_in_focus_chain': + [QWebSettings.LinksIncludedInFocusChain], + + 'zoom.text_only': + [QWebSettings.ZoomTextOnly], + 'scrolling.smooth': + [QWebSettings.ScrollAnimatorEnabled], + } + + _FONT_SIZES = { + 'fonts.web.size.minimum': + QWebSettings.MinimumFontSize, + 'fonts.web.size.minimum_logical': + QWebSettings.MinimumLogicalFontSize, + 'fonts.web.size.default': + QWebSettings.DefaultFontSize, + 'fonts.web.size.default_fixed': + QWebSettings.DefaultFixedFontSize, + } + + _FONT_FAMILIES = { + 'fonts.web.family.standard': QWebSettings.StandardFont, + 'fonts.web.family.fixed': QWebSettings.FixedFont, + 'fonts.web.family.serif': QWebSettings.SerifFont, + 'fonts.web.family.sans_serif': QWebSettings.SansSerifFont, + 'fonts.web.family.cursive': QWebSettings.CursiveFont, + 'fonts.web.family.fantasy': QWebSettings.FantasyFont, + } + + # Mapping from QWebSettings::QWebSettings() in + # qtwebkit/Source/WebKit/qt/Api/qwebsettings.cpp + _FONT_TO_QFONT = { + QWebSettings.StandardFont: QFont.Serif, + QWebSettings.FixedFont: QFont.Monospace, + QWebSettings.SerifFont: QFont.Serif, + QWebSettings.SansSerifFont: QFont.SansSerif, + QWebSettings.CursiveFont: QFont.Cursive, + QWebSettings.FantasyFont: QFont.Fantasy, + } + + def set_attribute(self, name, value): + for attribute in self._ATTRIBUTES[name]: + if value is configutils.UNSET: + self._settings.resetAttribute(attribute) + else: + self._settings.setAttribute(attribute, value) -class Setter(Base, websettings.Setter): - - """A setting set via a QWebSettings setter method.""" - - pass +def _set_user_stylesheet(settings): + """Set the generated user-stylesheet.""" + stylesheet = shared.get_user_stylesheet().encode('utf-8') + url = urlutils.data_url('text/css;charset=utf-8', stylesheet) + settings.setUserStyleSheetUrl(url) -class StaticSetter(Base, websettings.StaticSetter): - - """A setting set via a static QWebSettings setter method.""" - - pass - - -class FontFamilySetter(Base, websettings.FontFamilySetter): - - """A setter for a font family. - - Gets the default value from QFont. - """ - - def __init__(self, font): - # Mapping from QWebSettings::QWebSettings() in - # qtwebkit/Source/WebKit/qt/Api/qwebsettings.cpp - font_to_qfont = { - QWebSettings.StandardFont: QFont.Serif, - QWebSettings.FixedFont: QFont.Monospace, - QWebSettings.SerifFont: QFont.Serif, - QWebSettings.SansSerifFont: QFont.SansSerif, - QWebSettings.CursiveFont: QFont.Cursive, - QWebSettings.FantasyFont: QFont.Fantasy, - } - super().__init__(setter=QWebSettings.setFontFamily, font=font, - qfont=font_to_qfont[font]) - - -class CookiePolicy(Base): - - """The ThirdPartyCookiePolicy setting is different from other settings.""" - - MAPPING = { +def _set_cookie_accept_policy(settings): + """Update the content.cookies.accept setting.""" + mapping = { 'all': QWebSettings.AlwaysAllowThirdPartyCookies, 'no-3rdparty': QWebSettings.AlwaysBlockThirdPartyCookies, 'never': QWebSettings.AlwaysBlockThirdPartyCookies, 'no-unknown-3rdparty': QWebSettings.AllowThirdPartyWithExistingCookies, } - - def _set(self, value, settings=None): - for obj in self._get_settings(settings): - obj.setThirdPartyCookiePolicy(self.MAPPING[value]) + value = config.val.content.cookies.accept + settings.setThirdPartyCookiePolicy(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 _set_cache_maximum_pages(settings): + """Update the content.cache.maximum_pages setting.""" + value = config.val.content.cache.maximum_pages + settings.setMaximumPagesInCache(value) def _update_settings(option): """Update global settings when qwebsettings changed.""" + global_settings.update_setting(option) + + settings = QWebSettings.globalSettings() if option in ['scrollbar.hide', 'content.user_stylesheets']: - _set_user_stylesheet() - websettings.update_mappings(MAPPINGS, option) - - -def update_for_tab(tab, url): - websettings.update_for_tab(MAPPINGS, tab, url) + _set_user_stylesheet(settings) + elif option == 'content.cookies.accept': + _set_cookie_accept_policy(settings) + elif option == 'content.cache.maximum_pages': + _set_cache_maximum_pages(settings) def init(_args): @@ -135,92 +180,20 @@ def init(_args): QWebSettings.setOfflineStoragePath( os.path.join(data_path, 'offline-storage')) - websettings.init_mappings(MAPPINGS) - _set_user_stylesheet() + settings = QWebSettings.globalSettings() + _set_user_stylesheet(settings) + _set_cookie_accept_policy(settings) + _set_cache_maximum_pages(settings) + config.instance.changed.connect(_update_settings) + global global_settings + global_settings = WebKitSettings(QWebSettings.globalSettings()) + global_settings.init_settings() + def shutdown(): """Disable storage so removing tmpdir will work.""" QWebSettings.setIconDatabasePath('') QWebSettings.setOfflineWebApplicationCachePath('') QWebSettings.globalSettings().setLocalStoragePath('') - - -MAPPINGS = { - 'content.images': - Attribute(QWebSettings.AutoLoadImages), - 'content.javascript.enabled': - Attribute(QWebSettings.JavascriptEnabled), - 'content.javascript.can_open_tabs_automatically': - Attribute(QWebSettings.JavascriptCanOpenWindows), - 'content.javascript.can_close_tabs': - Attribute(QWebSettings.JavascriptCanCloseWindows), - 'content.javascript.can_access_clipboard': - Attribute(QWebSettings.JavascriptCanAccessClipboard), - 'content.plugins': - Attribute(QWebSettings.PluginsEnabled), - 'content.webgl': - Attribute(QWebSettings.WebGLEnabled), - 'content.hyperlink_auditing': - Attribute(QWebSettings.HyperlinkAuditingEnabled), - 'content.local_content_can_access_remote_urls': - Attribute(QWebSettings.LocalContentCanAccessRemoteUrls), - 'content.local_content_can_access_file_urls': - Attribute(QWebSettings.LocalContentCanAccessFileUrls), - 'content.cookies.accept': - CookiePolicy(), - 'content.dns_prefetch': - Attribute(QWebSettings.DnsPrefetchEnabled), - 'content.frame_flattening': - Attribute(QWebSettings.FrameFlatteningEnabled), - 'content.cache.appcache': - Attribute(QWebSettings.OfflineWebApplicationCacheEnabled), - 'content.local_storage': - Attribute(QWebSettings.LocalStorageEnabled, - QWebSettings.OfflineStorageDatabaseEnabled), - 'content.cache.maximum_pages': - StaticSetter(QWebSettings.setMaximumPagesInCache), - 'content.developer_extras': - Attribute(QWebSettings.DeveloperExtrasEnabled), - 'content.print_element_backgrounds': - Attribute(QWebSettings.PrintElementBackgrounds), - 'content.xss_auditing': - Attribute(QWebSettings.XSSAuditingEnabled), - 'content.default_encoding': - Setter(QWebSettings.setDefaultTextEncoding), - # content.user_stylesheets is handled separately - - 'input.spatial_navigation': - Attribute(QWebSettings.SpatialNavigationEnabled), - 'input.links_included_in_focus_chain': - Attribute(QWebSettings.LinksIncludedInFocusChain), - - 'fonts.web.family.standard': - FontFamilySetter(QWebSettings.StandardFont), - 'fonts.web.family.fixed': - FontFamilySetter(QWebSettings.FixedFont), - 'fonts.web.family.serif': - FontFamilySetter(QWebSettings.SerifFont), - 'fonts.web.family.sans_serif': - FontFamilySetter(QWebSettings.SansSerifFont), - 'fonts.web.family.cursive': - FontFamilySetter(QWebSettings.CursiveFont), - 'fonts.web.family.fantasy': - FontFamilySetter(QWebSettings.FantasyFont), - 'fonts.web.size.minimum': - Setter(QWebSettings.setFontSize, args=[QWebSettings.MinimumFontSize]), - 'fonts.web.size.minimum_logical': - Setter(QWebSettings.setFontSize, - args=[QWebSettings.MinimumLogicalFontSize]), - 'fonts.web.size.default': - Setter(QWebSettings.setFontSize, args=[QWebSettings.DefaultFontSize]), - 'fonts.web.size.default_fixed': - Setter(QWebSettings.setFontSize, - args=[QWebSettings.DefaultFixedFontSize]), - - 'zoom.text_only': - Attribute(QWebSettings.ZoomTextOnly), - 'scrolling.smooth': - Attribute(QWebSettings.ScrollAnimatorEnabled), -} diff --git a/qutebrowser/browser/webkit/webkittab.py b/qutebrowser/browser/webkit/webkittab.py index 73a2f2648..5184550cd 100644 --- a/qutebrowser/browser/webkit/webkittab.py +++ b/qutebrowser/browser/webkit/webkittab.py @@ -645,6 +645,8 @@ class WebKitTab(browsertab.AbstractTab): self.printing = WebKitPrinting() self.elements = WebKitElements(tab=self) self.action = WebKitAction(tab=self) + # We're assigning settings in _set_widget + self.settings = webkitsettings.WebKitSettings(settings=None) self._set_widget(widget) self._connect_signals() self.backend = usertypes.Backend.QtWebKit @@ -785,7 +787,7 @@ class WebKitTab(browsertab.AbstractTab): navigation.accepted = False if navigation.is_main_frame: - webkitsettings.update_for_tab(self, navigation.url) + self.settings.update_for_url(navigation.url) def _connect_signals(self): view = self._widget diff --git a/qutebrowser/config/websettings.py b/qutebrowser/config/websettings.py index eb6c2ce49..98d5339fe 100644 --- a/qutebrowser/config/websettings.py +++ b/qutebrowser/config/websettings.py @@ -17,230 +17,96 @@ # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see . -# We get various "abstract but not overridden" warnings -# pylint: disable=abstract-method - """Bridge from QWeb(Engine)Settings to our own settings.""" from PyQt5.QtGui import QFont from qutebrowser.config import config, configutils -from qutebrowser.utils import log, utils, debug, usertypes +from qutebrowser.utils import log, usertypes from qutebrowser.misc import objects UNSET = object() -class Base: +class AbstractSettings: - """Base class for QWeb(Engine)Settings wrappers.""" + """Abstract base class for settings set via QWeb(Engine)Settings.""" - def __init__(self, default=UNSET): - self._default = default + _ATTRIBUTES = None + _FONT_SIZES = None + _FONT_FAMILIES = None + _FONT_TO_QFONT = None - def _get_global_settings(self): - """Get a list of global QWeb(Engine)Settings to use.""" + def __init__(self, settings): + self._settings = settings + + def set_attribute(self, name, value): + """Set the given QWebSettings/QWebEngineSettings attribute. + + If the value is configutils.UNSET, the value is reset instead. + """ raise NotImplementedError - def _get_settings(self, settings): - """Get a list of QWeb(Engine)Settings objects to use. + def set_font_size(self, name, value): + """Set the given QWebSettings/QWebEngineSettings font size.""" + assert value is not configutils.UNSET + self._settings.setFontSize(self._FONT_SIZES[name], value) - Args: - settings: The QWeb(Engine)Settings instance to use, or None to use - the global instance. + def set_font_family(self, name, value): + """Set the given QWebSettings/QWebEngineSettings font family. - Return: - A list of QWeb(Engine)Settings objects. The first one should be - used for reading. - """ - if settings is None: - return self._get_global_settings() - else: - return [settings] - - def set(self, value, settings=None): - """Set the value of this setting. - - Args: - value: The value to set, or None to restore the default. - settings: The QWeb(Engine)Settings instance to use, or None to use - the global instance. + With None (the default), QFont is used to get the default font for the + family. """ + assert value is not configutils.UNSET if value is None: - self.set_default(settings=settings) - else: - self._set(value, settings=settings) + font = QFont() + font.setStyleHint(self._FONT_TO_QFONT[self._FONT_FAMILIES[name]]) + value = font.defaultFamily() - def set_default(self, settings=None): - """Set the default value for this setting. + self._settings.setFontFamily(self._FONT_FAMILIES[name], value) - Not implemented for most settings. + def set_default_text_encoding(self, encoding): + """Set the default text encoding to use.""" + assert encoding is not configutils.UNSET + self._settings.setDefaultTextEncoding(encoding) + + def _update_setting(self, setting, value): + """Update the given setting/value. + + Unknown settings are ignored. """ - if self._default is UNSET: - raise ValueError("No default set for {!r}".format(self)) - else: - self._set(self._default, settings=settings) + if setting in self._ATTRIBUTES: + self.set_attribute(setting, value) + elif setting in self._FONT_SIZES: + self.set_font_size(setting, value) + elif setting in self._FONT_FAMILIES: + self.set_font_family(setting, value) + elif setting == 'content.default_encoding': + self.set_default_text_encoding(value) - def _set(self, value, settings): - """Inner function to set the value of this setting. + def update_setting(self, setting): + """Update the given setting.""" + value = config.instance.get(setting) + self._update_setting(setting, value) - Must be overridden by subclasses. + def update_for_url(self, url): + """Update settings customized for the given tab.""" + for values in config.instance: + if not values.opt.supports_pattern: + continue - Args: - value: The value to set. - settings: The QWeb(Engine)Settings instance to use, or None to use - the global instance. - """ - raise NotImplementedError + value = values.get_for_url(url, fallback=False) + log.config.debug("Updating for {}: {} = {}".format( + url.toDisplayString(), values.opt.name, value)) - def unset(self, settings=None): - """Unset a customized setting. + self._update_setting(values.opt.name, value) - Must be overridden by subclasses. - """ - raise NotImplementedError - - -class Attribute(Base): - - """A setting set via QWeb(Engine)Settings::setAttribute. - - Attributes: - self._attributes: A list of QWeb(Engine)Settings::WebAttribute members. - """ - - ENUM_BASE = None - - def __init__(self, *attributes, default=UNSET): - super().__init__(default=default) - self._attributes = list(attributes) - - def __repr__(self): - attributes = [debug.qenum_key(self.ENUM_BASE, attr) - for attr in self._attributes] - return utils.get_repr(self, attributes=attributes, constructor=True) - - def _set(self, value, settings=None): - for obj in self._get_settings(settings): - for attribute in self._attributes: - obj.setAttribute(attribute, value) - - def unset(self, settings=None): - for obj in self._get_settings(settings): - for attribute in self._attributes: - obj.resetAttribute(attribute) - - -class Setter(Base): - - """A setting set via a QWeb(Engine)Settings setter method. - - This will pass the QWeb(Engine)Settings instance ("self") as first argument - to the methods, so self._setter is the *unbound* method. - - Attributes: - _setter: The unbound QWeb(Engine)Settings method to set this value. - _args: An iterable of the arguments to pass to the setter (before the - value). - _unpack: Whether to unpack args (True) or pass them directly (False). - """ - - def __init__(self, setter, args=(), unpack=False, default=UNSET): - super().__init__(default=default) - self._setter = setter - self._args = args - self._unpack = unpack - - def __repr__(self): - return utils.get_repr(self, setter=self._setter, args=self._args, - unpack=self._unpack, constructor=True) - - def _set(self, value, settings=None): - for obj in self._get_settings(settings): - args = [obj] - args.extend(self._args) - if self._unpack: - args.extend(value) - else: - args.append(value) - self._setter(*args) - - -class StaticSetter(Setter): - - """A setting set via a static QWeb(Engine)Settings method. - - self._setter is the *bound* method. - """ - - def _set(self, value, settings=None): - if settings is not None: - raise ValueError("'settings' may not be set with StaticSetters!") - args = list(self._args) - if self._unpack: - args.extend(value) - else: - args.append(value) - self._setter(*args) - - -class FontFamilySetter(Setter): - - """A setter for a font family. - - Gets the default value from QFont. - """ - - def __init__(self, setter, font, qfont): - super().__init__(setter=setter, args=[font]) - self._qfont = qfont - - def set_default(self, settings=None): - font = QFont() - font.setStyleHint(self._qfont) - value = font.defaultFamily() - self._set(value, settings=settings) - - -def init_mappings(mappings): - """Initialize all settings based on a settings mapping.""" - for option, mapping in mappings.items(): - value = config.instance.get(option) - log.config.vdebug("Setting {} to {!r}".format(option, value)) - mapping.set(value) - - -def update_mappings(mappings, option): - """Update global settings when QWeb(Engine)Settings changed.""" - try: - mapping = mappings[option] - except KeyError: - return - value = config.instance.get(option) - mapping.set(value) - - -def update_for_tab(mappings, tab, url): - """Update settings customized for the given tab.""" - for values in config.instance: - if values.opt.name not in mappings: - continue - if not values.opt.supports_pattern: - continue - - mapping = mappings[values.opt.name] - - value = values.get_for_url(url, fallback=False) - log.config.debug("Updating for {}: {} = {}".format( - url.toDisplayString(), values.opt.name, value)) - - # FIXME:conf have a proper API for this. - settings = tab._widget.settings() # pylint: disable=protected-access - - if value is configutils.UNSET: - mapping.unset(settings=settings) - else: - mapping.set(value, settings=settings) + def init_settings(self): + """Set all supported settings correctly.""" + for setting in (list(self._ATTRIBUTES) + list(self._FONT_SIZES) + + list(self._FONT_FAMILIES)): + self.update_setting(setting) def init(args): diff --git a/tests/unit/browser/webengine/test_webenginesettings.py b/tests/unit/browser/webengine/test_webenginesettings.py index 995dec44a..1fbe38f00 100644 --- a/tests/unit/browser/webengine/test_webenginesettings.py +++ b/tests/unit/browser/webengine/test_webenginesettings.py @@ -32,7 +32,8 @@ def init_profiles(qapp, config_stub, cache_tmpdir, data_tmpdir): def test_big_cache_size(config_stub): """Make sure a too big cache size is handled correctly.""" config_stub.val.content.cache.size = 2 ** 63 - 1 - webenginesettings._update_settings('content.cache.size') + profile = webenginesettings.default_profile - size = webenginesettings.default_profile.httpCacheMaximumSize() - assert size == 2 ** 31 - 1 + webenginesettings._set_http_cache_size(profile) + + assert profile.httpCacheMaximumSize() == 2 ** 31 - 1