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

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

#import <Carbon/Carbon.h>
#include <fcntl.h>
#include <sys/stat.h>

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

#include "base/compiler_specific.h"
#include "base/containers/span.h"
#import "base/mac/mac_util.h"
#import "base/mac/scoped_sending_event.h"
#import "base/message_loop/message_pump_apple.h"
#include "base/task/current_thread.h"
#include "base/task/thread_pool.h"
#include "base/threading/thread_restrictions.h"
#include "components/remote_cocoa/app_shim/window_move_loop.h"
#include "components/remote_cocoa/browser/ns_view_ids.h"
#include "components/remote_cocoa/common/application.mojom.h"
#include "content/app_shim_remote_cocoa/web_contents_ns_view_bridge.h"
#import "content/app_shim_remote_cocoa/web_contents_view_cocoa.h"
#include "content/browser/download/drag_download_file.h"
#include "content/browser/download/drag_download_util.h"
#include "content/browser/renderer_host/popup_menu_helper_mac.h"
#include "content/browser/renderer_host/render_view_host_factory.h"
#include "content/browser/renderer_host/render_view_host_impl.h"
#include "content/browser/renderer_host/render_widget_host_view_mac.h"
#include "content/browser/web_contents/web_contents_impl.h"
#import "content/browser/web_contents/web_drag_dest_mac.h"
#include "content/common/web_contents_ns_view_bridge.mojom-shared.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/clipboard_types.h"
#include "content/public/browser/web_contents_delegate.h"
#include "content/public/browser/web_contents_view_delegate.h"
#include "mojo/public/cpp/bindings/pending_associated_receiver.h"
#include "mojo/public/cpp/bindings/pending_associated_remote.h"
#include "third_party/blink/public/common/tokens/tokens.h"
#include "ui/base/dragdrop/mojom/drag_drop_types.mojom.h"
#include "ui/display/display_util.h"
#include "ui/gfx/mac/coordinate_conversion.h"
#include "ui/gfx/native_ui_types.h"

using blink::DragOperationsMask;
using remote_cocoa::mojom::DraggingInfoPtr;
using remote_cocoa::mojom::SelectionDirection;

// Ensure that the blink::DragOperationsMask enum values stay in sync with
// NSDragOperation constants, since the code below static_casts between 'em.
#define STATIC_ASSERT_ENUM(a, b)                            \
  static_assert(static_cast<int>(a) == static_cast<int>(b), \
                "enum mismatch: " #a)
STATIC_ASSERT_ENUM(NSDragOperationNone, blink::kDragOperationNone);
STATIC_ASSERT_ENUM(NSDragOperationCopy, blink::kDragOperationCopy);
STATIC_ASSERT_ENUM(NSDragOperationLink, blink::kDragOperationLink);
STATIC_ASSERT_ENUM(NSDragOperationMove, blink::kDragOperationMove);
STATIC_ASSERT_ENUM(NSDragOperationEvery, blink::kDragOperationEvery);

namespace content {
namespace {

// This helper's sole task is to write out data for a promised file; the caller
// is responsible for opening the file. It takes the drop data and an open file
// stream.
void PromiseWriterHelper(const DropData& drop_data, base::File file) {
  DCHECK(file.IsValid());
  file.WriteAtCurrentPos(drop_data.file_contents);
}

WebContentsViewMac::RenderWidgetHostViewCreateFunction
    g_create_render_widget_host_view = nullptr;

// Sets read/write permissions on `file` based on the users umask.
void SetReadWritePermissionsForFile(base::File& file) {
  // Get the umask. There's no way to get the mask without changing it, so
  // immediately set it back to its original value.
  mode_t current_umask = umask(0);
  umask(current_umask);

  mode_t default_permissions = 0666;
  mode_t effective_permissions = default_permissions & ~current_umask;

  if (fchmod(file.GetPlatformFile(), effective_permissions) == -1) {
    PLOG(ERROR) << "Failed to set drag file permissions using fchmod";
  }
}

}  // namespace

// static
void WebContentsViewMac::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;
}

void WebContentsViewMac::SetReadWritePermissionsForFileForTests(
    base::File& file) {
  SetReadWritePermissionsForFile(file);
}

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<WebContentsViewMac>(web_contents, std::move(delegate));
  *render_view_host_delegate_view = rv.get();
  return rv;
}

WebContentsViewMac::WebContentsViewMac(
    WebContentsImpl* web_contents,
    std::unique_ptr<WebContentsViewDelegate> delegate)
    : web_contents_(web_contents),
      delegate_(std::move(delegate)),
      ns_view_id_(remote_cocoa::GetNewNSViewId()),
      deferred_close_weak_ptr_factory_(this) {}

WebContentsViewMac::~WebContentsViewMac() {
  if (views_host_)
    views_host_->OnHostableViewDestroying();
  DCHECK(!views_host_);
  in_process_ns_view_bridge_.reset();
}

WebContentsViewCocoa* WebContentsViewMac::GetInProcessNSView() const {
  return in_process_ns_view_bridge_ ? in_process_ns_view_bridge_->GetNSView()
                                    : nil;
}

gfx::NativeView WebContentsViewMac::GetNativeView() const {
  return gfx::NativeView(GetInProcessNSView());
}

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

gfx::NativeWindow WebContentsViewMac::GetTopLevelNativeWindow() const {
  NSWindow* window = [GetInProcessNSView() window];
  if (window) {
    return gfx::NativeWindow(window);
  }
  if (delegate_) {
    return delegate_->GetNativeWindow();
  }
  return gfx::NativeWindow();
}

gfx::Rect WebContentsViewMac::GetContainerBounds() const {
  NSView* view = GetInProcessNSView();
  NSWindow* window = [view window];
  NSRect bounds;

  if (window)  {
    bounds = [view bounds];

    // Convert bounds to window coordinate space.
    bounds = [view convertRect:bounds toView:nil];

    // Convert bounds to screen coordinate space.
    bounds = [window convertRectToScreen:bounds];
  } else {
    // The only time Chrome calls this method with no NSWindow is very early in
    // web contents creation cycle when the view has zero origin and size, so
    // calling |bounds| or |frame| makes no difference. However, headless always
    // runs with no NSWindow so it is important to retrieve view origin, hence
    // we need to call |frame|. https://crbug.com/378531862.
    bounds = [view frame];

    // Convert bounds to the root view coordinate space.
    NSView* root_view = view;
    while (NSView* parent = [root_view superview]) {
      root_view = parent;
    }

    bounds = [view convertRect:bounds toView:root_view];
  }

  return gfx::ScreenRectFromNSRect(bounds);
}

void WebContentsViewMac::OnCapturerCountChanged() {}

void WebContentsViewMac::FullscreenStateChanged(bool is_fullscreen) {}

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

void WebContentsViewMac::DestroyBackForwardTransitionAnimationManager() {}

void WebContentsViewMac::StartDragging(
    RenderFrameHost& source_rfh,
    const DropData& drop_data,
    DragOperationsMask allowed_operations,
    const gfx::ImageSkia& image,
    const gfx::Vector2d& cursor_offset,
    const gfx::Rect& drag_obj_rect,
    const blink::mojom::DragEventSourceInfo& event_info) {
  RenderWidgetHostImpl* source_rwh =
      static_cast<RenderWidgetHostImpl*>(source_rfh.GetRenderWidgetHost());
  // Disallow reentrant drag which could be an attempt to exploit drag state.
  if (drag_source_start_rwh_) {
    return;
  }
  // A window move loop already owns the held mouse button; refuse to start a
  // dragging session that would interfere with it.
  if (remote_cocoa::CocoaWindowMoveLoop::IsActive()) {
    web_contents_->SystemDragEnded(source_rwh);
    return;
  }
  url::Origin source_origin = source_rfh.GetLastCommittedOrigin();
  // By allowing nested tasks, the code below also allows Close(),
  // which would deallocate |this|.  The same problem can occur while
  // processing -sendEvent:, so Close() is deferred in that case.
  // Drags from web content do not come via -sendEvent:, this sets the
  // same flag -sendEvent: would.
  base::mac::ScopedSendingEvent sending_event_scoper;

  // The drag invokes a nested event loop, arrange to continue
  // processing events.
  base::CurrentThread::ScopedAllowApplicationTasksInNativeNestedLoop allow;

  ClipboardEndpoint source_endpoint = CreateClipboardEndpoint(source_rfh);

  // Checks if the drag operation is allowed by enterprise policies
  if (!GetContentClient()->browser()->IsDragAllowedByPolicy(source_endpoint,
                                                            drop_data)) {
    web_contents_->SystemDragEnded(source_rwh);
    return;
  }

  NSDragOperation mask = static_cast<NSDragOperation>(allowed_operations);

  [drag_dest_ initiateDragWithRenderWidgetHost:source_rwh dropData:drop_data];
  drag_source_start_rwh_ = source_rwh->GetWeakPtr();

  WebContentsDelegate* contents_delegate = web_contents_->GetDelegate();
  bool is_privileged =
      contents_delegate ? contents_delegate->IsPrivileged() : false;

  // TODO(crbug.com/40825138): The param `drag_obj_rect` is unused.

  const content::ChildProcessId render_process_id =
      source_rfh.GetProcess()->GetID();
  const blink::DocumentToken& document_token =
      static_cast<RenderFrameHostImpl&>(source_rfh).GetDocumentToken();
  if (remote_ns_view_) {
    remote_ns_view_->StartDrag(render_process_id, document_token, source_origin,
                               drop_data, mask, image, cursor_offset,
                               is_privileged);
  } else {
    in_process_ns_view_bridge_->StartDrag(render_process_id, document_token,
                                          source_origin, drop_data, mask, image,
                                          cursor_offset, is_privileged);
  }
}

void WebContentsViewMac::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 WebContentsViewMac::SetInitialFocus() {
  if (delegate())
    delegate()->ResetStoredFocus();

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

void WebContentsViewMac::StoreFocus() {
  if (delegate())
    delegate()->StoreFocus();
}

void WebContentsViewMac::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 WebContentsViewMac::FocusThroughTabTraversal(bool reverse) {
  if (delegate())
    delegate()->ResetStoredFocus();

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

DropData* WebContentsViewMac::GetDropData() const {
  return [drag_dest_ currentDropData];
}

void WebContentsViewMac::UpdateDragOperation(ui::mojom::DragOperation operation,
                                             bool document_is_handling_drag) {
  [drag_dest_ setCurrentOperation:operation
           documentIsHandlingDrag:document_is_handling_drag];
}

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

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

// This is called when the renderer asks us to take focus back (i.e., it has
// iterated past the last focusable element on the page).
void WebContentsViewMac::TakeFocus(bool reverse) {
  if (delegate())
    delegate()->ResetStoredFocus();

  if (web_contents_->GetDelegate() &&
      web_contents_->GetDelegate()->TakeFocus(web_contents_, reverse))
    return;
  if (delegate() && delegate()->TakeFocus(reverse))
    return;
  if (reverse) {
    [[GetInProcessNSView() window] selectPreviousKeyView:GetInProcessNSView()];
  } else {
    [[GetInProcessNSView() window] selectNextKeyView:GetInProcessNSView()];
  }
  if (remote_ns_view_)
    remote_ns_view_->TakeFocus(reverse);
}

void WebContentsViewMac::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.";
}

#if BUILDFLAG(USE_EXTERNAL_POPUP_MENU)
void WebContentsViewMac::ShowPopupMenu(
    RenderFrameHost* render_frame_host,
    mojo::PendingRemote<blink::mojom::PopupMenuClient> popup_client,
    const gfx::Rect& bounds,
    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_font_size, selected_item,
                                    std::move(menu_items), right_aligned,
                                    allow_multiple_selection);
  // Note: |this| may be deleted here.
}
#endif

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

gfx::Rect WebContentsViewMac::GetViewBounds() const {
  NSRect window_bounds =
      [GetInProcessNSView() convertRect:GetInProcessNSView().bounds toView:nil];
  window_bounds.origin =
      [GetInProcessNSView().window convertPointToScreen:window_bounds.origin];
  return gfx::ScreenRectFromNSRect(window_bounds);
}

void WebContentsViewMac::Resize(const gfx::Rect& new_bounds) {
  NSView* view = GetNativeView().GetNativeNSView();
  NSRect old_wcv_frame = view.frame;
  CGFloat new_x = old_wcv_frame.origin.x;
  CGFloat new_y = old_wcv_frame.origin.y +
                  (old_wcv_frame.size.height - new_bounds.size().height());
  NSRect new_wcv_frame = NSMakeRect(new_x, new_y, new_bounds.size().width(),
                                    new_bounds.size().height());
  view.frame = new_wcv_frame;
}

gfx::Size WebContentsViewMac::GetSize() const {
  NSView* view = GetNativeView().GetNativeNSView();
  NSRect frame = view.frame;
  return gfx::Size(NSWidth(frame), NSHeight(frame));
}

void WebContentsViewMac::CreateView(gfx::NativeView context) {
  in_process_ns_view_bridge_ =
      std::make_unique<remote_cocoa::WebContentsNSViewBridge>(ns_view_id_,
                                                              this);
  if (web_contents_->GetVisibility() == Visibility::HIDDEN) {
    in_process_ns_view_bridge_->SetVisible(false);
  }

  drag_dest_ = [[WebDragDest alloc] initWithWebContentsImpl:web_contents_];
  if (delegate_)
    [drag_dest_ setDragDelegate:delegate_->GetDragDestDelegate()];
}

RenderWidgetHostViewBase* WebContentsViewMac::CreateViewForWidget(
    RenderWidgetHost* render_widget_host) {
  if (render_widget_host->GetView()) {
    // During testing, the view will already be set up in most cases to the
    // test view, so we don't want to clobber it with a real one. To verify that
    // this actually is happening (and somebody isn't accidentally creating the
    // view twice), we check for the RVH Factory, which will be set when we're
    // making special ones (which go along with the special views).
    DCHECK(RenderViewHostFactory::has_factory());
    return static_cast<RenderWidgetHostViewBase*>(
        render_widget_host->GetView());
  }

  RenderWidgetHostViewMac* view =
      g_create_render_widget_host_view
          ? g_create_render_widget_host_view(render_widget_host)
          : new RenderWidgetHostViewMac(render_widget_host);
  if (delegate()) {
    view->SetDelegate(
        delegate()->GetDelegateForHost(render_widget_host, /*is_popup=*/false));
  }

  // Add the RenderWidgetHostView to the ui::Layer hierarchy.
  child_views_.push_back(view->GetWeakPtr());
  if (views_host_) {
    auto* remote_cocoa_application = views_host_->GetRemoteCocoaApplication();
    view->MigrateNSViewBridge(remote_cocoa_application, ns_view_id_);
    view->SetParentUiLayer(views_host_->GetUiLayer());
    view->SetParentAccessibilityElement(
        views_host_accessibility_element_.Get());
  }

  // Fancy layout comes later; for now just make it our size and resize it
  // with us. In case there are other siblings of the content area, we want
  // to make sure the content area is on the bottom so other things draw over
  // it.
  NSView* view_view = view->GetNativeView().GetNativeNSView();
  [view_view setFrame:[GetInProcessNSView() bounds]];
  [view_view setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
  // Add the new view below all other views; this also keeps it below any
  // overlay view installed.
  [GetInProcessNSView() addSubview:view_view
                        positioned:NSWindowBelow
                        relativeTo:nil];
  [GetInProcessNSView() setNextKeyView:view_view];

  // There is no NSWindow when running headless, so send the frame bounds to the
  // view synchronously to ensure its initial screen info is accurate.
  if (![GetInProcessNSView() window]) {
    view->SetWindowFrameInScreen(
        gfx::ScreenRectFromNSRect([GetInProcessNSView() frame]));
  }
  return view;
}

RenderWidgetHostViewBase* WebContentsViewMac::CreateViewForChildWidget(
    RenderWidgetHost* render_widget_host) {
  RenderWidgetHostViewMac* view =
      new RenderWidgetHostViewMac(render_widget_host);

  // If the parent RenderWidgetHostViewMac is hosted in another process, ensure
  // that the popup window will be created in the same process.
  // https://crbug.com/1091179
  if (views_host_) {
    auto* remote_cocoa_application = views_host_->GetRemoteCocoaApplication();
    view->MigrateNSViewBridge(remote_cocoa_application,
                              remote_cocoa::kInvalidNSViewId);
  }

  if (delegate()) {
    view->SetDelegate(
        delegate()->GetDelegateForHost(render_widget_host, /*is_popup=*/true));
  }
  return view;
}

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

void WebContentsViewMac::RenderViewReady() {}

void WebContentsViewMac::RenderViewHostChanged(RenderViewHost* old_host,
                                               RenderViewHost* new_host) {}

void WebContentsViewMac::SetOverscrollControllerEnabled(bool enabled) {
}

// Arrange to call CloseTab() after we're back to the main event loop.
// The obvious way to do this would be to post a NonNestable task, but that
// would fire when the event-tracking loop polls for events.  So we need to
// bounce the message via Cocoa, instead.
bool WebContentsViewMac::CloseTabAfterEventTrackingIfNeeded() {
  if (!base::message_pump_apple::IsHandlingSendEvent()) {
    return false;
  }

  deferred_close_weak_ptr_factory_.InvalidateWeakPtrs();
  auto weak_ptr = deferred_close_weak_ptr_factory_.GetWeakPtr();
  CFRunLoopPerformBlock(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, ^{
    if (weak_ptr)
      weak_ptr->CloseTab();
  });
  return true;
}

void WebContentsViewMac::CloseTab() {
  web_contents_->Close();
}

std::list<RenderWidgetHostViewMac*> WebContentsViewMac::GetChildViews() {
  // Remove any child NSViews that have been destroyed.
  std::list<RenderWidgetHostViewMac*> result;
  for (auto iter = child_views_.begin(); iter != child_views_.end();) {
    if (*iter) {
      result.push_back(static_cast<RenderWidgetHostViewMac*>(iter->get()));
      iter++;
    } else {
      iter = child_views_.erase(iter);
    }
  }
  return result;
}

////////////////////////////////////////////////////////////////////////////////
// WebContentsViewMac, mojom::WebContentsNSViewHost:

void WebContentsViewMac::OnMouseEvent(std::unique_ptr<ui::Event> event) {
  if (!web_contents_ || !web_contents_->GetDelegate() || !event) {
    return;
  }

  web_contents_->GetDelegate()->ContentsMouseEvent(web_contents_, *event);
}

void WebContentsViewMac::OnBecameFirstResponder(SelectionDirection direction) {
  if (!web_contents_)
    return;
  if (direction == SelectionDirection::kDirect)
    return;

  web_contents_->FocusThroughTabTraversal(direction ==
                                          SelectionDirection::kReverse);
}

void WebContentsViewMac::OnWindowVisibilityChanged(
    remote_cocoa::mojom::Visibility mojo_visibility) {
  if (!web_contents_ || web_contents_->IsBeingDestroyed())
    return;

  // TODO: make content use the mojo type for visibility.
  Visibility visibility = Visibility::VISIBLE;
  switch (mojo_visibility) {
    case remote_cocoa::mojom::Visibility::kVisible:
      visibility = Visibility::VISIBLE;
      break;
    case remote_cocoa::mojom::Visibility::kOccluded:
      visibility = Visibility::OCCLUDED;
      break;
    case remote_cocoa::mojom::Visibility::kHidden:
      visibility = Visibility::HIDDEN;
      break;
  }

  web_contents_->UpdateWebContentsVisibility(visibility);
}

void WebContentsViewMac::SetDropData(const DropData& drop_data) {
  [drag_dest_ setDropData:drop_data];
}

bool WebContentsViewMac::DraggingEntered(DraggingInfoPtr dragging_info,
                                         uint32_t* out_result) {
  *out_result = [drag_dest_ draggingEntered:dragging_info.get()];
  return true;
}

void WebContentsViewMac::DraggingExited() {
  [drag_dest_ draggingExited];
}

bool WebContentsViewMac::DraggingUpdated(DraggingInfoPtr dragging_info,
                                         uint32_t* out_result) {
  *out_result = [drag_dest_ draggingUpdated:dragging_info.get()];
  return true;
}

bool WebContentsViewMac::PerformDragOperation(DraggingInfoPtr dragging_info,
                                              bool* out_result) {
  *out_result = [drag_dest_ performDragOperation:dragging_info.get()
                     withWebContentsViewDelegate:delegate_.get()];
  return true;
}

bool WebContentsViewMac::DragPromisedFileTo(
    content::ChildProcessId render_process_id,
    const blink::DocumentToken& document_token,
    const base::FilePath& file_path,
    const DropData& drop_data,
    base::FilePath* out_file_path) {
  *out_file_path = file_path;

  RenderFrameHostImpl* source_rfh =
      RenderFrameHostImpl::FromDocumentToken(render_process_id, document_token);
  if (!source_rfh) {
    *out_file_path = base::FilePath();
    return true;
  }

  // This is called by -namesOfPromisedFilesDroppedAtDestination, which is
  // requesting, on the UI thread, the name of the file that will be written
  // by a drag operation. To know the name of this file, it is necessary to
  // query the filesystem before returning, which will block the UI thread.
  base::ScopedAllowBlocking allow_blocking;
  base::File file(content::CreateFileForDrop(out_file_path));
  if (!file.IsValid()) {
    *out_file_path = base::FilePath();
    return true;
  }

  SetReadWritePermissionsForFile(file);

  if (drop_data.download_metadata && web_contents_) {
    auto drag_file_downloader = std::make_unique<DragDownloadFile>(
        source_rfh->GetWeakDocumentPtr(), *out_file_path, std::move(file),
        drop_data.download_metadata->url,
        content::Referrer(source_rfh->GetLastCommittedURL(),
                          drop_data.referrer_policy),
        source_rfh->GetPage().GetEncoding());

    DragDownloadFile* downloader = drag_file_downloader.get();
    // The finalizer will take care of closing and deletion.
    downloader->Start(
        new PromiseFileFinalizer(std::move(drag_file_downloader)));
  } else {
    // The writer will take care of closing and deletion.
    base::ThreadPool::PostTask(
        FROM_HERE, {base::TaskPriority::USER_VISIBLE, base::MayBlock()},
        base::BindOnce(&PromiseWriterHelper, drop_data, std::move(file)));
  }

  return true;
}

void WebContentsViewMac::EndDrag(uint32_t drag_operation,
                                 const gfx::PointF& local_point,
                                 const gfx::PointF& screen_point) {
  [drag_dest_
      endDrag:base::BindOnce(&WebContentsViewMac::PerformEndDrag,
                             deferred_close_weak_ptr_factory_.GetWeakPtr(),
                             drag_operation, local_point, screen_point)];
}

void WebContentsViewMac::PerformEndDrag(uint32_t drag_operation,
                                        const gfx::PointF& local_point,
                                        const gfx::PointF& screen_point) {
  // Validate internal members are non-null as this method can be called
  // asynchronously.
  if (!web_contents_ || !drag_source_start_rwh_) {
    return;
  }

  web_contents_->SystemDragEnded(drag_source_start_rwh_.get());

  // |localPoint| and |screenPoint| are in the root coordinate space, for
  // non-root RenderWidgetHosts they need to be transformed.
  gfx::PointF transformed_point = local_point;
  gfx::PointF transformed_screen_point = screen_point;
  if (web_contents_->GetRenderWidgetHostView()) {
    content::RenderWidgetHostViewBase* contentsViewBase =
        static_cast<content::RenderWidgetHostViewBase*>(
            web_contents_->GetRenderWidgetHostView());
    content::RenderWidgetHostViewBase* dragStartViewBase =
        static_cast<content::RenderWidgetHostViewBase*>(
            drag_source_start_rwh_->GetView());
    contentsViewBase->TransformPointToCoordSpaceForView(
        local_point, dragStartViewBase, &transformed_point);
    contentsViewBase->TransformPointToCoordSpaceForView(
        screen_point, dragStartViewBase, &transformed_screen_point);
  }

  web_contents_->DragSourceEndedAt(
      transformed_point.x(), transformed_point.y(),
      transformed_screen_point.x(), transformed_screen_point.y(),
      static_cast<ui::mojom::DragOperation>(drag_operation),
      drag_source_start_rwh_.get());

  drag_source_start_rwh_.reset();
}

void WebContentsViewMac::DraggingEntered(DraggingInfoPtr dragging_info,
                                         DraggingEnteredCallback callback) {
  uint32_t result = 0;
  DraggingEntered(std::move(dragging_info), &result);
  std::move(callback).Run(result);
}

void WebContentsViewMac::DraggingUpdated(DraggingInfoPtr dragging_info,
                                         DraggingUpdatedCallback callback) {
  uint32_t result = false;
  DraggingUpdated(std::move(dragging_info), &result);
  std::move(callback).Run(result);
}

void WebContentsViewMac::PerformDragOperation(
    DraggingInfoPtr dragging_info,
    PerformDragOperationCallback callback) {
  bool result = false;
  PerformDragOperation(std::move(dragging_info), &result);
  std::move(callback).Run(result);
}

void WebContentsViewMac::DragPromisedFileTo(
    content::ChildProcessId render_process_id,
    const blink::DocumentToken& document_token,
    const base::FilePath& file_path,
    const DropData& drop_data,
    DragPromisedFileToCallback callback) {
  base::FilePath actual_file_path;
  DragPromisedFileTo(render_process_id, document_token, file_path, drop_data,
                     &actual_file_path);
  std::move(callback).Run(actual_file_path);
}

////////////////////////////////////////////////////////////////////////////////
// WebContentsViewMac, ViewsHostableView:

void WebContentsViewMac::ViewsHostableAttach(
    ViewsHostableView::Host* views_host) {
  views_host_ = views_host;
  // Create an NSView in the target process, if one exists.
  auto* remote_cocoa_application = views_host_->GetRemoteCocoaApplication();
  if (remote_cocoa_application) {
    mojo::PendingAssociatedRemote<remote_cocoa::mojom::WebContentsNSViewHost>
        host;
    remote_ns_view_host_receiver_.Bind(
        host.InitWithNewEndpointAndPassReceiver());
    mojo::PendingAssociatedReceiver<remote_cocoa::mojom::WebContentsNSView>
        ns_view_receiver = remote_ns_view_.BindNewEndpointAndPassReceiver();

    // Cast from mojo::PendingAssociatedRemote<mojom::WebContentsNSViewHost> and
    // mojo::PendingAssociatedReceiver<remote_cocoa::mojom::WebContentsNSView>
    // to the public interfaces accepted by the application.
    // TODO(ccameron): Remove the need for this cast.
    // https://crbug.com/888290
    mojo::PendingAssociatedRemote<remote_cocoa::mojom::StubInterface> stub_host(
        host.PassHandle(), 0);
    mojo::PendingAssociatedReceiver<remote_cocoa::mojom::StubInterface>
        stub_ns_view_receiver(ns_view_receiver.PassHandle());

    remote_cocoa_application->CreateWebContentsNSView(
        ns_view_id_, std::move(stub_host), std::move(stub_ns_view_receiver));
    if (web_contents_->GetVisibility() == Visibility::HIDDEN) {
      remote_ns_view_->SetVisible(false);
    }
    remote_ns_view_->SetParentNSView(views_host_->GetNSViewId());

    // Because this view is being displayed from a remote process, reset the
    // in-process NSView's client pointer, so that the in-process NSView will
    // not call back into |this|.
    [GetInProcessNSView() setHost:nullptr];
  }

  // TODO(crbug.com/41442285): WebContentsNSViewBridge::SetParentView
  // will look up the parent NSView by its id, but this has been observed to
  // fail in the field, so assume that the caller handles updating the NSView
  // hierarchy.
  // in_process_ns_view_bridge_->SetParentNSView(views_host_->GetNSViewId());

  for (auto* rwhv_mac : GetChildViews()) {
    rwhv_mac->MigrateNSViewBridge(remote_cocoa_application, ns_view_id_);
    rwhv_mac->SetParentUiLayer(views_host_->GetUiLayer());
  }
}

void WebContentsViewMac::ViewsHostableDetach() {
  DCHECK(views_host_);
  // Disconnect from the remote bridge, if it exists. This will have the effect
  // of destroying the associated bridge instance with its NSView.
  if (remote_ns_view_) {
    remote_ns_view_->SetVisible(false);
    remote_ns_view_->ResetParentNSView();
    remote_ns_view_host_receiver_.reset();
    remote_ns_view_->Destroy();
    remote_ns_view_.reset();
    // Permit the in-process NSView to call back into |this| again.
    [GetInProcessNSView() setHost:this];
  }
  in_process_ns_view_bridge_->SetVisible(false);
  in_process_ns_view_bridge_->ResetParentNSView();
  views_host_ = nullptr;

  for (auto* rwhv_mac : GetChildViews()) {
    rwhv_mac->MigrateNSViewBridge(nullptr, 0);
    rwhv_mac->SetParentUiLayer(nullptr);
    rwhv_mac->SetParentAccessibilityElement(nil);
  }
}

void WebContentsViewMac::ViewsHostableSetBounds(
    const gfx::Rect& bounds_in_superview,
    int superview_height) {
  // Update both the in-process and out-of-process NSViews' bounds.
  in_process_ns_view_bridge_->SetBounds(bounds_in_superview, superview_height);
  if (remote_ns_view_)
    remote_ns_view_->SetBounds(bounds_in_superview, superview_height);
}

void WebContentsViewMac::ViewsHostableSetVisible(bool visible) {
  // Update both the in-process and out-of-process NSViews' visibility.
  in_process_ns_view_bridge_->SetVisible(visible);
  if (remote_ns_view_)
    remote_ns_view_->SetVisible(visible);
}

void WebContentsViewMac::ViewsHostableMakeFirstResponder() {
  // Only make the true NSView become the first responder.
  if (remote_ns_view_)
    remote_ns_view_->MakeFirstResponder();
  else
    in_process_ns_view_bridge_->MakeFirstResponder();
}

void WebContentsViewMac::ViewsHostableSetParentAccessible(
    gfx::NativeViewAccessible parent_accessibility_element) {
  views_host_accessibility_element_ = parent_accessibility_element;
  for (auto* rwhv_mac : GetChildViews())
    rwhv_mac->SetParentAccessibilityElement(
        views_host_accessibility_element_.Get());
}

gfx::NativeViewAccessible
WebContentsViewMac::ViewsHostableGetParentAccessible() {
  return views_host_accessibility_element_;
}

gfx::NativeViewAccessible
WebContentsViewMac::ViewsHostableGetAccessibilityElement() {
  RenderWidgetHostView* rwhv = web_contents_->GetRenderWidgetHostView();
  if (!rwhv) {
    return gfx::NativeViewAccessible();
  }
  return rwhv->GetNativeViewAccessible();
}

}  // namespace content
