Skip to content

Commit 28ea1ae

Browse files
jhaygood86claude
authored andcommitted
Re-evaluate @media when the system colour scheme changes
The adapters already invalidated their cached theme on a system preference change, but nothing acted on it: the cascade is only re-evaluated during layout, and an idle window never lays out - so switching Windows between light and dark left prefers-color-scheme rules stuck at whatever was true when the document loaded. RAdapter gains a ColorSchemeChanged event. HtmlContainerInt subscribes and asks the host to lay out and repaint, which runs the existing viewport check and re-cascades if the outcome moved. The event only reports a real change: the WinForms and WPF adapters re-read the theme and compare, since the General preference category covers much more than the theme and would otherwise force a re-cascade on every unrelated setting change. The handler is detached on dispose - the adapter is a process-wide singleton and would otherwise keep every container that ever rendered alive. The re-cascade itself stays in PerformLayout rather than the event handler, because SystemEvents can raise on a thread other than the host's. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 191af34 commit 28ea1ae

4 files changed

Lines changed: 67 additions & 5 deletions

File tree

Source/HtmlRenderer.WPF/Adapters/WpfAdapter.cs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,15 @@ private WpfAdapter()
7474

7575
SystemEvents.UserPreferenceChanged += (sender, e) =>
7676
{
77-
if (e.Category == UserPreferenceCategory.General)
78-
_colorScheme = null;
77+
if (e.Category != UserPreferenceCategory.General) return;
78+
79+
// The General category covers far more than the theme, so re-read and only report a
80+
// change if the scheme really moved - otherwise every unrelated preference change
81+
// would force a re-cascade and repaint.
82+
var previous = _colorScheme;
83+
_colorScheme = null;
84+
if (previous.HasValue && previous.Value != SystemColorScheme)
85+
OnColorSchemeChanged();
7986
};
8087
}
8188

Source/HtmlRenderer.WinForms/Adapters/WinFormsAdapter.cs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,15 @@ private WinFormsAdapter()
5151

5252
Microsoft.Win32.SystemEvents.UserPreferenceChanged += (sender, e) =>
5353
{
54-
if (e.Category == Microsoft.Win32.UserPreferenceCategory.General)
55-
_colorScheme = null;
54+
if (e.Category != Microsoft.Win32.UserPreferenceCategory.General) return;
55+
56+
// The General category covers far more than the theme, so re-read and only report a
57+
// change if the scheme really moved - otherwise every unrelated preference change
58+
// would force a re-cascade and repaint.
59+
var previous = _colorScheme;
60+
_colorScheme = null;
61+
if (previous.HasValue && previous.Value != SystemColorScheme)
62+
OnColorSchemeChanged();
5663
};
5764
}
5865

Source/HtmlRenderer/Adapters/RAdapter.cs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,13 +92,35 @@ public virtual string DefaultMediaType
9292
/// <summary>
9393
/// The colour scheme the rendering surface presents, answering the <c>prefers-color-scheme</c>
9494
/// media feature. Defaults to <see cref="RColorScheme.Light"/>; an adapter that renders onto a
95-
/// themed surface should report the system setting instead.
95+
/// themed surface should report the system setting instead, and raise
96+
/// <see cref="ColorSchemeChanged"/> when it changes.
9697
/// </summary>
9798
public virtual RColorScheme SystemColorScheme
9899
{
99100
get { return RColorScheme.Light; }
100101
}
101102

103+
/// <summary>
104+
/// Raised when <see cref="SystemColorScheme"/> has changed, so anything rendered against it can
105+
/// re-evaluate its <c>prefers-color-scheme</c> rules and repaint. Never raised by an adapter
106+
/// whose scheme is fixed.
107+
/// </summary>
108+
/// <remarks>
109+
/// Handlers are held for the lifetime of the adapter, which is typically a process-wide
110+
/// singleton, so a subscriber must unsubscribe when it is disposed.
111+
/// </remarks>
112+
public event EventHandler ColorSchemeChanged;
113+
114+
/// <summary>
115+
/// Raises <see cref="ColorSchemeChanged"/>. For adapters that track a system theme.
116+
/// </summary>
117+
protected void OnColorSchemeChanged()
118+
{
119+
var handler = ColorSchemeChanged;
120+
if (handler != null)
121+
handler(this, EventArgs.Empty);
122+
}
123+
102124
/// <summary>
103125
/// Get the default CSS stylesheet data.
104126
/// </summary>

Source/HtmlRenderer/Core/HtmlContainerInt.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,30 @@ public HtmlContainerInt(RAdapter adapter)
224224

225225
_adapter = adapter;
226226
_cssParser = new CssParser(adapter);
227+
228+
// A system theme change alters what prefers-color-scheme reports, which can change which
229+
// rules apply. Nothing else would notice: the cascade is only re-evaluated during layout,
230+
// and an idle window does not lay out.
231+
_colorSchemeChangedHandler = (sender, e) => OnAdapterColorSchemeChanged();
232+
_adapter.ColorSchemeChanged += _colorSchemeChangedHandler;
233+
}
234+
235+
/// <summary>
236+
/// Kept so the handler can be detached from the adapter - which is typically a process-wide
237+
/// singleton, and would otherwise keep every container that ever rendered alive.
238+
/// </summary>
239+
private readonly EventHandler _colorSchemeChangedHandler;
240+
241+
/// <summary>
242+
/// The colour scheme changed, so ask the host to lay out and repaint. The re-cascade itself
243+
/// happens in <see cref="PerformLayout"/>, on the host's own thread - this event can arrive on
244+
/// another one.
245+
/// </summary>
246+
private void OnAdapterColorSchemeChanged()
247+
{
248+
if (_root == null) return;
249+
250+
RequestRefresh(true);
227251
}
228252

229253
/// <summary>
@@ -1081,6 +1105,8 @@ private void Dispose(bool all)
10811105
ImageLoad = null;
10821106
}
10831107

1108+
_adapter.ColorSchemeChanged -= _colorSchemeChangedHandler;
1109+
10841110
_cssData = null;
10851111
if (_root != null)
10861112
_root.Dispose();

0 commit comments

Comments
 (0)