mirror of
https://github.com/Flow-Launcher/Flow.Launcher.git
synced 2026-03-11 08:54:32 +00:00
Fix BrowserBookmark '100% CPU' issue
# Fix BrowserBookmark plugin locking threads at 100% CPU
The following files were modified to implement this fix:
1. /Flow.Launcher.Plugin.BrowserBookmark.csproj
- packages SkiaSharp and Svg.Skia added to output WEBP
2. /Helper/FaviconHelper.cs
- new method, TryConvertToWebp, added to take any image data and encode to WEBP
3. /FirefoxBookmarkLoader.cs
- LoadFaviconsFromDb to use the new helper which ensures only safe WEBP files are used by the UI, which resolves the CPU-locking. It was GZIPped SVGs that were causing the thread / CPU lock.
This commit is contained in:
parent
3cdf9197b9
commit
78b4c7db85
3 changed files with 82 additions and 30 deletions
|
|
@ -2,6 +2,7 @@
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.IO.Compression;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Flow.Launcher.Plugin.BrowserBookmark.Helper;
|
using Flow.Launcher.Plugin.BrowserBookmark.Helper;
|
||||||
|
|
@ -134,10 +135,6 @@ public abstract class FirefoxBookmarkLoaderBase : IBookmarkLoader
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(bookmark.Url))
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Extract domain from URL
|
|
||||||
if (!Uri.TryCreate(bookmark.Url, UriKind.Absolute, out Uri uri))
|
if (!Uri.TryCreate(bookmark.Url, UriKind.Absolute, out Uri uri))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
@ -146,43 +143,48 @@ public abstract class FirefoxBookmarkLoaderBase : IBookmarkLoader
|
||||||
// Query for latest Firefox version favicon structure
|
// Query for latest Firefox version favicon structure
|
||||||
using var cmd = connection.CreateCommand();
|
using var cmd = connection.CreateCommand();
|
||||||
cmd.CommandText = @"
|
cmd.CommandText = @"
|
||||||
SELECT i.data
|
SELECT i.id, i.data
|
||||||
FROM moz_icons i
|
FROM moz_icons i
|
||||||
JOIN moz_icons_to_pages ip ON i.id = ip.icon_id
|
JOIN moz_icons_to_pages ip ON i.id = ip.icon_id
|
||||||
JOIN moz_pages_w_icons p ON ip.page_id = p.id
|
JOIN moz_pages_w_icons p ON ip.page_id = p.id
|
||||||
WHERE p.page_url LIKE @url
|
WHERE p.page_url LIKE @domain
|
||||||
AND i.data IS NOT NULL
|
ORDER BY i.width DESC
|
||||||
ORDER BY i.width DESC -- Select largest icon available
|
|
||||||
LIMIT 1";
|
LIMIT 1";
|
||||||
|
|
||||||
cmd.Parameters.AddWithValue("@url", $"%{domain}%");
|
cmd.Parameters.AddWithValue("@domain", $"%{domain}%");
|
||||||
|
|
||||||
using var reader = cmd.ExecuteReader();
|
using var reader = cmd.ExecuteReader();
|
||||||
if (!reader.Read() || reader.IsDBNull(0))
|
if (!reader.Read() || reader.IsDBNull(1))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
var iconId = reader.GetInt64(0).ToString();
|
||||||
var imageData = (byte[])reader["data"];
|
var imageData = (byte[])reader["data"];
|
||||||
|
|
||||||
if (imageData is not { Length: > 0 })
|
if (imageData is not { Length: > 0 })
|
||||||
return;
|
return;
|
||||||
|
|
||||||
string faviconPath;
|
if (imageData.Length > 2 && imageData[0] == 0x1f && imageData[1] == 0x8b)
|
||||||
if (FaviconHelper.IsSvgData(imageData))
|
|
||||||
{
|
{
|
||||||
faviconPath = Path.Combine(_faviconCacheDir, $"firefox_{domain}.svg");
|
using var inputStream = new MemoryStream(imageData);
|
||||||
|
using var gZipStream = new GZipStream(inputStream, CompressionMode.Decompress);
|
||||||
|
using var outputStream = new MemoryStream();
|
||||||
|
gZipStream.CopyTo(outputStream);
|
||||||
|
imageData = outputStream.ToArray();
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
var webpData = FaviconHelper.TryConvertToWebp(imageData);
|
||||||
|
|
||||||
|
if (webpData != null)
|
||||||
{
|
{
|
||||||
faviconPath = Path.Combine(_faviconCacheDir, $"firefox_{domain}.png");
|
var faviconPath = Path.Combine(_faviconCacheDir, $"firefox_{domain}_{iconId}.webp");
|
||||||
}
|
|
||||||
|
|
||||||
// Filter out duplicate favicons
|
if (savedPaths.TryAdd(faviconPath, true))
|
||||||
if (savedPaths.TryAdd(faviconPath, true))
|
{
|
||||||
{
|
FaviconHelper.SaveBitmapData(webpData, faviconPath);
|
||||||
FaviconHelper.SaveBitmapData(imageData, faviconPath);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
bookmark.FaviconPath = faviconPath;
|
bookmark.FaviconPath = faviconPath;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -97,6 +97,8 @@
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
||||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="9.0.3" />
|
<PackageReference Include="Microsoft.Data.Sqlite" Version="9.0.3" />
|
||||||
|
<PackageReference Include="Svg.Skia" Version="3.0.3" />
|
||||||
|
<PackageReference Include="SkiaSharp" Version="3.119.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using SkiaSharp;
|
||||||
|
using Svg.Skia;
|
||||||
|
|
||||||
namespace Flow.Launcher.Plugin.BrowserBookmark.Helper;
|
namespace Flow.Launcher.Plugin.BrowserBookmark.Helper;
|
||||||
|
|
||||||
|
|
@ -65,12 +67,58 @@ public static class FaviconHelper
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool IsSvgData(byte[] data)
|
public static byte[] TryConvertToWebp(byte[] data)
|
||||||
{
|
{
|
||||||
if (data.Length < 5)
|
if (data == null || data.Length == 0)
|
||||||
return false;
|
return null;
|
||||||
string start = System.Text.Encoding.ASCII.GetString(data, 0, Math.Min(100, data.Length));
|
|
||||||
return start.Contains("<svg") ||
|
SKBitmap bitmap = null;
|
||||||
(start.StartsWith("<?xml") && start.Contains("<svg"));
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var ms = new MemoryStream(data))
|
||||||
|
{
|
||||||
|
var svg = new SKSvg();
|
||||||
|
if (svg.Load(ms) != null && svg.Picture != null)
|
||||||
|
{
|
||||||
|
bitmap = new SKBitmap((int)svg.Picture.CullRect.Width, (int)svg.Picture.CullRect.Height);
|
||||||
|
using (var canvas = new SKCanvas(bitmap))
|
||||||
|
{
|
||||||
|
canvas.Clear(SKColors.Transparent);
|
||||||
|
canvas.DrawPicture(svg.Picture);
|
||||||
|
canvas.Flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { /* Not an SVG */ }
|
||||||
|
|
||||||
|
if (bitmap == null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
bitmap = SKBitmap.Decode(data);
|
||||||
|
}
|
||||||
|
catch { /* Not a decodable bitmap */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bitmap != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var image = SKImage.FromBitmap(bitmap))
|
||||||
|
using (var webp = image.Encode(SKEncodedImageFormat.Webp, 65))
|
||||||
|
{
|
||||||
|
if (webp != null)
|
||||||
|
return webp.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
bitmap.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in a new issue