chromium/ios/web/public/navigation/web_state_policy_decider.h

// Copyright 2015 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_WEB_PUBLIC_NAVIGATION_WEB_STATE_POLICY_DECIDER_H_
#define IOS_WEB_PUBLIC_NAVIGATION_WEB_STATE_POLICY_DECIDER_H_

#import <Foundation/Foundation.h>

#include "base/functional/callback.h"
#import "base/memory/raw_ptr.h"
#include "base/observer_list_types.h"
#include "ui/base/page_transition_types.h"
#include "url/gurl.h"

namespace web {

class WebState;
class FakeWebState;

// Decides the navigation policy for a web state.
class WebStatePolicyDecider : public base::CheckedObserver {
 public:
  // Specifies a navigation decision. Used as a return value by
  // WebStatePolicyDecider::ShouldAllowRequest(), and used by
  // WebStatePolicyDecider::ShouldAllowResponse() when sending its decision to
  // its callback.
  struct PolicyDecision {
    // A policy decision which allows the navigation.
    static PolicyDecision Allow();

    // A policy decision which cancels the navigation.
    static PolicyDecision Cancel();

    // A policy decision which cancels the navigation and displays `error`.
    // NOTE: The `error` will only be displayed if the associated navigation is
    // being loaded in the main frame.
    static PolicyDecision CancelAndDisplayError(NSError* error);

    // Whether or not the navigation will continue.
    bool ShouldAllowNavigation() const;

    // Whether or not the navigation will be cancelled.
    bool ShouldCancelNavigation() const;

    // Whether or not an error should be displayed. Always returns false if
    // `ShouldAllowNavigation` is true.
    // NOTE: Will return true when the receiver is created with
    // `CancelAndDisplayError` even though an error will only end up being
    // displayed if the associated navigation is occurring in the main frame.
    bool ShouldDisplayError() const;

    // The error to display when `ShouldDisplayError` is true.
    NSError* GetDisplayError() const;

   private:
    // The decisions which can be taken for a given navigation.
    enum class Decision {
      // Allow the navigation to proceed.
      kAllow,

      // Cancel the navigation.
      kCancel,

      // Cancel the navigation and display an error.
      kCancelAndDisplayError,
    };

    PolicyDecision(Decision decision, NSError* error)
        : decision(decision), error(error) {}

    // The decision to be taken for a given navigation.
    Decision decision = Decision::kAllow;

    // An error associated with the navigation. This error will be displayed if
    // `decision` is `kCancelAndDisplayError`.
    NSError* error = nil;
  };

  // Callback used to provide asynchronous policy decisions.
  typedef base::OnceCallback<void(PolicyDecision)> PolicyDecisionCallback;

  // Data Transfer Object for the additional information about navigation
  // request passed to WebStatePolicyDecider::ShouldAllowRequest().
  struct RequestInfo {
    RequestInfo(ui::PageTransition transition_type,
                bool target_frame_is_main,
                bool target_frame_is_cross_origin,
                bool target_window_is_cross_origin,
                bool is_user_initiated,
                bool user_tapped_recently)
        : transition_type(transition_type),
          target_frame_is_main(target_frame_is_main),
          target_frame_is_cross_origin(target_frame_is_cross_origin),
          target_window_is_cross_origin(target_window_is_cross_origin),
          is_user_initiated(is_user_initiated),
          user_tapped_recently(user_tapped_recently) {}
    // The navigation page transition type.
    ui::PageTransition transition_type =
        ui::PageTransition::PAGE_TRANSITION_FIRST;
    // Indicates whether the navigation target frame is the main frame.
    bool target_frame_is_main = false;
    // Indicates whether the navigation target frame is cross-origin with
    // respect to the the navigation source frame.
    bool target_frame_is_cross_origin = false;
    // Indicates whether the navigation target frame is in another window and is
    // cross-origin with respect to the the navigation source frame.
    bool target_window_is_cross_origin = false;
    // Indicates if the request is user initiated (to the best of our
    // knowledge).
    bool is_user_initiated = false;
    // Indicates if there was a recent user interaction with the web view (not
    // necessarily on the page).
    bool user_tapped_recently = false;
  };

  // Data Transfer Object for the additional information about response
  // request passed to WebStatePolicyDecider::ShouldAllowResponse().
  struct ResponseInfo {
    explicit ResponseInfo(bool for_main_frame)
        : for_main_frame(for_main_frame) {}
    // Indicates whether the response target frame is the main frame.
    bool for_main_frame = false;
  };

  WebStatePolicyDecider(const WebStatePolicyDecider&) = delete;
  WebStatePolicyDecider& operator=(const WebStatePolicyDecider&) = delete;

  // Removes self as a policy decider of `web_state_`.
  ~WebStatePolicyDecider() override;

  // Asks the decider whether the navigation corresponding to `request` should
  // be allowed to continue. Defaults to PolicyDecision::Allow() if not
  // overridden. Called before WebStateObserver::DidStartNavigation. Calls
  // `callback` with the decision. Never called in the following cases:
  //  - same-document back-forward and state change navigations
  virtual void ShouldAllowRequest(NSURLRequest* request,
                                  RequestInfo request_info,
                                  PolicyDecisionCallback callback);

  // Asks the decider whether the navigation corresponding to `response` should
  // be allowed to continue. Defaults to PolicyDecision::Allow() if not
  // overridden. Called before WebStateObserver::DidFinishNavigation. Calls
  // `callback` with the decision.
  // Never called in the following cases:
  //  - same-document navigations (unless initiated via LoadURLWithParams)
  //  - going back after form submission navigation
  //  - user-initiated POST navigation on iOS 10
  virtual void ShouldAllowResponse(NSURLResponse* response,
                                   ResponseInfo response_info,
                                   PolicyDecisionCallback callback);

  // Notifies the policy decider that the web state is being destroyed.
  // Gives subclasses a chance to cleanup.
  // The policy decider must not be destroyed while in this call, as removing
  // while iterating is not supported.
  virtual void WebStateDestroyed() {}

  WebState* web_state() const { return web_state_; }

 protected:
  // Designated constructor. Subscribes to `web_state`.
  explicit WebStatePolicyDecider(WebState* web_state);

 private:
  friend class ContentWebState;
  friend class FakeWebState;
  friend class WebStateImpl;

  // Resets the current web state.
  void ResetWebState();

  // The web state to decide navigation policy for.
  raw_ptr<WebState> web_state_;
};
}  // namespace web

#endif  // IOS_WEB_PUBLIC_NAVIGATION_WEB_STATE_POLICY_DECIDER_H_