Refactor websettings

This refactors the whole web(kit|engine|) settings mess a bit so there's a
Web(Kit|Engine)Settings object for (non-static) settings set on a
QWeb(Engine)Settings object in Qt. Everything else is set on module-level a bit
less declaratively.

The whole inheritance mess is gone, and we can now also construct a
Web(Kit|Engine)Settings object for a given tab.

Fixes #2701
This commit is contained in:
Florian Bruhin 2018-02-23 09:51:28 +01:00
parent 49ead32f13
commit 3956f81e73
7 changed files with 375 additions and 559 deletions

View File

@ -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()

View File

@ -17,9 +17,6 @@
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
# 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()

View File

@ -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

View File

@ -17,9 +17,6 @@
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
# 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),
}

View File

@ -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

View File

@ -17,230 +17,96 @@
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
# 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):

View File

@ -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