| // Copyright 2014 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ui/views/controls/native/native_view_host_mac.h" |
| |
| #import <Cocoa/Cocoa.h> |
| |
| #include "base/apple/foundation_util.h" |
| #import "ui/accessibility/platform/ax_platform_node_mac.h" |
| #include "ui/compositor/layer.h" |
| #import "ui/views/cocoa/native_widget_mac_ns_window_host.h" |
| #include "ui/views/controls/native/native_view_host.h" |
| #include "ui/views/widget/native_widget_mac.h" |
| #include "ui/views/widget/widget.h" |
| |
| namespace views { |
| namespace { |
| |
| void EnsureNativeViewHasNoChildWidgets(NSView* native_view) { |
| DCHECK(native_view); |
| // Mac's NativeViewHost has no support for hosting its own child widgets. |
| // This check is probably overly restrictive, since the Widget containing the |
| // NativeViewHost _is_ allowed child Widgets. However, we don't know yet |
| // whether those child Widgets need to be distinguished from Widgets that code |
| // might want to associate with the hosted NSView instead. |
| { |
| Widget::Widgets child_widgets; |
| Widget::GetAllChildWidgets(native_view, &child_widgets); |
| CHECK_GE(1u, child_widgets.size()); // 1 (itself) or 0 if detached. |
| } |
| } |
| |
| // Searches for the first Widget with a kMovedContentNSView reference to |
| // `native_view`. If found, removes the reference and returns the Widget's |
| // NSWindow. |
| NSWindow* RemoveReferenceToMovedContentView(NSView* native_view) { |
| for (NSWindow* window in NSApp.windows) { |
| Widget* widget = |
| views::Widget::GetWidgetForNativeWindow(gfx::NativeWindow(window)); |
| if (widget == nullptr) { |
| continue; |
| } |
| |
| NSView* moved_content_view = |
| (__bridge NSView*)widget->GetNativeWindowProperty( |
| views::NativeWidgetMacNSWindowHost::kMovedContentNSView); |
| |
| if (moved_content_view == native_view) { |
| widget->SetNativeWindowProperty( |
| views::NativeWidgetMacNSWindowHost::kMovedContentNSView, nullptr); |
| return window; |
| } |
| } |
| |
| return nullptr; |
| } |
| |
| } // namespace |
| |
| NativeViewHostMac::NativeViewHostMac(NativeViewHost* host) : host_(host) { |
| // Ensure that |host_| has its own ui::Layer and that it draws nothing. |
| host_->SetPaintToLayer(ui::LAYER_NOT_DRAWN); |
| } |
| |
| NativeViewHostMac::~NativeViewHostMac() { |
| // The native_view_ is going away, so clear out any reference to it. |
| RemoveReferenceToMovedContentView(native_view_); |
| } |
| |
| NativeWidgetMacNSWindowHost* NativeViewHostMac::GetNSWindowHost() const { |
| return NativeWidgetMacNSWindowHost::GetFromNativeWindow( |
| host_->GetWidget()->GetNativeWindow()); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // NativeViewHostMac, ViewsHostableView::Host implementation: |
| |
| ui::Layer* NativeViewHostMac::GetUiLayer() const { |
| return host_->layer(); |
| } |
| |
| remote_cocoa::mojom::Application* NativeViewHostMac::GetRemoteCocoaApplication() |
| const { |
| if (auto* window_host = GetNSWindowHost()) { |
| if (auto* application_host = window_host->application_host()) |
| return application_host->GetApplication(); |
| } |
| return nullptr; |
| } |
| |
| uint64_t NativeViewHostMac::GetNSViewId() const { |
| auto* window_host = GetNSWindowHost(); |
| if (window_host) |
| return window_host->GetRootViewNSViewId(); |
| return 0; |
| } |
| |
| void NativeViewHostMac::OnHostableViewDestroying() { |
| DCHECK(native_view_hostable_); |
| host_->NativeViewDestroyed(); |
| DCHECK(!native_view_hostable_); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // NativeViewHostMac, NativeViewHostWrapper implementation: |
| |
| void NativeViewHostMac::AttachNativeView() { |
| DCHECK(host_->native_view()); |
| DCHECK(!native_view_); |
| native_view_ = host_->native_view().GetNativeNSView(); |
| if ([native_view_ conformsToProtocol:@protocol(ViewsHostable)]) { |
| id hostable = native_view_; |
| native_view_hostable_ = [hostable viewsHostableView]; |
| } |
| EnsureNativeViewHasNoChildWidgets(native_view_); |
| |
| auto* window_host = GetNSWindowHost(); |
| CHECK(window_host); |
| |
| // Save these for later. |
| NSWindow* ns_window = [native_view_ window]; |
| Widget* widget = Widget::GetWidgetForNativeView(host_->native_view()); |
| |
| // TODO(https://crbug.com/933679): This is lifted out the ViewsHostableAttach |
| // call below because of crashes being observed in the field. |
| NSView* superview = |
| window_host->native_widget_mac()->GetNativeView().GetNativeNSView(); |
| [superview addSubview:native_view_]; |
| |
| // If adding the native view to another view hierarchy removed it from its |
| // host window where it was the contentView (new AppKit behavior as of macOS |
| // 13), save a reference to it, otherwise the Views Widget machinery will |
| // break. |
| if (![ns_window contentView] && widget) { |
| widget->SetNativeWindowProperty( |
| views::NativeWidgetMacNSWindowHost::kMovedContentNSView, |
| (__bridge void*)native_view_); |
| } |
| |
| if (native_view_hostable_) { |
| native_view_hostable_->ViewsHostableAttach(this); |
| // Initially set the parent to match the views::Views parent. Note that this |
| // may be overridden (e.g, by views::WebView). |
| native_view_hostable_->ViewsHostableSetParentAccessible( |
| host_->parent()->GetNativeViewAccessible()); |
| } |
| |
| window_host->OnNativeViewHostAttach(host_, native_view_); |
| } |
| |
| void NativeViewHostMac::NativeViewDetaching(bool destroyed) { |
| // |destroyed| is only true if this class calls host_->NativeViewDestroyed(), |
| // which is called if a hosted WebContentsView about to be destroyed (note |
| // that its corresponding NSView may still exist). |
| |
| // |native_view_| can be nil here if RemovedFromWidget() is called before |
| // NativeViewHost::Detach(). |
| NSView* host_native_view = host_->native_view().GetNativeNSView(); |
| if (!native_view_) { |
| DCHECK(![host_native_view superview]); |
| return; |
| } |
| |
| DCHECK(native_view_ == host_native_view); |
| |
| if (native_view_hostable_) { |
| native_view_hostable_->ViewsHostableDetach(); |
| native_view_hostable_ = nullptr; |
| } else { |
| [native_view_ setHidden:YES]; |
| [native_view_ removeFromSuperview]; |
| } |
| |
| EnsureNativeViewHasNoChildWidgets(native_view_); |
| auto* window_host = GetNSWindowHost(); |
| // NativeWidgetNSWindowBridge can be null when Widget is closing. |
| if (window_host) |
| window_host->OnNativeViewHostDetach(host_); |
| |
| // If the previous call to AttachNativeView() removed the native_view_ from |
| // its window (and it was the window's contentView), remove the reference we |
| // created for it in its originating window. |
| NSWindow* originating_window = |
| RemoveReferenceToMovedContentView(native_view_); |
| |
| // Prior to macOS Ventura, AttachNativeView() added native_view_ as a subview |
| // of another view tree while leaving it as its originating window's |
| // contentView. After removing native_view_ from its superview in this method, |
| // it still remained as its window's contentView. With Ventura, the AppKit |
| // won't allow a view to live in two separate window view hierarchies at the |
| // same time. Restoring it as the contentView of its originating window |
| // preserves the pre-Ventura behavior of NativeViewDetaching(). |
| if ([originating_window contentView] == nil) { |
| [originating_window setContentView:native_view_]; |
| } |
| |
| native_view_ = nil; |
| } |
| |
| void NativeViewHostMac::AddedToWidget() { |
| if (!host_->native_view()) |
| return; |
| |
| AttachNativeView(); |
| host_->Layout(); |
| } |
| |
| void NativeViewHostMac::RemovedFromWidget() { |
| if (!host_->native_view()) |
| return; |
| |
| NativeViewDetaching(false); |
| } |
| |
| bool NativeViewHostMac::SetCornerRadii( |
| const gfx::RoundedCornersF& corner_radii) { |
| ui::Layer* layer = GetUiLayer(); |
| DCHECK(layer); |
| layer->SetRoundedCornerRadius(corner_radii); |
| layer->SetIsFastRoundedCorner(true); |
| return true; |
| } |
| |
| bool NativeViewHostMac::SetCustomMask(std::unique_ptr<ui::LayerOwner> mask) { |
| NOTIMPLEMENTED(); |
| return false; |
| } |
| |
| void NativeViewHostMac::SetHitTestTopInset(int top_inset) { |
| NOTIMPLEMENTED(); |
| } |
| |
| int NativeViewHostMac::GetHitTestTopInset() const { |
| NOTIMPLEMENTED(); |
| return 0; |
| } |
| |
| void NativeViewHostMac::InstallClip(int x, int y, int w, int h) { |
| NOTIMPLEMENTED(); |
| } |
| |
| bool NativeViewHostMac::HasInstalledClip() { |
| return false; |
| } |
| |
| void NativeViewHostMac::UninstallClip() { |
| NOTIMPLEMENTED(); |
| } |
| |
| void NativeViewHostMac::ShowWidget(int x, |
| int y, |
| int w, |
| int h, |
| int native_w, |
| int native_h) { |
| // TODO(https://crbug.com/415024): Implement host_->fast_resize(). |
| |
| if (native_view_hostable_) { |
| native_view_hostable_->ViewsHostableSetBounds(gfx::Rect(x, y, w, h)); |
| native_view_hostable_->ViewsHostableSetVisible(true); |
| } else { |
| // Coordinates will be from the top left of the parent Widget. The |
| // NativeView is already in the same NSWindow, so just flip to get Cocoa |
| // coordinates and then convert to the containing view. |
| NSRect window_rect = NSMakeRect( |
| x, host_->GetWidget()->GetClientAreaBoundsInScreen().height() - y - h, |
| w, h); |
| |
| // Convert window coordinates to the hosted view's superview, since that's |
| // how coordinates of the hosted view's frame is based. |
| NSRect container_rect = [[native_view_ superview] convertRect:window_rect |
| fromView:nil]; |
| [native_view_ setFrame:container_rect]; |
| [native_view_ setHidden:NO]; |
| } |
| } |
| |
| void NativeViewHostMac::HideWidget() { |
| if (native_view_hostable_) |
| native_view_hostable_->ViewsHostableSetVisible(false); |
| else |
| [native_view_ setHidden:YES]; |
| } |
| |
| void NativeViewHostMac::SetFocus() { |
| if (native_view_hostable_) { |
| native_view_hostable_->ViewsHostableMakeFirstResponder(); |
| } else { |
| if ([native_view_ acceptsFirstResponder]) |
| [[native_view_ window] makeFirstResponder:native_view_]; |
| } |
| } |
| |
| gfx::NativeView NativeViewHostMac::GetNativeViewContainer() const { |
| NOTIMPLEMENTED(); |
| return nullptr; |
| } |
| |
| gfx::NativeViewAccessible NativeViewHostMac::GetNativeViewAccessible() { |
| if (native_view_hostable_) |
| return native_view_hostable_->ViewsHostableGetAccessibilityElement(); |
| else |
| return native_view_; |
| } |
| |
| ui::Cursor NativeViewHostMac::GetCursor(int x, int y) { |
| // Intentionally not implemented: Not required on non-aura Mac because macOS |
| // will query the native view for the cursor directly. For NativeViewHostMac |
| // in practice, macOS will retrieve the cursor that was last set by |
| // -[RenderWidgetHostViewCocoa updateCursor:] whenever the pointer is over the |
| // hosted view. With some plumbing, NativeViewHostMac could return that same |
| // cursor here, but it doesn't achieve anything. The implications of returning |
| // null simply mean that the "fallback" cursor on the window itself will be |
| // cleared (see -[NativeWidgetMacNSWindow cursorUpdate:]). However, while the |
| // pointer is over a RenderWidgetHostViewCocoa, macOS won't ask for the |
| // fallback cursor. |
| return ui::Cursor(); |
| } |
| |
| void NativeViewHostMac::SetVisible(bool visible) { |
| if (native_view_hostable_) |
| native_view_hostable_->ViewsHostableSetVisible(visible); |
| else |
| [native_view_ setHidden:!visible]; |
| } |
| |
| void NativeViewHostMac::SetParentAccessible( |
| gfx::NativeViewAccessible parent_accessibility_element) { |
| if (native_view_hostable_) { |
| native_view_hostable_->ViewsHostableSetParentAccessible( |
| parent_accessibility_element); |
| } else { |
| // It is not easy to force a generic NSView to return a different |
| // accessibility parent. Fortunately, this interface is only ever used |
| // in practice to host a WebContentsView. |
| } |
| } |
| |
| gfx::NativeViewAccessible NativeViewHostMac::GetParentAccessible() { |
| return native_view_hostable_ |
| ? native_view_hostable_->ViewsHostableGetParentAccessible() |
| : nullptr; |
| } |
| |
| // static |
| NativeViewHostWrapper* NativeViewHostWrapper::CreateWrapper( |
| NativeViewHost* host) { |
| return new NativeViewHostMac(host); |
| } |
| |
| } // namespace views |