blob: d3c74e346d51874d0df49912b044dc7503b93218 [file]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/browser_navigator.h"
#include "base/memory/raw_ptr.h"
#include "base/notimplemented.h"
#include "base/task/single_thread_task_runner.h"
#include "chrome/browser/android/tab_android.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/tab_list/tab_list_interface.h"
#include "chrome/browser/ui/android/tab_model/tab_model.h"
#include "chrome/browser/ui/browser_navigator_params.h"
#include "chrome/browser/ui/browser_navigator_params_utils.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/browser_window/public/create_browser_window.h"
#include "components/tabs/public/tab_interface.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/web_contents.h"
#include "ui/base/window_open_disposition.h"
namespace {
// Returns true if inserting WebContents via `contents_to_insert` is supported.
// TODO(crbug.com/477944342): Expand the scenarios where we support it.
bool SupportsContentsToInsert(NavigateParams* params) {
switch (params->disposition) {
case WindowOpenDisposition::NEW_BACKGROUND_TAB:
case WindowOpenDisposition::NEW_FOREGROUND_TAB:
return true;
default:
return false;
}
}
// Returns true if NavigateParams are valid, false otherwise.
bool ValidNavigateParams(NavigateParams* params) {
// TODO (crbug.com/441594986) Confirm this is correct.
DCHECK(params->browser);
DCHECK(!params->contents_to_insert || SupportsContentsToInsert(params));
DCHECK(!params->switch_to_singleton_tab);
if (!params->initiating_profile) {
params->initiating_profile = params->browser->GetProfile();
}
DCHECK(params->initiating_profile);
if (params->initiating_profile->ShutdownStarted()) {
// Don't navigate when the profile is shutting down.
return false;
}
// If OFF_THE_RECORD disposition does not require a new window,
// convert it into NEW_FOREGROUND_TAB.
if (params->disposition == WindowOpenDisposition::OFF_THE_RECORD &&
params->initiating_profile->IsOffTheRecord()) {
params->disposition = WindowOpenDisposition::NEW_FOREGROUND_TAB;
}
return true;
}
// Helper to create/locate windows.
void GetOrCreateBrowserWindowForDisposition(
NavigateParams* params,
base::OnceCallback<void(BrowserWindowInterface*)> callback) {
raw_ptr<Profile> profile = params->initiating_profile;
switch (params->disposition) {
case WindowOpenDisposition::OFF_THE_RECORD:
// The existing profile was already checked and is not OTR
// so we get an OTR profile and create a new window.
profile = profile->GetPrimaryOTRProfile(/*create_if_needed=*/true);
[[fallthrough]];
case WindowOpenDisposition::NEW_WINDOW: {
BrowserWindowCreateParams create_params(*profile, params->user_gesture);
CreateBrowserWindow(std::move(create_params), std::move(callback));
break;
}
case WindowOpenDisposition::NEW_POPUP: {
BrowserWindowCreateParams create_params(
BrowserWindowInterface::Type::TYPE_POPUP, *profile,
params->user_gesture);
CreateBrowserWindow(std::move(create_params), std::move(callback));
break;
}
case WindowOpenDisposition::NEW_BACKGROUND_TAB:
[[fallthrough]];
case WindowOpenDisposition::NEW_FOREGROUND_TAB:
[[fallthrough]];
case WindowOpenDisposition::CURRENT_TAB: {
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), params->browser));
break;
}
default:
NOTIMPLEMENTED();
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), nullptr));
}
}
// Helper called in GetOrCreateTabForDisposition().
// Maps the NavigateParams to the appropriate Android TabLaunchType.
TabModel::TabLaunchType GetTabLaunchType(const NavigateParams* params) {
using TabLaunchType = TabModel::TabLaunchType;
bool is_background =
params->disposition == WindowOpenDisposition::NEW_BACKGROUND_TAB;
bool is_link = ui::PageTransitionCoreTypeIs(params->transition,
ui::PAGE_TRANSITION_LINK);
// 1. Explicit Index:
// If an explicit index is requested, use FROM_CHROME_UI. This type does NOT
// trigger "adjacency" logic in Java, allowing the passed index to be
// respected.
if (params->tabstrip_index != -1) {
return is_background ? TabLaunchType::FROM_SYNC_BACKGROUND
: TabLaunchType::FROM_CHROME_UI;
}
// 2. Background Navigation:
if (is_background) {
if (is_link) {
// Background Link (e.g., Ctrl+Click).
// Use FROM_LONGPRESS_BACKGROUND to trigger "Group with Parent" logic
// while keeping the tab in the background.
return TabLaunchType::FROM_LONGPRESS_BACKGROUND;
}
// Generic Background (e.g., Middle-click Bookmark).
// Use FROM_BROWSER_ACTIONS. In Java, this forces the tab to the END
// of the model (ignoring adjacency) and keeps it backgrounded.
return TabLaunchType::FROM_BROWSER_ACTIONS;
}
// 3. Foreground Navigation:
if (is_link) {
// Foreground Link.
// Use FROM_LINK to trigger "Adjacent to Parent" logic and selection.
return TabLaunchType::FROM_LINK;
}
if (ui::PageTransitionCoreTypeIs(params->transition,
ui::PAGE_TRANSITION_TYPED) ||
ui::PageTransitionCoreTypeIs(params->transition,
ui::PAGE_TRANSITION_GENERATED)) {
// Omnibox Navigation.
// Use FROM_OMNIBOX to disable adjacency logic (placing it at the end).
return TabLaunchType::FROM_OMNIBOX;
}
// Default Generic Foreground.
return TabLaunchType::FROM_CHROME_UI;
}
// Helper to create/locate tabs.
// If params->contents_to_insert is non-null, std::move() will be called on it.
raw_ptr<tabs::TabInterface> GetOrCreateTabForDisposition(
NavigateParams* params) {
// On Android, TabListInterface is TabModel.
TabModel* tab_model =
static_cast<TabModel*>(TabListInterface::From(params->browser));
if (!tab_model) {
return nullptr;
}
switch (params->disposition) {
case WindowOpenDisposition::NEW_BACKGROUND_TAB:
[[fallthrough]];
case WindowOpenDisposition::NEW_FOREGROUND_TAB: {
// Select the TabLaunchType.
TabModel::TabLaunchType launch_type = GetTabLaunchType(params);
// Identify parent tab.
// Parent tab is set to nullptr to avoid adjacency overrides when
// launching from the Omnibox (where we always append to the end).
TabAndroid* parent = nullptr;
if (params->source_contents &&
launch_type != TabModel::TabLaunchType::FROM_OMNIBOX) {
parent = TabAndroid::FromWebContents(params->source_contents);
}
// Use the supplied WebContents or create a new one.
std::unique_ptr<content::WebContents> web_contents;
if (params->contents_to_insert) {
web_contents = std::move(params->contents_to_insert);
} else {
content::WebContents::CreateParams create_params(
params->initiating_profile);
web_contents = content::WebContents::Create(create_params);
}
// Create a new tab.
tabs::TabInterface* new_tab = tab_model->CreateTab(
parent, std::move(web_contents), params->tabstrip_index, launch_type,
/*should_pin=*/false);
if (!new_tab || !new_tab->GetContents()) {
return nullptr;
}
// Bring the new tab to the foreground if necessary.
if (params->disposition != WindowOpenDisposition::NEW_BACKGROUND_TAB) {
tabs::TabHandle new_tab_handle = new_tab->GetHandle();
tab_model->HighlightTabs(new_tab_handle, {new_tab_handle});
}
// The new tab's WebContents is the target for our navigation.
params->source_contents = new_tab->GetContents();
return new_tab;
}
case WindowOpenDisposition::CURRENT_TAB:
if (params->source_contents) {
return tabs::TabInterface::GetFromContents(params->source_contents);
}
// Otherwise use the active tab.
[[fallthrough]];
case WindowOpenDisposition::OFF_THE_RECORD:
// A new incognito window has already been created with a new tab.
[[fallthrough]];
case WindowOpenDisposition::NEW_POPUP:
[[fallthrough]];
case WindowOpenDisposition::NEW_WINDOW: {
// A new tab is already created when the new window is created on Android.
// Just get the active tab.
raw_ptr<tabs::TabInterface> active_tab = tab_model->GetActiveTab();
CHECK(active_tab);
params->source_contents = active_tab->GetContents();
return active_tab;
}
default:
NOTIMPLEMENTED();
return nullptr;
}
}
base::WeakPtr<content::NavigationHandle> GetTabAndPerformNavigation(
NavigateParams* params) {
bool is_contents_inserted = params->contents_to_insert != nullptr;
tabs::TabInterface* tab = GetOrCreateTabForDisposition(params);
if (!tab || !tab->GetContents()) {
return nullptr;
}
params->navigated_or_inserted_contents = tab->GetContents();
// Skip navigation if we inserted existing contents.
if (is_contents_inserted || !params->source_contents) {
return nullptr;
}
// Perform navigation.
content::NavigationController::LoadURLParams load_url_params =
LoadURLParamsFromNavigateParams(params);
return params->navigated_or_inserted_contents->GetController()
.LoadURLWithParams(load_url_params);
}
void GetTabAndPerformNavigationAsync(
NavigateParams* params,
base::OnceCallback<void(base::WeakPtr<content::NavigationHandle>)> callback,
BrowserWindowInterface* bwi) {
base::WeakPtr<content::NavigationHandle> handle = nullptr;
if (bwi) {
params->browser = bwi;
handle = GetTabAndPerformNavigation(params);
}
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), handle));
}
} // end namespace
base::WeakPtr<content::NavigationHandle> Navigate(NavigateParams* params) {
if (!ValidNavigateParams(params)) {
return nullptr;
}
// Only handles dispositions that do not create new windows.
if (params->disposition != WindowOpenDisposition::CURRENT_TAB &&
params->disposition != WindowOpenDisposition::NEW_BACKGROUND_TAB &&
params->disposition != WindowOpenDisposition::NEW_FOREGROUND_TAB) {
return nullptr;
}
return GetTabAndPerformNavigation(params);
}
void Navigate(NavigateParams* params,
base::OnceCallback<void(base::WeakPtr<content::NavigationHandle>)>
callback) {
if (!ValidNavigateParams(params)) {
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), nullptr));
return;
}
GetOrCreateBrowserWindowForDisposition(
params, base::BindOnce(&GetTabAndPerformNavigationAsync, params,
std::move(callback)));
}