Okay I'm using this package for reliable (apparently) incognito mode detection : https://github.com/Joe12387/detectIncognito, and
window.matchMedia('(prefers-color-scheme: dark)');
For the dark mode.
In the html I have :
<link class="favicon" rel="icon" href="/icons/favicon.ico" />
<link class="favicon" rel="apple-touch-icon" sizes="57x57" href="/icons/apple-icon-57x57.png" />
<link class="favicon" rel="apple-touch-icon" sizes="60x60" href="/icons/apple-icon-60x60.png" />
<link class="favicon" rel="apple-touch-icon" sizes="72x72" href="/icons/apple-icon-72x72.png" />
(notice the class)
Then in a js file :
import { detectIncognito } from 'detectincognitojs';
const matcher = window.matchMedia('(prefers-color-scheme: dark)');
class FaviconService {
isIncognitoMode() {
return detectIncognito();
}
isDarkMode() {
return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
}
async isIncognitoOrDarkMode() {
return Promise.all([this.isDarkMode(), this.isIncognitoMode()]).then(function ([
darkMode,
incognitoModeResult,
]) {
return darkMode || incognitoModeResult.isPrivate;
});
}
getFaviconLinks() {
return document.getElementsByClassName('favicon');
}
setFaviconsForLightMode() {
const links = this.getFaviconLinks();
for (const link of links) {
link.href = link.href.replace('/dark-icons/', '/icons/');
}
}
setFaviconsForDarkMode() {
const links = this.getFaviconLinks();
for (const link of links) {
link.href = link.href.replace('/icons/', '/dark-icons/');
}
}
async updateFavicons() {
const ret = await this.isIncognitoOrDarkMode();
if (ret) {
this.setFaviconsForDarkMode();
} else {
this.setFaviconsForLightMode();
}
}
setup() {
matcher.addEventListener('change', () => {
this.updateFavicons();
});
}
}
export default new FaviconService();
Then where I init my things (depending on the framework)
FaviconService.setup();
FaviconService.updateFavicons();
It works great. Tested on chrome, firefox, edge.