Repository: darkreader/darkreader
Pull request #877 (2018-12-02 15:21:52)

fixing hdgo.cc player controls


Patch:
diff --git a/src/_locales/be.config b/src/_locales/be.config
index b4d88648..ae794ffa 100644
--- a/src/_locales/be.config
+++ b/src/_locales/be.config
@@ -4,6 +4,7 @@
 @loading_please_wait
 Ідзе загрузка, пачакайце
 
+
 #==== Top section ====
 
 @on
@@ -12,9 +13,6 @@
 @off
 Выкл.
 
-@toggle
-Перакл.
-
 @toggle_current_site
 Пераключыць гэты сайт
 
@@ -29,6 +27,12 @@
 Уст. спалучэнне
 клавіш
 
+@time_settings
+Налады часу
+
+@set_active_hours
+Усталюйце перыяд актыўнасці
+
 @page_protected
 Гэтая старонка ахоўваецца
 браўзэрам
diff --git a/src/_locales/cs.config b/src/_locales/cs.config
index ba008f4d..a9e06bfd 100644
--- a/src/_locales/cs.config
+++ b/src/_locales/cs.config
@@ -4,6 +4,7 @@ Tmavý motiv pro každou stránku. Používejte Dark Reader pro celodenní prohl
 @loading_please_wait
 Načítání, prosím čekejte
 
+
 #==== Top section ====
 
 @on
@@ -12,9 +13,6 @@ Zap
 @off
 Vyp
 
-@toggle
-Přepnout
-
 @toggle_current_site
 Přepnout současnou stránku
 
@@ -29,6 +27,12 @@ Přepnout rozšíření
 Nastavit klávesovou
 pro přepínání rozšíř.
 
+@time_settings
+Nastavení času
+
+@set_active_hours
+Změňte doby aktivního používání
+
 @page_protected
 Tato stránka je chráněna
 prohlížečem
diff --git a/src/_locales/de.config b/src/_locales/de.config
index 8d7570dc..ed165ee3 100644
--- a/src/_locales/de.config
+++ b/src/_locales/de.config
@@ -4,6 +4,7 @@ Dunkles Design für jede Website. Schonen Sie Ihre Augen und verwenden Sie Dark
 @loading_please_wait
 Laden, bitte warten
 
+
 #==== Top section ====
 
 @on
@@ -12,9 +13,6 @@ Ein
 @off
 Aus
 
-@toggle
-↻
-
 @toggle_current_site
 Aktuelle Seite umschalten
 
@@ -29,6 +27,12 @@ Erweiterung
 Hotkey zum (de)aktivieren
 der Erweiterung
 
+@time_settings
+Zeit Einstellungen
+
+@set_active_hours
+Ändern die Nutzungszeit
+
 @page_protected
 Diese Seite ist durch
 den Browser geschützt
diff --git a/src/_locales/en.config b/src/_locales/en.config
index fc3aca06..bf2712b3 100644
--- a/src/_locales/en.config
+++ b/src/_locales/en.config
@@ -4,6 +4,7 @@ Dark theme for every website. Care your eyes, use Dark Reader for night and dail
 @loading_please_wait
 Loading, please wait
 
+
 #==== Top section ====
 
 @on
@@ -12,9 +13,6 @@ On
 @off
 Off
 
-@toggle
-Toggle
-
 @toggle_current_site
 Toggle current site
 
@@ -29,6 +27,12 @@ Toggle extension
 Setup extension
 toggle hotkey
 
+@time_settings
+Time settings
+
+@set_active_hours
+Set active hours
+
 @page_protected
 This page is protected
 by browser
diff --git a/src/_locales/en_GB.config b/src/_locales/en_GB.config
index 826dadad..6bf753ad 100644
--- a/src/_locales/en_GB.config
+++ b/src/_locales/en_GB.config
@@ -4,6 +4,7 @@ Dark theme for every website. Care your eyes, use Dark Reader for night and dail
 @loading_please_wait
 Loading, please wait
 
+
 #==== Top section ====
 
 @on
@@ -12,9 +13,6 @@ On
 @off
 Off
 
-@toggle
-Toggle
-
 @toggle_current_site
 Toggle current site
 
@@ -29,6 +27,12 @@ Toggle extension
 Setup extension
 toggle hotkey
 
+@time_settings
+Time settings
+
+@set_active_hours
+Set active hours
+
 @page_protected
 This page is protected
 by browser
diff --git a/src/_locales/en_US.config b/src/_locales/en_US.config
index fc3aca06..bf2712b3 100644
--- a/src/_locales/en_US.config
+++ b/src/_locales/en_US.config
@@ -4,6 +4,7 @@ Dark theme for every website. Care your eyes, use Dark Reader for night and dail
 @loading_please_wait
 Loading, please wait
 
+
 #==== Top section ====
 
 @on
@@ -12,9 +13,6 @@ On
 @off
 Off
 
-@toggle
-Toggle
-
 @toggle_current_site
 Toggle current site
 
@@ -29,6 +27,12 @@ Toggle extension
 Setup extension
 toggle hotkey
 
+@time_settings
+Time settings
+
+@set_active_hours
+Set active hours
+
 @page_protected
 This page is protected
 by browser
diff --git a/src/_locales/es.config b/src/_locales/es.config
index f24ebe7b..a28f1765 100644
--- a/src/_locales/es.config
+++ b/src/_locales/es.config
@@ -4,6 +4,7 @@ Tema oscuro para todos los sitios. Cuide sus ojos, utilice Dark Reader para nave
 @loading_please_wait
 Cargando, espere por favor
 
+
 #==== Top section ====
 
 @on
@@ -12,9 +13,6 @@ Sí
 @off
 No
 
-@toggle
-Alternar
-
 @toggle_current_site
 Alternar sitio actual
 
@@ -29,6 +27,12 @@ Alternar extensión
 Configurar extensión
 Tecla de atajo
 
+@time_settings
+Ajustes de hora
+
+@set_active_hours
+Establece horas activas
+
 @page_protected
 Esta página está protegida
 por el navegador
diff --git a/src/_locales/fr.config b/src/_locales/fr.config
index 22a545ab..003a9d40 100644
--- a/src/_locales/fr.config
+++ b/src/_locales/fr.config
@@ -4,6 +4,7 @@ Thème sombre pour chaque site Web. Prenez soin de vos yeux, utilisez Dark Reade
 @loading_please_wait
 Chargement, veuillez patienter
 
+
 #==== Top section ====
 
 @on
@@ -12,9 +13,6 @@ Marche
 @off
 Arrêt
 
-@toggle
-Basculer
-
 @toggle_current_site
 Basculer le site actuel
 
@@ -29,6 +27,12 @@ l'extension bascule
 Configuration de l'extension
 Touche de raccourci bascule
 
+@time_settings
+Paramètres de temps
+
+@set_active_hours
+Définissez les heures d’activité
+
 @page_protected
 Cette page est protégée
 par un navigateur
diff --git a/src/_locales/hi.config b/src/_locales/hi.config
index 6634cd6d..e427c3eb 100644
--- a/src/_locales/hi.config
+++ b/src/_locales/hi.config
@@ -4,6 +4,7 @@
 @loading_please_wait
 लोड हो रहा है, कृपया प्रतीक्षा करें
 
+
 #==== Top section ====
 
 @on
@@ -12,9 +13,6 @@
 @off
 बंद करें
 
-@toggle
-टॉगल
-
 @toggle_current_site
 वर्तमान साइट टॉगल करें
 
@@ -29,6 +27,12 @@
 सेटअप एक्सटेंशन
 टॉगल हॉटकी
 
+@time_settings
+समय सैट करना
+
+@set_active_hours
+गतिविधि का समय बदलें
+
 @page_protected
 यह पृष्ठ ब्राउज़र
 द्वारा संरक्षित है
diff --git a/src/_locales/id.config b/src/_locales/id.config
index ddcc45eb..beaebc08 100644
--- a/src/_locales/id.config
+++ b/src/_locales/id.config
@@ -4,6 +4,7 @@ Mode Malam untuk semua website. Jaga kesehatan mata and dengan menggunakan Dark
 @loading_please_wait
 Sedang Memuat, harap menunggu
 
+
 #==== Top section ====
 
 @on
@@ -12,9 +13,6 @@ On
 @off
 Off
 
-@toggle
-Saklar
-
 @toggle_current_site
 Saklar situs yang terbuka
 
@@ -29,6 +27,12 @@ Saklar ekstensi
 Setelan ekstensi
 Saklar Pintasan
 
+@time_settings
+Pengaturan waktu
+
+@set_active_hours
+Ubah waktu aktivitas
+
 @page_protected
 Halaman ini dilindungi
 oleh browser
diff --git a/src/_locales/it.config b/src/_locales/it.config
index e3869f18..95a16760 100644
--- a/src/_locales/it.config
+++ b/src/_locales/it.config
@@ -4,6 +4,7 @@ Tema scuro per ogni sito. Prenditi cura dei tuoi occhi, usa Dark Reader per navi
 @loading_please_wait
 Sto caricando, attendere prego
 
+
 #==== Top section ====
 
 @on
@@ -12,9 +13,6 @@ Accesa
 @off
 Spenta
 
-@toggle
-Inverti
-
 @toggle_current_site
 Inverti il sito attuale
 
@@ -29,6 +27,12 @@ per il sito attuale
 Tasti di scelta rapida
 dell'estensione
 
+@time_settings
+Impostazioni del tempo
+
+@set_active_hours
+Imposta l'orario di attività
+
 @page_protected
 Questa pagina è protetta
 dal browser
diff --git a/src/_locales/ja.config b/src/_locales/ja.config
index 6b98518f..3864b1d7 100644
--- a/src/_locales/ja.config
+++ b/src/_locales/ja.config
@@ -4,6 +4,7 @@
 @loading_please_wait
 読み込み中…しばらくお待ちください
 
+
 #==== Top section ====
 
 @on
@@ -12,9 +13,6 @@
 @off
 オフ
 
-@toggle
-切り替え
-
 @toggle_current_site
 現在のサイトを切り替える
 
@@ -29,6 +27,12 @@
 セットアップ拡張
 トグルホットキー
 
+@time_settings
+時間設定
+
+@set_active_hours
+アクティブ時間を設定して
+
 @page_protected
 このページはブラウザに
 よって保護されています
diff --git a/src/_locales/ko.config b/src/_locales/ko.config
index fe461ef3..b02bce9e 100644
--- a/src/_locales/ko.config
+++ b/src/_locales/ko.config
@@ -4,6 +4,7 @@
 @loading_please_wait
 로드하는 동안 기다려주십시오. 잠시 기다려주십시오.
 
+
 #==== Top section ====
 
 @on
@@ -12,9 +13,6 @@
 @off
 닫기
 
-@toggle
-스위치
-
 @toggle_current_site
 현재 웹 사이트 전환
 
@@ -29,6 +27,12 @@
 확장 기능 설정하기
 바로 가기 전환
 
+@time_settings
+시간 설정
+
+@set_active_hours
+사용 시간을 설정하
+
 @page_protected
 이 페이지는
 브라우저 보호
diff --git a/src/_locales/no.config b/src/_locales/no.config
index e4827317..83a06d7e 100644
--- a/src/_locales/no.config
+++ b/src/_locales/no.config
@@ -4,6 +4,7 @@ Mørkt tema for alle nettsteder. Ta vare på øynene, bruk Dark Reader for nettl
 @loading_please_wait
 Laster, vennligst vent
 
+
 #==== Top section ====
 
 @on
@@ -12,9 +13,6 @@ På
 @off
 Av
 
-@toggle
-Inverter
-
 @toggle_current_site
 Inverter denne siden
 
@@ -29,6 +27,12 @@ Slå utvidelsen på/av
 Sett opp hurtigtast for
 å slå utvidelsen på/av
 
+@time_settings
+Tidsinnstillinger
+
+@set_active_hours
+Angi aktivitetsperiode
+
 @page_protected
 Denne siden er beskyttet
 av nettleseren
diff --git a/src/_locales/pt_BR.config b/src/_locales/pt_BR.config
index 4b6c482e..49c6e2c6 100644
--- a/src/_locales/pt_BR.config
+++ b/src/_locales/pt_BR.config
@@ -4,6 +4,7 @@ Tema escuro para todos os sites. Cuide dos seus olhos, use o Dark Reader para na
 @loading_please_wait
 Carregando, por favor espere
 
+
 #==== Top section ====
 
 @on
@@ -12,9 +13,6 @@ Ligar
 @off
 Des.
 
-@toggle
-Alternancia
-
 @toggle_current_site
 Alternar o site atual
 
@@ -29,6 +27,12 @@ Alternar extensão
 Tecla de atalho de alternância
 da extensão de configuração
 
+@time_settings
+Configurações de tempo
+
+@set_active_hours
+Defina o horário ativo
+
 @page_protected
 Esta página é protegida
 pelo navegador
diff --git a/src/_locales/pt_PT.config b/src/_locales/pt_PT.config
index 1c982d05..2bd10abf 100644
--- a/src/_locales/pt_PT.config
+++ b/src/_locales/pt_PT.config
@@ -4,6 +4,7 @@ Tema escuro para todos os sites. Cuide dos seus olhos, use o Dark Reader para na
 @loading_please_wait
 Carregando, por favor espere
 
+
 #==== Top section ====
 
 @on
@@ -12,9 +13,6 @@ Ligar
 @off
 Des.
 
-@toggle
-Alternancia
-
 @toggle_current_site
 Alternar o site atual
 
@@ -29,6 +27,12 @@ Alternar extensão
 Tecla de atalho de alternância
 da extensão de configuração
 
+@time_settings
+Configurações de tempo
+
+@set_active_hours
+Defina as horas de atividade
+
 @page_protected
 Esta página é protegida
 pelo navegador
diff --git a/src/_locales/ro.config b/src/_locales/ro.config
index 94888e18..2b5b5233 100755
--- a/src/_locales/ro.config
+++ b/src/_locales/ro.config
@@ -4,6 +4,7 @@ Temă întunecată pentru orice site. Ai grijă de ochii tăi, folosește Dark R
 @loading_please_wait
 Se încarcă, vă rog așteptați
 
+
 #==== Top section ====
 
 @on
@@ -12,9 +13,6 @@ Pornit
 @off
 Oprit
 
-@toggle
-Comută
-
 @toggle_current_site
 Comută siteul curent
 
@@ -29,6 +27,12 @@ Comută extensia
 Setează extensia
 scurtătură comutare
 
+@time_settings
+Setări de timp
+
+@set_active_hours
+Setați orele de activitate
+
 @page_protected
 Această pagină este protejată
 de navigator
diff --git a/src/_locales/ru.config b/src/_locales/ru.config
index 423b3ac7..5247f3de 100644
--- a/src/_locales/ru.config
+++ b/src/_locales/ru.config
@@ -4,6 +4,7 @@
 @loading_please_wait
 Идет загрузка, подождите
 
+
 #==== Top section ====
 
 @on
@@ -12,9 +13,6 @@
 @off
 Выкл.
 
-@toggle
-Перекл.
-
 @toggle_current_site
 Переключить тек. сайт
 
@@ -29,6 +27,12 @@
 Уст. сочетание
 клавиш
 
+@time_settings
+Настройки времени
+
+@set_active_hours
+Установите период активности
+
 @page_protected
 Эта страница защищена
 браузером
diff --git a/src/_locales/zh_CN.config b/src/_locales/zh_CN.config
index a216bbf8..148e186c 100644
--- a/src/_locales/zh_CN.config
+++ b/src/_locales/zh_CN.config
@@ -4,6 +4,7 @@
 @loading_please_wait
 加载中，请稍候
 
+
 #==== Top section ====
 
 @on
@@ -12,9 +13,6 @@
 @off
 关闭
 
-@toggle
-切换
-
 @toggle_current_site
 切换当前网站
 
@@ -29,6 +27,12 @@
 设置扩展的
 开关快捷键
 
+@time_settings
+时间设置
+
+@set_active_hours
+设置使用时段
+
 @page_protected
 此页面受
 浏览器保护
diff --git a/src/_locales/zh_TW.config b/src/_locales/zh_TW.config
index 8572dac3..4d7460df 100644
--- a/src/_locales/zh_TW.config
+++ b/src/_locales/zh_TW.config
@@ -4,6 +4,7 @@
 @loading_please_wait
 加載中，請稍候
 
+
 #==== Top section ====
 
 @on
@@ -12,9 +13,6 @@
 @off
 關閉
 
-@toggle
-切換
-
 @toggle_current_site
 切換當前網站
 
@@ -29,6 +27,12 @@
 設置擴展的
 開關快捷鍵
 
+@time_settings
+時間設置
+
+@set_active_hours
+設定使用時間
+
 @page_protected
 此頁面受
 瀏覽器保護
diff --git a/src/background/extension.ts b/src/background/extension.ts
index c5150dd4..b03900b6 100644
--- a/src/background/extension.ts
+++ b/src/background/extension.ts
@@ -6,16 +6,18 @@ import Newsmaker from './newsmaker';
 import TabManager from './tab-manager';
 import UserStorage from './user-storage';
 import {setWindowTheme, resetWindowTheme} from './window-theme';
-import {getFontList, getCommands, setShortcut} from './utils/extension-api';
+import {getFontList, getCommands, setShortcut, canInjectScript} from './utils/extension-api';
 import {isFirefox} from '../utils/platform';
-import {isInTimeInterval} from '../utils/time';
-import {isURLInList, getURLHost} from '../utils/url';
+import {isInTimeInterval, getDuration} from '../utils/time';
+import {isURLInList, getURLHost, isURLEnabled} from '../utils/url';
 import ThemeEngines from '../generators/theme-engines';
 import createCSSFilterStylesheet from '../generators/css-filter';
 import {getDynamicThemeFixesFor} from '../generators/dynamic-theme';
 import createStaticStylesheet from '../generators/static-theme';
 import {createSVGFilterStylesheet, getSVGFilterMatrixValue, getSVGReverseFilterMatrixValue} from '../generators/svg-filter';
-import {ExtensionData, FilterConfig, News, Shortcuts, UserSettings} from '../definitions';
+import {ExtensionData, FilterConfig, News, Shortcuts, UserSettings, TabInfo} from '../definitions';
+
+const AUTO_TIME_CHECK_INTERVAL_MS = getDuration({seconds: 10});
 
 export class Extension {
     ready: boolean;
@@ -45,11 +47,10 @@ export class Extension {
     }
 
     isEnabled() {
-        // TODO: There should be a separate property for time settings.
-        // if (this.user.settings.enabled === 'auto') {
-        //     const now = new Date();
-        //     return isInTimeInterval(now, this.user.settings.activationTime, this.user.settings.deactivationTime);
-        // }
+        if (this.user.settings.automation === 'time') {
+            const now = new Date();
+            return isInTimeInterval(now, this.user.settings.time.activation, this.user.settings.time.deactivation);
+        }
         return this.user.settings.enabled;
     }
 
@@ -72,8 +73,10 @@ export class Extension {
         this.awaiting.forEach((ready) => ready());
         this.awaiting = null;
 
+        this.startAutoTimeCheck();
         this.config.load({local: false});
         this.news.subscribe();
+        this.user.cleanup();
     }
 
     private popupOpeningListener: () => void = null;
@@ -90,7 +93,8 @@ export class Extension {
                 if (!this.ready) {
                     await new Promise((resolve) => this.awaiting.push(resolve));
                 }
-                return await this.tabs.getActiveTabInfo(this.config);
+                const url = await this.tabs.getActiveTabURL();
+                return await this.getURLInfo(url);
             },
             changeSettings: (settings) => this.changeSettings(settings),
             setTheme: (theme) => this.setTheme(theme),
@@ -115,7 +119,10 @@ export class Extension {
         chrome.commands.onCommand.addListener((command) => {
             if (command === 'toggle') {
                 console.log('Toggle command entered');
-                this.changeSettings({enabled: !this.isEnabled()});
+                this.changeSettings({
+                    enabled: !this.isEnabled(),
+                    automation: '',
+                });
             }
             if (command === 'addSite') {
                 console.log('Add Site command entered');
@@ -175,12 +182,34 @@ export class Extension {
         }
     }
 
+    private wasEnabledOnLastCheck: boolean;
+
+    private startAutoTimeCheck() {
+        setInterval(() => {
+            if (!this.ready || this.user.settings.automation !== 'time') {
+                return;
+            }
+            const isEnabled = this.isEnabled();
+            if (this.wasEnabledOnLastCheck !== isEnabled) {
+                this.wasEnabledOnLastCheck = isEnabled;
+                this.onAppToggle();
+                this.tabs.sendMessage(this.getTabMessage);
+                this.reportChanges();
+            }
+        }, AUTO_TIME_CHECK_INTERVAL_MS);
+    }
+
     changeSettings($settings: Partial<UserSettings>) {
         const prev = {...this.user.settings};
 
         this.user.set($settings);
 
-        if (prev.enabled !== this.user.settings.enabled) {
+        if (
+            (prev.enabled !== this.user.settings.enabled) ||
+            (prev.automation !== this.user.settings.automation) ||
+            (prev.time.activation !== this.user.settings.time.activation) ||
+            (prev.time.deactivation !== this.user.settings.time.deactivation)
+        ) {
             this.onAppToggle();
         }
 
@@ -226,7 +255,7 @@ export class Extension {
      * into Sites List (or removes).
      */
     async toggleCurrentSite() {
-        const {url} = await this.tabs.getActiveTabInfo(this.config);
+        const url = await this.tabs.getActiveTabURL();
         const host = getURLHost(url);
         this.toggleSitePattern(host);
     }
@@ -256,6 +285,7 @@ export class Extension {
             return;
         }
 
+        this.wasEnabledOnLastCheck = this.isEnabled();
         this.tabs.sendMessage(this.getTabMessage);
         this.saveUserSettings();
         this.reportChanges();
@@ -268,15 +298,20 @@ export class Extension {
     //
     //----------------------
 
-    private getTabMessage = (url: string, frameURL: string) => {
+    private getURLInfo(url: string): TabInfo {
         const {DARK_SITES} = this.config;
-        const isURLInDarkList = isURLInList(url, DARK_SITES);
-        const isURLInUserList = isURLInList(url, this.user.settings.siteList);
+        const isInDarkList = isURLInList(url, DARK_SITES);
+        const isProtected = !canInjectScript(url);
+        return {
+            url,
+            isInDarkList,
+            isProtected,
+        };
+    }
 
-        if (this.isEnabled() && (
-            (isURLInUserList && this.user.settings.applyToListedOnly) ||
-            (!isURLInDarkList && !this.user.settings.applyToListedOnly && !isURLInUserList)
-        )) {
+    private getTabMessage = (url: string, frameURL: string) => {
+        const urlInfo = this.getURLInfo(url);
+        if (this.isEnabled() && isURLEnabled(url, this.user.settings, urlInfo)) {
             const custom = this.user.settings.customThemes.find(({url: urlList}) => isURLInList(url, urlList));
             const filterConfig = custom ? custom.theme : this.user.settings.theme;
 
diff --git a/src/background/tab-manager.ts b/src/background/tab-manager.ts
index c226756a..2c946e56 100644
--- a/src/background/tab-manager.ts
+++ b/src/background/tab-manager.ts
@@ -1,7 +1,5 @@
-import {isURLInList, getURLHost} from '../utils/url';
 import {canInjectScript} from '../background/utils/extension-api';
-import ConfigManager from './config-manager';
-import {TabInfo, Message} from '../definitions';
+import {Message} from '../definitions';
 
 function queryTabs(query: chrome.tabs.QueryInfo) {
     return new Promise<chrome.tabs.Tab[]>((resolve) => {
@@ -113,22 +111,17 @@ export default class TabManager {
             });
     }
 
-    async getActiveTabInfo(config: ConfigManager) {
+    async getActiveTabURL() {
         let tab = (await queryTabs({
             active: true,
             lastFocusedWindow: true
         }))[0];
+        // When Dark Reader's Dev Tools are open, query can return extension's page instead of expected page
         const isExtensionPage = (url: string) => url.startsWith('chrome-extension:') || url.startsWith('moz-extension:');
         if (isExtensionPage(tab.url)) {
             const tabs = (await queryTabs({active: true}));
-            tab = tabs.find((t) => !isExtensionPage(t.url));
+            tab = tabs.find((t) => !isExtensionPage(t.url)) || tab;
         }
-        const {DARK_SITES} = config;
-        const url = tab.url;
-        return <TabInfo>{
-            url,
-            isProtected: !canInjectScript(url),
-            isInDarkList: isURLInList(url, DARK_SITES),
-        };
+        return tab.url;
     }
 }
diff --git a/src/background/user-storage.ts b/src/background/user-storage.ts
index 56dbd8e9..7106b660 100644
--- a/src/background/user-storage.ts
+++ b/src/background/user-storage.ts
@@ -27,10 +27,13 @@ export default class UserStorage {
             siteList: [],
             applyToListedOnly: false,
             changeBrowserTheme: false,
-            activationTime: '18:00',
-            deactivationTime: '9:00',
             notifyOfNews: false,
             syncSettings: true,
+            automation: '',
+            time: {
+                activation: '18:00',
+                deactivation: '9:00',
+            },
         };
         this.settings = null;
     }
@@ -41,11 +44,17 @@ export default class UserStorage {
         this.settings = await this.loadSettingsFromStorage();
     }
 
+    cleanup() {
+        chrome.storage.local.remove(['activationTime', 'deactivationTime']);
+        chrome.storage.sync.remove(['activationTime', 'deactivationTime']);
+    }
+
     private loadSettingsFromStorage() {
         return new Promise<UserSettings>((resolve) => {
             chrome.storage.local.get(this.defaultSettings, (local: UserSettings) => {
                 if (!local.syncSettings) {
                     local.theme = {...this.defaultSettings.theme, ...local.theme};
+                    local.time = {...this.defaultSettings.time, ...local.time};
                     resolve(local);
                     return;
                 }
@@ -59,6 +68,7 @@ export default class UserStorage {
                         sync = this.migrateSettings_4_6_2($sync) as UserSettings;
                     }
                     sync.theme = {...this.defaultSettings.theme, ...sync.theme};
+                    sync.time = {...this.defaultSettings.time, ...sync.time};
                     resolve(sync);
                 });
             });
diff --git a/src/config/dark-sites.config b/src/config/dark-sites.config
index 45432e87..e38864db 100644
--- a/src/config/dark-sites.config
+++ b/src/config/dark-sites.config
@@ -54,6 +54,7 @@ gogoanime.se
 greenmangaming.com
 gunshipmusic.com
 hackaday.com
+hackersforcharity.org
 hackertyper.com
 hardcoregaming101.net
 heapmedia.com
@@ -111,6 +112,7 @@ vakhtangov.ru
 vinesauce.com
 visualboxsite.com
 vuecinemas.nl
+w0rp.com
 weboas.is
 www.directvnow.com
 www.gdax.com
diff --git a/src/config/dynamic-theme-fixes.config b/src/config/dynamic-theme-fixes.config
index d919e299..f2801915 100644
--- a/src/config/dynamic-theme-fixes.config
+++ b/src/config/dynamic-theme-fixes.config
@@ -104,6 +104,15 @@ img
 
 ================================
 
+fivethirtyeight.com
+
+INVERT
+.site-logo
+#searchform
+.header-espn-link
+
+================================
+
 gitlab.com
 
 INVERT
@@ -137,6 +146,14 @@ img[src*="//tex.s2cms.ru/"]
 
 ================================
 
+hdgo.cc
+
+INVERT
+.hdplayer .big_play_button div
+.hdplayer .hdgo_controls div.hdgo_pause_control div
+
+================================
+
 inbox.google.com
 
 INVERT
@@ -145,6 +162,14 @@ nav li img
 
 ================================
 
+instagram.com
+
+INVERT
+.Igw0E.rBNOH.eGOV_.ybXk5._4EzTm
+._47KiJ
+
+================================
+
 keep.google.com
 
 INVERT
@@ -157,9 +182,21 @@ INVERT
 
 ================================
 
+mastarti.com
+
+INVERT
+.fp-play-icon div
+.fp-pause-icon .fp-pause-block div
+.fp-volumebar em.fp-color
+.fp-fullscreen-line
+.fp-fullscreen-dot
+
+================================
+
 medium.com
 
 INVERT
+.svgIcon
 .svgIcon-use
 
 ================================
@@ -188,6 +225,16 @@ INVERT
 
 ================================
 
+nzbget.*
+
+INVERT
+.icon-top
+.icon-bottom
+.icon-up
+.icon-down
+
+================================
+
 producthunt.com
 
 INVERT
diff --git a/src/config/inversion-fixes.config b/src/config/inversion-fixes.config
index 0ec4aeb8..4fa4b4df 100644
--- a/src/config/inversion-fixes.config
+++ b/src/config/inversion-fixes.config
@@ -1497,6 +1497,13 @@ img
 
 ================================
 
+regexr.com
+
+INVERT
+canvas.highlights
+
+================================
+
 rijnijssel.elo.education-online.nl
 
 NO INVERT
diff --git a/src/definitions.d.ts b/src/definitions.d.ts
index 4c25ce03..91e405c4 100644
--- a/src/definitions.d.ts
+++ b/src/definitions.d.ts
@@ -56,10 +56,15 @@ export interface UserSettings {
     siteList: string[];
     applyToListedOnly: boolean;
     changeBrowserTheme: boolean;
-    activationTime: string;
-    deactivationTime: string;
     notifyOfNews: boolean;
     syncSettings: boolean;
+    automation: string;
+    time: TimeSettings;
+}
+
+export interface TimeSettings {
+    activation: string;
+    deactivation: string;
 }
 
 export interface TabInfo {
diff --git a/src/inject/dynamic-theme/image.ts b/src/inject/dynamic-theme/image.ts
index 0b0ab056..13d602f7 100644
--- a/src/inject/dynamic-theme/image.ts
+++ b/src/inject/dynamic-theme/image.ts
@@ -1,8 +1,5 @@
 import {getSVGFilterMatrixValue} from '../../generators/svg-filter';
 import {bgFetch} from './network';
-import {getAbsoluteURL} from './url';
-import {scale, clamp} from '../../utils/math';
-import {isFirefox} from '../../utils/platform';
 import {logWarn} from '../utils/log';
 import {FilterConfig} from '../../definitions';
 
@@ -146,9 +143,6 @@ export function getFilteredImageDataURL({dataURL, width, height}: ImageDetails,
         `<image width="${width}" height="${height}" filter="url(#darkreader-image-filter)" xlink:href="${dataURL}" />`,
         '</svg>',
     ].join('');
-    if (isFirefox()) {
-        return `data:image/svg+xml;base64,${btoa(svg)}`;
-    }
     const bytes = new Uint8Array(svg.length);
     for (let i = 0; i < svg.length; i++) {
         bytes[i] = svg.charCodeAt(i);
diff --git a/src/inject/dynamic-theme/style-manager.ts b/src/inject/dynamic-theme/style-manager.ts
index 54547c20..dcd119e5 100644
--- a/src/inject/dynamic-theme/style-manager.ts
+++ b/src/inject/dynamic-theme/style-manager.ts
@@ -282,38 +282,43 @@ export function manageStyle(element: HTMLLinkElement | HTMLStyleElement, {update
             property: string;
             value: string;
             important: boolean;
+            asyncKey?: number;
+        }
+
+        function getCSSRuleText(declarations: ReadyDeclaration[]) {
+            const {selector} = declarations[0];
+            const readyItems = declarations.filter(({value}) => value != null);
+            const cssRuleText = `${selector} { ${readyItems.map(({property, value, important}) => `${property}: ${value}${important ? ' !important' : ''};`).join(' ')} }`;
+            return cssRuleText;
         }
 
         const readyDeclarations: ReadyDeclaration[] = [];
+        const asyncDeclarations = new Map<number, {declarations: ReadyDeclaration[], target: (CSSStyleSheet | CSSGroupingRule), index: number}>();
+        const asyncQueue = new Set<number>();
+        let asyncDeclarationCounter = 0;
+        let firstRun = true;
 
         function buildStyleSheet() {
-            const groups: ReadyDeclaration[][] = [];
-            readyDeclarations.filter((d) => d).forEach((decl) => {
-                let group: ReadyDeclaration[];
-                const prev = groups.length > 0 ? groups[groups.length - 1] : null;
-                if (prev && prev[0].selector === decl.selector && prev[0].media === decl.media) {
-                    group = prev;
+            const groups: ReadyDeclaration[][][] = [];
+            readyDeclarations.forEach((decl, i) => {
+                let mediaGroup: ReadyDeclaration[][];
+                let selectorGroup: ReadyDeclaration[];
+                const prev = i === 0 ? null : readyDeclarations[i - 1];
+                const isSameMedia = prev && prev.media === decl.media;
+                const isSameMediaAndSelector = prev && isSameMedia && prev.selector === decl.selector;
+                if (isSameMedia) {
+                    mediaGroup = groups[groups.length - 1];
                 } else {
-                    group = [];
-                    groups.push(group);
-                }
-                group.push(decl);
-            });
-
-            const lines: string[] = [];
-            groups.forEach((group) => {
-                const {media, selector} = group[0];
-                if (media) {
-                    lines.push(`@media ${media} {`);
+                    mediaGroup = [];
+                    groups.push(mediaGroup);
                 }
-                lines.push(`${selector} {`);
-                group.forEach(({property, value, important}) => {
-                    lines.push(`    ${property}: ${value}${important ? ' !important' : ''};`);
-                });
-                lines.push('}');
-                if (media) {
-                    lines.push('}')
+                if (isSameMediaAndSelector) {
+                    selectorGroup = mediaGroup[mediaGroup.length - 1];
+                } else {
+                    selectorGroup = [];
+                    mediaGroup.push(selectorGroup);
                 }
+                selectorGroup.push(decl);
             });
 
             if (!syncStyle) {
@@ -324,21 +329,73 @@ export function manageStyle(element: HTMLLinkElement | HTMLStyleElement, {update
             }
             syncStylePositionWatcher && syncStylePositionWatcher.stop();
             element.parentNode.insertBefore(syncStyle, corsCopy ? corsCopy.nextSibling : element.nextSibling);
-            syncStyle.textContent = lines.join('\n');
+
+            const sheet = syncStyle.sheet;
+
+            if (firstRun) {
+                firstRun = false;
+                for (let i = sheet.cssRules.length - 1; i >= 0; i--) {
+                    sheet.deleteRule(i);
+                }
+            }
+
+            groups.forEach((mediaGroup) => {
+                const {media} = mediaGroup[0][0];
+                let target: CSSStyleSheet | CSSGroupingRule;
+                if (media) {
+                    sheet.insertRule(`@media ${media} {}`, sheet.cssRules.length);
+                    target = sheet.cssRules[sheet.cssRules.length - 1] as CSSMediaRule;
+                } else {
+                    target = sheet;
+                }
+                mediaGroup.forEach((selectorGroup) => {
+                    const asyncItems = selectorGroup.filter(({value}) => value == null);
+                    if (asyncItems.length > 0) {
+                        asyncItems.forEach(({asyncKey}) => asyncDeclarations.set(asyncKey, {declarations: selectorGroup, target, index: target.cssRules.length}));
+                    }
+                    const cssRuleText = getCSSRuleText(selectorGroup);
+                    target.insertRule(cssRuleText, target.cssRules.length);
+                });
+            });
+
             syncStylePositionWatcher = watchForNodePosition(syncStyle);
         }
 
-        const RULES_PER_MS = 100;
-        const REQUESTED_RESOURCES = 1 / 4;
-        const declarationsCount = modRules.filter((r) => r).reduce((total, {declarations}) => total + declarations.length, 0);
-        const timeout = declarationsCount / RULES_PER_MS / REQUESTED_RESOURCES;
+        function onAsyncDeclarationReady(key: number, currentRenderId: number) {
+            asyncQueue.add(key);
+            throttledRebuildAsyncRules(currentRenderId);
+        }
 
-        const throttledBuildStyleSheet = throttle((currentRenderId: number) => {
+        function rebuildAsyncRules() {
+            const items = Array.from(asyncQueue).map((key) => asyncDeclarations.get(key));
+            const foundItems = new Map<any, Set<number>>();
+            const uniqItems = items.filter(({target, index}) => {
+                if (foundItems.has(target)) {
+                    if (foundItems.get(target).has(index)) {
+                        return false;
+                    } else {
+                        foundItems.get(target).add(index);
+                    }
+                } else {
+                    foundItems.set(target, new Set());
+                }
+                return true;
+            });
+            uniqItems.forEach(({declarations, target, index}) => {
+                const cssRuleText = getCSSRuleText(declarations);
+                target.deleteRule(index);
+                target.insertRule(cssRuleText, index);
+            });
+            Array.from(asyncQueue).forEach((key) => asyncDeclarations.delete(key));
+            asyncQueue.clear();
+        }
+
+        const throttledRebuildAsyncRules = throttle((currentRenderId: number) => {
             if (cancelAsyncOperations || renderId !== currentRenderId) {
                 return;
             }
-            buildStyleSheet();
-        }, timeout);
+            rebuildAsyncRules();
+        });
 
         modRules.filter((r) => r).forEach(({selector, declarations, media}) => {
             declarations.forEach(({property, value, important}) => {
@@ -346,15 +403,16 @@ export function manageStyle(element: HTMLLinkElement | HTMLStyleElement, {update
                     const modified = value(filter);
                     if (modified instanceof Promise) {
                         const index = readyDeclarations.length;
-                        readyDeclarations.push(null);
+                        const asyncKey = asyncDeclarationCounter++;
+                        readyDeclarations.push({media, selector, property, value: null, important, asyncKey});
                         const promise = modified;
                         const currentRenderId = renderId;
                         promise.then((asyncValue) => {
                             if (!asyncValue || cancelAsyncOperations || currentRenderId !== renderId) {
                                 return;
                             }
-                            readyDeclarations[index] = {media, selector, property, value: asyncValue, important};
-                            throttledBuildStyleSheet(currentRenderId);
+                            readyDeclarations[index].value = asyncValue;
+                            onAsyncDeclarationReady(asyncKey, currentRenderId);
                         });
                     } else {
                         readyDeclarations.push({media, selector, property, value: modified, important});
@@ -365,7 +423,7 @@ export function manageStyle(element: HTMLLinkElement | HTMLStyleElement, {update
             });
         });
 
-        throttledBuildStyleSheet(renderId);
+        buildStyleSheet();
     }
 
     let rulesCount: number = null;
diff --git a/src/inject/dynamic-theme/watch.ts b/src/inject/dynamic-theme/watch.ts
index bc7f2795..bb26fa5d 100644
--- a/src/inject/dynamic-theme/watch.ts
+++ b/src/inject/dynamic-theme/watch.ts
@@ -60,7 +60,13 @@ export function watchForStyleChanges(update: (styles: ChangedStyles) => void) {
             });
         }
 
-        iterateShadowNodes(mutations.reduce((nodes, m) => nodes.concat(Array.from(m.addedNodes)), []), subscribeForShadowRootChanges);
+        const allAddedNodes = [];
+        mutations.forEach((m) => {
+            m.addedNodes.forEach((n) => {
+                allAddedNodes.push(n);
+            });
+        });
+        iterateShadowNodes(allAddedNodes, subscribeForShadowRootChanges);
     }
 
     function subscribeForShadowRootChanges(node: Element) {
diff --git a/src/ui/connect/mock.ts b/src/ui/connect/mock.ts
index 73d9d1af..325ab165 100644
--- a/src/ui/connect/mock.ts
+++ b/src/ui/connect/mock.ts
@@ -22,10 +22,13 @@ export function getMockData(override = {}): ExtensionData {
             siteList: [],
             applyToListedOnly: false,
             changeBrowserTheme: false,
-            activationTime: '18:00',
-            deactivationTime: '9:00',
             notifyOfNews: false,
             syncSettings: true,
+            automation: '',
+            time: {
+                activation: '18:00',
+                deactivation: '9:00',
+            },
         } as UserSettings,
         fonts: [
             'serif',
diff --git a/src/ui/controls/index.ts b/src/ui/controls/index.ts
index d9220153..6f04ceff 100644
--- a/src/ui/controls/index.ts
+++ b/src/ui/controls/index.ts
@@ -6,6 +6,7 @@ import Shortcut from './shortcut';
 import TabPanel from './tab-panel';
 import TextBox from './textbox';
 import TextList from './text-list';
+import TimeRangePicker from './time-range-picker';
 import Toggle from './toggle';
 import UpDown from './updown';
 
@@ -18,6 +19,7 @@ export {
     TabPanel,
     TextBox,
     TextList,
+    TimeRangePicker,
     Toggle,
     UpDown,
 };
diff --git a/src/ui/controls/style.less b/src/ui/controls/style.less
index 1f1f8361..08b630a3 100644
--- a/src/ui/controls/style.less
+++ b/src/ui/controls/style.less
@@ -6,5 +6,6 @@
 @import "./select/style";
 @import "./text-list/style";
 @import "./textbox/style";
+@import "./time-range-picker/style";
 @import "./toggle/style";
 @import "./updown/style";
diff --git a/src/ui/controls/time-range-picker/index.tsx b/src/ui/controls/time-range-picker/index.tsx
new file mode 100644
index 00000000..17b38dcb
--- /dev/null
+++ b/src/ui/controls/time-range-picker/index.tsx
@@ -0,0 +1,79 @@
+import {html} from 'malevic';
+import TextBox from '../textbox';
+import {getUILanguage} from '../../../utils/locales';
+import {parseTime} from '../../../utils/time';
+
+interface TimePickerProps {
+    startTime: string;
+    endTime: string;
+    onChange: ([start, end]: [string, string]) => void;
+}
+
+const is12H = (new Date()).toLocaleTimeString(getUILanguage()).endsWith('M');
+
+function toLocaleTime($time: string) {
+    const [hours, minutes] = parseTime($time);
+
+    const mm = `${minutes < 10 ? '0' : ''}${minutes}`;
+
+    if (is12H) {
+        const h = (hours === 0 ?
+            '12' :
+            hours > 12 ?
+                (hours - 12) :
+                hours);
+        return `${h}:${mm}${hours < 12 ? 'AM' : 'PM'}`;
+    }
+
+    return `${hours}:${mm}`;
+}
+
+function to24HTime($time: string) {
+    const [hours, minutes] = parseTime($time);
+    const mm = `${minutes < 10 ? '0' : ''}${minutes}`;
+    return `${hours}:${mm}`;
+}
+
+export default function TimeRangePicker(props: TimePickerProps) {
+    function onStartTimeChange($startTime: string) {
+        props.onChange([to24HTime($startTime), props.endTime])
+    }
+
+    function onEndTimeChange($endTime: string) {
+        props.onChange([props.startTime, to24HTime($endTime)])
+    }
+
+    return (
+        <span class="time-range-picker">
+            <TextBox
+                class="time-range-picker__input time-range-picker__input--start"
+                placeholder={toLocaleTime('18:00')}
+                didmount={(node: HTMLInputElement) => node.value = toLocaleTime(props.startTime)}
+                didupdate={(node: HTMLInputElement) => node.value = toLocaleTime(props.startTime)}
+                onchange={(e) => onStartTimeChange((e.target as HTMLInputElement).value)}
+                onkeypress={(e) => {
+                    if (e.key === 'Enter') {
+                        const input = e.target as HTMLInputElement;
+                        input.blur();
+                        onStartTimeChange(input.value);
+                    }
+                }}
+
+            />
+            <TextBox
+                class="time-range-picker__input time-range-picker__input--end"
+                placeholder={toLocaleTime('9:00')}
+                didmount={(node: HTMLInputElement) => node.value = toLocaleTime(props.endTime)}
+                didupdate={(node: HTMLInputElement) => node.value = toLocaleTime(props.endTime)}
+                onchange={(e) => onEndTimeChange((e.target as HTMLInputElement).value)}
+                onkeypress={(e) => {
+                    if (e.key === 'Enter') {
+                        const input = e.target as HTMLInputElement;
+                        input.blur();
+                        onEndTimeChange(input.value);
+                    }
+                }}
+            />
+        </span>
+    );
+}
\ No newline at end of file
diff --git a/src/ui/controls/time-range-picker/style.less b/src/ui/controls/time-range-picker/style.less
new file mode 100644
index 00000000..b28b8f3e
--- /dev/null
+++ b/src/ui/controls/time-range-picker/style.less
@@ -0,0 +1,20 @@
+@import "../../theme";
+
+.time-range-picker {
+    display: inline-flex;
+
+    &__input {
+        flex: auto;
+        text-align: center;
+        text-indent: 0;
+        width: 100%;
+    }
+
+    &__input--start {
+        border-right: none;
+    }
+
+    &__input--end {
+        border-left: @size-border-inner solid @color-border;
+    }
+}
diff --git a/src/ui/popup/components/body.tsx b/src/ui/popup/components/body.tsx
index ce8cbfc4..fd62ab6a 100644
--- a/src/ui/popup/components/body.tsx
+++ b/src/ui/popup/components/body.tsx
@@ -3,7 +3,7 @@ import withForms from 'malevic/forms';
 import withState from 'malevic/state';
 import {TabPanel, Button} from '../../controls';
 import FilterSettings from './filter-settings';
-import Header from './header';
+import {Header, MoreToggleSettings} from './header';
 import Loader from './loader';
 import MoreSettings from './more-settings';
 import {News, NewsButton} from './news';
@@ -27,6 +27,7 @@ interface BodyProps {
 interface BodyState {
     activeTab?: string;
     newsOpen?: boolean;
+    moreToggleSettingsOpen?: boolean;
 }
 
 function openDevTools() {
@@ -74,11 +75,20 @@ function Body(props: BodyProps) {
         }
     }
 
+    function toggleMoreToggleSettings() {
+        setState({moreToggleSettingsOpen: !state.moreToggleSettingsOpen});
+    }
+
     return (
         <body class={{'ext-disabled': !props.data.isEnabled}}>
             <Loader complete />
 
-            <Header data={props.data} tab={props.tab} actions={props.actions} />
+            <Header
+                data={props.data}
+                tab={props.tab}
+                actions={props.actions}
+                onMoreToggleSettingsClick={toggleMoreToggleSettings}
+            />
 
             <TabPanel
                 activeTab={state.activeTab || 'Filter'}
@@ -124,6 +134,12 @@ function Body(props: BodyProps) {
                 onNewsOpen={onNewsOpen}
                 onClose={toggleNews}
             />
+            <MoreToggleSettings
+                data={props.data}
+                actions={props.actions}
+                isExpanded={state.moreToggleSettingsOpen}
+                onClose={toggleMoreToggleSettings}
+            />
         </body>
     );
 }
diff --git a/src/ui/popup/components/header/index.tsx b/src/ui/popup/components/header/index.tsx
index e49994a3..bcb4001b 100644
--- a/src/ui/popup/components/header/index.tsx
+++ b/src/ui/popup/components/header/index.tsx
@@ -1,5 +1,7 @@
 import {html} from 'malevic';
 import SiteToggle from '../site-toggle';
+import MoreToggleSettings from './more-toggle-settings';
+import WatchIcon from './watch-icon';
 import {Shortcut, Toggle} from '../../../controls';
 import {getLocalMessage} from '../../../../utils/locales';
 import {ExtWrapper, TabInfo} from '../../../../definitions';
@@ -8,12 +10,23 @@ function multiline(...lines) {
     return lines.join('\n');
 }
 
-export default function TopSection({data, actions, tab}: ExtWrapper & {tab: TabInfo}) {
+type HeaderProps = ExtWrapper & {
+    tab: TabInfo;
+    onMoreToggleSettingsClick: () => void;
+};
+
+function Header({data, actions, tab, onMoreToggleSettingsClick}: HeaderProps) {
 
     function toggleExtension(enabled) {
-        actions.changeSettings({enabled});
+        actions.changeSettings({
+            enabled,
+            automation: '',
+        });
     }
 
+    const isTimeAutomation = data.settings.automation === 'time';
+    const now = new Date();
+
     return (
         <header class="header">
             <img class="header__logo" src="../assets/images/darkreader-type.svg" alt="Dark Reader" />
@@ -54,7 +67,26 @@ export default function TopSection({data, actions, tab}: ExtWrapper & {tab: TabI
                     )}
                     onSetShortcut={(shortcut) => actions.setShortcut('toggle', shortcut)}
                 />
+                <span
+                    class="header__app-toggle__more-button"
+                    onclick={onMoreToggleSettingsClick}
+                ></span>
+                <span
+                    class={{
+                        'header__app-toggle__time': true,
+                        'header__app-toggle__time--active': isTimeAutomation,
+                    }}
+                >
+                    {(isTimeAutomation
+                        ? <WatchIcon hours={now.getHours()} minutes={now.getMinutes()} />
+                        : null)}
+                </span>
             </div>
         </header>
     );
 }
+
+export {
+    Header,
+    MoreToggleSettings, // TODO: Implement portals to place elements into <body>.
+};
diff --git a/src/ui/popup/components/header/more-toggle-settings.tsx b/src/ui/popup/components/header/more-toggle-settings.tsx
new file mode 100644
index 00000000..137cfeff
--- /dev/null
+++ b/src/ui/popup/components/header/more-toggle-settings.tsx
@@ -0,0 +1,41 @@
+import {html} from 'malevic';
+import {CheckBox, TimeRangePicker} from '../../../controls';
+import {getLocalMessage} from '../../../../utils/locales';
+import {ExtWrapper} from '../../../../definitions';
+
+type MoreToggleSettingsProps = ExtWrapper & {
+    isExpanded: boolean;
+    onClose: () => void;
+};
+
+export default function MoreToggleSettings({data, actions, isExpanded, onClose}: MoreToggleSettingsProps) {
+    return (
+        <div
+            class={{
+                'header__app-toggle__more-settings': true,
+                'header__app-toggle__more-settings--expanded': isExpanded,
+            }}
+        >
+            <div class="header__app-toggle__more-settings__top">
+                <span class="header__app-toggle__more-settings__top__text">{getLocalMessage('time_settings')}</span>
+                <span class="header__app-toggle__more-settings__top__close" role="button" onclick={onClose}>✕</span>
+            </div>
+            <div class="header__app-toggle__more-settings__content">
+                <div class="header__app-toggle__more-settings__line">
+                    <CheckBox
+                        checked={data.settings.automation === 'time'}
+                        onchange={(e) => actions.changeSettings({automation: e.target.checked ? 'time' : ''})}
+                    />
+                    <TimeRangePicker
+                        startTime={data.settings.time.activation}
+                        endTime={data.settings.time.deactivation}
+                        onChange={([start, end]) => actions.changeSettings({time: {activation: start, deactivation: end}})}
+                    />
+                </div>
+                <p class="header__app-toggle__more-settings__description">
+                    {getLocalMessage('set_active_hours')}
+                </p>
+            </div>
+        </div>
+    );
+}
diff --git a/src/ui/popup/components/header/style.less b/src/ui/popup/components/header/style.less
index e7fefa56..106f20a7 100644
--- a/src/ui/popup/components/header/style.less
+++ b/src/ui/popup/components/header/style.less
@@ -17,11 +17,11 @@
     &__control {
         display: flex;
         flex-direction: column;
-        overflow: hidden;
     }
 
     &__site-toggle {
         grid-area: site-toggle;
+        overflow: hidden;
 
         &__unable-text {
             color: @color-heading;
@@ -35,5 +35,174 @@
 
     &__app-toggle {
         grid-area: app-toggle;
+        position: relative;
+
+        &__more-button {
+            background-color: @color-control-back;
+            color: @color-input-fore;
+            cursor: pointer;
+            display: inline-block;
+            height: @size-control-inner + 2 * @size-border;
+            left: 100%;
+            opacity: 0;
+            position: absolute;
+            top: 0;
+            transition: opacity @time-slow, background-color @time-slow, color @time-slow;
+            width: @size-control-inner / 2;
+
+            &:hover {
+                background-color: @color-control-hover;
+                color: @color-input-fore-active;
+            }
+
+            &::before {
+                content: "⋮";
+                display: inline-block;
+                font-size: @size-text-large;
+                font-weight: bold;
+                height: 100%;
+                line-height: @size-control-inner;
+                text-align: center;
+                width: 100%;
+            }
+        }
+
+        &:hover &__more-button {
+            opacity: 1;
+            transition: opacity @time-slow @time-slow * 2, background-color @time-slow, color @time-slow;
+
+            &:hover {
+                transition: opacity @time-slow, background-color @time-slow, color @time-slow;
+            }
+        }
+
+        &__time {
+            background-color: @color-heading;
+            border-radius: 50%;
+            color: @color-control-fore;
+            cursor: pointer;
+            display: none;
+            height: @size-text-small-height;
+            pointer-events: none;
+            position: absolute;
+            right: -@size-text-small-height / 4;
+            top: -@size-text-small-height / 4;
+            transition: opacity @time-slow @time-slow;
+            width: @size-text-small-height;
+
+            &--active {
+                display: inline-block;
+            }
+        }
+
+        &__more-settings {
+            background-color: fadeout(@color-back, 10%);
+            border-bottom: @size-border solid @color-border;
+            border-top: @size-border solid @color-border;
+            display: flex;
+            flex-direction: column;
+            height: @size-control-inner
+                + @indent-large
+                + 2 * @size-border + @size-control-inner
+                + @size-text-small-height
+                + @indent-large;
+            left: 0;
+            position: absolute;
+            top: @popup-content-padding + @size-control-inner * 2 + @size-border + @indent-small;
+            transition: all @time-slow;
+            z-index: 2;
+
+            &:not(&--expanded) {
+                border-bottom-width: 0;
+                border-top-width: 0;
+                height: 0;
+                opacity: 0;
+                pointer-events: none;
+            }
+            
+            &:not(&--expanded) &__content {
+                padding-bottom: 0;
+                padding-top: 0;
+            }
+
+            &:not(&--expanded) &::after {
+                height: 0;
+            }
+
+            &__top {
+                align-items: flex-end;
+                color: @color-heading;
+                display: flex;
+                flex: auto;
+                font-size: @size-text-large;
+                height: @size-control-inner;
+                justify-content: space-between;
+                overflow: hidden;
+                padding-left: @popup-content-padding;
+
+                &__text {
+                    line-height: @size-text-large-height;
+                }
+
+                &__close {
+                    color: @color-heading;
+                    cursor: pointer;
+                    display: inline-block;
+                    font-size: @size-control-inner * 3 / 4;
+                    height: @size-control-inner;
+                    line-height: @size-control-inner;
+                    text-align: center;
+                    user-select: none;
+                    width: @size-control-inner;
+
+                    &:hover {
+                        background-color: fade(@color-control-active, 25%);
+                    }
+                }
+            }
+
+            &__content {
+                flex: auto;
+                overflow: hidden;
+                padding: @indent-large @popup-content-padding;
+                transition: padding @time-slow;
+            }
+
+            &__line {
+                display: flex;
+                flex-direction: row;
+
+                .checkbox {
+                    border-right: none;
+                    flex: none;
+                }
+
+                .time-range-picker .time-range-picker__input--start {
+                    border-left: @size-border-inner solid @color-border;
+                }
+            }
+
+            &__description {
+                font-size: @size-text-small;
+                line-height: @size-text-small-height;
+                margin: 0;
+                text-align: center;
+                white-space: nowrap;
+            }
+
+            &::after {
+                background-image: linear-gradient(to bottom, fadeout(@color-back, 10%), fadeout(@color-back, 100%));
+                content: "";
+                display: block;
+                height: @size-control-inner;
+                left: 0;
+                margin-top: @size-border;
+                pointer-events: none;
+                position: absolute;
+                top: 100%;
+                transition: height @time-slow;
+                width: 100%;
+            }
+        }
     }
 }
diff --git a/src/ui/popup/components/header/watch-icon.tsx b/src/ui/popup/components/header/watch-icon.tsx
new file mode 100644
index 00000000..0c165a7f
--- /dev/null
+++ b/src/ui/popup/components/header/watch-icon.tsx
@@ -0,0 +1,20 @@
+import {html} from 'malevic';
+
+export default function WatchIcon({hours, minutes}) {
+    const cx = 8;
+    const cy = 8;
+    const lh = 5;
+    const lm = 7;
+    const ah = ((hours > 11 ? hours - 12 : hours) + minutes / 60) / 12 * Math.PI * 2;
+    const am = minutes / 60 * Math.PI * 2;
+    const hx = cx + lh * Math.sin(ah);
+    const hy = cy - lh * Math.cos(ah);
+    const mx = cx + lm * Math.sin(am);
+    const my = cy - lm * Math.cos(am);
+
+    return (
+        <svg viewBox="0 0 16 16">
+            <path fill="none" stroke="white" stroke-width="2" d={`M${hx},${hy} L${cx},${cy} L${mx},${my}`} />
+        </svg>
+    );
+}
diff --git a/src/ui/popup/components/site-toggle/checkmark-icon.tsx b/src/ui/popup/components/site-toggle/checkmark-icon.tsx
new file mode 100644
index 00000000..35d99169
--- /dev/null
+++ b/src/ui/popup/components/site-toggle/checkmark-icon.tsx
@@ -0,0 +1,13 @@
+import {html} from 'malevic';
+
+export default function CheckmarkIcon({isEnabled}) {
+    return (
+        <svg viewBox="0 0 8 8">
+            <path
+                d={(isEnabled ?
+                    'M1,4 l2,2 l4,-4 v1 l-4,4 l-2,-2 Z' :
+                    'M2,2 l4,4 v1 l-4,-4 Z M2,6 l4,-4 v1 l-4,4 Z'
+                )} />
+        </svg>
+    );
+}
diff --git a/src/ui/popup/components/site-toggle/index.tsx b/src/ui/popup/components/site-toggle/index.tsx
index 548e61b0..5ea6377d 100644
--- a/src/ui/popup/components/site-toggle/index.tsx
+++ b/src/ui/popup/components/site-toggle/index.tsx
@@ -1,7 +1,7 @@
-import {html, render} from 'malevic';
+import {html} from 'malevic';
+import CheckmarkIcon from './checkmark-icon';
 import {Button} from '../../../controls';
-import {getURLHost} from '../../../../utils/url';
-import {getLocalMessage} from '../../../../utils/locales';
+import {getURLHost, isURLEnabled} from '../../../../utils/url';
 import {ExtWrapper, TabInfo} from '../../../../definitions';
 
 export default function SiteToggleButton({data, tab, actions}: ExtWrapper & {tab: TabInfo}) {
@@ -10,7 +10,7 @@ export default function SiteToggleButton({data, tab, actions}: ExtWrapper & {tab
         !tab.isProtected &&
         (data.settings.applyToListedOnly || !tab.isInDarkList)
     );
-
+    const isSiteEnabled = isURLEnabled(tab.url, data.settings, tab);
     const host = getURLHost(tab.url || '');
 
     const urlText = (host
@@ -26,11 +26,14 @@ export default function SiteToggleButton({data, tab, actions}: ExtWrapper & {tab
         <Button
             class={{
                 'site-toggle': true,
+                'site-toggle--active': isSiteEnabled,
                 'site-toggle--disabled': !toggleHasEffect
             }}
             onclick={() => actions.toggleSitePattern(host)}
         >
-            {getLocalMessage('toggle')} <span class="site-toggle__url" >{urlText}</span>
+            <span class="site-toggle__mark"><CheckmarkIcon isEnabled={isSiteEnabled} /></span>
+            {' '}
+            <span class="site-toggle__url" >{urlText}</span>
         </Button>
     );
 }
diff --git a/src/ui/popup/components/site-toggle/style.less b/src/ui/popup/components/site-toggle/style.less
index cef02dda..69f93708 100644
--- a/src/ui/popup/components/site-toggle/style.less
+++ b/src/ui/popup/components/site-toggle/style.less
@@ -1,7 +1,7 @@
 @import "../../../theme";
 
 .site-toggle {
-    color: @color-fore;
+    color: @color-input-fore;
     white-space: normal;
 
     &--disabled {
@@ -9,7 +9,16 @@
         pointer-events: none;
     }
 
-    &__url {
-        color: @color-control-fore;
+    &__mark {
+        svg {
+            fill: currentColor;
+            height: @size-text-normal;
+            vertical-align: bottom;
+            width: @size-text-normal;
+        }
+    }
+
+    &--active {
+        color: @color-input-fore-active;
     }
 }
diff --git a/src/utils/time.ts b/src/utils/time.ts
index 5b00280d..ebdbaf83 100644
--- a/src/utils/time.ts
+++ b/src/utils/time.ts
@@ -1,4 +1,29 @@
-function parseTime(time: string) {
+export function parseTime($time: string) {
+    const parts = $time.split(':').slice(0, 2);
+    const lowercased = $time.trim().toLowerCase();
+    const isAM = lowercased.endsWith('am') || lowercased.endsWith('a.m.');
+    const isPM = lowercased.endsWith('pm') || lowercased.endsWith('p.m.');
+
+    let hours = parts.length > 0 ? parseInt(parts[0]) : 0;
+    if (isNaN(hours) || hours > 23) {
+        hours = 0;
+    }
+    if (isAM && hours === 12) {
+        hours = 0;
+    }
+    if (isPM && hours < 12) {
+        hours += 12;
+    }
+
+    let minutes = parts.length > 1 ? parseInt(parts[1]) : 0;
+    if (isNaN(minutes) || minutes > 59) {
+        minutes = 0;
+    }
+
+    return [hours, minutes];
+}
+
+function parse24HTime(time: string) {
     return time.split(':').map((x) => parseInt(x));
 }
 
@@ -7,19 +32,19 @@ function compareTime(a: number[], b: number[]) {
         return 0;
     }
     if (a[0] < b[0] || (a[0] === b[0] && a[1] < b[1])) {
-        return 1;
+        return -1;
     }
-    return -1;
+    return 1;
 }
 
 export function isInTimeInterval(date: Date, time0: string, time1: string) {
-    const t0 = parseTime(time0);
-    const t1 = parseTime(time1);
+    const a = parse24HTime(time0);
+    const b = parse24HTime(time1);
     const t = [date.getHours(), date.getMinutes()];
-    if (compareTime(t0, t1) < 0) {
-        t1[0] += 24;
+    if (compareTime(a, b) > 0) {
+        return compareTime(a, t) <= 0 || compareTime(t, b) < 0;
     }
-    return compareTime(t0, t) >= 0 && compareTime(t, t1) > 0;
+    return compareTime(a, t) <= 0 && compareTime(t, b) < 0;
 }
 
 interface Duration {
diff --git a/src/utils/url.ts b/src/utils/url.ts
index b0890a26..df1b6abf 100644
--- a/src/utils/url.ts
+++ b/src/utils/url.ts
@@ -1,3 +1,5 @@
+import {UserSettings} from '../definitions';
+
 export function getURLHost(url: string) {
     return url.match(/^(.*?:\/{2,3})?(.+?)(\/|$)/)[2];
 }
@@ -93,3 +95,15 @@ function createUrlRegex(urlTemplate: string): RegExp {
 
     return new RegExp(result, 'i');
 }
+
+export function isURLEnabled(url: string, userSettings: UserSettings, {isProtected, isInDarkList}) {
+    const isURLInUserList = isURLInList(url, userSettings.siteList);
+    const isForcedEnable = isURLInUserList && userSettings.applyToListedOnly;
+    const isForcedDisable = isURLInUserList && !userSettings.applyToListedOnly;
+    return (
+        !isProtected && (
+            isForcedEnable ||
+            (!isInDarkList && !isForcedDisable)
+        )
+    );
+}


Test patch:
diff --git a/tests/time.tests.ts b/tests/time.tests.ts
new file mode 100644
index 00000000..95d1acd3
--- /dev/null
+++ b/tests/time.tests.ts
@@ -0,0 +1,34 @@
+import {isInTimeInterval, parseTime, getDuration} from '../src/utils/time';
+
+test('Time interval', () => {
+    expect(isInTimeInterval(new Date(2018, 11, 4, 10), '9:00', '12:00')).toBe(true);
+    expect(isInTimeInterval(new Date(2018, 11, 4, 10), '10:00', '10:00')).toBe(false);
+    expect(isInTimeInterval(new Date(2018, 11, 4, 12), '10:00', '10:00')).toBe(false);
+    expect(isInTimeInterval(new Date(2018, 11, 4, 8), '10:00', '10:00')).toBe(false);
+    expect(isInTimeInterval(new Date(2018, 11, 4, 10), '9:00', '10:00')).toBe(false);
+    expect(isInTimeInterval(new Date(2018, 11, 4, 9, 2), '9:01', '10:00')).toBe(true);
+    expect(isInTimeInterval(new Date(2018, 11, 4, 10), '9:00', '10:01')).toBe(true);
+    expect(isInTimeInterval(new Date(2018, 11, 4, 10), '18:00', '12:00')).toBe(true);
+    expect(isInTimeInterval(new Date(2018, 11, 4, 10), '18:00', '9:00')).toBe(false);
+    expect(isInTimeInterval(new Date(2018, 11, 4, 22), '18:00', '9:00')).toBe(true);
+});
+
+test('Time parse', () => {
+    expect(parseTime('10:30')).toEqual([10, 30]);
+    expect(parseTime('10:30AM')).toEqual([10, 30]);
+    expect(parseTime('10:30 a.m.')).toEqual([10, 30]);
+    expect(parseTime('10:30PM')).toEqual([22, 30]);
+    expect(parseTime('10:30 p.m.')).toEqual([22, 30]);
+    expect(parseTime('0:30')).toEqual([0, 30]);
+    expect(parseTime('12:30am')).toEqual([0, 30]);
+    expect(parseTime('12:30pm')).toEqual([12, 30]);
+});
+
+test('Duration', () => {
+    expect(getDuration({
+        seconds: 48,
+        minutes: 24,
+        hours: 8,
+        days: 3
+    })).toEqual(289488000);
+});