Require a filename for user-stylesheet; add hide-scrollbar setting

This commit is contained in:
Florian Bruhin 2016-11-15 22:58:56 +01:00
parent 964ddb472b
commit fcb955458c
8 changed files with 52 additions and 77 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -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), '',

View File

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

View File

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

View File

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