Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement fundamentals of clever dark mode #50

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions src/background/modules/ActionButton.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,5 +54,3 @@ export function init() {
color: BADGE_BACKGROUND_COLOR
});
}


101 changes: 96 additions & 5 deletions src/common/modules/DarkModeLogic.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import * as AddonSettings from "/common/modules/AddonSettings/AddonSettings.js";
import { isControllable } from "/common/modules/BrowserSettings/BrowserSettings.js";

const darkColorSchemeMediaQuery = "(prefers-color-scheme: dark)";
let isListening = false;

/**
* A simplified API callback providing the result of the about:config setting.
*
Expand Down Expand Up @@ -40,7 +43,8 @@ export async function toggleDarkMode() {
const darkModeVariant = await AddonSettings.get("darkModeVariant");
const lightModeVariant = await AddonSettings.get("lightModeVariant");

const currentBrowserSetting = await getCurrentState();
let currentBrowserSetting = await getCurrentState();

let newBrowserSetting = "";
if (currentBrowserSetting === COLOR_OVERRIDE.DARK ||
currentBrowserSetting === darkModeVariant) {
Expand All @@ -52,9 +56,30 @@ export async function toggleDarkMode() {
await applySetting(newBrowserSetting);
}

/**
* Return the currently used design.
*
* @returns {string}
*/
export async function getCurrentDesign() {
// need to temporarily clean override to get "real" value
const oldOverride = await getCurrentBrowserOverride();
applySetting(null);
const isDarkMode = window.matchMedia(darkColorSchemeMediaQuery).matches;
applySetting(oldOverride);

if (isDarkMode) {
return COLOR_OVERRIDE.DARK;
} else {
return COLOR_OVERRIDE.LIGHT;
}
}

/**
* Return the currently used option.
*
* If the clever mode is enabled, this returns the current design.
*
* @returns {Promise<string>}
*/
export async function getCurrentState() {
Expand All @@ -69,7 +94,22 @@ export async function getCurrentState() {
// }
// }

// reload the setting from browser again as that is the safer value to rely on
const cleverDarkMode = await AddonSettings.get("cleverDarkMode");

if (cleverDarkMode) {
return getCurrentDesign();
} else {
// reload the setting from browser again as that is the safer value to rely on
return getCurrentBrowserOverride();
}
}

/**
* Return the currently used browser override (by this addon).
*
* @returns {Promise<string>}
*/
export async function getCurrentBrowserOverride() {
return (await browser.browserSettings.overrideContentColorScheme.get({})).value;
}

Expand All @@ -87,9 +127,12 @@ export async function applySetting(newOption) {
console.log("current browser setting for overrideContentColorScheme:", currentBrowserSetting);

if (!isControllable(currentBrowserSetting.levelOfControl)) {
throw Error("Browser setting is not controllable.");
throw new Error("Browser setting is not controllable.");
}

// temporarily unregister listeners to prevend endless loop
unregisterBrowserListener();

let couldBeModified;
if (newOption === "null" || newOption === null) {
couldBeModified = await browser.browserSettings.overrideContentColorScheme.clear({});
Expand All @@ -99,11 +142,20 @@ export async function applySetting(newOption) {
});
}

// re-register after some time
setTimeout(() => {
registerBrowserListener();
}, 1000);

if (!couldBeModified) {
throw Error("Browser setting could not be modified.");
}

await AddonSettings.set("prefersColorSchemeOverride", newOption);
const cleverDarkMode = await AddonSettings.get("cleverDarkMode");
// do not save setting if interactive clever dark mode is used
if (!cleverDarkMode) {
await AddonSettings.set("prefersColorSchemeOverride", newOption);
}
}

/**
Expand All @@ -129,6 +181,19 @@ function internalTriggerHandler(details) {
onChangeCallbacks.forEach((callback) => callback(currentValue));
}

/**
* Handle any change to the browser setting value and trigger registered callbacks.
*
* @private
* @param {MediaQueryList} mediaQueryList
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/MediaQueryList/addListener}
* @returns {void}
*/
async function internalTriggerHandlerForVisibilityChange(mediaQueryList) {
const currentValue = await getCurrentDesign();
onChangeCallbacks.forEach((callback) => callback(currentValue));
}

/**
* Register the internal handler ({@link internalTriggerHandler)}) for handling changes to the browser setting.
*
Expand All @@ -137,7 +202,23 @@ function internalTriggerHandler(details) {
* @returns {void}
*/
function registerBrowserListener() {
browser.browserSettings.overrideContentColorScheme.onChange.addListener(internalTriggerHandler);
if (isListening) {
console.warn("Dark mode listener is listening already, no need to register.");
return;
}

const cleverDarkMode = AddonSettings.get("cleverDarkMode");

if (cleverDarkMode) {
// TODO: switch to new EventListener API
// https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener
const darkModeQueryList = window.matchMedia(darkColorSchemeMediaQuery);
darkModeQueryList.addListener(internalTriggerHandlerForVisibilityChange);
} else {
browser.browserSettings.overrideContentColorScheme.onChange.addListener(internalTriggerHandler);
}

isListening = true;
}

/**
Expand All @@ -148,7 +229,17 @@ function registerBrowserListener() {
* @returns {void}
*/
function unregisterBrowserListener() {
if (!isListening) {
console.warn("Dark mode listener is not listening already, no need to unregister.");
return;
}

browser.browserSettings.overrideContentColorScheme.onChange.removeListener(internalTriggerHandler);

const darkModeQueryList = window.matchMedia(darkColorSchemeMediaQuery);
darkModeQueryList.removeListener(internalTriggerHandlerForVisibilityChange);

isListening = false;
}

// automatically init itself as fast as possible
Expand Down
2 changes: 1 addition & 1 deletion src/options/modules/CustomOptionTriggers.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ function applyPrefersColorSchemeOverride(optionValue) {
/**
* Apply the setting and show it as the used one.
*
* @param {string} currentSetting The settting to show as the currently selected one.
* @param {string} currentSetting The setting to show as the currently selected one.
* @returns {void}
*/
function applySetting(currentSetting) {
Expand Down
6 changes: 3 additions & 3 deletions src/options/options.html
Original file line number Diff line number Diff line change
Expand Up @@ -68,15 +68,15 @@ <h1 data-i18n="__MSG_titleNehaviour__">Behaviour</h1>
</div>
</p>
<ul>
<!-- <li>
<li>
<div class="line">
<input class="setting save-on-change" type="checkbox" id="cleverDarkMode" name="cleverDarkMode">
<label data-i18n="__MSG_optionCleverDarkMode__" for="cleverDarkMode">Clever dark mode</label>
</div>
<div class="line indent">
<span class="helper-text" data-i18n="__MSG_optionCleverDarkModeDescr__">Always toggle the current dark mode in the context of the current browser theme.</span>
<span class="helper-text" data-i18n="__MSG_optionCleverDarkModeDescr__">Shows the current style in the toolbar and toggles the current dark mode depending on the current website style.</span>
</div>
</li> -->
</li>
<li class="line">
<label for="prefersColorSchemeOverride" data-i18n="__MSG_optionsWebsiteStyle__">Website style: </label>
</li>
Expand Down