| // 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. |
| |
| #ifndef CHROME_BROWSER_UI_WEB_APPLICATIONS_NAVIGATION_CAPTURING_PROCESS_H_ |
| #define CHROME_BROWSER_UI_WEB_APPLICATIONS_NAVIGATION_CAPTURING_PROCESS_H_ |
| |
| #include <memory> |
| #include <optional> |
| #include <tuple> |
| |
| #include "base/memory/raw_ptr.h" |
| #include "base/values.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/web_applications/web_app_launch_navigation_handle_user_data.h" |
| #include "chrome/browser/web_applications/web_app_constants.h" |
| #include "components/webapps/common/web_app_id.h" |
| #include "ui/base/window_open_disposition.h" |
| |
| class Browser; |
| struct NavigateParams; |
| |
| namespace web_app { |
| |
| class NavigationCapturingSettings; |
| |
| // This class encompasses all of the logic for navigations in the browser to be |
| // captured into installed web app launches. |
| // |
| // Instances are created by `::Navigate()` in `browser_navigator.cc`, which |
| // also calls into this class to possibly capture the initial navigation. The |
| // instance is then eventually attached to the `NavigationHandle` for the |
| // navigation that was started. Later when all redirects for the navigation |
| // has been resolved, `NavigationCapturingRedirectThrottle()` again calls into |
| // this class to handle the result of the redirects, possible changing what app |
| // captures the navigation (or not capturing a previously captured navigation |
| // any longer). |
| // |
| // This class is also responsible for populating |
| // WebAppLaunchNavigationHandleUserData, which is used to gather launch metrics, |
| // populate the launch queue, and possibly trigger IPH. |
| class NavigationCapturingProcess |
| : public content::NavigationHandleUserData<NavigationCapturingProcess> { |
| public: |
| // Returns null if navigation capturing is disabled for this navigation. This |
| // returning non-null however does not mean that navigation capturing will |
| // definitely happen for this navigation; the logic in |
| // `GetInitialBrowserAndTabOverrideForNavigation()` can still decide that no |
| // capturing should apply to this navigation. |
| static std::unique_ptr<NavigationCapturingProcess> MaybeHandleAppNavigation( |
| const NavigateParams& navigate_params); |
| |
| ~NavigationCapturingProcess() override; |
| |
| // The first step of the navigation capturing process. This is called by |
| // `browser_navigator.cc` to check if the navigation capturing process wants |
| // to override the browser and or tab to use. A return value of `nullopt` |
| // means that no overriding should happen (but the navigation could still be |
| // captured on later redirects). A null `Browser*` means that the navigation |
| // should be aborted. |
| using BrowserAndTabOverride = std::optional<std::tuple<Browser*, int>>; |
| BrowserAndTabOverride GetInitialBrowserAndTabOverrideForNavigation( |
| const NavigateParams& params); |
| |
| // Called by `browesr_navigator.cc` after the WebContents and optionally |
| // NavigationHandle are known for the navigation it handled. Will CHECK-fail |
| // if called before `GetInitialBrowserAndTabOverrideForNavigation()`. |
| static void AfterWebContentsCreation( |
| std::unique_ptr<NavigationCapturingProcess> process, |
| content::WebContents& web_contents, |
| content::NavigationHandle* navigation_handle); |
| |
| // Attaches this NavigationCapturing process to a specific navigation. Will |
| // CHECK-fail if called before |
| // `GetInitialBrowserAndTabOverrideForNavigation()` is called. If that method |
| // decided no navigation capturing should apply to this navigation, this will |
| // destroy `user_data` rather than attach it. |
| static void AttachToNavigationHandle( |
| content::NavigationHandle& navigation_handle, |
| std::unique_ptr<NavigationCapturingProcess> user_data); |
| // Similar to `AttachToNavigationHandle()`, but instead attaches to the next |
| // navigation to start in the given `web_contents`. |
| static void AttachToNextNavigationInWebContents( |
| content::WebContents& web_contents, |
| std::unique_ptr<NavigationCapturingProcess> user_data); |
| |
| // The second step of the navigation capturing process. Called by |
| // `NavigationCapturingRedirectionThrottle` when the final URL is known after |
| // any possible redirects have happened. Will only be called after this class |
| // has been attached to a NavigationHandle, and will CHECK-fail otherwise. |
| using ThrottleCheckResult = content::NavigationThrottle::ThrottleCheckResult; |
| ThrottleCheckResult HandleRedirect(); |
| |
| content::NavigationHandle* navigation_handle() const { |
| return navigation_handle_; |
| } |
| |
| private: |
| friend NavigationHandleUserData; |
| |
| // To ensure the (public) methods of this class get called in the correct |
| // order, this enum is used to verify what state of the pipeline we're |
| // currently in. |
| enum class PipelineState { |
| kCreated, |
| kInitialOverrideCalculated, |
| kAttachedToWebContents, |
| kAttachedToNavigationHandle, |
| kFinished |
| }; |
| |
| // TODO(crbug.com/336371044): Support web apps that open in a new tab. |
| // The initial result of navigation handling, stored as an enum to prevent |
| // transferring a `Browser` instance everywhere. |
| // Note: Apps can be configured to open in a browser tab or open in a |
| // standalone window. |
| enum class InitialResult { |
| // A browser tab was opened that wasn't the result of web app navigation |
| // capturing, but due to redirection the final behavior could change. |
| // Note: New context & capturable behavior for open-in-browser-tab apps |
| // apply to the cases below, and are not part of this category. |
| kBrowserTab = 0, |
| // The navigation was captured by an app and it resulted in the creation of |
| // a new app window for the navigation. This can only occur when the app |
| // opens in a standalone window. |
| kNavigateCapturedNewAppWindow = 1, |
| // The navigation was captured and it resulted in the creation of a new |
| // browser tab for the navigation. This can only occur when the app opens in |
| // a browser tab. |
| kNavigateCapturedNewBrowserTab = 2, |
| // The navigation was captured and it resulted in a existing web contents |
| // (either in an app window or browser tab) to be navigated. |
| kNavigateCapturingNavigateExisting = 3, |
| // The capturing logic forced this to launch the app in a new app window |
| // context, with the same behavior of `navigate-new`. This is used when it |
| // was a user-modified navigation, triggered by a shift or middle click. |
| // Launch parameters are enqueued. |
| kForcedNewAppContextAppWindow = 4, |
| // Same as above but for an app that opens in a browser tab. |
| kForcedNewAppContextBrowserTab = 5, |
| // The navigation open an auxiliary context, and thus the 'window container' |
| // (app or browser) needs to stay the same. |
| kAuxContext = 6, |
| // This navigation should be excluded from redirection handling. The |
| // NavigationCapturingProcess instance will not be attached to the |
| // NavigationHandle. |
| kNotHandledByNavigationHandling = 7, |
| kMaxValue = kNotHandledByNavigationHandling |
| }; |
| |
| explicit NavigationCapturingProcess(const NavigateParams& params); |
| |
| // Returns true if based on the NavigateParams this instance was created with |
| // the navigation capturing reimpl experiment is enabled. |
| bool IsNavigationCapturingReimplExperimentEnabled(); |
| |
| // Called when this process is attached to a NavigationHandle. |
| void OnAttachedToNavigationHandle(); |
| |
| // Returns the effective client mode for the given app, taking into account |
| // the app's effective display mode as well as what windows and tabs are |
| // currently open. |
| // |
| // If the effective client mode is `kNavigateNew`, only the `browser` will be |
| // populated, indicating the window in which the new tab should be opened. If |
| // a new window should be opened `browser` will be null. |
| // |
| // For an effective client mode of `kNavigateExisting` or `kFocusExisting`, |
| // both `browser` and `tab_index` will be populated, indicating the tab that |
| // should be navigated or focused. |
| struct ClientModeAndBrowser { |
| LaunchHandler::ClientMode effective_client_mode = |
| LaunchHandler::ClientMode::kNavigateNew; |
| raw_ptr<Browser> browser = nullptr; |
| std::optional<int> tab_index; |
| }; |
| ClientModeAndBrowser GetEffectiveClientModeAndBrowser( |
| const webapps::AppId& app_id); |
| |
| // Helper methods for `GetInitialBrowserAndTabOverrideForNavigation()` that |
| // return the correct return value and update internal state of this class |
| // with the corresponding outcome. |
| BrowserAndTabOverride CapturingDisabled(); |
| BrowserAndTabOverride CancelInitialNavigation(); |
| BrowserAndTabOverride NoCapturingOverrideBrowser(Browser* browser); |
| BrowserAndTabOverride AuxiliaryContext(); |
| BrowserAndTabOverride AuxiliaryContextInAppWindow(Browser* app_browser); |
| BrowserAndTabOverride NoInitialActionRedirectionHandlingEligible(); |
| BrowserAndTabOverride ForcedNewAppContext( |
| blink::mojom::DisplayMode app_display_mode, |
| Browser* host_browser); |
| BrowserAndTabOverride CapturedNewClient( |
| blink::mojom::DisplayMode app_display_mode, |
| Browser* host_browser); |
| BrowserAndTabOverride CapturedNavigateExisting(Browser* app_browser, |
| int browser_tab); |
| |
| // Updates the `launched_app_id_` field, and if this process as already been |
| // attached to a `NavigationHandle`, also creates or updates the |
| // `WebAppLaunchNavigationHandleUserData` for that handle. An empty `app_id` |
| // will cause the launch data to be cleared. |
| void SetLaunchedAppId(const webapps::AppId& app_id, |
| bool force_iph_off = false); |
| |
| // Returns true if `initial_nav_handling_result_` is one of the values where |
| // the navigation was captured by an app. |
| bool InitialResultWasCaptured() const; |
| |
| bool is_user_modified_click() const { |
| return disposition_ == WindowOpenDisposition::NEW_WINDOW || |
| disposition_ == WindowOpenDisposition::NEW_BACKGROUND_TAB; |
| } |
| |
| PipelineState state_ = PipelineState::kCreated; |
| |
| std::unique_ptr<NavigationCapturingSettings> navigation_capturing_settings_; |
| |
| // These fields are copied or derived from the NavigateParams of the original |
| // navigation. |
| const raw_ref<Profile> profile_; |
| const std::optional<webapps::AppId> source_browser_app_id_; |
| const std::optional<webapps::AppId> source_tab_app_id_; |
| const GURL navigation_params_url_; |
| const WindowOpenDisposition disposition_; |
| const raw_ptr<Browser> navigation_params_browser_; |
| std::optional<webapps::AppId> first_navigation_app_id_; |
| std::optional<blink::mojom::DisplayMode> first_navigation_app_display_mode_; |
| |
| // This field records the outcome of handling the initial navigation,before |
| // any redirects might have happened. |
| InitialResult initial_nav_handling_result_ = |
| InitialResult::kNotHandledByNavigationHandling; |
| |
| // The app that ended up being launched as a result of the navigation being |
| // captured. This is initially set by |
| // `GetInitialBrowserAndTabOverrideForNavigation()` and can be cleared or |
| // reset by `HandleRedirect()` if the redirect results in a different app |
| // handling the launch. |
| bool force_iph_off_ = false; |
| std::optional<webapps::AppId> launched_app_id_; |
| |
| // Set to the NavigationHandle this process is owned by, once it is attached |
| // to one. |
| raw_ptr<content::NavigationHandle> navigation_handle_ = nullptr; |
| |
| // Debug information persisted to chrome://web-app-internals on destruction of |
| // this class. |
| base::Value::Dict debug_data_; |
| |
| NAVIGATION_HANDLE_USER_DATA_KEY_DECL(); |
| }; |
| } // namespace web_app |
| |
| #endif // CHROME_BROWSER_UI_WEB_APPLICATIONS_NAVIGATION_CAPTURING_PROCESS_H_ |