chromium/ios/chrome/browser/app_launcher/model/app_launcher_tab_helper.h

// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef IOS_CHROME_BROWSER_APP_LAUNCHER_MODEL_APP_LAUNCHER_TAB_HELPER_H_
#define IOS_CHROME_BROWSER_APP_LAUNCHER_MODEL_APP_LAUNCHER_TAB_HELPER_H_

#import <optional>

#import "base/memory/raw_ptr.h"
#import "ios/web/public/navigation/web_state_policy_decider.h"
#import "ios/web/public/web_state_user_data.h"

@class AppLauncherAbuseDetector;
enum class AppLauncherAlertCause;
@protocol AppLauncherTabHelperBrowserPresentationProvider;
class AppLauncherTabHelperDelegate;
class GURL;

// A tab helper that handles requests to launch another application.
class AppLauncherTabHelper
    : public web::WebStatePolicyDecider,
      public web::WebStateUserData<AppLauncherTabHelper> {
 public:
  AppLauncherTabHelper(const AppLauncherTabHelper&) = delete;
  AppLauncherTabHelper& operator=(const AppLauncherTabHelper&) = delete;

  ~AppLauncherTabHelper() override;

  // Returns true, if the `url` has a scheme for an external application
  // (eg. twitter:// , calshow://).
  static bool IsAppUrl(const GURL& url);

  // Sets the delegate.
  virtual void SetDelegate(AppLauncherTabHelperDelegate* delegate);

  // Sets the provider to retrieve the browser presentation state.
  void SetBrowserPresentationProvider(
      id<AppLauncherTabHelperBrowserPresentationProvider>
          browser_presentation_provider);

  // Requests to open the application with `url`.
  // The method checks if the application for `url` has been opened repeatedly
  // by the `source_page_url` page in a short time frame, in that case a prompt
  // will appear to the user with an option to block the application from
  // launching. Then the method also checks for user interaction and for schemes
  // that require special handling (eg. facetime, mailto) and may present the
  // user with a confirmation dialog to open the application.
  void RequestToLaunchApp(const GURL& url,
                          const GURL& source_page_url,
                          bool link_transition,
                          bool is_user_initiated,
                          bool user_tapped_recently);

  // web::WebStatePolicyDecider implementation
  void ShouldAllowRequest(
      NSURLRequest* request,
      web::WebStatePolicyDecider::RequestInfo request_info,
      web::WebStatePolicyDecider::PolicyDecisionCallback callback) override;

 protected:
  // Constructor for AppLauncherTabHelper. `abuse_detector` provides policy for
  // launching apps.
  // Protected to allow test overriding.
  AppLauncherTabHelper(web::WebState* web_state,
                       AppLauncherAbuseDetector* abuse_detector,
                       bool incognito);

 private:
  friend class web::WebStateUserData<AppLauncherTabHelper>;

  // Getter for the delegate.
  AppLauncherTabHelperDelegate* delegate() const { return delegate_; }

  // Callback to AppLauncherTabHelperDelegate::LaunchAppForTabHelper when a
  // prompt was previously shown to the user.
  // If the launch was not successful, another prompt can be shown.
  void OnAppLaunchTried(bool success);

  // Called by the delegate when the failure prompt was shown to the user.
  // `user_allowed` is ignored as there is only one button.
  void ShowFailureAlertDone(bool user_allowed);

  // Called when external app is launched.
  void OnAppLaunchCompleted(bool success);

  // If app was launched successfully, called when application leaves the
  // the foreground inactive state.
  void AppNoLongerInactive();

  // Resets `is_app_launch_request_pending_` to `false` and call all callbacks
  // waiting for app completion.
  void LaunchAppRequestCompleted();

  // Triggers an in tab prompt to ask the user if the app launch should proceed.
  void ShowAppLaunchAlert(AppLauncherAlertCause cause, const GURL& url);

  // Called by the delegate once the user has been prompted. If `user_allowed`,
  // then `LaunchAppForTabHelper()` will be called on the delegate.
  void OnShowAppLaunchAlertDone(const GURL& url, bool user_allowed);

  // Holds the necessary data for a call to `RequestToLaunchApp()`. A value for
  // this type can optionally be returned by
  // `GetPolicyDecisionAndOptionalAppLaunchRequest()`.
  struct AppLaunchRequest {
    GURL url;
    GURL source_page_url;
    bool link_transition;
    bool is_user_initiated;
    bool user_tapped_recently;
  };
  using PolicyDecisionAndOptionalAppLaunchRequest =
      std::pair<web::WebStatePolicyDecider::PolicyDecision,
                std::optional<AppLaunchRequest>>;
  // Returns the appropriate policy decision for the given `request`. If the
  // request should trigger an app launch request, returns an app launch request
  // too.
  PolicyDecisionAndOptionalAppLaunchRequest
  GetPolicyDecisionAndOptionalAppLaunchRequest(
      NSURLRequest* request,
      web::WebStatePolicyDecider::RequestInfo request_info) const;

  // The WebState that this object is attached to.
  raw_ptr<web::WebState> web_state_ = nullptr;

  // Used to check for repeated launches and provide policy for launching apps.
  AppLauncherAbuseDetector* abuse_detector_ = nil;

  // Whether this TabHelper is in incognito.
  bool incognito_ = false;

  // Used to launch apps and present UI.
  raw_ptr<AppLauncherTabHelperDelegate> delegate_ = nullptr;

  // Used to know if the browser is currently presenting another VC.
  __weak id<AppLauncherTabHelperBrowserPresentationProvider>
      browser_presentation_provider_ = nil;

  // Returns whether there is a prompt shown by `RequestToOpenUrl` or not.
  bool is_prompt_active_ = false;

  // Whether there is an app launch request pending. Set to `true` before
  // calling `LaunchAppForTabHelper()` on the delegate.
  bool is_app_launch_request_pending_ = false;

  // Stores callbacks which should be called once the ongoing app launch
  // completes. When `ShouldAllowRequest()` asks this tab helper for a policy
  // decision, the call to the policy decision callback might need to be delayed
  // in case an app launch is still ongoing for this tab. These callbacks would
  // then be stored here until `AppLaunchCompleted()` is called. Then all
  // callbacks will run and `callbacks_waiting_for_app_launch_completion_` will
  // be cleared.
  // Note: Some of these callbacks may trigger a new app launch call or even
  // close the tab (and hence delete this TabHelper).
  std::vector<base::OnceClosure> callbacks_waiting_for_app_launch_completion_;

  // Must be last member to ensure it is destroyed last.
  base::WeakPtrFactory<AppLauncherTabHelper> weak_factory_{this};

  WEB_STATE_USER_DATA_KEY_DECL();
};

#endif  // IOS_CHROME_BROWSER_APP_LAUNCHER_MODEL_APP_LAUNCHER_TAB_HELPER_H_