fix container assignment issue

This commit is contained in:
Fabian Freund
2026-07-06 16:53:08 +02:00
parent 252735ee7e
commit 10052ed3a3
2 changed files with 53 additions and 4 deletions
@@ -940,6 +940,23 @@ class TabRepository extends _$TabRepository {
return; return;
} }
// The tab is already in the target container, so there is nothing
// to reconcile. This notably fires when reassigning a tab into a
// container that shares the default Gecko context: assignContainer
// recreates the tab in the target container, and that new tab's
// load re-triggers this event. Without this guard the transiently
// empty new tab would be treated as an empty tab and churn yet
// another tab (re-prompting app-links).
final currentTabContainerId = await ref
.read(tabDataRepositoryProvider.notifier)
.getTabContainerId(currentTabState.id);
if (!ref.mounted) {
return;
}
if (currentTabContainerId == targetContainerId) {
return;
}
final tabIsEmpty = final tabIsEmpty =
currentTabState.url == TabState.defaultUrl && currentTabState.url == TabState.defaultUrl &&
currentTabState.historyState.items.isEmpty; currentTabState.historyState.items.isEmpty;
@@ -985,7 +1002,11 @@ class TabRepository extends _$TabRepository {
if (originUri == null) { if (originUri == null) {
await ref await ref
.read(tabDataRepositoryProvider.notifier) .read(tabDataRepositoryProvider.notifier)
.assignContainer(latestTabState.id, containerData); .assignContainer(
latestTabState.id,
containerData,
replacementUrl: uri,
);
} else if (latestTabState.url == originUri) { } else if (latestTabState.url == originUri) {
await ref await ref
.read(tabDataRepositoryProvider.notifier) .read(tabDataRepositoryProvider.notifier)
@@ -993,6 +1014,7 @@ class TabRepository extends _$TabRepository {
latestTabState.id, latestTabState.id,
containerData, containerData,
closeOldTab: false, closeOldTab: false,
replacementUrl: uri,
); );
} else { } else {
logger.w( logger.w(
@@ -17,6 +17,7 @@
* You should have received a copy of the GNU Affero General Public License * You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.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/selected_tab.dart'; import 'package:weblibre/features/geckoview/domain/providers/selected_tab.dart';
@@ -37,6 +38,14 @@ class TabDataRepository extends _$TabDataRepository {
String tabId, String tabId,
ContainerData targetContainer, { ContainerData targetContainer, {
bool closeOldTab = true, bool closeOldTab = true,
// When the reassignment is driven by a navigation to a site assigned to
// [targetContainer] (see the site-assignment listener), the recreated tab
// must load that requested URL rather than the tab's current URL. The
// current URL can be a stale origin page (e.g. the search result the link
// was opened from) when the assignment fires before the navigation
// commits. Left null for manual moves (drag/drop, menu), which keep the
// tab on its current page.
Uri? replacementUrl,
}) async { }) async {
final selectedTabId = ref.read(selectedTabProvider); final selectedTabId = ref.read(selectedTabProvider);
final tabState = ref.read(tabStatesProvider)[tabId]; final tabState = ref.read(tabStatesProvider)[tabId];
@@ -52,8 +61,20 @@ class TabDataRepository extends _$TabDataRepository {
final currentContainerData = await getTabContainerData(tabId); final currentContainerData = await getTabContainerData(tabId);
if (targetContainer.metadata.contextualIdentity == final sameContext =
currentContainerData?.metadata.contextualIdentity) { targetContainer.metadata.contextualIdentity ==
currentContainerData?.metadata.contextualIdentity;
// A pure regrouping (no navigation) can stay in place when the Gecko
// context is unchanged — this is the manual-move case (drag/drop, menu),
// which passes no [replacementUrl] and keeps the tab on its current page.
//
// An assignment-driven move ([replacementUrl] set) must send the tab to the
// requested site. Reloading in place on the origin tab is unreliable after
// an app-link "open in app" cancel (the engine session is left in a state
// where the load never commits), so we recreate the tab in that case even
// when the context is unchanged. The fresh session loads the URL reliably.
if (sameContext && replacementUrl == null) {
await ref await ref
.read(tabDatabaseProvider) .read(tabDatabaseProvider)
.tabDao .tabDao
@@ -67,13 +88,19 @@ class TabDataRepository extends _$TabDataRepository {
await ref await ref
.read(tabRepositoryProvider.notifier) .read(tabRepositoryProvider.notifier)
.addTab( .addTab(
url: tabState.url, url: replacementUrl ?? tabState.url,
tabMode: tabState.tabMode, tabMode: tabState.tabMode,
containerSelection: TabContainerSelection.specific( containerSelection: TabContainerSelection.specific(
targetContainer, targetContainer,
), ),
// parentId defaults to null - breaks parent chain when changing contextual identity // parentId defaults to null - breaks parent chain when changing contextual identity
selectTab: selectedTabId == tabState.id, selectTab: selectedTabId == tabState.id,
// Assignment-driven navigation to an assigned site: bypass the
// app-links delegate so cancelling an "open in app" prompt does
// not re-trigger it on the recreated tab's load.
flags: replacementUrl != null
? LoadUrlFlags.LOAD_FLAGS_BYPASS_LOAD_URI_DELEGATE
: LoadUrlFlags.NONE,
); );
} }
} }