Skip to content

Commit c601ad7

Browse files
author
GitLab CI
committed
fix: smarter tab discovery — prefer same-origin tabs to avoid duplicates
_discoverTarget now uses 5-tier priority: 1. Exact URL match (reuse existing tab) 2. Same origin + same path (query param differences) 3. Same origin (navigate within same tab) 4. Any non-blank page tab 5. First page tab as fallback This prevents the bug where every fs_connect_cdp/navigate call would pick a random unrelated tab and navigate it to the target URL, creating 6+ duplicate tabs for the same site. Fixes #20
1 parent 37f8b37 commit c601ad7

File tree

1 file changed

+45
-15
lines changed

1 file changed

+45
-15
lines changed

lib/src/bridge/cdp_driver.dart

Lines changed: 45 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2150,29 +2150,59 @@ class CdpDriver implements AppDriver {
21502150
client.close();
21512151

21522152
final tabs = jsonDecode(body) as List;
2153-
// Prefer tab already showing the target URL
2154-
for (final tab in tabs) {
2155-
if (tab is Map && tab['type'] == 'page' && tab['url'] == _url) {
2153+
final pageTabs = tabs.where((t) => t is Map && t['type'] == 'page').cast<Map>().toList();
2154+
2155+
// 1. Exact URL match
2156+
for (final tab in pageTabs) {
2157+
if (tab['url'] == _url) {
2158+
connectedToExistingTab = true;
21562159
return tab['webSocketDebuggerUrl'] as String?;
21572160
}
21582161
}
2159-
// Prefer page with actual content (not devtools/about:blank)
2160-
for (final tab in tabs) {
2161-
if (tab is Map &&
2162-
tab['type'] == 'page' &&
2163-
tab['url'] is String &&
2164-
!tab['url'].toString().startsWith('devtools://') &&
2165-
!tab['url'].toString().startsWith('chrome://') &&
2166-
tab['url'] != 'about:blank') {
2167-
return tab['webSocketDebuggerUrl'] as String?;
2162+
// 2. Same-origin match (e.g. URL with different query params)
2163+
if (_url.isNotEmpty) {
2164+
final targetUri = Uri.tryParse(_url);
2165+
if (targetUri != null) {
2166+
final targetOrigin = '${targetUri.scheme}://${targetUri.host}';
2167+
final targetPath = targetUri.path;
2168+
for (final tab in pageTabs) {
2169+
final tabUrl = tab['url']?.toString() ?? '';
2170+
final tabUri = Uri.tryParse(tabUrl);
2171+
if (tabUri != null) {
2172+
final tabOrigin = '${tabUri.scheme}://${tabUri.host}';
2173+
// Same origin + same path = very likely the same page
2174+
if (tabOrigin == targetOrigin && tabUri.path == targetPath) {
2175+
connectedToExistingTab = true;
2176+
return tab['webSocketDebuggerUrl'] as String?;
2177+
}
2178+
}
2179+
}
2180+
// 3. Same-origin only (different path — will navigate within same tab)
2181+
for (final tab in pageTabs) {
2182+
final tabUrl = tab['url']?.toString() ?? '';
2183+
final tabUri = Uri.tryParse(tabUrl);
2184+
if (tabUri != null) {
2185+
final tabOrigin = '${tabUri.scheme}://${tabUri.host}';
2186+
if (tabOrigin == targetOrigin) {
2187+
return tab['webSocketDebuggerUrl'] as String?;
2188+
}
2189+
}
2190+
}
21682191
}
21692192
}
2170-
// Fall back to first page tab
2171-
for (final tab in tabs) {
2172-
if (tab is Map && tab['type'] == 'page') {
2193+
// 4. Prefer page with actual content (not devtools/about:blank)
2194+
for (final tab in pageTabs) {
2195+
final tabUrl = tab['url']?.toString() ?? '';
2196+
if (!tabUrl.startsWith('devtools://') &&
2197+
!tabUrl.startsWith('chrome://') &&
2198+
tabUrl != 'about:blank') {
21732199
return tab['webSocketDebuggerUrl'] as String?;
21742200
}
21752201
}
2202+
// 5. Fall back to first page tab
2203+
if (pageTabs.isNotEmpty) {
2204+
return pageTabs.first['webSocketDebuggerUrl'] as String?;
2205+
}
21762206
} catch (_) {
21772207
await Future.delayed(const Duration(milliseconds: 500));
21782208
}

0 commit comments

Comments
 (0)