Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
c4549d6
Implement reusing of existing tabs
andrzejmartyna Dec 28, 2025
02248eb
Add ReuseTabs to Settings
andrzejmartyna Dec 28, 2025
20f898c
Additional attribution
andrzejmartyna Dec 28, 2025
df900f5
small tweaks
andrzejmartyna Dec 28, 2025
c8bf643
Merge branch 'dev' into feat/reuse-tabs-in-bookmarks-plugin
andrzejmartyna Dec 28, 2025
9a2e340
remove self-marketing
andrzejmartyna Dec 28, 2025
382dd79
Update THIRD_PARTY_NOTICES
andrzejmartyna Dec 28, 2025
ecfe8c3
Resolved: TabsTracker is not disposed - potential resource leak.
andrzejmartyna Dec 28, 2025
f2dd4d9
Resolved: Fix grammar: use 'its' instead of 'it's'.
andrzejmartyna Dec 28, 2025
944e3d7
Resolved: Minor grammar and style corrections.
andrzejmartyna Dec 28, 2025
951ef81
Resolved: Populate or clarify empty Type and Commit fields for Browse…
andrzejmartyna Dec 28, 2025
f8c1780
Resolved: Populate empty PackageUrl and Copyright fields for Flow.Lau…
andrzejmartyna Dec 28, 2025
7890102
Resolved: Incorrect ClassName - copy-paste error.
andrzejmartyna Dec 28, 2025
b390645
Resolved: Revert changes in Non-English language. They will be update…
andrzejmartyna Dec 29, 2025
876cbfd
Resolved: a few issues reported by coderabbitai
andrzejmartyna Dec 29, 2025
1f45c02
Added TODO.md for ideas to improve
andrzejmartyna Dec 29, 2025
1bac95d
Resolved: Use the wrapper property for consistent binding. by coderab…
andrzejmartyna Dec 29, 2025
0f7ef77
typo
andrzejmartyna Dec 29, 2025
791ff47
Fix typos
Jack251970 Dec 29, 2025
a6acdd4
Fix typos
Jack251970 Dec 29, 2025
d1e5b70
Fix typos
Jack251970 Dec 29, 2025
9d27bcf
feat: added cleaning cache on tabs and windows closing
andrzejmartyna Dec 29, 2025
736e376
Resolved: several issues reported in PR
andrzejmartyna Dec 29, 2025
75a03d0
Fix typos
Jack251970 Dec 30, 2025
cdb2d9e
Take Thread.Sleep out of OnFocusChanged
andrzejmartyna Dec 30, 2025
a472725
Resolved: Thread-safety concern: _browserWindowsTracked modified with…
andrzejmartyna Dec 30, 2025
36b7799
small tweaks
andrzejmartyna Dec 30, 2025
42a91fb
Refactor tab tracking for thread safety and nullability
Jack251970 Dec 30, 2025
9fadec3
Remove null check from _tabsTracker.Dispose() call
Jack251970 Dec 30, 2025
6c5695b
Unify logging messages
andrzejmartyna Dec 31, 2025
680de61
Significant improvements in handling tabs in multi-browsers, multi-wi…
andrzejmartyna Jan 2, 2026
a30180c
doc: README.md updated as known issue was resolved
andrzejmartyna Jan 2, 2026
f901836
improve _structureInvalidations handling
andrzejmartyna Jan 2, 2026
c75cb1a
Resolved: a few PR issues reported by coderabbitai
andrzejmartyna Jan 2, 2026
37c7337
fixed: fixed KeyNotFoundException and improvement
andrzej-martyna-he Jan 3, 2026
84ef5aa
Remove 'experimental' label from reuse tabs option
andrzejmartyna Jan 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="BrowserTabs" Version="0.2.0" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
<PackageReference Include="Flow.Launcher.Localization" Version="0.0.6" />
<PackageReference Include="Microsoft.Data.Sqlite" Version="10.0.1" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,6 @@
<system:String x:Key="flowlauncher_plugin_browserbookmark_guideMessage01">If you are not using Chrome, Firefox or Edge, or you are using their portable version, you need to add bookmarks data directory and select correct browser engine to make this plugin work.</system:String>
<system:String x:Key="flowlauncher_plugin_browserbookmark_guideMessage02">For example: Brave's engine is Chromium; and its default bookmarks data location is: "%LOCALAPPDATA%\BraveSoftware\Brave-Browser\UserData". For Firefox engine, the bookmarks directory is the userdata folder contains the places.sqlite file.</system:String>
<system:String x:Key="flowlauncher_plugin_browserbookmark_enable_favicons">Load favicons (can be time consuming during startup)</system:String>
<system:String x:Key="flowlauncher_plugin_browserbookmark_reuse_tabs">Reuse existing tabs</system:String>

</ResourceDictionary>
</ResourceDictionary>
26 changes: 18 additions & 8 deletions Plugins/Flow.Launcher.Plugin.BrowserBookmark/Main.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using System.Windows.Controls;
using Flow.Launcher.Plugin.BrowserBookmark.Commands;
using Flow.Launcher.Plugin.BrowserBookmark.Models;
using Flow.Launcher.Plugin.BrowserBookmark.Tabs;
using Flow.Launcher.Plugin.BrowserBookmark.Views;
using Flow.Launcher.Plugin.SharedCommands;

Expand All @@ -26,7 +27,9 @@
private static List<Bookmark> _cachedBookmarks = new();

private static bool _initialized = false;


private static readonly TabsReservationService _tabsReservationService = new();

public void Init(PluginInitContext context)
{
Context = context;
Expand All @@ -45,7 +48,7 @@
foreach (var file in files)
{
var extension = Path.GetExtension(file);
if (extension is ".db-shm" or ".db-wal" or ".sqlite-shm" or ".sqlite-wal")

Check warning on line 51 in Plugins/Flow.Launcher.Plugin.BrowserBookmark/Main.cs

View workflow job for this annotation

GitHub Actions / Check Spelling

`wal` is not a recognized word. (unrecognized-spelling)

Check warning on line 51 in Plugins/Flow.Launcher.Plugin.BrowserBookmark/Main.cs

View workflow job for this annotation

GitHub Actions / Check Spelling

`shm` is not a recognized word. (unrecognized-spelling)
{
File.Delete(file);
}
Expand All @@ -58,6 +61,13 @@
}

LoadBookmarksIfEnabled();
SetReuseTabs(_settings.ReuseTabs);
}

public static void SetReuseTabs(bool reuseTabs)
{
_tabsReservationService.EnableTracking(reuseTabs);
_settings.ReuseTabs = reuseTabs;
}

private static void LoadBookmarksIfEnabled()
Expand Down Expand Up @@ -92,7 +102,7 @@
if (!topResults)
{
// Since we mixed chrome and firefox bookmarks, we should order them again
return _cachedBookmarks
return _tabsReservationService.InjectExistingTabs(_settings, _cachedBookmarks
.Select(
c => new Result
{
Expand All @@ -104,19 +114,18 @@
Score = BookmarkLoader.MatchProgram(c, param).Score,
Action = _ =>
{
Context.API.OpenUrl(c.Url);

_tabsReservationService.OpenUrlAndTrack(_settings, c.Url);
return true;
},
ContextData = new BookmarkAttributes { Url = c.Url }
}
)
.Where(r => r.Score > 0)
.ToList();
.ToList());
}
else
{
return _cachedBookmarks
return _tabsReservationService.InjectExistingTabs(_settings, _cachedBookmarks
.Select(
c => new Result
{
Expand All @@ -128,13 +137,13 @@
Score = 5,
Action = _ =>
{
Context.API.OpenUrl(c.Url);
_tabsReservationService.OpenUrlAndTrack(_settings, c.Url);
return true;
},
ContextData = new BookmarkAttributes { Url = c.Url }
}
)
.ToList();
.ToList());
}
}

Expand Down Expand Up @@ -261,6 +270,7 @@

public void Dispose()
{
_tabsReservationService.Dispose();
DisposeFileWatchers();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public class Settings : BaseModel
public bool LoadChromeBookmark { get; set; } = true;
public bool LoadFirefoxBookmark { get; set; } = true;
public bool LoadEdgeBookmark { get; set; } = true;
public bool ReuseTabs { get; set; } = false;

public ObservableCollection<CustomBrowser> CustomChromiumBrowsers { get; set; } = new();
}
104 changes: 104 additions & 0 deletions Plugins/Flow.Launcher.Plugin.BrowserBookmark/THIRD_PARTY_NOTICES.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
[
{
"PackageName": "BrowserTabs",
"PackageVersion": "0.2.0",
"PackageUrl": "https://github.com/jjw24/BrowserTabs",
"Copyright": "Jeremy Wu",
"Authors": [
"Jeremy Wu"
],
"Description": "Library for retrieving all opened browser tabs in Chromium-based and Firefox-based browsers",
"LicenseUrl": "https://licenses.nuget.org/Apache-2.0",
"LicenseType": "Apache-2.0",
"Repository": {
"Type": "git",
"Url": "https://github.com/jjw24/BrowserTabs",
"Commit": "8d81f8f686e82ddceb3e1a8a49e698cec56b5e3d"
}
},
{
"PackageName": "CommunityToolkit.Mvvm",
"PackageVersion": "8.4.0",
"PackageUrl": "https://github.com/CommunityToolkit/dotnet",
"Copyright": "(c) .NET Foundation and Contributors. All rights reserved.",
"Authors": [
"Microsoft"
],
"Description": "This package includes a .NET MVVM library with helpers such as:\r\n - ObservableObject: a base class for objects implementing the INotifyPropertyChanged interface.\r\n - ObservableRecipient: a base class for observable objects with support for the IMessenger service.\r\n - ObservableValidator: a base class for objects implementing the INotifyDataErrorInfo interface.\r\n - RelayCommand: a simple delegate command implementing the ICommand interface.\r\n - AsyncRelayCommand: a delegate command supporting asynchronous operations and cancellation.\r\n - WeakReferenceMessenger: a messaging system to exchange messages through different loosely-coupled objects.\r\n - StrongReferenceMessenger: a high-performance messaging system that trades weak references for speed.\r\n - Ioc: a helper class to configure dependency injection service containers.",
"LicenseUrl": "https://licenses.nuget.org/MIT",
"LicenseType": "MIT",
"Repository": {
"Type": "git",
"Url": "https://github.com/CommunityToolkit/dotnet",
"Commit": "638b41dad30dffabb123a39aa38eabc7e3721371"
}
},
{
"PackageName": "Flow.Launcher.Localization",
"PackageVersion": "0.0.6",
"PackageUrl": "https://github.com/Flow-Launcher/Flow.Launcher.Localization",
"Copyright": "Flow-Launcher",
"Authors": [
"Flow-Launcher"
],
"Description": "Localization toolkit for Flow Launcher and its plugins",
"LicenseUrl": "https://licenses.nuget.org/MIT",
"LicenseType": "MIT",
"Repository": {
"Type": "git",
"Url": "https://github.com/Flow-Launcher/Flow.Launcher.Localization",
"Commit": "456bdc7a986487d691a3ae8d36f8bce7b88b9bc7"
}
},
{
"PackageName": "Microsoft.Data.Sqlite",
"PackageVersion": "10.0.1",
"PackageUrl": "https://docs.microsoft.com/dotnet/standard/data/sqlite/",
"Copyright": "© Microsoft Corporation. All rights reserved.",
"Authors": [
"Microsoft"
],
"Description": "Microsoft.Data.Sqlite is a lightweight ADO.NET provider for SQLite.\r\n\r\nCommonly Used Types:\r\nMicrosoft.Data.Sqlite.SqliteCommand\r\nMicrosoft.Data.Sqlite.SqliteConnection\r\nMicrosoft.Data.Sqlite.SqliteConnectionStringBuilder\r\nMicrosoft.Data.Sqlite.SqliteDataReader\r\nMicrosoft.Data.Sqlite.SqliteException\r\nMicrosoft.Data.Sqlite.SqliteFactory\r\nMicrosoft.Data.Sqlite.SqliteParameter\r\nMicrosoft.Data.Sqlite.SqliteTransaction",
"LicenseUrl": "https://licenses.nuget.org/MIT",
"LicenseType": "MIT",
"Repository": {
"Type": "git",
"Url": "https://github.com/dotnet/dotnet",
"Commit": "fad253f51b461736dfd3cd9c15977bb7493becef"
}
},
{
"PackageName": "SkiaSharp",
"PackageVersion": "3.119.1",
"PackageUrl": "https://go.microsoft.com/fwlink/?linkid=868515",
"Copyright": "© Microsoft Corporation. All rights reserved.",
"Authors": [
"Microsoft"
],
"Description": "SkiaSharp is a cross-platform 2D graphics API for .NET platforms based on Google's Skia Graphics Library.\r\nIt provides a comprehensive 2D API that can be used across mobile, server and desktop models to render images.",

Check warning on line 78 in Plugins/Flow.Launcher.Plugin.BrowserBookmark/THIRD_PARTY_NOTICES.json

View workflow job for this annotation

GitHub Actions / Check Spelling

`Skia` is not a recognized word. (unrecognized-spelling)

Check warning on line 78 in Plugins/Flow.Launcher.Plugin.BrowserBookmark/THIRD_PARTY_NOTICES.json

View workflow job for this annotation

GitHub Actions / Check Spelling

`Skia` is not a recognized word. (unrecognized-spelling)
"LicenseUrl": "https://licenses.nuget.org/MIT",
"LicenseType": "MIT",
"Repository": {
"Type": "git",
"Url": "https://go.microsoft.com/fwlink/?linkid=868515",
"Commit": "cc78b5933d23e6383db5d246e70db915770d55d6"
}
},
{
"PackageName": "Svg.Skia",

Check warning on line 88 in Plugins/Flow.Launcher.Plugin.BrowserBookmark/THIRD_PARTY_NOTICES.json

View workflow job for this annotation

GitHub Actions / Check Spelling

`Skia` is not a recognized word. (unrecognized-spelling)
"PackageVersion": "3.2.1",
"PackageUrl": "https://github.com/wieslawsoltes/Svg.Skia",
"Copyright": "Copyright © Wiesław Šoltés 2025",
"Authors": [
"Wiesław Šoltés"

Check warning on line 93 in Plugins/Flow.Launcher.Plugin.BrowserBookmark/THIRD_PARTY_NOTICES.json

View workflow job for this annotation

GitHub Actions / Check Spelling

`Wies` is not a recognized word. (unrecognized-spelling)
],
"Description": "An SVG rendering library.",
"LicenseUrl": "https://licenses.nuget.org/MIT",
"LicenseType": "MIT",
"Repository": {
"Type": "git",
"Url": "https://github.com/wieslawsoltes/Svg.Skia",
"Commit": "0164d01769a8b577f6dcc678f25d4802a06ff8c0"
}
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Third-party notices

This project uses third-party NuGet packages.

| Reference | Version | License Type | License |
|---------------------------------------------------------------------------------------------|
| BrowserTabs | 0.2.0 | Apache-2.0 | https://licenses.nuget.org/Apache-2.0 |
| CommunityToolkit.Mvvm | 8.4.0 | MIT | https://licenses.nuget.org/MIT |
| Flow.Launcher.Localization | 0.0.6 | MIT | https://licenses.nuget.org/MIT |
| Microsoft.Data.Sqlite | 10.0.1 | MIT | https://licenses.nuget.org/MIT |
| SkiaSharp | 3.119.1 | MIT | https://licenses.nuget.org/MIT |

Check warning on line 11 in Plugins/Flow.Launcher.Plugin.BrowserBookmark/THIRD_PARTY_NOTICES.md

View workflow job for this annotation

GitHub Actions / Check Spelling

`Skia` is not a recognized word. (unrecognized-spelling)
| Svg.Skia | 3.2.1 | MIT | https://licenses.nuget.org/MIT |

Detailed information (package id, version, license, repository URL) is available in [THIRD_PARTY_NOTICES.json](THIRD_PARTY_NOTICES.json).

# Additional credits

Initially there was a plan to integrate [Browser Bookmarks plugin](https://github.com/Flow-Launcher/Flow.Launcher/tree/dev/Plugins/Flow.Launcher.Plugin.BrowserBookmark) and [Browser Tabs plugin](https://github.com/Flow-Launcher/Flow.Launcher.Plugin.BrowserTabs) but it looks like inter-plugin communication or integration of plugins is not possible.
Finally Browser Tabs plugin wasn't used but **its code had great impact on this final solution**.

# How to generate the list

1. Install `dotnet-project-licenses`
1. Use the tool as below
1. Copy markdown above
1. Rename `licenses.json` to `THIRD_PARTY_NOTICES.json` and format the json

```
cd Plugins\Flow.Launcher.Plugin.BrowserBookmark
dotnet tool install --global dotnet-project-licenses
dotnet-project-licenses --input Flow.Launcher.Plugin.BrowserBookmark.csproj --json
```
27 changes: 27 additions & 0 deletions Plugins/Flow.Launcher.Plugin.BrowserBookmark/Tabs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Context

Existing plugins focus on their areas of operation - e.g. Browser Bookmarks on bookmarks only, Browser Tabs on tabs only.
Why not join these two worlds to create synergy?
Especially if one works with **tens or hundreds of bookmarks and open tabs** (I do and I constantly struggle finding the correct tab. It is underestimated mental cost of finding, clicking several times, etc.).

That's where "Reuse tabs" setting in Browser Bookmarks plugin makes sense.

I believe it is in line with why Flow Launcher was created in the first place.
I strongly believe in a higher-level concept of **"just take me to THIS place - as fast as possible, as easy as possible"**.
Thus making bridges between plugins may sometimes produce huge value! BTW wouldn't it be nice to allow inter-plugin communication to create this kind of "bridges" more easily?

# How it works

The core is Browser Bookmarks plugin, unchanged by default.
You may enable "Reuse tabs" in the plugin settings.
Then, whenever one opens a bookmark, it also registers a new tab in its cache.
Next, each time the bookmark is triggered again, it just switches to the existing tab instead of launching a new one.
**It takes milliseconds instead of long seconds** (or sometimes close to half a minute in corporate environments where all is slow even if you have a high-end laptop - you won't believe it until you live it!).

# Alternatives

Reading URLs of existing tabs was tried. It would make mapping of bookmarks to tabs more reliable.
However due to security reasons it has several limitations:

- different browsers expose internals differently
- it is not easily accessible (e.g. you cannot make Chrome expose internal details on a dev TCP port from default profile so user would have to take care about special settings).
Loading
Loading