chromium/content/app_shim_remote_cocoa/web_contents_ns_view_bridge.mm

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

#include "content/app_shim_remote_cocoa/web_contents_ns_view_bridge.h"

#include "base/apple/foundation_util.h"
#import "base/task/sequenced_task_runner.h"
#import "components/remote_cocoa/app_shim/native_widget_mac_nswindow.h"
#include "components/remote_cocoa/app_shim/ns_view_ids.h"
#import "content/app_shim_remote_cocoa/web_contents_view_cocoa.h"
#include "content/browser/web_contents/web_contents_view_mac.h"
#include "ui/accelerated_widget_mac/window_resize_helper_mac.h"
#include "ui/gfx/image/image_skia_util_mac.h"

namespace remote_cocoa {

WebContentsNSViewBridge::WebContentsNSViewBridge(
    uint64_t view_id,
    mojo::PendingAssociatedRemote<mojom::WebContentsNSViewHost> client)
    : host_(std::move(client),
            ui::WindowResizeHelperMac::Get()->task_runner()) {
  ns_view_ = [[WebContentsViewCocoa alloc] initWithViewsHostableView:nullptr];
  [ns_view_ setHost:host_.get()];
  [ns_view_ enableDroppedScreenShotCopier];
  view_id_ =
      std::make_unique<remote_cocoa::ScopedNSViewIdMapping>(view_id, ns_view_);
}

WebContentsNSViewBridge::WebContentsNSViewBridge(
    uint64_t view_id,
    content::WebContentsViewMac* web_contents_view) {
  ns_view_ = [[WebContentsViewCocoa alloc]
      initWithViewsHostableView:web_contents_view];
  [ns_view_ setHost:web_contents_view];
  view_id_ =
      std::make_unique<remote_cocoa::ScopedNSViewIdMapping>(view_id, ns_view_);
}

WebContentsNSViewBridge::~WebContentsNSViewBridge() {
  // This handles the case where a renderer close call was deferred
  // while the user was operating a UI control which resulted in a
  // close.  In that case, the Cocoa view outlives the
  // WebContentsViewMac instance due to Cocoa retain count.
  [ns_view_ setHost:nullptr];
  [ns_view_ clearViewsHostableView];
  [ns_view_ removeFromSuperview];
}

void WebContentsNSViewBridge::Bind(
    mojo::PendingAssociatedReceiver<mojom::WebContentsNSView> receiver,
    scoped_refptr<base::SequencedTaskRunner> task_runner) {
  receiver_.Bind(std::move(receiver), std::move(task_runner));
  receiver_.set_disconnect_handler(base::BindOnce(
      &WebContentsNSViewBridge::Destroy, base::Unretained(this)));
}

void WebContentsNSViewBridge::Destroy() {
  delete this;
}

void WebContentsNSViewBridge::SetParentNSView(uint64_t parent_ns_view_id) {
  NSView* parent_ns_view = remote_cocoa::GetNSViewFromId(parent_ns_view_id);
  // If the browser passed an invalid handle, then there is no recovery.
  CHECK(parent_ns_view);
  [parent_ns_view addSubview:ns_view_];
}

void WebContentsNSViewBridge::ResetParentNSView() {
  [ns_view_ removeFromSuperview];
}

void WebContentsNSViewBridge::SetBounds(const gfx::Rect& bounds_in_window) {
  NSWindow* window = [ns_view_ window];
  NSRect window_content_rect = [window contentRectForFrameRect:[window frame]];
  NSRect ns_bounds_in_window =
      NSMakeRect(bounds_in_window.x(),
                 window_content_rect.size.height - bounds_in_window.y() -
                     bounds_in_window.height(),
                 bounds_in_window.width(), bounds_in_window.height());
  NSRect ns_bounds_in_superview =
      [[ns_view_ superview] convertRect:ns_bounds_in_window fromView:nil];
  [ns_view_ setFrame:ns_bounds_in_superview];
}

void WebContentsNSViewBridge::SetVisible(bool visible) {
  // If the first responder is a child of the current view, AppKit will search
  // for a new first responder during `-setHidden:`. The key view loop is
  // searched for a view that can become key. Typically this search yields no
  // results and the window becomes the default first responder. However if this
  // occurs after an immersive fullscreen restore an infinite loop can occur
  // leading to an OOM. This occurs because of the existence of an NSToolbar,
  // which causes the key loop traversal to jump back and forth between the
  // view's window and the AppKit owned NSToolbarFullscreenWindow which hosts
  // the toolbar in immersive fullscreen. To prevent this set the window's first
  // responder to nil which will make the window the first responder before the
  // hide.
  // TODO(http://crbug.com/40261565): Remove when FB12010731 is fixed in
  // AppKit.
  NativeWidgetMacNSWindow* widget_window =
      base::apple::ObjCCast<NativeWidgetMacNSWindow>(ns_view_.window);
  if (!visible && [widget_window immersiveFullscreen]) {
    NSView* first_responder =
        base::apple::ObjCCast<NSView>(ns_view_.window.firstResponder);
    if ([first_responder isDescendantOf:ns_view_]) {
      [ns_view_.window makeFirstResponder:nil];
    }
  }

  [ns_view_ setHidden:!visible];
}

void WebContentsNSViewBridge::MakeFirstResponder() {
  if ([ns_view_ acceptsFirstResponder])
    [[ns_view_ window] makeFirstResponder:ns_view_];
}

void WebContentsNSViewBridge::TakeFocus(bool reverse) {
  if (reverse)
    [[ns_view_ window] selectPreviousKeyView:ns_view_];
  else
    [[ns_view_ window] selectNextKeyView:ns_view_];
}

void WebContentsNSViewBridge::StartDrag(const content::DropData& drop_data,
                                        const url::Origin& source_origin,
                                        uint32_t operation_mask,
                                        const gfx::ImageSkia& image,
                                        const gfx::Vector2d& image_offset,
                                        bool is_privileged) {
  NSPoint offset = NSPointFromCGPoint(
      gfx::PointAtOffsetFromOrigin(image_offset).ToCGPoint());
  [ns_view_ startDragWithDropData:drop_data
                     sourceOrigin:source_origin
                dragOperationMask:operation_mask
                            image:gfx::NSImageFromImageSkia(image)
                           offset:offset
                     isPrivileged:is_privileged];
}

void WebContentsNSViewBridge::UpdateWindowControlsOverlay(
    const gfx::Rect& bounding_rect) {
  [ns_view_ updateWindowControlsOverlay:bounding_rect];
}

}  // namespace remote_cocoa