fix new tab assignments

This commit is contained in:
Fabian Freund
2026-05-26 13:42:16 +02:00
parent 481e991e97
commit 33fd8e65f1
3 changed files with 118 additions and 6 deletions
@@ -21,12 +21,14 @@ import 'dart:async';
import 'package:collection/collection.dart';
import 'package:drift/drift.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:nullability/nullability.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:synchronized/synchronized.dart';
import 'package:weblibre/core/logger.dart';
import 'package:weblibre/core/routing/routes.dart';
import 'package:weblibre/extensions/uri.dart';
import 'package:weblibre/features/geckoview/domain/entities/states/tab.dart';
import 'package:weblibre/features/geckoview/domain/entities/tab_container_selection.dart';
import 'package:weblibre/features/geckoview/domain/providers.dart';
@@ -50,6 +52,20 @@ import 'package:weblibre/utils/debouncer.dart';
part 'tab.g.dart';
@visibleForTesting
ContainerData? resolveAssignedContainerForTabOpen({
required TabContainerSelection containerSelection,
required ContainerData? requestedContainer,
required ContainerData? siteAssignedContainer,
}) {
return switch (containerSelection) {
UseSelectedContainerTabSelection() =>
siteAssignedContainer ?? requestedContainer,
UnassignedContainerTabSelection() ||
SpecificContainerTabSelection() => requestedContainer,
};
}
sealed class TabBackPromptBehavior {
const TabBackPromptBehavior();
}
@@ -124,13 +140,32 @@ class TabRepository extends _$TabRepository {
}) async {
final tabDao = ref.read(tabDatabaseProvider).tabDao;
final assignedContainer = switch (containerSelection) {
var assignedContainer = switch (containerSelection) {
UseSelectedContainerTabSelection() =>
await ref.read(selectedContainerProvider.notifier).fetchData(),
UnassignedContainerTabSelection() => null,
SpecificContainerTabSelection(:final container) => container,
};
if (tabMode is RegularTabMode &&
url != null &&
url.hasAuthority &&
url.isHttpOrHttps) {
final siteAssignedContainerId = await ref
.read(containerRepositoryProvider.notifier)
.siteAssignedContainerId(url);
final siteAssignedContainer = await siteAssignedContainerId.mapNotNull(
(id) =>
ref.read(containerRepositoryProvider.notifier).getContainerData(id),
);
assignedContainer = resolveAssignedContainerForTabOpen(
containerSelection: containerSelection,
requestedContainer: assignedContainer,
siteAssignedContainer: siteAssignedContainer,
);
}
// For isolated tabs, skip parent context validation since
// isolated tabs use their own immutable context ID.
final validatedParentId = tabMode is IsolatedTabMode
@@ -869,7 +904,7 @@ class TabRepository extends _$TabRepository {
currentTabState.historyState.items.isEmpty;
if (event.blocked || tabIsEmpty) {
await addTab(
final newTabId = await addTab(
url: uri,
tabMode: currentTabState.tabMode,
containerSelection: TabContainerSelection.specific(
@@ -885,6 +920,10 @@ class TabRepository extends _$TabRepository {
if (currentTabState.historyState.items.isEmpty) {
await closeTab(currentTabState.id);
if (!ref.mounted) {
return;
}
await selectTab(newTabId);
}
} else {
final tabContainerId = await ref
@@ -65,11 +65,17 @@ _buildSharingIntentTransformer(
final shortcutContextId = intent.action == 'android.intent.action.VIEW'
? intent.extra['pwa_context_id'] as String?
: null;
final shortcutContainerMode =
intent.extra['shortcut_container_mode'] as String?;
final hasShortcutContainerMetadata =
shortcutContextId != null || shortcutContainerMode != null;
final containerMode = intent.action == 'android.intent.action.VIEW'
? IntentContainerMode.fromWireValue(
intent.extra['shortcut_container_mode'] as String?,
contextId: shortcutContextId,
)
? hasShortcutContainerMetadata
? IntentContainerMode.fromWireValue(
shortcutContainerMode,
contextId: shortcutContextId,
)
: IntentContainerMode.unassigned
: IntentContainerMode.useSelected;
final allowed = await gatekeeper.shouldAllow(
@@ -0,0 +1,67 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:weblibre/features/geckoview/domain/entities/tab_container_selection.dart';
import 'package:weblibre/features/geckoview/domain/repositories/tab.dart';
import 'package:weblibre/features/geckoview/features/tabs/data/models/container_data.dart';
void main() {
group('resolveAssignedContainerForTabOpen', () {
final selectedContainer = _container('selected', 'selected-context');
final siteAssignedContainer = _container('assigned', 'assigned-context');
test('prefers the site-assigned container for useSelected', () {
final resolved = resolveAssignedContainerForTabOpen(
containerSelection: const TabContainerSelection.useSelected(),
requestedContainer: selectedContainer,
siteAssignedContainer: siteAssignedContainer,
);
expect(resolved, siteAssignedContainer);
});
test(
'falls back to the requested container when no site assignment exists',
() {
final resolved = resolveAssignedContainerForTabOpen(
containerSelection: const TabContainerSelection.useSelected(),
requestedContainer: selectedContainer,
siteAssignedContainer: null,
);
expect(resolved, selectedContainer);
},
);
test('preserves an explicit specific container selection', () {
final explicitContainer = _container('explicit', 'explicit-context');
final resolved = resolveAssignedContainerForTabOpen(
containerSelection: TabContainerSelection.specific(explicitContainer),
requestedContainer: explicitContainer,
siteAssignedContainer: siteAssignedContainer,
);
expect(resolved, explicitContainer);
});
test('preserves an explicit unassigned selection', () {
final resolved = resolveAssignedContainerForTabOpen(
containerSelection: const TabContainerSelection.unassigned(),
requestedContainer: null,
siteAssignedContainer: siteAssignedContainer,
);
expect(resolved, isNull);
});
});
}
ContainerData _container(String id, String contextId) {
return ContainerData(
id: id,
name: id,
color: Colors.blue,
orderKey: id,
metadata: ContainerMetadata.withDefaults(contextualIdentity: contextId),
);
}