fix new tab assignments
This commit is contained in:
@@ -21,12 +21,14 @@ import 'dart:async';
|
|||||||
|
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
|
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
|
||||||
import 'package:nullability/nullability.dart';
|
import 'package:nullability/nullability.dart';
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
import 'package:synchronized/synchronized.dart';
|
import 'package:synchronized/synchronized.dart';
|
||||||
import 'package:weblibre/core/logger.dart';
|
import 'package:weblibre/core/logger.dart';
|
||||||
import 'package:weblibre/core/routing/routes.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/states/tab.dart';
|
||||||
import 'package:weblibre/features/geckoview/domain/entities/tab_container_selection.dart';
|
import 'package:weblibre/features/geckoview/domain/entities/tab_container_selection.dart';
|
||||||
import 'package:weblibre/features/geckoview/domain/providers.dart';
|
import 'package:weblibre/features/geckoview/domain/providers.dart';
|
||||||
@@ -50,6 +52,20 @@ import 'package:weblibre/utils/debouncer.dart';
|
|||||||
|
|
||||||
part 'tab.g.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 {
|
sealed class TabBackPromptBehavior {
|
||||||
const TabBackPromptBehavior();
|
const TabBackPromptBehavior();
|
||||||
}
|
}
|
||||||
@@ -124,13 +140,32 @@ class TabRepository extends _$TabRepository {
|
|||||||
}) async {
|
}) async {
|
||||||
final tabDao = ref.read(tabDatabaseProvider).tabDao;
|
final tabDao = ref.read(tabDatabaseProvider).tabDao;
|
||||||
|
|
||||||
final assignedContainer = switch (containerSelection) {
|
var assignedContainer = switch (containerSelection) {
|
||||||
UseSelectedContainerTabSelection() =>
|
UseSelectedContainerTabSelection() =>
|
||||||
await ref.read(selectedContainerProvider.notifier).fetchData(),
|
await ref.read(selectedContainerProvider.notifier).fetchData(),
|
||||||
UnassignedContainerTabSelection() => null,
|
UnassignedContainerTabSelection() => null,
|
||||||
SpecificContainerTabSelection(:final container) => container,
|
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
|
// For isolated tabs, skip parent context validation since
|
||||||
// isolated tabs use their own immutable context ID.
|
// isolated tabs use their own immutable context ID.
|
||||||
final validatedParentId = tabMode is IsolatedTabMode
|
final validatedParentId = tabMode is IsolatedTabMode
|
||||||
@@ -869,7 +904,7 @@ class TabRepository extends _$TabRepository {
|
|||||||
currentTabState.historyState.items.isEmpty;
|
currentTabState.historyState.items.isEmpty;
|
||||||
|
|
||||||
if (event.blocked || tabIsEmpty) {
|
if (event.blocked || tabIsEmpty) {
|
||||||
await addTab(
|
final newTabId = await addTab(
|
||||||
url: uri,
|
url: uri,
|
||||||
tabMode: currentTabState.tabMode,
|
tabMode: currentTabState.tabMode,
|
||||||
containerSelection: TabContainerSelection.specific(
|
containerSelection: TabContainerSelection.specific(
|
||||||
@@ -885,6 +920,10 @@ class TabRepository extends _$TabRepository {
|
|||||||
|
|
||||||
if (currentTabState.historyState.items.isEmpty) {
|
if (currentTabState.historyState.items.isEmpty) {
|
||||||
await closeTab(currentTabState.id);
|
await closeTab(currentTabState.id);
|
||||||
|
if (!ref.mounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await selectTab(newTabId);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
final tabContainerId = await ref
|
final tabContainerId = await ref
|
||||||
|
|||||||
@@ -65,11 +65,17 @@ _buildSharingIntentTransformer(
|
|||||||
final shortcutContextId = intent.action == 'android.intent.action.VIEW'
|
final shortcutContextId = intent.action == 'android.intent.action.VIEW'
|
||||||
? intent.extra['pwa_context_id'] as String?
|
? intent.extra['pwa_context_id'] as String?
|
||||||
: null;
|
: null;
|
||||||
|
final shortcutContainerMode =
|
||||||
|
intent.extra['shortcut_container_mode'] as String?;
|
||||||
|
final hasShortcutContainerMetadata =
|
||||||
|
shortcutContextId != null || shortcutContainerMode != null;
|
||||||
final containerMode = intent.action == 'android.intent.action.VIEW'
|
final containerMode = intent.action == 'android.intent.action.VIEW'
|
||||||
? IntentContainerMode.fromWireValue(
|
? hasShortcutContainerMetadata
|
||||||
intent.extra['shortcut_container_mode'] as String?,
|
? IntentContainerMode.fromWireValue(
|
||||||
contextId: shortcutContextId,
|
shortcutContainerMode,
|
||||||
)
|
contextId: shortcutContextId,
|
||||||
|
)
|
||||||
|
: IntentContainerMode.unassigned
|
||||||
: IntentContainerMode.useSelected;
|
: IntentContainerMode.useSelected;
|
||||||
|
|
||||||
final allowed = await gatekeeper.shouldAllow(
|
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),
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user