PluginProbe
Darkify – Dark Mode & Night Mode for Website & Admin (Dark Theme Included) / 2.1.3
Darkify – Dark Mode & Night Mode for Website & Admin (Dark Theme Included) v2.1.3
2.1.3 2.1.2 2.1.1 2.1.0 2.0.4 2.0.3 2.0.2 2.0.1 2.0.0 1.5.5 1.5.4 1.5.3 1.5.2 1.5.1 1.5.0 trunk 1.0.1 1.1.0 1.2.0 1.2.1 1.2.2 1.2.3 1.2.4 1.2.5 1.3.0 All 57 releases
← All changes | src/Frontend/Templates/header_script.php +114 -5 2.0.42.1.3 View file →
@@ -6,8 +6,33 @@
6 6 if (!defined('ABSPATH')) {
7 7 exit;
8 8 }
9 9
10 +if (!function_exists('darkify_js_string')) {
11 + /**
12 + * Emit a value as a JavaScript string literal (quotes included).
13 + *
14 + * For anything the browser parses rather than displays — CSS selectors,
15 + * above all — esc_attr() is the wrong escaper inside a <script> block: it
16 + * HTML-encodes `>`, `&` and `"`, and script bodies are raw text, so those
17 + * entities reach the JS engine verbatim and corrupt the value. JSON is the
18 + * correct escaping for a JS string; the HEX flags additionally keep `<`,
19 + * `>`, `&`, `'` and `"` out of the raw output so the literal can never
20 + * close the surrounding <script> tag.
21 + *
22 + * @param mixed $value Value to emit (cast to string).
23 + * @return string A quoted JS string literal, e.g. '"#id, #id *"'.
24 + */
25 + function darkify_js_string($value)
26 + {
27 + $json = wp_json_encode(
28 + (string) $value,
29 + JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT
30 + );
31 + return false === $json ? '""' : $json;
32 + }
33 +}
34 +
10 35 ob_start();
11 36 $options = get_option('darkify');
12 37 // Controls
13 38 $enable_default_dark_mode = isset($options['enable_default_dark_mode']) ? $options['enable_default_dark_mode'] : '';
@@ -122,9 +147,54 @@
122 147 } else {
123 148 $video_replacements = "[]";
124 149 }
125 150 if (!is_admin()) { ?>
126 - <script type="text/javascript" class="darkify_inline_js">(function(){var s=localStorage.darkify_last_state,t=localStorage.darkify_selected_theme||'set1';if(s==='1'){var h=document.documentElement;h.classList.add('darkify_dark_mode_enabled');h.classList.add('darkify-'+t);h.classList.add('darkify_prepaint');setTimeout(function(){h.classList.remove('darkify_prepaint')},800);}}());</script>
151 + <?php
152 + /*
153 + * Release the pre-paint guard when the engine is done, not on a fixed timer.
154 + *
155 + * This used to be a flat `setTimeout(..., 800)`. On a warm reload that is
156 + * invisible — every asset is in cache, the engine's first walk lands early,
157 + * and it drops both classes itself long before the timer. On a HARD reload
158 + * it is the flicker: nothing is served from cache, so the engine script and
159 + * the stylesheets it reads are re-fetched and the first walk lands well
160 + * after 800ms. The timer fired first, un-hid `body` while the page still
161 + * carried the theme's LIGHT colours, and the walk then flipped the whole
162 + * page to dark a few hundred ms later. That flip is what the visitor sees
163 + * flickering.
164 + *
165 + * The timeout's real job is the failsafe: if the engine never runs (script
166 + * 404s, blocked by an optimiser, JS disabled mid-flight) the page must not
167 + * stay hidden. So the guard now comes off on a condition rather than a
168 + * duration:
169 + *
170 + * - engine still to paint -> keep waiting; it removes the classes itself
171 + * the moment its first walk has real colours on the page.
172 + * - parsing finished and `window.darkify_engine_loaded` still unset ->
173 + * the engine is not on this page and never will be, so release at once.
174 + * That is strictly quicker than the old fixed 800ms in the case the
175 + * timer actually existed for.
176 + * - 4000ms -> release regardless, so no failure mode can leave the page
177 + * hidden. Comfortably above the slowest measured hard reload.
178 + *
179 + * Parse state is the gate, not the presence of the engine's script tag: on
180 + * a large page the tag can sit hundreds of KB into <head> and simply not be
181 + * parsed yet at the moment we look, which reads as "no engine" on exactly
182 + * the slow load this is meant to protect.
183 + *
184 + * Only `darkify_fouc_guard` is polled. Once that is gone the engine has
185 + * demonstrably painted, so `darkify_prepaint` can be left to it; the two are
186 + * still dropped together if the failsafe ever has to fire, which preserves
187 + * the original guarantee that neither class can outlive the page load.
188 + *
189 + * No `>`/`<`/`&` anywhere in the snippet, so the ceiling is counted in
190 + * 100ms ticks (40 = 4s) and every test is an `===`/`!==`. wp_kses
191 + * entity-encodes all three characters inside script text — `n>=40` reaches
192 + * the browser as `n&gt;=40` and dies with a SyntaxError — which is also why
193 + * the gates are nested ifs rather than `&&`.
194 + */
195 + ?>
196 + <script type="text/javascript" class="darkify_inline_js">(function(){var s=localStorage.darkify_last_state,t=localStorage.darkify_selected_theme||'set1';if(s==='1'){var h=document.documentElement;h.classList.add('darkify_dark_mode_enabled');h.classList.add('darkify-'+t);h.classList.add('darkify_prepaint');h.classList.add('darkify_fouc_guard');var r=function(){h.classList.remove('darkify_prepaint');h.classList.remove('darkify_fouc_guard');};var n=0;var p=function(){if(!h.classList.contains('darkify_fouc_guard')){return;}n=n+1;if(n===40){r();return;}if(document.readyState!=='loading'){if(!window.darkify_engine_loaded){r();return;}}setTimeout(p,100);};setTimeout(p,100);}}());</script>
127 197 <style type="text/css" class="darkify_inline_css">
128 198 :root {
129 199 <?php
130 200 $dark_mode_color_palettes = [
@@ -403,8 +473,31 @@
403 473
404 474 <script type="text/javascript" class="darkify_inline_js">
405 475 var darkify_switch_unique_id = "<?php echo esc_attr($this->unique_id); ?>";
406 476 var darkify_is_this_admin_panel = "<?php echo esc_attr(is_admin() ? "1" : "0"); ?>";
477 + <?php
478 + /*
479 + * Admin Panel Dark Mode, exposed so the admin engine can refuse to darken
480 + * while the option is off. Darkify's own SPA screens load the engine even
481 + * then, so the admin-bar icon can be toggled without a reload, which means
482 + * the engine has to honour the option itself rather than relying on simply
483 + * not being enqueued.
484 + */
485 + ?>
486 + var darkify_admin_panel_dark_enabled = "<?php echo esc_attr(isset($darkify_effective_admin_dark) ? $darkify_effective_admin_dark : (!empty($options['enable_admin_panel_dark_mode']) ? '1' : '0')); ?>";
487 + <?php
488 + /*
489 + * Site-wide palette defaults for the Dark Reader admin engine, one for the
490 + * block editor and one for everything else. "auto" means "let Dark Reader
491 + * derive every colour", which is the default and the recommended value.
492 + *
493 + * These are defaults, not overrides: the editor's toolbar dropdown writes a
494 + * per-user choice to localStorage and that still wins, which is how that
495 + * control has always behaved.
496 + */
497 + ?>
498 + var darkify_admin_palette_default = "<?php echo esc_attr(!empty($options['admin_panel_palette']) ? $options['admin_panel_palette'] : 'auto'); ?>";
499 + var darkify_editor_palette_default = "<?php echo esc_attr(!empty($options['block_editor_palette']) ? $options['block_editor_palette'] : 'auto'); ?>";
407 500 var darkify_enable_default_dark_mode = "<?php echo esc_attr($enable_default_dark_mode); ?>";
408 501 var darkify_enable_os_aware = "<?php echo esc_attr($enable_os_aware); ?>";
409 502 var darkify_enable_keyboard_shortcut = "<?php echo esc_attr($enable_keyboard_shortcut); ?>";
410 503 var darkify_keyboard_shortcut_keys = "<?php echo esc_attr($keyboard_shortcut_keys); ?>";
@@ -433,14 +526,30 @@
433 526 var darkify_video_brightness_to = "<?php echo esc_attr($low_video_brightness_label); ?>";
434 527 var darkify_enable_video_grayscale = "<?php echo esc_attr($enable_video_grayscale); ?>";
435 528 var darkify_video_grayscale_to = "<?php echo esc_attr($video_grayscale_label); ?>";
436 529 var darkify_video_replacements = "<?PHP echo esc_attr($video_replacements); ?>";
437 - var darkify_allowed_elements = "<?php echo esc_attr($this->utils->generateAllowedElementsStr($options)); ?>";
438 - var darkify_allowed_elements_raw = "<?php echo esc_attr($allowed_elements); ?>";
530 + <?php
531 + /*
532 + * These four carry CSS selectors, not display text, and they are read back
533 + * by element.matches() in client_main.js. esc_attr() would HTML-encode the
534 + * selector combinators (`>` becomes `&gt;`, `&` becomes `&amp;`), and a
535 + * <script> body is raw text — the browser never decodes those back. One
536 + * encoded combinator anywhere in the list makes the whole comma-joined
537 + * selector a SyntaxError, matches() throws, and Disallowed Elements
538 + * silently stops restricting anything. Custom CSS selectors are folded into
539 + * the disallowed list automatically (see generateDisallowedElementsStr), so
540 + * a perfectly ordinary `.card > .title` rule was enough to trigger it.
541 + * darkify_js_string() emits a proper JSON string literal instead, which
542 + * keeps the selector intact and is still safe to sit inside <script>
543 + * (tags/ampersands are \u-escaped).
544 + */
545 + ?>
546 + var darkify_allowed_elements = <?php echo darkify_js_string($this->utils->generateAllowedElementsStr($options)); ?>;
547 + var darkify_allowed_elements_raw = <?php echo darkify_js_string($allowed_elements); ?>;
439 548 var darkify_allowed_elements_force_to_correct = "<?php echo esc_attr($allowed_elements_force_to_correct); ?>";
440 549
441 - var darkify_disallowed_elements = "<?php echo esc_attr($this->utils->generateDisallowedElementsStr($options, $this->external_support)); ?>";
442 - var darkify_disallowed_elements_raw = "<?php echo esc_attr($disallowed_elements); ?>";
550 + var darkify_disallowed_elements = <?php echo darkify_js_string($this->utils->generateDisallowedElementsStr($options, $this->external_support)); ?>;
551 + var darkify_disallowed_elements_raw = <?php echo darkify_js_string($disallowed_elements); ?>;
443 552 var darkify_disallowed_elements_force_to_correct = "<?php echo esc_attr($disallowed_elements_force_to_correct); ?>";
444 553
445 554 var darkify_allowed_btn_class = <?php echo json_encode($this->utils->addButtonClassByDarkify($options)); ?>;
446 555