mirror of
https://github.com/Flow-Launcher/Flow.Launcher.git
synced 2026-03-11 08:54:32 +00:00
feat: added cleaning cache on tabs and windows closing
This commit is contained in:
parent
d1e5b70759
commit
9d27bcf589
4 changed files with 107 additions and 18 deletions
|
|
@ -1,8 +0,0 @@
|
|||
# Items to consider to improve the code
|
||||
|
||||
- TabsWalker.GetCurrentTabFromWindow
|
||||
- Research browsers' settings and check if it may break current assumption of just taking the last tab
|
||||
|
||||
- TabsTracker
|
||||
- AutomationFocusChangedEventHandler could be replaced with AutomationStructureChangedEventHandler, StructureChangeType.ChildAdded
|
||||
- Removal of tabs should be handled to save memory by using AutomationStructureChangedEventHandler, StructureChangeType.ChildRemoved
|
||||
|
|
@ -1,5 +1,11 @@
|
|||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows.Automation;
|
||||
using System.Windows.Forms;
|
||||
using System.Windows.Input;
|
||||
using System.Xml.Linq;
|
||||
using static Flow.Launcher.Plugin.BrowserBookmark.Main;
|
||||
|
||||
namespace Flow.Launcher.Plugin.BrowserBookmark.Tabs;
|
||||
|
||||
|
|
@ -9,13 +15,16 @@ namespace Flow.Launcher.Plugin.BrowserBookmark.Tabs;
|
|||
/// </summary>
|
||||
internal class TabsCache
|
||||
{
|
||||
private readonly HashSet<string> _knownTabs = new();
|
||||
private readonly object sync = new();
|
||||
private static readonly string ClassName = nameof(TabsCache);
|
||||
private readonly HashSet<string> _knownTabs = [];
|
||||
private readonly object _sync = new();
|
||||
|
||||
private static string RuntimeIdToKey(AutomationElement elem) {
|
||||
public static string RuntimeIdToKey(int[] runtimeId) => string.Join("-", runtimeId);
|
||||
|
||||
public static string RuntimeIdToKey(AutomationElement elem) {
|
||||
try
|
||||
{
|
||||
return elem != null ? string.Join("-", elem.GetRuntimeId()) : null;
|
||||
return elem != null ? RuntimeIdToKey(elem.GetRuntimeId()) : null;
|
||||
}
|
||||
catch (ElementNotAvailableException)
|
||||
{
|
||||
|
|
@ -25,7 +34,7 @@ internal class TabsCache
|
|||
|
||||
public bool Empty()
|
||||
{
|
||||
lock (sync)
|
||||
lock (_sync)
|
||||
{
|
||||
return _knownTabs.Count == 0;
|
||||
}
|
||||
|
|
@ -33,16 +42,16 @@ internal class TabsCache
|
|||
|
||||
public void Add(AutomationElement tab)
|
||||
{
|
||||
lock (sync)
|
||||
lock (_sync)
|
||||
{
|
||||
var key = RuntimeIdToKey(tab);
|
||||
if (key != null)
|
||||
{
|
||||
Context.API.LogDebug(ClassName, $"Adding a tab to cache: {tab.Current.Name}");
|
||||
_knownTabs.Add(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Add(IEnumerable<AutomationElement> tabs)
|
||||
{
|
||||
foreach (var tab in tabs)
|
||||
|
|
@ -53,7 +62,7 @@ internal class TabsCache
|
|||
|
||||
public bool Contains(AutomationElement tab)
|
||||
{
|
||||
lock (sync)
|
||||
lock (_sync)
|
||||
{
|
||||
var key = RuntimeIdToKey(tab);
|
||||
if (key != null)
|
||||
|
|
@ -63,4 +72,25 @@ internal class TabsCache
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void RemoveAllNonExistentTabs(AutomationElement rootElement, IEnumerable<AutomationElement> existingTabs)
|
||||
{
|
||||
if (rootElement == null || existingTabs == null)
|
||||
return;
|
||||
|
||||
var rootKey = RuntimeIdToKey(rootElement);
|
||||
var existingKeys = existingTabs.Select(RuntimeIdToKey).Where(k => k != null).ToHashSet();
|
||||
var keysToRemove = _knownTabs.Where(t => t.StartsWith(rootKey) && !existingKeys.Contains(t));
|
||||
|
||||
//Context.API.LogDebug(ClassName, $"Rootkey: {rootKey}");
|
||||
//Context.API.LogDebug(ClassName, $"Existing keys:\r\n{string.Join("\r\n", existingKeys)}");
|
||||
//Context.API.LogDebug(ClassName, $"Known Tabs:\r\n{string.Join("\r\n", _knownTabs)}");
|
||||
//Context.API.LogDebug(ClassName, $"Tabs to remove:\r\n{string.Join("\r\n", keysToRemove)}");
|
||||
|
||||
foreach (var key in keysToRemove)
|
||||
{
|
||||
Context.API.LogDebug(ClassName, $"Removing a tab from cache: {key}");
|
||||
_knownTabs.Remove(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Windows.Automation;
|
||||
using BrowserTabs;
|
||||
|
|
@ -27,6 +28,7 @@ public class TabsTracker : IDisposable
|
|||
private readonly object _sync = new();
|
||||
|
||||
private AutomationFocusChangedEventHandler? _focusHandler;
|
||||
private readonly HashSet<AutomationElement> _browserWindowsTracked = [];
|
||||
private bool _initialized;
|
||||
|
||||
public void OpenUrlAndTrack(Settings settings, string url)
|
||||
|
|
@ -82,7 +84,15 @@ public class TabsTracker : IDisposable
|
|||
public void Dispose()
|
||||
{
|
||||
if (_focusHandler != null)
|
||||
{
|
||||
Automation.RemoveAutomationFocusChangedEventHandler(_focusHandler);
|
||||
_focusHandler = null;
|
||||
}
|
||||
var windowsToUnsubscribe = _browserWindowsTracked.ToList();
|
||||
foreach (var wnd in windowsToUnsubscribe)
|
||||
{
|
||||
UnsubscribeStructureChangedForWindow(wnd);
|
||||
}
|
||||
}
|
||||
|
||||
public void ExpectUrl(string url)
|
||||
|
|
@ -163,6 +173,10 @@ public class TabsTracker : IDisposable
|
|||
// required to take the tab into account by Flow Launcher main UI search window
|
||||
Context.API.ReQuery();
|
||||
}
|
||||
|
||||
Automation.AddStructureChangedEventHandler(rootElement, TreeScope.Subtree, OnStructureChanged);
|
||||
Automation.AddAutomationEventHandler(WindowPattern.WindowClosedEvent, rootElement, TreeScope.Subtree, OnWindowClosed);
|
||||
_browserWindowsTracked.Add(rootElement);
|
||||
}
|
||||
}
|
||||
catch (ArgumentException)
|
||||
|
|
@ -176,4 +190,44 @@ public class TabsTracker : IDisposable
|
|||
Context.API.LogException(ClassName, "Exception", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnStructureChanged(object sender, StructureChangedEventArgs e)
|
||||
{
|
||||
// TODO: Consider ChildAdded to handle new tabs appearance instead of AutomationFocusChangedEventHandler
|
||||
// However think twice if it is worthwhile as the current approach might already be a good one
|
||||
//if (e.StructureChangeType == StructureChangeType.ChildAdded)
|
||||
//{
|
||||
//}
|
||||
if (e.StructureChangeType == StructureChangeType.ChildRemoved)
|
||||
{
|
||||
var eventRuntimeId = TabsCache.RuntimeIdToKey(e.GetRuntimeId());
|
||||
foreach (var window in _browserWindowsTracked)
|
||||
{
|
||||
var windowRuntimeId = TabsCache.RuntimeIdToKey(window);
|
||||
if (windowRuntimeId != null && eventRuntimeId.StartsWith(windowRuntimeId))
|
||||
{
|
||||
_walker.RescanTabsForContainer(window);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnWindowClosed(object sender, AutomationEventArgs e)
|
||||
{
|
||||
var element = sender as AutomationElement;
|
||||
if (element == null)
|
||||
return;
|
||||
UnsubscribeStructureChangedForWindow(element);
|
||||
}
|
||||
|
||||
private void UnsubscribeStructureChangedForWindow(AutomationElement wnd)
|
||||
{
|
||||
if (_browserWindowsTracked.Contains(wnd))
|
||||
{
|
||||
Context.API.LogDebug(ClassName, "Unsubscribe window from StructureChanged events");
|
||||
Automation.RemoveStructureChangedEventHandler(wnd, OnStructureChanged);
|
||||
_browserWindowsTracked.Remove(wnd);
|
||||
_walker?.RemoveAllTabs(wnd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -79,6 +79,7 @@ internal class TabsWalker
|
|||
// Let's take the last one and assume this is the one that was created recently
|
||||
// This is the best known approach as of today
|
||||
// There might be some browsers' settings that change this behavior but weren't tested nor considered yet
|
||||
// TODO: Research browsers' settings and check if it may break current assumption of just taking the last tab
|
||||
return InitiateTab(process, tabs.Last());
|
||||
}
|
||||
|
||||
|
|
@ -91,7 +92,7 @@ internal class TabsWalker
|
|||
var tab = tabs[i];
|
||||
if (!_cache.Contains(tab))
|
||||
{
|
||||
Context.API.LogDebug(ClassName, $"FOUND NEW TAB: name={tab.Current.Name}, className={tab.Current.ClassName}");
|
||||
Context.API.LogDebug(ClassName, $"FOUND A NEW TAB: name={tab.Current.Name}, className={tab.Current.ClassName}");
|
||||
_cache.Add(tab);
|
||||
return InitiateTab(process, tab);
|
||||
}
|
||||
|
|
@ -113,4 +114,16 @@ internal class TabsWalker
|
|||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
internal void RescanTabsForContainer(AutomationElement browserWindow)
|
||||
{
|
||||
Context.API.LogDebug(ClassName, "Rescaning tabs in order to find removed tabs");
|
||||
_cache.RemoveAllNonExistentTabs(browserWindow, FindAllValidTabs(browserWindow));
|
||||
}
|
||||
|
||||
internal void RemoveAllTabs(AutomationElement browserWindow)
|
||||
{
|
||||
Context.API.LogDebug(ClassName, "Removing all tabs in a window");
|
||||
_cache.RemoveAllNonExistentTabs(browserWindow, []);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue