diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index e15136487..8dc7caa3a 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -50,6 +50,8 @@ Added - New `cast` userscript to show a video on a Google Chromecast - New `:run-with-count` command which replaces the (undocumented) `:count:command` syntax. - New `:record-macro` (`q`) and `:run-macro` (`@`) commands for keyboard macros. +- New `ui -> hide-scrollbar` setting to hide the scrollbar independently of the + `user-stylesheet` setting. Changed ~~~~~~~ @@ -143,6 +145,7 @@ Changed - Various functionality now works when javascript is disabled with QtWebKit - Various commands/settings taking `left`/`right`/`previous` arguments now take `prev`/`next`/`last-used` to remove ambiguity. +- The `ui -> user-stylesheet` setting now only takes filenames, not CSS snippets Deprecated ~~~~~~~~~~ diff --git a/qutebrowser/browser/shared.py b/qutebrowser/browser/shared.py index ea1597ee5..5db2d9fa0 100644 --- a/qutebrowser/browser/shared.py +++ b/qutebrowser/browser/shared.py @@ -222,3 +222,19 @@ def get_tab(win_id, target): tabbed_browser = objreg.get('tabbed-browser', scope='window', window=win_id) return tabbed_browser.tabopen(url=None, background=bg_tab) + + +def get_user_stylesheet(): + """Get the combined user-stylesheet.""" + filename = config.get('ui', 'user-stylesheet') + + if filename is None: + css = '' + else: + with open(filename, 'r', encoding='utf-8') as f: + css = f.read() + + if config.get('ui', 'hide-scrollbar'): + css += '\nhtml > ::-webkit-scrollbar { width: 0px; height: 0px; }' + + return css diff --git a/qutebrowser/browser/webkit/webkitsettings.py b/qutebrowser/browser/webkit/webkitsettings.py index 86505162f..800206c5a 100644 --- a/qutebrowser/browser/webkit/webkitsettings.py +++ b/qutebrowser/browser/webkit/webkitsettings.py @@ -26,10 +26,12 @@ Module attributes: import os.path +from PyQt5.QtCore import QUrl from PyQt5.QtWebKit import QWebSettings from qutebrowser.config import config, websettings -from qutebrowser.utils import standarddir, objreg +from qutebrowser.utils import standarddir, objreg, urlutils +from qutebrowser.browser import shared class Attribute(websettings.Attribute): @@ -80,14 +82,24 @@ class CookiePolicy(websettings.Base): self.MAPPING[value]) +def _set_user_stylesheet(): + """Set the generated user-stylesheet.""" + stylesheet = shared.get_user_stylesheet().encode('utf-8') + url = urlutils.data_url('text/css;charset=utf-8', stylesheet) + QWebSettings.globalSettings().setUserStyleSheetUrl(url) + + def update_settings(section, option): """Update global settings when qwebsettings changed.""" - cache_path = standarddir.cache() if (section, option) == ('general', 'private-browsing'): + cache_path = standarddir.cache() if config.get('general', 'private-browsing') or cache_path is None: QWebSettings.setIconDatabasePath('') else: QWebSettings.setIconDatabasePath(cache_path) + elif section == 'ui' and option in ['hide-scrollbar', 'user-stylesheet']: + _set_user_stylesheet() + websettings.update_mappings(MAPPINGS, section, option) @@ -108,6 +120,7 @@ def init(): os.path.join(data_path, 'offline-storage')) websettings.init_mappings(MAPPINGS) + _set_user_stylesheet() objreg.get('config').changed.connect(update_settings) @@ -204,9 +217,7 @@ MAPPINGS = { Attribute(QWebSettings.ZoomTextOnly), 'frame-flattening': Attribute(QWebSettings.FrameFlatteningEnabled), - 'user-stylesheet': - Setter(getter=QWebSettings.userStyleSheetUrl, - setter=QWebSettings.setUserStyleSheetUrl), + # user-stylesheet is handled separately 'css-media-type': NullStringSetter(getter=QWebSettings.cssMediaType, setter=QWebSettings.setCSSMediaType), diff --git a/qutebrowser/config/config.py b/qutebrowser/config/config.py index 38d8aa6eb..57c498400 100644 --- a/qutebrowser/config/config.py +++ b/qutebrowser/config/config.py @@ -437,6 +437,11 @@ class ConfigManager(QObject): ('fonts', 'hints'): _transform_hint_font, ('completion', 'show'): _get_value_transformer({'false': 'never', 'true': 'always'}), + ('ui', 'user-stylesheet'): + _get_value_transformer({ + 'html > ::-webkit-scrollbar { width: 0px; height: 0px; }': '', + '::-webkit-scrollbar { width: 0px; height: 0px; }': '', + }), } changed = pyqtSignal(str, str) diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index abcb5e226..ebc89145c 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -323,13 +323,15 @@ def data(readonly=False): "page."), ('user-stylesheet', - SettingValue(typ.UserStyleSheet(none_ok=True), - 'html > ::-webkit-scrollbar { width: 0px; ' - 'height: 0px; }', + SettingValue(typ.File(none_ok=True), '', backends=[usertypes.Backend.QtWebKit]), - "User stylesheet to use (absolute filename, filename relative to " - "the config directory or CSS string). Will expand environment " - "variables."), + "User stylesheet to use (absolute filename or filename relative to " + "the config directory). Will expand environment variables."), + + ('hide-scrollbar', + SettingValue(typ.Bool(), 'true', + backends=[usertypes.Backend.QtWebKit]), + "Hide the main scrollbar."), ('css-media-type', SettingValue(typ.String(none_ok=True), '', diff --git a/qutebrowser/config/configtypes.py b/qutebrowser/config/configtypes.py index d4e8a3514..b5aa8798a 100644 --- a/qutebrowser/config/configtypes.py +++ b/qutebrowser/config/configtypes.py @@ -1165,40 +1165,6 @@ class Encoding(BaseType): raise configexc.ValidationError(value, "is not a valid encoding!") -class UserStyleSheet(File): - - """QWebSettings UserStyleSheet.""" - - def transform(self, value): - if not value: - return None - - path = super().transform(value) - if path is not None and os.path.exists(path): - return QUrl.fromLocalFile(path) - else: - return urlutils.data_url('text/css', value.encode('utf-8')) - - def validate(self, value): - self._basic_validation(value) - if not value: - return - value = os.path.expandvars(value) - value = os.path.expanduser(value) - try: - super().validate(value) - except configexc.ValidationError: - try: - if not os.path.isabs(value): - # probably a CSS, so we don't handle it as filename. - # FIXME We just try if it is encodable, maybe we should - # validate CSS? - # https://github.com/The-Compiler/qutebrowser/issues/115 - value.encode('utf-8') - except UnicodeEncodeError as e: - raise configexc.ValidationError(value, str(e)) - - class AutoSearch(BaseType): """Whether to start a search when something else than a URL is entered.""" diff --git a/tests/unit/config/test_configtypes.py b/tests/unit/config/test_configtypes.py index 2f0c3ce7a..ec52570e6 100644 --- a/tests/unit/config/test_configtypes.py +++ b/tests/unit/config/test_configtypes.py @@ -1211,15 +1211,9 @@ def unrequired_class(**kwargs): @pytest.mark.usefixtures('qapp') @pytest.mark.usefixtures('config_tmpdir') -class TestFileAndUserStyleSheet: +class TestFile: - """Test File/UserStyleSheet.""" - - @pytest.fixture(params=[ - configtypes.File, - configtypes.UserStyleSheet, - unrequired_class, - ]) + @pytest.fixture(params=[configtypes.File, unrequired_class]) def klass(self, request): return request.param @@ -1227,18 +1221,12 @@ class TestFileAndUserStyleSheet: def file_class(self): return configtypes.File - @pytest.fixture - def userstylesheet_class(self): - return configtypes.UserStyleSheet - def _expected(self, klass, arg): """Get the expected value.""" if not arg: return None elif klass is configtypes.File: return arg - elif klass is configtypes.UserStyleSheet: - return QUrl.fromLocalFile(arg) elif klass is unrequired_class: return arg else: @@ -1262,11 +1250,6 @@ class TestFileAndUserStyleSheet: os_mock.path.isfile.return_value = False configtypes.File(required=False).validate('foobar') - def test_validate_does_not_exist_userstylesheet(self, os_mock): - """Test validate with a file which does not exist (UserStyleSheet).""" - os_mock.path.isfile.return_value = False - configtypes.UserStyleSheet().validate('foobar') - def test_validate_exists_abs(self, klass, os_mock): """Test validate with a file which does exist.""" os_mock.path.isfile.return_value = True @@ -1286,11 +1269,8 @@ class TestFileAndUserStyleSheet: @pytest.mark.parametrize('configtype, value, raises', [ (configtypes.File(), 'foobar', True), - (configtypes.UserStyleSheet(), 'foobar', False), - (configtypes.UserStyleSheet(), '\ud800', True), (configtypes.File(required=False), 'foobar', False), - ], ids=['file-foobar', 'userstylesheet-foobar', 'userstylesheet-unicode', - 'file-optional-foobar']) + ], ids=['file-foobar', 'file-optional-foobar']) def test_validate_rel_inexistent(self, os_mock, monkeypatch, configtype, value, raises): """Test with a relative path and standarddir.config returning None.""" @@ -1339,7 +1319,6 @@ class TestFileAndUserStyleSheet: def test_transform_relative(self, klass, os_mock, monkeypatch): """Test transform() with relative dir and an available configdir.""" - os_mock.path.exists.return_value = True # for TestUserStyleSheet os_mock.path.isabs.return_value = False monkeypatch.setattr( 'qutebrowser.config.configtypes.standarddir.config', @@ -1347,12 +1326,6 @@ class TestFileAndUserStyleSheet: expected = self._expected(klass, '/configdir/foo') assert klass().transform('foo') == expected - def test_transform_userstylesheet_base64(self, monkeypatch): - """Test transform with a data string.""" - b64 = base64.b64encode(b"test").decode('ascii') - url = QUrl("data:text/css;base64,{}".format(b64)) - assert configtypes.UserStyleSheet().transform("test") == url - class TestDirectory: diff --git a/tests/unit/config/test_configtypes_hypothesis.py b/tests/unit/config/test_configtypes_hypothesis.py index 329d7357c..ed45ccc0b 100644 --- a/tests/unit/config/test_configtypes_hypothesis.py +++ b/tests/unit/config/test_configtypes_hypothesis.py @@ -50,8 +50,7 @@ def gen_classes(): @hypothesis.given(strategies.text()) @hypothesis.example('\x00') def test_configtypes_hypothesis(klass, s): - if (klass in [configtypes.File, configtypes.UserStyleSheet] and - sys.platform == 'linux' and + if (klass == configtypes.File and sys.platform == 'linux' and not os.environ.get('DISPLAY', '')): pytest.skip("No DISPLAY available")