From ac3e3832d20a3ab00dc72f42ad95aa03b7b16ccd Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 24 Mar 2026 12:56:09 +0000 Subject: [PATCH] perf: cache HTML sanitize regex; replace ToLowerInvariant with FrozenDictionary in ShouldShowNotification; fix GetRawText double-call MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CanvasWindow.SanitizeHtml: two anonymous Regex.Replace calls now use static readonly compiled fields (s_sanitizeBlock, s_sanitizeSelfClose). Previously each call to SanitizeHtml recompiled both patterns from scratch (Regex JIT + allocation on every canvas load). - App.ShouldShowNotification: replaced 'Type?.ToLowerInvariant()' switch with a static FrozenDictionary> using OrdinalIgnoreCase. Runs for every incoming notification; old code allocated a lowercase string copy every call; new code does an O(1) hash-table lookup with zero allocation. - OpenClawGatewayClient.HandleChatEvent: GetRawText() was called twice in the same debug expression, materialising the JSON string twice. Now called once and stored in a local. All 503 shared tests pass, 18 skipped (infra). All 93 Tray tests pass. WinUI build fails on Linux (XAML compiler is Windows-only PE) — same infrastructure limitation as all other WinUI PRs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/OpenClaw.Shared/OpenClawGatewayClient.cs | 3 +- src/OpenClaw.Tray.WinUI/App.xaml.cs | 31 +++++++++++-------- .../Windows/CanvasWindow.xaml.cs | 16 ++++++---- 3 files changed, 30 insertions(+), 20 deletions(-) diff --git a/src/OpenClaw.Shared/OpenClawGatewayClient.cs b/src/OpenClaw.Shared/OpenClawGatewayClient.cs index 0e21836..c6fcdf9 100644 --- a/src/OpenClaw.Shared/OpenClawGatewayClient.cs +++ b/src/OpenClaw.Shared/OpenClawGatewayClient.cs @@ -798,7 +798,8 @@ private void HandleToolEvent(JsonElement payload, string sessionKey, bool isMain private void HandleChatEvent(JsonElement root) { - _logger.Debug($"Chat event received: {root.GetRawText().Substring(0, Math.Min(200, root.GetRawText().Length))}"); + var rawText = root.GetRawText(); + _logger.Debug($"Chat event received: {rawText.Substring(0, Math.Min(200, rawText.Length))}"); if (!root.TryGetProperty("payload", out var payload)) return; diff --git a/src/OpenClaw.Tray.WinUI/App.xaml.cs b/src/OpenClaw.Tray.WinUI/App.xaml.cs index de0780f..daa91f1 100644 --- a/src/OpenClaw.Tray.WinUI/App.xaml.cs +++ b/src/OpenClaw.Tray.WinUI/App.xaml.cs @@ -55,6 +55,21 @@ public partial class App : Application private DateTime _lastCheckTime = DateTime.Now; private DateTime _lastUsageActivityLogUtc = DateTime.MinValue; + // FrozenDictionary for O(1) case-insensitive notification type → setting lookup — no per-call allocation. + private static readonly System.Collections.Frozen.FrozenDictionary> s_notifTypeMap = + new Dictionary>(StringComparer.OrdinalIgnoreCase) + { + ["health"] = s => s.NotifyHealth, + ["urgent"] = s => s.NotifyUrgent, + ["reminder"] = s => s.NotifyReminder, + ["email"] = s => s.NotifyEmail, + ["calendar"] = s => s.NotifyCalendar, + ["build"] = s => s.NotifyBuild, + ["stock"] = s => s.NotifyStock, + ["info"] = s => s.NotifyInfo, + ["error"] = s => s.NotifyUrgent, // errors follow urgent setting + }.ToFrozenDictionary(StringComparer.OrdinalIgnoreCase); + // Session-aware activity tracking private readonly Dictionary _sessionActivities = new(); private string? _displayedSessionKey; @@ -1459,19 +1474,9 @@ private bool ShouldShowNotification(OpenClawNotification notification) if (notification.IsChat && !_settings.NotifyChatResponses) return false; - return notification.Type?.ToLowerInvariant() switch - { - "health" => _settings.NotifyHealth, - "urgent" => _settings.NotifyUrgent, - "reminder" => _settings.NotifyReminder, - "email" => _settings.NotifyEmail, - "calendar" => _settings.NotifyCalendar, - "build" => _settings.NotifyBuild, - "stock" => _settings.NotifyStock, - "info" => _settings.NotifyInfo, - "error" => _settings.NotifyUrgent, // errors follow urgent setting - _ => true - }; + var type = notification.Type; + if (type == null) return true; + return s_notifTypeMap.TryGetValue(type, out var selector) ? selector(_settings) : true; } #endregion diff --git a/src/OpenClaw.Tray.WinUI/Windows/CanvasWindow.xaml.cs b/src/OpenClaw.Tray.WinUI/Windows/CanvasWindow.xaml.cs index 8ba163d..90c1136 100644 --- a/src/OpenClaw.Tray.WinUI/Windows/CanvasWindow.xaml.cs +++ b/src/OpenClaw.Tray.WinUI/Windows/CanvasWindow.xaml.cs @@ -22,6 +22,14 @@ public sealed partial class CanvasWindow : WindowEx private readonly TaskCompletionSource _webViewReadyTcs = new(TaskCreationOptions.RunContinuationsAsynchronously); private TaskCompletionSource? _navigationTcs; + // HTML sanitization — block embedded iframes/objects/embeds/applets + private static readonly Regex s_sanitizeBlock = new( + @"<\s*(iframe|object|embed|applet)\b[^>]*>.*?<\s*/\s*\1\s*>", + RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.Compiled); + private static readonly Regex s_sanitizeSelfClose = new( + @"<\s*(iframe|object|embed|applet)\b[^>]*/?\s*>", + RegexOptions.IgnoreCase | RegexOptions.Compiled); + // URL validation - block dangerous schemes and private networks (IPv4 + IPv6) private static readonly Regex DangerousUrlPattern = new( @"^(file|javascript|data|vbscript):|" + // Dangerous schemes @@ -247,13 +255,9 @@ public void LoadHtml(string html) private static string SanitizeHtml(string html) { // Remove