| // 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]; |
| } |
| |
| } // namespace remote_cocoa |