| // Copyright 2012 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 <algorithm> |
| #include <memory> |
| #include <optional> |
| #include <string> |
| #include <string_view> |
| #include <utility> |
| |
| #include "base/memory/raw_ptr.h" |
| #include "base/notimplemented.h" |
| #include "base/notreached.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/trace_event/trace_event.h" |
| #include "build/build_config.h" |
| #include "chrome/browser/apps/app_service/web_contents_app_id_utils.h" |
| #include "chrome/browser/apps/link_capturing/link_capturing_tab_data.h" |
| #include "chrome/browser/browser_about_handler.h" |
| #include "chrome/browser/picture_in_picture/picture_in_picture_window_manager.h" |
| #include "chrome/browser/platform_util.h" |
| #include "chrome/browser/prefs/incognito_mode_prefs.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/renderer_host/chrome_navigation_ui_data.h" |
| #include "chrome/browser/signin/signin_promo.h" |
| #include "chrome/browser/tab_contents/tab_util.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/browser_finder.h" |
| #include "chrome/browser/ui/browser_navigator_params.h" |
| #include "chrome/browser/ui/browser_window.h" |
| #include "chrome/browser/ui/location_bar/location_bar.h" |
| #include "chrome/browser/ui/singleton_tabs.h" |
| #include "chrome/browser/ui/status_bubble.h" |
| #include "chrome/browser/ui/tab_helpers.h" |
| #include "chrome/browser/ui/tabs/tab_strip_model.h" |
| #include "chrome/browser/ui/tabs/tab_strip_user_gesture_details.h" |
| #include "chrome/browser/ui/web_applications/app_browser_controller.h" |
| #include "chrome/browser/ui/web_applications/web_app_tabbed_utils.h" |
| #include "chrome/browser/web_applications/web_app_helpers.h" |
| #include "chrome/browser/web_applications/web_app_tab_helper.h" |
| #include "chrome/common/url_constants.h" |
| #include "components/captive_portal/core/buildflags.h" |
| #include "components/constrained_window/constrained_window_views.h" |
| #include "components/no_state_prefetch/browser/no_state_prefetch_manager.h" |
| #include "components/password_manager/content/common/web_ui_constants.h" |
| #include "components/policy/core/common/policy_pref_names.h" |
| #include "components/prefs/pref_service.h" |
| #include "content/public/browser/browser_url_handler.h" |
| #include "content/public/browser/navigation_entry.h" |
| #include "content/public/browser/picture_in_picture_window_controller.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/browser/render_process_host.h" |
| #include "content/public/browser/render_view_host.h" |
| #include "content/public/browser/site_isolation_policy.h" |
| #include "content/public/browser/web_contents.h" |
| #include "extensions/buildflags/buildflags.h" |
| #include "ui/base/window_open_disposition.h" |
| #include "ui/display/display.h" |
| #include "ui/display/screen.h" |
| #include "url/url_constants.h" |
| |
| #if !BUILDFLAG(IS_ANDROID) |
| #include "chrome/browser/ui/web_applications/navigation_capturing_process.h" |
| #include "chrome/browser/ui/web_applications/web_app_launch_utils.h" |
| #endif // !BUILDFLAG(IS_ANDROID) |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| #include "ash/public/cpp/multi_user_window_manager.h" |
| #include "chrome/browser/ui/ash/multi_user/multi_user_window_manager_helper.h" |
| #include "chrome/browser/ui/ash/system_web_apps/system_web_app_ui_utils.h" |
| #include "components/account_id/account_id.h" |
| #endif |
| |
| #if defined(USE_AURA) |
| #include "ui/aura/window.h" |
| #endif |
| |
| #if BUILDFLAG(ENABLE_CAPTIVE_PORTAL_DETECTION) |
| #include "components/captive_portal/content/captive_portal_tab_helper.h" |
| #endif |
| |
| using content::GlobalRequestID; |
| using content::NavigationController; |
| using content::WebContents; |
| using WebExposedIsolationLevel = content::WebExposedIsolationLevel; |
| |
| namespace { |
| |
| // Returns true if |params.browser| exists and can open a new tab for |
| // |params.url|. Not all browsers support multiple tabs, such as app frames and |
| // popups. TYPE_APP will open a new tab if the browser was launched from a |
| // template, otherwise only if the URL is within the app scope. |
| bool WindowCanOpenTabs(const NavigateParams& params) { |
| if (!params.browser) { |
| return false; |
| } |
| |
| // If the browser is created from a template, we do not need to check if the |
| // url is in the app scope since we know it was saved directly from the app. |
| if (params.browser->creation_source() != |
| Browser::CreationSource::kDeskTemplate && |
| params.browser->app_controller() && |
| !params.browser->app_controller()->IsUrlInAppScope(params.url)) { |
| return false; |
| } |
| |
| return params.browser->CanSupportWindowFeature(Browser::FEATURE_TABSTRIP) || |
| params.browser->tab_strip_model()->empty(); |
| } |
| |
| // Finds an existing Browser compatible with |profile|, making a new one if no |
| // such Browser is located. |
| Browser* GetOrCreateBrowser(Profile* profile, bool user_gesture) { |
| Browser* browser = chrome::FindTabbedBrowser(profile, false); |
| |
| if (!browser && Browser::GetCreationStatusForProfile(profile) == |
| Browser::CreationStatus::kOk) { |
| browser = Browser::Create(Browser::CreateParams(profile, user_gesture)); |
| } |
| return browser; |
| } |
| |
| bool IncognitoModeForced(const Profile* profile) { |
| return IncognitoModePrefs::GetAvailability(profile->GetPrefs()) == |
| policy::IncognitoModeAvailability::kForced; |
| } |
| |
| // Change some of the navigation parameters based on the particular URL. |
| // Returns true on success. Otherwise, if changing params leads the browser |
| // into an erroneous state, returns false. |
| bool AdjustNavigateParamsForURL(NavigateParams* params) { |
| // Check for some chrome:// pages which we always want to open in a |
| // non-incognito window. Note that even though a ChromeOS guest session is |
| // technically an incognito window, these URLs are allowed. |
| Profile* profile = params->initiating_profile; |
| if (!params->contents_to_insert && !params->switch_to_singleton_tab && |
| !IsURLAllowedInIncognito(params->url) && !profile->IsGuestSession() && |
| (profile->IsOffTheRecord() || |
| params->disposition == WindowOpenDisposition::OFF_THE_RECORD)) { |
| profile = profile->GetOriginalProfile(); |
| |
| // If incognito is forced, we punt. |
| if (IncognitoModeForced(profile)) { |
| return false; |
| } |
| params->disposition = WindowOpenDisposition::SINGLETON_TAB; |
| params->browser = GetOrCreateBrowser(profile, params->user_gesture); |
| params->window_action = NavigateParams::SHOW_WINDOW; |
| } |
| |
| // Clicking a link to the home tab in a tabbed web app should always open the |
| // link in the home tab. |
| if (web_app::IsHomeTabUrl(params->browser, params->url)) { |
| params->browser->tab_strip_model()->ActivateTabAt(0); |
| // If the navigation URL is the same as the current home tab URL, skip the |
| // navigation. |
| if (params->browser->tab_strip_model() |
| ->GetActiveWebContents() |
| ->GetLastCommittedURL() == params->url) { |
| return false; |
| } |
| params->disposition = WindowOpenDisposition::CURRENT_TAB; |
| } |
| |
| return true; |
| } |
| |
| Browser::ValueSpecified GetOriginSpecified(const NavigateParams& params) { |
| return params.window_features.has_x && params.window_features.has_y |
| ? Browser::ValueSpecified::kSpecified |
| : Browser::ValueSpecified::kUnspecified; |
| } |
| |
| // Returns a Browser and tab index. The browser can host the navigation or |
| // tab addition specified in |params|. This might just return the same |
| // Browser specified in |params|, or some other if that Browser is deemed |
| // incompatible. The tab index will be -1 unless a singleton or tab switch |
| // was requested, in which case it might be the target tab index, or -1 |
| // if not found. |
| std::tuple<Browser*, int> GetBrowserAndTabForDisposition( |
| const NavigateParams& params) { |
| Profile* profile = params.initiating_profile; |
| |
| switch (params.disposition) { |
| case WindowOpenDisposition::SWITCH_TO_TAB: |
| #if !BUILDFLAG(IS_ANDROID) |
| { |
| std::pair<Browser*, int> browser_and_index = |
| GetIndexAndBrowserOfExistingTab(profile, params); |
| if (browser_and_index.first) { |
| return browser_and_index; |
| } |
| } |
| #endif |
| [[fallthrough]]; |
| case WindowOpenDisposition::CURRENT_TAB: |
| if (params.browser) { |
| return {params.browser, -1}; |
| } |
| // Find a compatible window and re-execute this command in it. Otherwise |
| // re-run with NEW_WINDOW. |
| return {GetOrCreateBrowser(profile, params.user_gesture), -1}; |
| case WindowOpenDisposition::SINGLETON_TAB: { |
| // If we have a browser window, check it first. |
| if (params.browser) { |
| int index = GetIndexOfExistingTab(params.browser, params); |
| if (index >= 0) { |
| return {params.browser, index}; |
| } |
| } |
| // If we don't have a a window, or if this window can't open tabs, then |
| // it would load in a random window, potentially opening a second copy. |
| // Instead, make an extra effort to see if there's an already open copy. |
| if (!WindowCanOpenTabs(params)) { |
| std::pair<Browser*, int> browser_and_index = |
| GetIndexAndBrowserOfExistingTab(profile, params); |
| if (browser_and_index.first) { |
| return browser_and_index; |
| } |
| } |
| } |
| [[fallthrough]]; |
| case WindowOpenDisposition::NEW_FOREGROUND_TAB: |
| case WindowOpenDisposition::NEW_BACKGROUND_TAB: |
| // See if we can open the tab in the window this navigator is bound to. |
| if (WindowCanOpenTabs(params)) { |
| return {params.browser, -1}; |
| } |
| |
| // Find a compatible window and re-execute this command in it. Otherwise |
| // re-run with NEW_WINDOW. |
| return {GetOrCreateBrowser(profile, params.user_gesture), -1}; |
| case WindowOpenDisposition::NEW_PICTURE_IN_PICTURE: |
| #if !BUILDFLAG(IS_ANDROID) |
| { |
| // The picture in picture window should be part of the opener's web app, |
| // if any. |
| std::string app_name; |
| if (!params.app_id.empty()) { |
| app_name = web_app::GenerateApplicationNameFromAppId(params.app_id); |
| } else if (params.browser && !params.browser->app_name().empty()) { |
| app_name = params.browser->app_name(); |
| } |
| |
| auto browser_params = Browser::CreateParams::CreateForPictureInPicture( |
| app_name, params.trusted_source, profile, params.user_gesture); |
| DCHECK(params.contents_to_insert); |
| auto pip_options = |
| params.contents_to_insert->GetPictureInPictureOptions(); |
| if (!pip_options.has_value()) { |
| return {nullptr, -1}; |
| } |
| |
| browser_params.pip_options = pip_options; |
| |
| const BrowserWindow* const browser_window = params.browser->window(); |
| const gfx::NativeWindow native_window = |
| browser_window ? browser_window->GetNativeWindow() |
| : gfx::NativeWindow(); |
| const display::Screen* const screen = display::Screen::GetScreen(); |
| const display::Display display = |
| browser_window ? screen->GetDisplayNearestWindow(native_window) |
| : screen->GetDisplayForNewWindows(); |
| |
| browser_params.initial_bounds = |
| PictureInPictureWindowManager::GetInstance() |
| ->CalculateInitialPictureInPictureWindowBounds(*pip_options, |
| display); |
| |
| browser_params.omit_from_session_restore = true; |
| return {Browser::Create(browser_params), -1}; |
| } |
| #else // !IS_ANDROID |
| // For TYPE_PICTURE_IN_PICTURE |
| NOTIMPLEMENTED_LOG_ONCE(); |
| return {nullptr, -1}; |
| #endif // !IS_ANDROID |
| |
| case WindowOpenDisposition::NEW_POPUP: { |
| // Make a new popup window. |
| // Coerce app-style if |source| represents an app. |
| std::string app_name; |
| if (!params.app_id.empty()) { |
| app_name = web_app::GenerateApplicationNameFromAppId(params.app_id); |
| } else if (params.browser && !params.browser->app_name().empty()) { |
| app_name = params.browser->app_name(); |
| } |
| if (Browser::GetCreationStatusForProfile(profile) != |
| Browser::CreationStatus::kOk) { |
| return {nullptr, -1}; |
| } |
| if (app_name.empty()) { |
| Browser::CreateParams browser_params(Browser::TYPE_POPUP, profile, |
| params.user_gesture); |
| browser_params.trusted_source = params.trusted_source; |
| browser_params.initial_bounds = params.window_features.bounds; |
| browser_params.initial_origin_specified = GetOriginSpecified(params); |
| browser_params.can_maximize = !params.is_tab_modal_popup_deprecated; |
| browser_params.can_fullscreen = !params.is_tab_modal_popup_deprecated; |
| return {Browser::Create(browser_params), -1}; |
| } |
| Browser::CreateParams browser_params = |
| Browser::CreateParams::CreateForAppPopup( |
| app_name, params.trusted_source, params.window_features.bounds, |
| profile, params.user_gesture); |
| browser_params.initial_origin_specified = GetOriginSpecified(params); |
| return {Browser::Create(browser_params), -1}; |
| } |
| case WindowOpenDisposition::NEW_WINDOW: { |
| // Make a new normal browser window. |
| Browser* browser = nullptr; |
| if (Browser::GetCreationStatusForProfile(profile) == |
| Browser::CreationStatus::kOk) { |
| browser = Browser::Create( |
| Browser::CreateParams(profile, params.user_gesture)); |
| } |
| return {browser, -1}; |
| } |
| case WindowOpenDisposition::OFF_THE_RECORD: |
| // Make or find an incognito window. |
| return {GetOrCreateBrowser( |
| profile->GetPrimaryOTRProfile(/*create_if_needed=*/true), |
| params.user_gesture), |
| -1}; |
| // The following types result in no navigation. |
| case WindowOpenDisposition::SAVE_TO_DISK: |
| case WindowOpenDisposition::IGNORE_ACTION: |
| return {nullptr, -1}; |
| default: |
| NOTREACHED(); |
| } |
| } |
| |
| // Fix disposition and other parameter values depending on prevailing |
| // conditions. |
| void NormalizeDisposition(NavigateParams* params) { |
| // Calculate the WindowOpenDisposition if necessary. |
| if (params->browser->tab_strip_model()->empty() && |
| (params->disposition == WindowOpenDisposition::NEW_BACKGROUND_TAB || |
| params->disposition == WindowOpenDisposition::CURRENT_TAB || |
| params->disposition == WindowOpenDisposition::SINGLETON_TAB)) { |
| params->disposition = WindowOpenDisposition::NEW_FOREGROUND_TAB; |
| } |
| if (params->browser->profile()->IsOffTheRecord() && |
| params->disposition == WindowOpenDisposition::OFF_THE_RECORD) { |
| params->disposition = WindowOpenDisposition::NEW_FOREGROUND_TAB; |
| } |
| if (!params->source_contents && |
| params->disposition == WindowOpenDisposition::CURRENT_TAB) { |
| params->disposition = WindowOpenDisposition::NEW_FOREGROUND_TAB; |
| } |
| |
| switch (params->disposition) { |
| case WindowOpenDisposition::NEW_BACKGROUND_TAB: |
| // Disposition trumps add types. ADD_ACTIVE is a default, so we need to |
| // remove it if disposition implies the tab is going to open in the |
| // background. |
| params->tabstrip_add_types &= ~AddTabTypes::ADD_ACTIVE; |
| break; |
| |
| case WindowOpenDisposition::NEW_PICTURE_IN_PICTURE: |
| PictureInPictureWindowManager::SetWindowParams(*params); |
| break; |
| |
| case WindowOpenDisposition::NEW_WINDOW: |
| case WindowOpenDisposition::NEW_POPUP: { |
| // Code that wants to open a new window typically expects it to be shown |
| // automatically. |
| if (params->window_action == NavigateParams::NO_ACTION) { |
| params->window_action = NavigateParams::SHOW_WINDOW; |
| } |
| [[fallthrough]]; |
| } |
| case WindowOpenDisposition::NEW_FOREGROUND_TAB: |
| case WindowOpenDisposition::SINGLETON_TAB: |
| params->tabstrip_add_types |= AddTabTypes::ADD_ACTIVE; |
| break; |
| |
| default: |
| break; |
| } |
| } |
| |
| // Obtain the profile used by the code that originated the Navigate() request. |
| Profile* GetSourceProfile(NavigateParams* params) { |
| // |source_site_instance| needs to be checked before |source_contents|. This |
| // might matter when chrome.windows.create is used to open multiple URLs, |
| // which would reuse |params| and modify |params->source_contents| across |
| // navigations. |
| if (params->source_site_instance) { |
| return Profile::FromBrowserContext( |
| params->source_site_instance->GetBrowserContext()); |
| } |
| |
| if (params->source_contents) { |
| return Profile::FromBrowserContext( |
| params->source_contents->GetBrowserContext()); |
| } |
| |
| return params->initiating_profile; |
| } |
| |
| base::WeakPtr<content::NavigationHandle> LoadURLInContents( |
| WebContents* target_contents, |
| const GURL& url, |
| NavigateParams* params) { |
| NavigationController::LoadURLParams load_url_params(url); |
| load_url_params.initiator_frame_token = params->initiator_frame_token; |
| load_url_params.initiator_process_id = params->initiator_process_id; |
| load_url_params.initiator_origin = params->initiator_origin; |
| load_url_params.initiator_base_url = params->initiator_base_url; |
| load_url_params.source_site_instance = params->source_site_instance; |
| load_url_params.referrer = params->referrer; |
| load_url_params.frame_name = params->frame_name; |
| load_url_params.frame_tree_node_id = params->frame_tree_node_id; |
| load_url_params.redirect_chain = params->redirect_chain; |
| load_url_params.transition_type = params->transition; |
| load_url_params.extra_headers = params->extra_headers; |
| load_url_params.should_replace_current_entry = |
| params->should_replace_current_entry; |
| load_url_params.is_renderer_initiated = params->is_renderer_initiated; |
| load_url_params.started_from_context_menu = params->started_from_context_menu; |
| load_url_params.has_user_gesture = params->user_gesture; |
| load_url_params.blob_url_loader_factory = params->blob_url_loader_factory; |
| load_url_params.input_start = params->input_start; |
| load_url_params.was_activated = params->was_activated; |
| load_url_params.href_translate = params->href_translate; |
| load_url_params.reload_type = params->reload_type; |
| load_url_params.impression = params->impression; |
| load_url_params.suggested_system_entropy = params->suggested_system_entropy; |
| |
| // |frame_tree_node_id| is invalid for main frame navigations. |
| if (params->frame_tree_node_id.is_null()) { |
| bool force_no_https_upgrade = |
| params->url_typed_with_http_scheme || |
| params->captive_portal_window_type != |
| captive_portal::CaptivePortalWindowType::kNone; |
| std::unique_ptr<ChromeNavigationUIData> navigation_ui_data = |
| ChromeNavigationUIData::CreateForMainFrameNavigation( |
| target_contents, params->is_using_https_as_default_scheme, |
| force_no_https_upgrade); |
| navigation_ui_data->set_navigation_initiated_from_sync( |
| params->navigation_initiated_from_sync); |
| load_url_params.navigation_ui_data = std::move(navigation_ui_data); |
| } |
| |
| if (params->post_data) { |
| load_url_params.load_type = NavigationController::LOAD_TYPE_HTTP_POST; |
| load_url_params.post_data = params->post_data; |
| } |
| |
| return target_contents->GetController().LoadURLWithParams(load_url_params); |
| } |
| |
| // This class makes sure the Browser object held in |params| is made visible |
| // by the time it goes out of scope, provided |params| wants it to be shown. |
| class ScopedBrowserShower { |
| public: |
| explicit ScopedBrowserShower(NavigateParams* params, |
| content::WebContents** contents) |
| : params_(params), contents_(contents) {} |
| |
| ScopedBrowserShower(const ScopedBrowserShower&) = delete; |
| ScopedBrowserShower& operator=(const ScopedBrowserShower&) = delete; |
| |
| ~ScopedBrowserShower() { |
| BrowserWindow* window = params_->browser->window(); |
| if (params_->window_action == NavigateParams::SHOW_WINDOW_INACTIVE) { |
| // TODO(crbug.com/40284685): investigate if SHOW_WINDOW_INACTIVE needs to |
| // be supported for tab modal popups. |
| CHECK_EQ(params_->is_tab_modal_popup_deprecated, false); |
| window->ShowInactive(); |
| } else if (params_->window_action == NavigateParams::SHOW_WINDOW) { |
| if (params_->is_tab_modal_popup_deprecated) { |
| CHECK_EQ(params_->disposition, WindowOpenDisposition::NEW_POPUP); |
| CHECK_NE(source_contents_, nullptr); |
| window->SetIsTabModalPopupDeprecated(true); |
| constrained_window::ShowModalDialog(window->GetNativeWindow(), |
| source_contents_); |
| } else { |
| window->Show(); |
| } |
| // If a user gesture opened a popup window, focus the contents. |
| if (params_->user_gesture && |
| (params_->disposition == WindowOpenDisposition::NEW_POPUP || |
| params_->disposition == |
| WindowOpenDisposition::NEW_PICTURE_IN_PICTURE) && |
| *contents_) { |
| (*contents_)->Focus(); |
| window->Activate(); |
| } |
| } |
| } |
| |
| void set_source_contents(content::WebContents* source_contents) { |
| source_contents_ = source_contents; |
| } |
| |
| private: |
| raw_ptr<NavigateParams> params_; |
| raw_ptr<content::WebContents*> contents_; |
| raw_ptr<content::WebContents> source_contents_; |
| }; |
| |
| std::unique_ptr<content::WebContents> CreateTargetContents( |
| const NavigateParams& params, |
| const GURL& url) { |
| // Always create the new WebContents in a new SiteInstance (and therefore a |
| // new BrowsingInstance), *unless* there's a |params.opener|. |
| // |
| // Note that the SiteInstance below is only for the "initial" placement of the |
| // new WebContents (i.e. if subsequent navigation [including the initial |
| // navigation] triggers a cross-process transfer, then the opener and new |
| // contents can end up in separate processes). This is fine, because even if |
| // subsequent navigation is cross-process (i.e. cross-SiteInstance), then it |
| // will stay in the same BrowsingInstance (creating frame proxies as needed) |
| // preserving the requested opener relationship along the way. |
| scoped_refptr<content::SiteInstance> initial_site_instance_for_new_contents = |
| params.opener |
| ? params.opener->GetSiteInstance() |
| : tab_util::GetSiteInstanceForNewTab(params.browser->profile(), url); |
| |
| WebContents::CreateParams create_params( |
| params.browser->profile(), initial_site_instance_for_new_contents); |
| create_params.main_frame_name = params.frame_name; |
| if (params.opener) { |
| create_params.opener_render_frame_id = params.opener->GetRoutingID(); |
| create_params.opener_render_process_id = |
| params.opener->GetProcess()->GetDeprecatedID(); |
| } |
| |
| create_params.opened_by_another_window = params.opened_by_another_window; |
| |
| if (params.disposition == WindowOpenDisposition::NEW_BACKGROUND_TAB) { |
| create_params.initially_hidden = true; |
| } |
| |
| #if defined(USE_AURA) |
| if (params.browser->window() && params.browser->window()->GetNativeWindow()) { |
| create_params.context = params.browser->window()->GetNativeWindow(); |
| } |
| #endif |
| |
| return WebContents::Create(create_params); |
| } |
| |
| bool IsHostAllowedInIncognito(const GURL& url) { |
| std::string scheme = url.scheme(); |
| std::string_view host = url.host_piece(); |
| if (scheme != content::kChromeUIScheme) { |
| return true; |
| } |
| |
| if (host == chrome::kChromeUIChromeSigninHost) { |
| #if BUILDFLAG(IS_WIN) |
| // Allow incognito mode for the chrome-signin url if we only want to |
| // retrieve the login scope token without touching any profiles. This |
| // option is only available on Windows for use with Google Credential |
| // Provider for Windows. |
| return signin::GetSigninReasonForEmbeddedPromoURL(url) == |
| signin_metrics::Reason::kFetchLstOnly; |
| #else |
| return false; |
| #endif // BUILDFLAG(IS_WIN) |
| } |
| |
| // Most URLs are allowed in incognito; the following are exceptions. |
| // chrome://extensions is on the list because it redirects to |
| // chrome://settings. |
| return host != chrome::kChromeUIAppLauncherPageHost && |
| host != chrome::kChromeUISettingsHost && |
| #if BUILDFLAG(IS_CHROMEOS) |
| host != chrome::kChromeUIOSSettingsHost && |
| #endif |
| host != chrome::kChromeUIHelpHost && |
| host != chrome::kChromeUIHistoryHost && |
| host != chrome::kChromeUIExtensionsHost && |
| host != password_manager::kChromeUIPasswordManagerHost; |
| } |
| |
| } // namespace |
| |
| base::WeakPtr<content::NavigationHandle> Navigate(NavigateParams* params) { |
| TRACE_EVENT1("navigation", "chrome::Navigate", "disposition", |
| params->disposition); |
| Browser* source_browser = params->browser; |
| if (source_browser) { |
| params->initiating_profile = source_browser->profile(); |
| } |
| DCHECK(params->initiating_profile); |
| |
| // If the created window is a partitioned popin, a valid source exists, and |
| // the disposition is NEW_POPUP then the resulting popup should be tab-modal. |
| // See: https://explainers-by-googlers.github.io/partitioned-popins/ |
| params->is_tab_modal_popup_deprecated |= |
| params->window_features.is_partitioned_popin && params->source_contents && |
| params->disposition == WindowOpenDisposition::NEW_POPUP; |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| if (params->initiating_profile->IsOffTheRecord() && |
| params->initiating_profile->GetOTRProfileID().IsCaptivePortal() && |
| params->disposition != WindowOpenDisposition::NEW_POPUP && |
| params->disposition != WindowOpenDisposition::CURRENT_TAB && |
| !IncognitoModeForced(params->initiating_profile)) { |
| // Navigation outside of the current tab or the initial popup window from a |
| // captive portal signin window should be prevented. |
| params->disposition = WindowOpenDisposition::CURRENT_TAB; |
| } |
| #endif |
| |
| if (params->initiating_profile->ShutdownStarted()) { |
| // Don't navigate when the profile is shutting down. |
| return nullptr; |
| } |
| |
| if (params->browser && params->browser->IsBrowserClosing()) { |
| return nullptr; |
| } |
| |
| // Block navigation requests when in locked fullscreen mode. We allow |
| // navigation requests in the webapp when locked for OnTask (only relevant for |
| // non-web browser scenarios). |
| // TODO(b/365146870): Remove once we consolidate locked fullscreen with |
| // OnTask. |
| if (source_browser) { |
| bool should_block_navigation = |
| platform_util::IsBrowserLockedFullscreen(source_browser); |
| #if BUILDFLAG(IS_CHROMEOS) |
| if (source_browser->IsLockedForOnTask()) { |
| should_block_navigation = false; |
| } |
| #endif // BUILDFLAG(IS_CHROMEOS) |
| if (should_block_navigation) { |
| return nullptr; |
| } |
| } |
| |
| // Open System Apps in their standalone window if necessary. |
| // TODO(crbug.com/40136163): Remove this code after we integrate with intent |
| // handling. |
| #if BUILDFLAG(IS_CHROMEOS) |
| const std::optional<ash::SystemWebAppType> capturing_system_app_type = |
| ash::GetCapturingSystemAppForURL(params->initiating_profile, params->url); |
| if (capturing_system_app_type && |
| (!params->browser || |
| !ash::IsBrowserForSystemWebApp(params->browser, |
| capturing_system_app_type.value()))) { |
| ash::SystemAppLaunchParams swa_params; |
| swa_params.url = params->url; |
| ash::LaunchSystemWebAppAsync(params->initiating_profile, |
| capturing_system_app_type.value(), swa_params); |
| |
| // It's okay to early return here, because LaunchSystemWebAppAsync uses a |
| // different logic to choose (and create if necessary) a browser window for |
| // system apps. |
| // |
| // It's okay to skip the checks and cleanups below. The link captured system |
| // app will either open in its own browser window, or navigate an existing |
| // browser window exclusively used by this app. For the initiating browser, |
| // the navigation should appear to be cancelled. |
| return nullptr; |
| } |
| #endif |
| |
| if (!AdjustNavigateParamsForURL(params)) { |
| return nullptr; |
| } |
| |
| // Picture-in-picture browser windows must have a source contents in order for |
| // the window to function correctly. If we have no source contents to work |
| // with (e.g. if an extension popup attempts to open a PiP window), we should |
| // cancel the navigation. The source URL must also be of a type that's |
| // allowed to open document PiP. See `PictureInPictureWindowManager` for |
| // details on what's allowed. |
| if (params->disposition == WindowOpenDisposition::NEW_PICTURE_IN_PICTURE) { |
| const GURL& url = params->source_contents |
| ? params->source_contents->GetLastCommittedURL() |
| : GURL(); |
| if (!PictureInPictureWindowManager::IsSupportedForDocumentPictureInPicture( |
| url)) { |
| return nullptr; |
| } |
| } |
| |
| // If no source WebContents was specified, we use the selected one from the |
| // target browser. This must happen before GetBrowserAndTabForDisposition() |
| // has a chance to replace |params->browser| with another one, but after the |
| // above check that relies on the original source_contents value. |
| if (!params->source_contents && params->browser) { |
| params->source_contents = |
| params->browser->tab_strip_model()->GetActiveWebContents(); |
| } |
| |
| WebContents* contents_to_navigate_or_insert = |
| params->contents_to_insert.get(); |
| if (params->switch_to_singleton_tab) { |
| DCHECK_EQ(params->disposition, WindowOpenDisposition::SINGLETON_TAB); |
| contents_to_navigate_or_insert = params->switch_to_singleton_tab; |
| } |
| |
| #if !BUILDFLAG(IS_ANDROID) |
| // If this is a Picture in Picture window, then notify the pip manager about |
| // it. This enables the opener and pip window to stay connected, so that (for |
| // example), the pip window does not outlive the opener. |
| // |
| // We do this before creating the browser window, so that the browser can talk |
| // to the PictureInPictureWindowManager. Otherwise, the manager has no idea |
| // that there's a pip window. |
| if (params->disposition == WindowOpenDisposition::NEW_PICTURE_IN_PICTURE) { |
| // Picture in picture windows may not be opened by other picture in |
| // picture windows, or without an opener. |
| if (!params->browser || params->browser->is_type_picture_in_picture()) { |
| params->browser = nullptr; |
| return nullptr; |
| } |
| |
| PictureInPictureWindowManager::GetInstance()->EnterDocumentPictureInPicture( |
| params->source_contents, contents_to_navigate_or_insert); |
| } |
| #endif // !BUILDFLAG(IS_ANDROID) |
| |
| // TODO(crbug.com/364657540): Revisit integration with web_application system |
| // later if needed. |
| int singleton_index; |
| |
| #if !BUILDFLAG(IS_ANDROID) |
| std::unique_ptr<web_app::NavigationCapturingProcess> app_navigation = |
| web_app::NavigationCapturingProcess::MaybeHandleAppNavigation(*params); |
| std::optional<std::tuple<Browser*, int>> app_browser_tab_override; |
| if (app_navigation) { |
| app_browser_tab_override = |
| app_navigation->GetInitialBrowserAndTabOverrideForNavigation(*params); |
| } |
| std::tie(params->browser, singleton_index) = |
| app_browser_tab_override.has_value() |
| ? *app_browser_tab_override |
| : GetBrowserAndTabForDisposition(*params); |
| #else // !BUILDFLAG(IS_ANDROID) |
| std::tie(params->browser, singleton_index) = |
| GetBrowserAndTabForDisposition(*params); |
| #endif |
| |
| if (!params->browser) { |
| return nullptr; |
| } |
| |
| // Trying to open a background tab when in a non-tabbed app browser results in |
| // focusing a regular browser window and opening a tab in the background |
| // of that window. Change the disposition to NEW_FOREGROUND_TAB so that |
| // the new tab is focused. |
| if (source_browser && source_browser->is_type_app() && |
| params->disposition == WindowOpenDisposition::NEW_BACKGROUND_TAB && |
| !(source_browser->app_controller() && |
| source_browser->app_controller()->has_tab_strip())) { |
| params->disposition = WindowOpenDisposition::NEW_FOREGROUND_TAB; |
| } |
| |
| if (singleton_index != -1) { |
| contents_to_navigate_or_insert = |
| params->browser->tab_strip_model()->GetWebContentsAt(singleton_index); |
| } else if (params->disposition == WindowOpenDisposition::SWITCH_TO_TAB) { |
| // The user is trying to open a tab that no longer exists. If we open a new |
| // tab, it could leave orphaned NTPs around, but always overwriting the |
| // current tab could could clobber state that the user was trying to |
| // preserve. Fallback to the behavior used for singletons: overwrite the |
| // current tab if it's the NTP, otherwise open a new tab. |
| params->disposition = WindowOpenDisposition::SINGLETON_TAB; |
| ShowSingletonTabOverwritingNTP(params); |
| return nullptr; |
| } |
| if (content::SiteIsolationPolicy::ShouldUrlUseApplicationIsolationLevel( |
| params->initiating_profile, params->url)) { |
| CHECK(web_app::AppBrowserController::IsWebApp(params->browser)); |
| } |
| #if BUILDFLAG(IS_CHROMEOS) |
| if (source_browser && source_browser != params->browser) { |
| // When the newly created browser was spawned by a browser which visits |
| // another user's desktop, it should be shown on the same desktop as the |
| // originating one. (This is part of the desktop separation per profile). |
| auto* window_manager = MultiUserWindowManagerHelper::GetWindowManager(); |
| // Some unit tests have no client instantiated. |
| if (window_manager) { |
| aura::Window* src_window = source_browser->window()->GetNativeWindow(); |
| aura::Window* new_window = params->browser->window()->GetNativeWindow(); |
| const AccountId& src_account_id = |
| window_manager->GetUserPresentingWindow(src_window); |
| if (src_account_id != |
| window_manager->GetUserPresentingWindow(new_window)) { |
| // Once the window gets presented, it should be shown on the same |
| // desktop as the desktop of the creating browser. Note that this |
| // command will not show the window if it wasn't shown yet by the |
| // browser creation. |
| window_manager->ShowWindowForUser(new_window, src_account_id); |
| } |
| } |
| } |
| #endif |
| |
| // Navigate() must not return early after this point. |
| |
| if (GetSourceProfile(params) != params->browser->profile()) { |
| // A tab is being opened from a link from a different profile, we must reset |
| // source information that may cause state to be shared. |
| params->opener = nullptr; |
| params->source_contents = nullptr; |
| params->source_site_instance = nullptr; |
| params->referrer = content::Referrer(); |
| } |
| |
| // Make sure the Browser is shown if params call for it. |
| ScopedBrowserShower shower(params, &contents_to_navigate_or_insert); |
| if (params->is_tab_modal_popup_deprecated) { |
| shower.set_source_contents(params->source_contents); |
| } |
| |
| // Some dispositions need coercion to base types. |
| NormalizeDisposition(params); |
| |
| // If a new window has been created, it needs to be shown. |
| if (params->window_action == NavigateParams::NO_ACTION && |
| source_browser != params->browser && |
| params->browser->tab_strip_model()->empty()) { |
| params->window_action = NavigateParams::SHOW_WINDOW; |
| } |
| |
| // If we create a popup window from a non user-gesture, don't activate it. |
| if (params->window_action == NavigateParams::SHOW_WINDOW && |
| params->disposition == WindowOpenDisposition::NEW_POPUP && |
| params->user_gesture == false) { |
| params->window_action = NavigateParams::SHOW_WINDOW_INACTIVE; |
| } |
| |
| // Determine if the navigation was user initiated. If it was, we need to |
| // inform the target WebContents, and we may need to update the UI. |
| bool user_initiated = |
| params->transition & ui::PAGE_TRANSITION_FROM_ADDRESS_BAR || |
| !ui::PageTransitionIsWebTriggerable(params->transition); |
| |
| base::WeakPtr<content::NavigationHandle> navigation_handle; |
| |
| std::unique_ptr<tabs::TabModel> tab_to_insert; |
| if (params->contents_to_insert) { |
| tab_to_insert = |
| std::make_unique<tabs::TabModel>(std::move(params->contents_to_insert), |
| params->browser->tab_strip_model()); |
| } |
| |
| // If no target WebContents was specified (and we didn't seek and find a |
| // singleton), we need to construct one if we are supposed to target a new |
| // tab. |
| if (!contents_to_navigate_or_insert) { |
| DCHECK(!params->url.is_empty()); |
| if (params->disposition != WindowOpenDisposition::CURRENT_TAB) { |
| tab_to_insert = std::make_unique<tabs::TabModel>( |
| CreateTargetContents(*params, params->url), |
| params->browser->tab_strip_model()); |
| contents_to_navigate_or_insert = tab_to_insert->GetContents(); |
| |
| apps::SetAppIdForWebContents(params->browser->profile(), |
| contents_to_navigate_or_insert, |
| params->app_id); |
| #if BUILDFLAG(ENABLE_CAPTIVE_PORTAL_DETECTION) |
| captive_portal::CaptivePortalTabHelper::FromWebContents( |
| contents_to_navigate_or_insert) |
| ->set_window_type(params->captive_portal_window_type); |
| #endif |
| } else { |
| // ... otherwise if we're loading in the current tab, the target is the |
| // same as the source. |
| DCHECK(params->source_contents); |
| contents_to_navigate_or_insert = params->source_contents; |
| } |
| |
| // Try to handle non-navigational URLs that popup dialogs and such, these |
| // should not actually navigate. |
| if (!HandleNonNavigationAboutURL( |
| params->url, contents_to_navigate_or_insert->GetBrowserContext())) { |
| // Perform the actual navigation, tracking whether it came from the |
| // renderer. |
| navigation_handle = LoadURLInContents(contents_to_navigate_or_insert, |
| params->url, params); |
| } |
| } else { |
| // |contents_to_navigate_or_insert| was specified non-NULL, and so we assume |
| // it has already been navigated appropriately. We need to do nothing more |
| // other than add it to the appropriate tabstrip. |
| } |
| |
| // If the user navigated from the omnibox, and the selected tab is going to |
| // lose focus, then make sure the focus for the source tab goes away from the |
| // omnibox. |
| if (params->source_contents && |
| (params->disposition == WindowOpenDisposition::NEW_FOREGROUND_TAB || |
| params->disposition == WindowOpenDisposition::NEW_WINDOW) && |
| (params->tabstrip_add_types & AddTabTypes::ADD_INHERIT_OPENER)) { |
| params->source_contents->Focus(); |
| } |
| |
| if (tab_to_insert) { |
| // Save data needed for link capturing into apps that cannot otherwise be |
| // inferred later in the navigation. These are only needed when the |
| // navigation happens in a different tab to the link click. |
| apps::SetLinkCapturingSourceDisposition(tab_to_insert->GetContents(), |
| params->disposition); |
| } |
| |
| if (params->source_contents == contents_to_navigate_or_insert) { |
| // The navigation occurred in the source tab. |
| params->browser->UpdateUIForNavigationInTab( |
| contents_to_navigate_or_insert, params->transition, |
| params->window_action, user_initiated); |
| } else if (singleton_index == -1) { |
| if (source_browser != params->browser) { |
| params->tabstrip_index = params->browser->tab_strip_model()->count(); |
| } |
| |
| // If some non-default value is set for the index, we should tell the |
| // TabStripModel to respect it. |
| if (params->tabstrip_index != -1) { |
| params->tabstrip_add_types |= AddTabTypes::ADD_FORCE_INDEX; |
| } |
| |
| // Maybe notify that an open operation has been done from a gesture. |
| // TODO(crbug.com/40719979): preferably pipe this information through the |
| // TabStripModel instead. See bug for deeper discussion. |
| if (params->user_gesture && source_browser == params->browser) { |
| params->browser->window()->LinkOpeningFromGesture(params->disposition); |
| } |
| |
| DCHECK(tab_to_insert); |
| // The navigation should insert a new tab into the target Browser. |
| params->browser->tab_strip_model()->AddTab( |
| std::move(tab_to_insert), params->tabstrip_index, params->transition, |
| params->tabstrip_add_types, params->group); |
| } |
| |
| if (singleton_index >= 0) { |
| // If switching browsers, make sure it is shown. |
| if (params->disposition == WindowOpenDisposition::SWITCH_TO_TAB && |
| params->browser != source_browser) { |
| params->window_action = NavigateParams::SHOW_WINDOW; |
| } |
| |
| if (contents_to_navigate_or_insert->IsCrashed()) { |
| contents_to_navigate_or_insert->GetController().Reload( |
| content::ReloadType::NORMAL, true); |
| } else if (params->path_behavior == NavigateParams::IGNORE_AND_NAVIGATE && |
| contents_to_navigate_or_insert->GetURL() != params->url) { |
| navigation_handle = LoadURLInContents(contents_to_navigate_or_insert, |
| params->url, params); |
| } |
| |
| // If the singleton tab isn't already selected, select it. |
| if (params->source_contents != contents_to_navigate_or_insert) { |
| // Use the index before the potential close below, because it could |
| // make the index refer to a different tab. |
| auto gesture_type = user_initiated |
| ? TabStripUserGestureDetails::GestureType::kOther |
| : TabStripUserGestureDetails::GestureType::kNone; |
| bool should_close_this_tab = false; |
| if (params->disposition == WindowOpenDisposition::SWITCH_TO_TAB) { |
| // Close orphaned NTP (and the like) with no history when the user |
| // switches away from them. |
| if (params->source_contents) { |
| if (params->source_contents->GetController().CanGoBack() || |
| (params->source_contents->GetLastCommittedURL().spec() != |
| chrome::kChromeUINewTabURL && |
| params->source_contents->GetLastCommittedURL().spec() != |
| url::kAboutBlankURL)) { |
| // Blur location bar before state save in ActivateTabAt() below. |
| params->source_contents->Focus(); |
| } else { |
| should_close_this_tab = true; |
| } |
| } |
| } |
| params->browser->tab_strip_model()->ActivateTabAt( |
| singleton_index, TabStripUserGestureDetails(gesture_type)); |
| // Close tab after switch so index remains correct. |
| if (should_close_this_tab) { |
| params->source_contents->Close(); |
| } |
| } |
| } |
| |
| params->navigated_or_inserted_contents = contents_to_navigate_or_insert; |
| |
| // At this point, the `params->navigated_or_inserted_contents` is guaranteed to |
| // be non null, so perform tasks if the navigation has been captured by a web |
| // app, like enqueueing launch params. |
| #if !BUILDFLAG(IS_ANDROID) |
| if (app_navigation) { |
| web_app::NavigationCapturingProcess::AfterWebContentsCreation( |
| std::move(app_navigation), *params->navigated_or_inserted_contents, |
| navigation_handle.get()); |
| } |
| #endif // !BUILDFLAG(IS_ANDROID) |
| return navigation_handle; |
| } |
| |
| bool IsURLAllowedInIncognito(const GURL& url) { |
| if (url.scheme() == content::kViewSourceScheme) { |
| // A view-source URL is allowed in incognito mode only if the URL itself |
| // is allowed in incognito mode. Remove the "view-source:" from the start |
| // of the URL and validate the rest. |
| const size_t scheme_len = strlen(content::kViewSourceScheme); |
| CHECK_GT(url.spec().size(), scheme_len); |
| std::string_view stripped_url_str(url.spec()); |
| // Adding +1 for ':' character. |
| stripped_url_str.remove_prefix(scheme_len + 1); |
| const GURL stripped_url(stripped_url_str); |
| if (stripped_url.is_empty()) { |
| return true; |
| } |
| return stripped_url.is_valid() && IsURLAllowedInIncognito(stripped_url); |
| } |
| |
| return IsHostAllowedInIncognito(url); |
| } |