1# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
2# Copyright 2020-2021 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
3
4# This file is part of qutebrowser.
5#
6# qutebrowser is free software: you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation, either version 3 of the License, or
9# (at your option) any later version.
10#
11# qutebrowser is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with qutebrowser.  If not, see <https://www.gnu.org/licenses/>.
18
19import logging
20
21import pytest
22
23from qutebrowser.config import configdata
24from qutebrowser.utils import usertypes, version, utils
25from qutebrowser.browser.webengine import darkmode
26from qutebrowser.misc import objects
27from helpers import testutils
28
29
30@pytest.fixture(autouse=True)
31def patch_backend(monkeypatch):
32    monkeypatch.setattr(objects, 'backend', usertypes.Backend.QtWebEngine)
33
34
35@pytest.fixture
36def gentoo_versions():
37    return version.WebEngineVersions(
38        webengine=utils.VersionNumber(5, 15, 2),
39        chromium='87.0.4280.144',
40        source='faked',
41    )
42
43
44@pytest.mark.parametrize('value, webengine_version, expected', [
45    # Auto
46    ("auto", "5.14", []),
47    ("auto", "5.15.0", []),
48    ("auto", "5.15.1", []),
49    ("auto", "5.15.2", [("preferredColorScheme", "2")]),  # QTBUG-89753
50    ("auto", "5.15.3", []),
51    ("auto", "6.0.0", []),
52
53    # Unset
54    (None, "5.14", []),
55    (None, "5.15.0", []),
56    (None, "5.15.1", []),
57    (None, "5.15.2", [("preferredColorScheme", "2")]),  # QTBUG-89753
58    (None, "5.15.3", []),
59    (None, "6.0.0", []),
60
61    # Dark
62    ("dark", "5.14", []),
63    ("dark", "5.15.0", []),
64    ("dark", "5.15.1", []),
65    ("dark", "5.15.2", [("preferredColorScheme", "1")]),
66    ("dark", "5.15.3", [("preferredColorScheme", "0")]),
67    ("dark", "6.0.0", [("preferredColorScheme", "0")]),
68
69    # Light
70    ("light", "5.14", []),
71    ("light", "5.15.0", []),
72    ("light", "5.15.1", []),
73    ("light", "5.15.2", [("preferredColorScheme", "2")]),
74    ("light", "5.15.3", [("preferredColorScheme", "1")]),
75    ("light", "6.0.0", [("preferredColorScheme", "1")]),
76])
77@testutils.qt514
78def test_colorscheme(config_stub, value, webengine_version, expected):
79    versions = version.WebEngineVersions.from_pyqt(webengine_version)
80    if value is not None:
81        config_stub.val.colors.webpage.preferred_color_scheme = value
82
83    darkmode_settings = darkmode.settings(versions=versions, special_flags=[])
84    assert darkmode_settings['blink-settings'] == expected
85
86
87@testutils.qt514
88def test_colorscheme_gentoo_workaround(config_stub, gentoo_versions):
89    config_stub.val.colors.webpage.preferred_color_scheme = "dark"
90    darkmode_settings = darkmode.settings(versions=gentoo_versions, special_flags=[])
91    assert darkmode_settings['blink-settings'] == [("preferredColorScheme", "0")]
92
93
94@pytest.mark.parametrize('settings, expected', [
95    # Disabled
96    ({}, []),
97
98    # Enabled without customization
99    ({'enabled': True}, [('darkModeEnabled', 'true')]),
100
101    # Algorithm
102    (
103        {'enabled': True, 'algorithm': 'brightness-rgb'},
104        [
105            ('darkModeEnabled', 'true'),
106            ('darkModeInversionAlgorithm', '2')
107        ],
108    ),
109])
110def test_basics(config_stub, settings, expected):
111    for k, v in settings.items():
112        config_stub.set_obj('colors.webpage.darkmode.' + k, v)
113
114    if expected:
115        expected.append(('darkModeImagePolicy', '2'))
116
117    # Using Qt 5.15.1 because it has the least special cases.
118    versions = version.WebEngineVersions.from_pyqt('5.15.1')
119    darkmode_settings = darkmode.settings(versions=versions, special_flags=[])
120    assert darkmode_settings['blink-settings'] == expected
121
122
123QT_514_SETTINGS = {'blink-settings': [
124    ('darkMode', '2'),
125    ('darkModeImagePolicy', '2'),
126    ('darkModeGrayscale', 'true'),
127]}
128
129
130QT_515_0_SETTINGS = {'blink-settings': [
131    ('darkModeEnabled', 'true'),
132    ('darkModeInversionAlgorithm', '2'),
133    ('darkModeGrayscale', 'true'),
134]}
135
136
137QT_515_1_SETTINGS = {'blink-settings': [
138    ('darkModeEnabled', 'true'),
139    ('darkModeInversionAlgorithm', '2'),
140    ('darkModeImagePolicy', '2'),
141    ('darkModeGrayscale', 'true'),
142]}
143
144
145QT_515_2_SETTINGS = {'blink-settings': [
146    ('preferredColorScheme', '2'),  # QTBUG-89753
147    ('forceDarkModeEnabled', 'true'),
148    ('forceDarkModeInversionAlgorithm', '2'),
149    ('forceDarkModeImagePolicy', '2'),
150    ('forceDarkModeGrayscale', 'true'),
151]}
152
153
154QT_515_3_SETTINGS = {
155    'blink-settings': [('forceDarkModeEnabled', 'true')],
156    'dark-mode-settings': [
157        ('InversionAlgorithm', '1'),
158        ('ImagePolicy', '2'),
159        ('IsGrayScale', 'true'),
160    ],
161}
162
163
164@pytest.mark.parametrize('qversion, expected', [
165    ('5.14.0', QT_514_SETTINGS),
166    ('5.14.1', QT_514_SETTINGS),
167    ('5.14.2', QT_514_SETTINGS),
168
169    ('5.15.0', QT_515_0_SETTINGS),
170    ('5.15.1', QT_515_1_SETTINGS),
171
172    ('5.15.2', QT_515_2_SETTINGS),
173    ('5.15.3', QT_515_3_SETTINGS),
174])
175def test_qt_version_differences(config_stub, qversion, expected):
176    settings = {
177        'enabled': True,
178        'algorithm': 'brightness-rgb',
179        'grayscale.all': True,
180    }
181    for k, v in settings.items():
182        config_stub.set_obj('colors.webpage.darkmode.' + k, v)
183
184    versions = version.WebEngineVersions.from_pyqt(qversion)
185    darkmode_settings = darkmode.settings(versions=versions, special_flags=[])
186    assert darkmode_settings == expected
187
188
189@testutils.qt514
190@pytest.mark.parametrize('setting, value, exp_key, exp_val', [
191    ('contrast', -0.5,
192     'Contrast', '-0.5'),
193    ('policy.page', 'smart',
194     'PagePolicy', '1'),
195    ('policy.images', 'smart',
196     'ImagePolicy', '2'),
197    ('threshold.text', 100,
198     'TextBrightnessThreshold', '100'),
199    ('threshold.background', 100,
200     'BackgroundBrightnessThreshold', '100'),
201    ('grayscale.all', True,
202     'Grayscale', 'true'),
203    ('grayscale.images', 0.5,
204     'ImageGrayscale', '0.5'),
205])
206def test_customization(config_stub, setting, value, exp_key, exp_val):
207    config_stub.val.colors.webpage.darkmode.enabled = True
208    config_stub.set_obj('colors.webpage.darkmode.' + setting, value)
209
210    expected = []
211    expected.append(('darkModeEnabled', 'true'))
212    if exp_key != 'ImagePolicy':
213        expected.append(('darkModeImagePolicy', '2'))
214    expected.append(('darkMode' + exp_key, exp_val))
215
216    versions = version.WebEngineVersions.from_pyqt('5.15.1')
217    darkmode_settings = darkmode.settings(versions=versions, special_flags=[])
218    assert darkmode_settings['blink-settings'] == expected
219
220
221@pytest.mark.parametrize('webengine_version, expected', [
222    ('5.13.0', darkmode.Variant.qt_511_to_513),
223    ('5.14.0', darkmode.Variant.qt_514),
224    ('5.15.0', darkmode.Variant.qt_515_0),
225    ('5.15.1', darkmode.Variant.qt_515_1),
226    ('5.15.2', darkmode.Variant.qt_515_2),
227    ('5.15.3', darkmode.Variant.qt_515_3),
228    ('6.0.0', darkmode.Variant.qt_515_3),
229])
230def test_variant(webengine_version, expected):
231    versions = version.WebEngineVersions.from_pyqt(webengine_version)
232    assert darkmode._variant(versions) == expected
233
234
235def test_variant_gentoo_workaround(gentoo_versions):
236    assert darkmode._variant(gentoo_versions) == darkmode.Variant.qt_515_3
237
238
239@pytest.mark.parametrize('value, is_valid, expected', [
240    ('invalid_value', False, darkmode.Variant.qt_515_0),
241    ('qt_515_2', True, darkmode.Variant.qt_515_2),
242])
243def test_variant_override(monkeypatch, caplog, value, is_valid, expected):
244    versions = version.WebEngineVersions.from_pyqt('5.15.0')
245    monkeypatch.setenv('QUTE_DARKMODE_VARIANT', value)
246
247    with caplog.at_level(logging.WARNING):
248        assert darkmode._variant(versions) == expected
249
250    log_msg = 'Ignoring invalid QUTE_DARKMODE_VARIANT=invalid_value'
251    assert (log_msg in caplog.messages) != is_valid
252
253
254def test_broken_smart_images_policy(config_stub, caplog):
255    config_stub.val.colors.webpage.darkmode.enabled = True
256    config_stub.val.colors.webpage.darkmode.policy.images = 'smart'
257    versions = version.WebEngineVersions.from_pyqt('5.15.0')
258
259    with caplog.at_level(logging.WARNING):
260        darkmode_settings = darkmode.settings(versions=versions, special_flags=[])
261
262    assert caplog.messages[-1] == (
263        'Ignoring colors.webpage.darkmode.policy.images = smart because of '
264        'Qt 5.15.0 bug')
265
266    expected = [
267        [('darkModeEnabled', 'true')],  # Qt 5.15
268        [('darkMode', '4')],  # Qt 5.14
269    ]
270    assert darkmode_settings['blink-settings'] in expected
271
272
273@pytest.mark.parametrize('flag, expected', [
274    ('--blink-settings=key=value', [('key', 'value')]),
275    ('--blink-settings=key=equal=rights', [('key', 'equal=rights')]),
276    ('--blink-settings=one=1,two=2', [('one', '1'), ('two', '2')]),
277    ('--enable-features=feat', []),
278])
279def test_pass_through_existing_settings(config_stub, flag, expected):
280    config_stub.val.colors.webpage.darkmode.enabled = True
281    versions = version.WebEngineVersions.from_pyqt('5.15.1')
282    settings = darkmode.settings(versions=versions, special_flags=[flag])
283
284    dark_mode_expected = [
285        ('darkModeEnabled', 'true'),
286        ('darkModeImagePolicy', '2'),
287    ]
288    assert settings['blink-settings'] == expected + dark_mode_expected
289
290
291def test_options(configdata_init):
292    """Make sure all darkmode options have the right attributes set."""
293    for name, opt in configdata.DATA.items():
294        if not name.startswith('colors.webpage.darkmode.'):
295            continue
296
297        assert not opt.supports_pattern, name
298        assert opt.restart, name
299
300        if opt.backends:
301            # On older Qt versions, this is an empty list.
302            assert opt.backends == [usertypes.Backend.QtWebEngine], name
303
304        if opt.raw_backends is not None:
305            assert not opt.raw_backends['QtWebKit'], name
306            assert opt.raw_backends['QtWebEngine'] == 'Qt 5.14', name
307