chromium/content/browser/web_contents/web_contents_view_ios.mm

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

#import "content/browser/web_contents/web_contents_view_ios.h"

#import <UIKit/UIKit.h>

#include <memory>
#include <string>
#include <utility>

#include "base/apple/foundation_util.h"
#include "base/memory/weak_ptr.h"
#include "content/browser/renderer_host/popup_menu_helper_ios.h"
#include "content/browser/renderer_host/render_widget_host_view_ios.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/browser/context_menu_params.h"
#include "content/public/browser/web_contents_delegate.h"
#include "content/public/browser/web_contents_view_delegate.h"
#include "ui/base/cocoa/animation_utils.h"
#include "ui/gfx/native_widget_types.h"

namespace content {

namespace {

WebContentsViewIOS::RenderWidgetHostViewCreateFunction
    g_create_render_widget_host_view = nullptr;

}  // namespace

// static
void WebContentsViewIOS::InstallCreateHookForTests(
    RenderWidgetHostViewCreateFunction create_render_widget_host_view) {
  CHECK_EQ(nullptr, g_create_render_widget_host_view);
  g_create_render_widget_host_view = create_render_widget_host_view;
}

// This class holds strongly so we don't leak that in the header of the
// WebContentsViewIOS.
class WebContentsUIViewHolder {
 public:
  UIScrollView* __strong view_;
};

std::unique_ptr<WebContentsView> CreateWebContentsView(
    WebContentsImpl* web_contents,
    std::unique_ptr<WebContentsViewDelegate> delegate,
    raw_ptr<RenderViewHostDelegateView>* render_view_host_delegate_view) {
  auto rv =
      std::make_unique<WebContentsViewIOS>(web_contents, std::move(delegate));
  *render_view_host_delegate_view = rv.get();
  return rv;
}

WebContentsViewIOS::WebContentsViewIOS(
    WebContentsImpl* web_contents,
    std::unique_ptr<WebContentsViewDelegate> delegate)
    : web_contents_(web_contents), delegate_(std::move(delegate)) {
  ui_view_ = std::make_unique<WebContentsUIViewHolder>();
  ui_view_->view_ = [[UIScrollView alloc] init];
  [ui_view_->view_ setScrollEnabled:NO];
  [ui_view_->view_ setAutoresizingMask:UIViewAutoresizingFlexibleWidth |
                                       UIViewAutoresizingFlexibleHeight];
}

WebContentsViewIOS::~WebContentsViewIOS() {}

gfx::NativeView WebContentsViewIOS::GetNativeView() const {
  return gfx::NativeView(ui_view_->view_);
}

gfx::NativeView WebContentsViewIOS::GetContentNativeView() const {
  RenderWidgetHostView* rwhv = web_contents_->GetRenderWidgetHostView();
  if (!rwhv) {
    return gfx::NativeView();
  }
  return rwhv->GetNativeView();
}

gfx::NativeWindow WebContentsViewIOS::GetTopLevelNativeWindow() const {
  gfx::NativeView view = GetContentNativeView();
  if (!view) {
    return gfx::NativeWindow();
  }
  return gfx::NativeWindow(view.Get().window);
}

gfx::Rect WebContentsViewIOS::GetContainerBounds() const {
  return gfx::Rect();
}

void WebContentsViewIOS::OnCapturerCountChanged() {}

void WebContentsViewIOS::FullscreenStateChanged(bool is_fullscreen) {
  if (is_fullscreen && popup_menu_helper_) {
    popup_menu_helper_->CloseMenu();
  }
}

void WebContentsViewIOS::UpdateWindowControlsOverlay(
    const gfx::Rect& bounding_rect) {}

void WebContentsViewIOS::Focus() {
  if (delegate_) {
    delegate_->ResetStoredFocus();
  }

  // Focus the the fullscreen view, if one exists; otherwise, focus the content
  // native view. This ensures that the view currently attached to a NSWindow is
  // being used to query or set first responder state.
  RenderWidgetHostView* rwhv = web_contents_->GetRenderWidgetHostView();
  if (!rwhv) {
    return;
  }

  static_cast<RenderWidgetHostViewBase*>(rwhv)->Focus();
}

void WebContentsViewIOS::SetInitialFocus() {
  if (delegate_) {
    delegate_->ResetStoredFocus();
  }

  if (web_contents_->FocusLocationBarByDefault()) {
    web_contents_->SetFocusToLocationBar();
  } else {
    Focus();
  }
}

void WebContentsViewIOS::StoreFocus() {
  if (delegate_) {
    delegate_->StoreFocus();
  }
}

void WebContentsViewIOS::RestoreFocus() {
  if (delegate_ && delegate_->RestoreFocus()) {
    return;
  }

  // Fall back to the default focus behavior if we could not restore focus.
  // TODO(shess): If location-bar gets focus by default, this will
  // select-all in the field.  If there was a specific selection in
  // the field when we navigated away from it, we should restore
  // that selection.
  SetInitialFocus();
}

void WebContentsViewIOS::FocusThroughTabTraversal(bool reverse) {
  if (delegate_) {
    delegate_->ResetStoredFocus();
  }

  web_contents_->GetRenderViewHost()->SetInitialFocus(reverse);
}

DropData* WebContentsViewIOS::GetDropData() const {
  return nullptr;
}

gfx::Rect WebContentsViewIOS::GetViewBounds() const {
  return gfx::Rect(ui_view_->view_.contentSize.width,
                   ui_view_->view_.contentSize.height);
}

void WebContentsViewIOS::GotFocus(RenderWidgetHostImpl* render_widget_host) {
  web_contents_->NotifyWebContentsFocused(render_widget_host);
}

void WebContentsViewIOS::LostFocus(RenderWidgetHostImpl* render_widget_host) {
  web_contents_->NotifyWebContentsLostFocus(render_widget_host);
}

void WebContentsViewIOS::ShowContextMenu(RenderFrameHost& render_frame_host,
                                         const ContextMenuParams& params) {
  if (delegate_) {
    delegate_->ShowContextMenu(render_frame_host, params);
  } else {
    DLOG(ERROR) << "Cannot show context menus without a delegate.";
  }
}

void WebContentsViewIOS::ShowPopupMenu(
    RenderFrameHost* render_frame_host,
    mojo::PendingRemote<blink::mojom::PopupMenuClient> popup_client,
    const gfx::Rect& bounds,
    int item_height,
    double item_font_size,
    int selected_item,
    std::vector<blink::mojom::MenuItemPtr> menu_items,
    bool right_aligned,
    bool allow_multiple_selection) {
  popup_menu_helper_ = std::make_unique<PopupMenuHelper>(
      this, render_frame_host, std::move(popup_client));
  popup_menu_helper_->ShowPopupMenu(bounds, item_height, item_font_size,
                                    selected_item, std::move(menu_items),
                                    right_aligned, allow_multiple_selection);
}

void WebContentsViewIOS::OnMenuClosed() {
  popup_menu_helper_.reset();
}

void WebContentsViewIOS::CreateView(gfx::NativeView context) {}

RenderWidgetHostViewBase* WebContentsViewIOS::CreateViewForWidget(
    RenderWidgetHost* render_widget_host) {
  if (g_create_render_widget_host_view) {
    return g_create_render_widget_host_view(render_widget_host);
  }
  return new RenderWidgetHostViewIOS(render_widget_host);
}

RenderWidgetHostViewBase* WebContentsViewIOS::CreateViewForChildWidget(
    RenderWidgetHost* render_widget_host) {
  return new RenderWidgetHostViewIOS(render_widget_host);
}

void WebContentsViewIOS::SetPageTitle(const std::u16string& title) {
  // Meaningless on the Mac; widgets don't have a "title" attribute
}

void WebContentsViewIOS::RenderViewReady() {}

void WebContentsViewIOS::RenderViewHostChanged(RenderViewHost* old_host,
                                               RenderViewHost* new_host) {
  ScopedCAActionDisabler disabler;
  if (old_host) {
    auto* rwhv = old_host->GetWidget()->GetView();
    if (rwhv && rwhv->GetNativeView()) {
      static_cast<RenderWidgetHostViewIOS*>(rwhv)->UpdateNativeViewTree(
          gfx::NativeView());
    }
  }

  auto* rwhv = new_host->GetWidget()->GetView();
  if (rwhv && rwhv->GetNativeView()) {
    static_cast<RenderWidgetHostViewIOS*>(rwhv)->UpdateNativeViewTree(
        GetNativeView());
  }
  web_contents_->UpdateBrowserControlsState(cc::BrowserControlsState::kBoth,
                                            cc::BrowserControlsState::kHidden,
                                            false, std::nullopt);
}

void WebContentsViewIOS::SetOverscrollControllerEnabled(bool enabled) {}

int WebContentsViewIOS::GetTopControlsHeight() const {
  auto* delegate = web_contents_->GetDelegate();
  return delegate ? delegate->GetTopControlsHeight() : 0;
}

int WebContentsViewIOS::GetTopControlsMinHeight() const {
  auto* delegate = web_contents_->GetDelegate();
  return delegate ? delegate->GetTopControlsMinHeight() : 0;
}

int WebContentsViewIOS::GetBottomControlsHeight() const {
  auto* delegate = web_contents_->GetDelegate();
  return delegate ? delegate->GetBottomControlsHeight() : 0;
}

int WebContentsViewIOS::GetBottomControlsMinHeight() const {
  auto* delegate = web_contents_->GetDelegate();
  return delegate ? delegate->GetBottomControlsMinHeight() : 0;
}

bool WebContentsViewIOS::ShouldAnimateBrowserControlsHeightChanges() const {
  auto* delegate = web_contents_->GetDelegate();
  return delegate && delegate->ShouldAnimateBrowserControlsHeightChanges();
}

bool WebContentsViewIOS::DoBrowserControlsShrinkRendererSize() const {
  auto* delegate = web_contents_->GetDelegate();
  return delegate &&
         delegate->DoBrowserControlsShrinkRendererSize(web_contents_);
}

bool WebContentsViewIOS::OnlyExpandTopControlsAtPageTop() const {
  auto* delegate = web_contents_->GetDelegate();
  return delegate && delegate->OnlyExpandTopControlsAtPageTop();
}

BackForwardTransitionAnimationManager*
WebContentsViewIOS::GetBackForwardTransitionAnimationManager() {
  return nullptr;
}

}  // namespace content