// 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.

#ifndef UI_VIEWS_CONTROLS_WEBVIEW_WEBVIEW_H_
#define UI_VIEWS_CONTROLS_WEBVIEW_WEBVIEW_H_

#include <stdint.h>

#include <memory>

#include "base/callback_list.h"
#include "base/functional/callback.h"
#include "base/location.h"
#include "base/memory/raw_ptr.h"
#include "base/scoped_observation.h"
#include "content/public/browser/web_contents_delegate.h"
#include "content/public/browser/web_contents_observer.h"
#include "ui/accessibility/platform/ax_mode_observer.h"
#include "ui/accessibility/platform/ax_platform.h"
#include "ui/gfx/native_ui_types.h"
#include "ui/views/controls/native/native_view_host.h"
#include "ui/views/controls/webview/webview_export.h"
#include "ui/views/metadata/view_factory.h"
#include "ui/views/view.h"
#include "ui/views/view_tracker.h"

class GURL;

namespace content {
class BrowserContext;
class WebContents;
}  // namespace content

namespace views {

// Provides a view of a WebContents instance.  WebView can be used standalone,
// creating and displaying an internally-owned WebContents; or within a full
// browser where the browser swaps its own WebContents instances in/out (e.g.,
// for browser tabs).
//
// WebView creates and owns a single child view, a NativeViewHost, which will
// hold and display the native view provided by a WebContents.
//
// EmbedFullscreenWidgetMode: When enabled, WebView will observe for WebContents
// fullscreen changes and automatically swap the normal native view with the
// fullscreen native view (if different).  In addition, if the WebContents is
// being screen-captured, the view will be centered within WebView, sized to
// the aspect ratio of the capture video resolution, and scaling will be avoided
// whenever possible.
class WEBVIEW_EXPORT WebView : public View,
                               public content::WebContentsDelegate,
                               public content::WebContentsObserver,
                               public ui::AXModeObserver {
  METADATA_HEADER(WebView, View)

 public:
  // Whether the navigation should be allowed to be automatically upgraded to
  // HTTPS. Only applies to initial loads.
  enum class HttpsUpgradePolicy {
    // Allows the navigation to be upgraded to HTTPS when possible.
    kAllowUpgrade,
    // Exempts the navigation from being upgraded to HTTPS (e.g. when loading
    // a captive portal login page).
    kNoUpgrade,
  };

  using WebContentsAttachedCallback = base::RepeatingCallback<void(WebView*)>;
  using WebContentsDetachedCallback = base::RepeatingCallback<void(WebView*)>;
  using WebContentsFocusedCallback = base::RepeatingCallback<void(WebView*)>;

  explicit WebView(content::BrowserContext* browser_context = nullptr);

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

  ~WebView() override;

  static bool IsWebViewContents(const content::WebContents* web_contents);

  // This creates a WebContents if |browser_context_| has been set and there is
  // not yet a WebContents associated with this WebView, otherwise it will
  // return the existing web contents. `url` is used to create a `SiteInstance`
  // for the `WebContents`. If `url` is empty, a default `SiteInstance` will be
  // used.
  content::WebContents* GetWebContents(
      const GURL& url = GURL(),
      base::Location creator_location = base::Location::Current());

  // WebView does not assume ownership of WebContents set via this method, only
  // those it implicitly creates via GetWebContents() above.
  virtual void SetWebContents(content::WebContents* web_contents);

  content::BrowserContext* GetBrowserContext();
  void SetBrowserContext(content::BrowserContext* browser_context);

  // Loads the initial URL to display in the attached WebContents. Creates the
  // WebContents if none is attached yet. Note that this is intended as a
  // convenience for loading the initial URL, and so URLs are navigated with
  // PAGE_TRANSITION_AUTO_TOPLEVEL, so this is not intended as a general purpose
  // navigation method - use WebContents' API directly.
  void LoadInitialURL(
      const GURL& url,
      HttpsUpgradePolicy https_upgrade_policy =
          HttpsUpgradePolicy::kAllowUpgrade,
      base::Location invoke_location = base::Location::Current());

  // Controls how the attached WebContents is resized.
  // false = WebContents' views' bounds are updated continuously as the
  //         WebView's bounds change (default).
  // true  = WebContents' views' position is updated continuously but its size
  //         is not (which may result in some clipping or under-painting) until
  //         a continuous size operation completes. This allows for smoother
  //         resizing performance during interactive resizes and animations.
  void SetFastResize(bool fast_resize);
  bool GetFastResize() const;

  // If enabled, this will make the WebView's preferred size dependent on the
  // WebContents' size.
  void EnableSizingFromWebContents(const gfx::Size& min_size,
                                   const gfx::Size& max_size);

  // Set the background color to use while resizing with a clip. This is white
  // by default.
  void SetResizeBackgroundColor(SkColor resize_background_color);

  // If provided, this View will be shown in place of the web contents
  // when the web contents is in a crashed state. This is cleared automatically
  // if the web contents is changed. The passed-in overlay view must be owned by
  // the client; this method never takes ownership of it.
  //
  // TODO(crbug.com/40278361): This method should take ownership of
  // `crashed_overlay_view`.
  void SetCrashedOverlayView(View* crashed_overlay_view);

  // Adds a callback for when a WebContents is attached to this WebView.
  base::CallbackListSubscription AddWebContentsAttachedCallback(
      WebContentsAttachedCallback callback);

  // Adds a callback for when a WebContents is detached from this WebView.
  base::CallbackListSubscription AddWebContentsDetachedCallback(
      WebContentsDetachedCallback callback);

  // Adds a callback for when the attached WebContents is focused.
  base::CallbackListSubscription AddWebContentsFocusedCallback(
      WebContentsFocusedCallback callback);

  // Sets whether this is the primary web contents for the window.
  void set_is_primary_web_contents_for_window(bool is_primary) {
    is_primary_web_contents_for_window_ = is_primary;
  }

  // When used to host UI, we need to explicitly allow accelerators to be
  // processed. Default is false.
  void set_allow_accelerators(bool allow_accelerators) {
    allow_accelerators_ = allow_accelerators;
  }

  // When `lock = true` changes in web contents will not reset the override.
  // Default is false.
  void set_lock_child_ax_tree_id_override(bool lock) {
    lock_child_ax_tree_id_override_ = lock;
  }

  // Overridden from content::WebContentsDelegate:
  void ResizeDueToAutoResize(content::WebContents* source,
                             const gfx::Size& new_size) override;

  NativeViewHost* holder() { return holder_; }
  using WebContentsCreator =
      base::RepeatingCallback<std::unique_ptr<content::WebContents>(
          content::BrowserContext*)>;

  // An instance of this class registers a WebContentsCreator on construction
  // and deregisters the WebContentsCreator on destruction.
  class WEBVIEW_EXPORT ScopedWebContentsCreatorForTesting {
   public:
    explicit ScopedWebContentsCreatorForTesting(WebContentsCreator creator);

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

    ~ScopedWebContentsCreatorForTesting();
  };

 protected:
  // Called when letterboxing (scaling the native view to preserve aspect
  // ratio) is enabled or disabled.
  virtual void OnLetterboxingChanged() {}
  bool is_letterboxing() const { return is_letterboxing_; }

  const gfx::Size& min_size() const { return min_size_; }
  const gfx::Size& max_size() const { return max_size_; }

  // View:
  void OnBoundsChanged(const gfx::Rect& previous_bounds) override;
  void ViewHierarchyChanged(
      const ViewHierarchyChangedDetails& details) override;
  bool SkipDefaultKeyEventProcessing(const ui::KeyEvent& event) override;
  bool OnMousePressed(const ui::MouseEvent& event) override;
  void OnFocus() override;
  void AboutToRequestFocusFromTabTraversal(bool reverse) override;
  gfx::NativeViewAccessible GetNativeViewAccessible() override;
  void AddedToWidget() override;
  void RemovedFromWidget() override;

  // Overridden from content::WebContentsObserver:
  void RenderFrameCreated(content::RenderFrameHost* render_frame_host) override;
  void RenderFrameDeleted(content::RenderFrameHost* render_frame_host) override;
  void RenderFrameHostChanged(content::RenderFrameHost* old_host,
                              content::RenderFrameHost* new_host) override;
  void DidToggleFullscreenModeForTab(bool entered_fullscreen,
                                     bool will_cause_resize) override;
  void OnWebContentsFocused(
      content::RenderWidgetHost* render_widget_host) override;
  void AXTreeIDForMainFrameHasChanged() override;
  void WebContentsDestroyed() override;

  // Override from ui::AXModeObserver
  void OnAXModeAdded(ui::AXMode mode) override;

 private:
  friend class WebViewUnitTest;

  void AttachWebContentsNativeView();
  void DetachWebContentsNativeView();
  void UpdateCrashedOverlayView();
  void NotifyAccessibilityWebContentsChanged();

  // Called when the main frame in the renderer becomes present.
  void SetUpNewMainFrame(content::RenderFrameHost* frame_host);
  // Called when the main frame in the renderer is no longer present.
  void LostMainFrame();

  // Registers for ResizeDueToAutoResize() notifications from `frame_host`'s
  // RenderWidgetHostView whenever it is created or changes, if
  // EnableSizingFromWebContents() has been called. This should only be called
  // for main frames; other frames can not have auto resize set.
  void MaybeEnableAutoResize(content::RenderFrameHost* frame_host);

  // Create a regular or test web contents (based on whether we're running
  // in a unit test or not).
  std::unique_ptr<content::WebContents> CreateWebContents(
      content::BrowserContext* browser_context,
      const GURL& url,
      base::Location creator_location);

  const raw_ptr<NativeViewHost> holder_ =
      AddChildView(std::make_unique<NativeViewHost>());
  base::ScopedObservation<ui::AXPlatform, ui::AXModeObserver>
      ax_mode_observation_{this};
  // Non-NULL if |web_contents()| was created and is owned by this WebView.
  std::unique_ptr<content::WebContents> wc_owner_;
  // Set to true when |holder_| is letterboxed (scaled to be smaller than this
  // view, to preserve its aspect ratio).
  bool is_letterboxing_ = false;
  raw_ptr<content::BrowserContext> browser_context_;
  bool allow_accelerators_ = false;
  ViewTracker crashed_overlay_view_;
  bool is_primary_web_contents_for_window_ = false;

  bool lock_child_ax_tree_id_override_ = false;

  // Minimum and maximum sizes to determine WebView bounds for auto-resizing.
  // Empty if auto resize is not enabled.
  gfx::Size min_size_;
  gfx::Size max_size_;

  // List of subscriptions listening for new WebContents being attached to this
  // WebView.
  base::RepeatingCallbackList<void(WebView*)> web_contents_attached_callbacks_;

  // List of subscriptions listening for the WebContents being detached from
  // this WebView.
  base::RepeatingCallbackList<void(WebView*)> web_contents_detached_callbacks_;

  // List of subscriptions listening for attached WebContents being focused.
  base::RepeatingCallbackList<void(WebView*)> web_contents_focused_callbacks_;
};

BEGIN_VIEW_BUILDER(WEBVIEW_EXPORT, WebView, View)
VIEW_BUILDER_PROPERTY(content::BrowserContext*, BrowserContext)
VIEW_BUILDER_PROPERTY(content::WebContents*, WebContents)
VIEW_BUILDER_PROPERTY(bool, FastResize)
VIEW_BUILDER_METHOD(EnableSizingFromWebContents,
                    const gfx::Size&,
                    const gfx::Size&)
VIEW_BUILDER_PROPERTY(View*, CrashedOverlayView)
VIEW_BUILDER_METHOD(set_is_primary_web_contents_for_window, bool)
VIEW_BUILDER_METHOD(set_allow_accelerators, bool)
END_VIEW_BUILDER

}  // namespace views

DEFINE_VIEW_BUILDER(WEBVIEW_EXPORT, WebView)

#endif  // UI_VIEWS_CONTROLS_WEBVIEW_WEBVIEW_H_