diff --git a/background/color-scheme.js b/background/color-scheme.js new file mode 100644 index 00000000..832e1c4f --- /dev/null +++ b/background/color-scheme.js @@ -0,0 +1,93 @@ +/* global prefs */ +/* exported colorScheme */ + +'use strict'; + +const colorScheme = (() => { + let systemPreferDark = false; + let timePreferDark = false; + const changeListeners = new Set(); + + const media = window.matchMedia('(prefers-color-scheme: dark)'); + media.addListener(updateSystemPreferDark); + + const checkTime = ['schemeSwitcher.nightStart', 'schemeSwitcher.nightEnd']; + prefs.subscribe(checkTime, (key, value) => { + updateTimePreferDark(); + createAlarm(key, value); + }); + checkTime.forEach(key => createAlarm(key, prefs.get(key))); + + prefs.subscribe(['schemeSwitcher.enabled'], emitChange); + + updateSystemPreferDark(); + updateTimePreferDark(); + + return {shouldIncludeStyle, onChange}; + + function createAlarm(key, value) { + const date = new Date(); + applyDate(date, value); + if (date.getTime() < Date.now()) { + date.setDate(date.getDate() + 1); + } + chrome.alarms.create(key, { + when: date.getTime(), + periodInMinutes: 24 * 60 + }); + } + + function shouldIncludeStyle(style) { + if (style.preferScheme === 'always') { + return true; + } + if (prefs.get('schemeSwitcher.enabled') === 'never') { + return true; + } + if (prefs.get('schemeSwitcher.enabled') === 'system') { + return systemPreferDark && style.preferScheme === 'dark' || + !systemPreferDark && style.preferScheme === 'light'; + } + return timePreferDark && style.preferScheme === 'dark' || + !timePreferDark && style.preferScheme === 'light'; + } + + function updateSystemPreferDark() { + const oldValue = systemPreferDark; + systemPreferDark = media.matches; + if (systemPreferDark !== oldValue) { + emitChange(); + } + } + + function updateTimePreferDark() { + const oldValue = timePreferDark; + const date = new Date(); + const now = date.getTime(); + applyDate(date, prefs.get('schemeSwitcher.nightStart')); + const start = date.getTime(); + applyDate(date, prefs.get('schemeSwitcher.nightEnd')); + const end = date.getTime(); + timePreferDark = start > end ? + now >= start || now < end : + now >= start && now < end; + if (timePreferDark !== oldValue) { + emitChange(); + } + } + + function applyDate(date, time) { + const [h, m] = time.split(':').map(Number); + date.setHours(h, m, 0, 0); + } + + function onChange(listener) { + changeListeners.add(listener); + } + + function emitChange() { + for (const listener of changeListeners) { + listener(); + } + } +})(); diff --git a/background/style-manager.js b/background/style-manager.js index d75d7b3f..d8a68c05 100644 --- a/background/style-manager.js +++ b/background/style-manager.js @@ -1,6 +1,6 @@ /* eslint no-eq-null: 0, eqeqeq: [2, "smart"] */ /* global createCache db calcStyleDigest db tryRegExp styleCodeEmpty - getStyleWithNoCode msg */ + getStyleWithNoCode msg colorScheme */ /* exported styleManager */ 'use strict'; @@ -479,6 +479,9 @@ const styleManager = (() => { if (!style.enabled) { return 'disabled'; } + if (!colorScheme.shouldIncludeStyle(style)) { + return 'excludedScheme'; + } return true; } diff --git a/js/prefs.js b/js/prefs.js index 03cbd31f..f7b84774 100644 --- a/js/prefs.js +++ b/js/prefs.js @@ -14,6 +14,10 @@ const prefs = (() => { // checkbox in style config dialog 'config.autosave': true, + 'schemeSwitcher.enabled': 'never', + 'schemeSwitcher.nightStart': '18:00', + 'schemeSwitcher.nightEnd': '06:00', + 'popup.breadcrumbs': true, // display 'New style' links as URL breadcrumbs 'popup.breadcrumbs.usePath': false, // use URL path for 'this URL' 'popup.enabledFirst': true, // display enabled styles before disabled styles diff --git a/manifest.json b/manifest.json index 385f9663..696f8837 100644 --- a/manifest.json +++ b/manifest.json @@ -37,6 +37,7 @@ "js/cache.js", "background/content-scripts.js", "background/db.js", + "background/color-scheme.js", "background/style-manager.js", "background/navigator-util.js", "background/icon-util.js",