| // 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/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/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 "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(base::as_bytes(base::span(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( |
| const DropData& drop_data, |
| const url::Origin& source_origin, |
| 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) { |
| // 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; |
| 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. |
| |
| if (remote_ns_view_) { |
| remote_ns_view_->StartDrag(drop_data, source_origin, mask, image, |
| cursor_offset, is_privileged); |
| } else { |
| in_process_ns_view_bridge_->StartDrag(drop_data, source_origin, 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."; |
| } |
| |
| 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. |
| } |
| |
| 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::CreateView(gfx::NativeView context) { |
| in_process_ns_view_bridge_ = |
| std::make_unique<remote_cocoa::WebContentsNSViewBridge>(ns_view_id_, |
| this); |
| |
| 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]; |
| 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 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(const base::FilePath& file_path, |
| const DropData& drop_data, |
| const GURL& download_url, |
| const url::Origin& source_origin, |
| base::FilePath* out_file_path) { |
| *out_file_path = file_path; |
| // 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 (download_url.is_valid() && web_contents_) { |
| auto drag_file_downloader = std::make_unique<DragDownloadFile>( |
| *out_file_path, std::move(file), download_url, |
| content::Referrer(web_contents_->GetLastCommittedURL(), |
| drop_data.referrer_policy), |
| web_contents_->GetEncoding(), source_origin, web_contents_); |
| |
| 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))); |
| } |
| |
| // The DragDownloadFile constructor may have altered the value of |
| // |*out_file_path| if, say, an existing file at the drop site has the same |
| // name. Return the actual name that was used to write the file. |
| *out_file_path = file_path; |
| 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 (drag_source_start_rwh_ && 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()); |
| } |
| |
| 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( |
| const base::FilePath& file_path, |
| const DropData& drop_data, |
| const GURL& download_url, |
| const url::Origin& source_origin, |
| DragPromisedFileToCallback callback) { |
| base::FilePath actual_file_path; |
| DragPromisedFileTo(file_path, drop_data, download_url, source_origin, |
| &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)); |
| 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_window) { |
| // Update both the in-process and out-of-process NSViews' bounds. |
| in_process_ns_view_bridge_->SetBounds(bounds_in_window); |
| if (remote_ns_view_) |
| remote_ns_view_->SetBounds(bounds_in_window); |
| } |
| |
| 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 |