| // 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. |
| |
| #include "content/public/test/content_browser_test_utils.h" |
| |
| #include <Carbon/Carbon.h> |
| #import <Cocoa/Cocoa.h> |
| |
| #include <memory> |
| |
| #include "base/apple/foundation_util.h" |
| #include "base/apple/scoped_objc_class_swizzler.h" |
| #include "base/functional/bind.h" |
| #include "base/lazy_instance.h" |
| #include "base/strings/sys_string_conversions.h" |
| #import "content/app_shim_remote_cocoa/render_widget_host_view_cocoa.h" |
| #include "content/app_shim_remote_cocoa/web_menu_runner_mac.h" |
| #include "content/browser/renderer_host/render_widget_host_view_mac.h" |
| #include "content/browser/renderer_host/text_input_client_mac.h" |
| #include "content/browser/web_contents/web_contents_impl.h" |
| #include "content/common/mac/attributed_string_type_converters.h" |
| #include "content/public/browser/render_widget_host.h" |
| #include "content/public/browser/render_widget_host_view.h" |
| #include "content/public/browser/web_contents.h" |
| #include "ui/base/mojom/attributed_string.mojom.h" |
| #include "ui/gfx/geometry/point.h" |
| #include "ui/gfx/geometry/rect.h" |
| #include "ui/gfx/range/range.h" |
| |
| // The interface class used to override the implementation of some of |
| // RenderWidgetHostViewCocoa methods for tests. |
| @interface RenderWidgetHostViewCocoaSwizzler : NSObject |
| - (void)showDefinitionForAttributedString:(NSAttributedString*)attrString |
| atPoint:(NSPoint)textBaselineOrigin; |
| @end |
| |
| namespace content { |
| |
| using base::apple::ScopedObjCClassSwizzler; |
| |
| // static |
| constexpr char |
| RenderWidgetHostViewCocoaObserver::kShowDefinitionForAttributedString[]; |
| |
| // static |
| std::map<std::string, std::unique_ptr<base::apple::ScopedObjCClassSwizzler>> |
| RenderWidgetHostViewCocoaObserver::rwhvcocoa_swizzlers_; |
| |
| // static |
| std::map<WebContents*, RenderWidgetHostViewCocoaObserver*> |
| RenderWidgetHostViewCocoaObserver::observers_; |
| |
| namespace { |
| |
| content::RenderWidgetHostViewMac* GetRenderWidgetHostViewMac( |
| WebContents* contents) { |
| auto* rwhv_base = static_cast<RenderWidgetHostViewBase*>( |
| contents->GetRenderWidgetHostView()); |
| if (rwhv_base && !rwhv_base->IsRenderWidgetHostViewChildFrame()) { |
| return static_cast<RenderWidgetHostViewMac*>(rwhv_base); |
| } |
| return nil; |
| } |
| |
| RenderWidgetHostViewCocoa* GetRenderWidgetHostViewCocoa(WebContents* contents) { |
| content::RenderWidgetHostViewMac* rwhv_mac = |
| GetRenderWidgetHostViewMac(contents); |
| if (!rwhv_mac) { |
| return nil; |
| } |
| return rwhv_mac->GetInProcessNSView(); |
| } |
| |
| content::RenderWidgetHostViewMac* GetRenderWidgetHostViewMac(NSObject* object) { |
| for (auto* contents : WebContentsImpl::GetAllWebContents()) { |
| content::RenderWidgetHostViewMac* rwhv_mac = |
| GetRenderWidgetHostViewMac(contents); |
| if (rwhv_mac && rwhv_mac->GetInProcessNSView() == object) { |
| return rwhv_mac; |
| } |
| } |
| return nullptr; |
| } |
| |
| } // namespace |
| |
| base::apple::ScopedObjCClassSwizzler* |
| RenderWidgetHostViewCocoaObserver::GetSwizzler(const std::string& method_name) { |
| return rwhvcocoa_swizzlers_.count(method_name) |
| ? rwhvcocoa_swizzlers_.at(method_name).get() |
| : nullptr; |
| } |
| |
| // static |
| RenderWidgetHostViewCocoaObserver* |
| RenderWidgetHostViewCocoaObserver::GetObserver(WebContents* web_contents) { |
| return observers_.count(web_contents) ? observers_.at(web_contents) : nullptr; |
| } |
| |
| RenderWidgetHostViewCocoaObserver::RenderWidgetHostViewCocoaObserver( |
| WebContents* web_contents) |
| : web_contents_(web_contents) { |
| if (rwhvcocoa_swizzlers_.empty()) { |
| SetUpSwizzlers(); |
| } |
| |
| MenuWasRunCallback callback = base::BindRepeating( |
| [](RenderWidgetHostViewCocoaObserver* observer, NSView* view, |
| NSRect bounds, int index) { |
| RenderWidgetHostViewCocoa* rwhv_cocoa = |
| base::apple::ObjCCast<RenderWidgetHostViewCocoa>(view); |
| gfx::Rect rect = [rwhv_cocoa flipNSRectToRect:bounds]; |
| observer->DidAttemptToShowPopup(rect, index); |
| }, |
| this); |
| [WebMenuRunner registerForTestingMenuRunCallback:callback |
| forView:GetRenderWidgetHostViewCocoa( |
| web_contents)]; |
| |
| DCHECK(!observers_.count(web_contents)); |
| observers_[web_contents] = this; |
| } |
| |
| RenderWidgetHostViewCocoaObserver::~RenderWidgetHostViewCocoaObserver() { |
| [WebMenuRunner |
| unregisterForTestingMenuRunCallbackForView:GetRenderWidgetHostViewCocoa( |
| web_contents_)]; |
| |
| observers_.erase(web_contents_); |
| |
| if (observers_.empty()) { |
| rwhvcocoa_swizzlers_.clear(); |
| } |
| } |
| |
| void RenderWidgetHostViewCocoaObserver::SetUpSwizzlers() { |
| if (!rwhvcocoa_swizzlers_.empty()) { |
| return; |
| } |
| |
| // [RenderWidgetHostViewCocoa showDefinitionForAttributedString:atPoint]. |
| rwhvcocoa_swizzlers_[kShowDefinitionForAttributedString] = |
| std::make_unique<ScopedObjCClassSwizzler>( |
| GetRenderWidgetHostViewCocoaClassForTesting(), |
| [RenderWidgetHostViewCocoaSwizzler class], |
| NSSelectorFromString(@(kShowDefinitionForAttributedString))); |
| } |
| |
| void SetWindowBounds(gfx::NativeWindow window, const gfx::Rect& bounds) { |
| NSRect new_bounds = NSRectFromCGRect(bounds.ToCGRect()); |
| if (NSScreen.screens.count > 0) { |
| new_bounds.origin.y = NSScreen.screens.firstObject.frame.size.height - |
| new_bounds.origin.y - new_bounds.size.height; |
| } |
| |
| [window.GetNativeNSWindow() setFrame:new_bounds display:NO]; |
| } |
| |
| void GetStringAtPointForRenderWidget( |
| RenderWidgetHost* rwh, |
| const gfx::Point& point, |
| base::OnceCallback<void(const std::string&, const gfx::Point&)> |
| result_callback) { |
| TextInputClientMac::GetInstance()->GetStringAtPoint( |
| rwh, point, |
| base::BindOnce( |
| [](base::OnceCallback<void(const std::string&, const gfx::Point&)> |
| callback, |
| ui::mojom::AttributedStringPtr attributed_string, |
| const gfx::Point& baseline_point) { |
| std::string string = |
| attributed_string |
| ? base::SysCFStringRefToUTF8(CFAttributedStringGetString( |
| attributed_string.To<CFAttributedStringRef>())) |
| : std::string(); |
| std::move(callback).Run(string, baseline_point); |
| }, |
| std::move(result_callback))); |
| } |
| |
| void GetStringFromRangeForRenderWidget( |
| RenderWidgetHost* rwh, |
| const gfx::Range& range, |
| base::OnceCallback<void(const std::string&, const gfx::Point&)> |
| result_callback) { |
| TextInputClientMac::GetInstance()->GetStringFromRange( |
| rwh, range, |
| base::BindOnce( |
| [](base::OnceCallback<void(const std::string&, const gfx::Point&)> |
| callback, |
| ui::mojom::AttributedStringPtr attributed_string, |
| const gfx::Point& baseline_point) { |
| std::string string = |
| attributed_string |
| ? base::SysCFStringRefToUTF8(CFAttributedStringGetString( |
| attributed_string.To<CFAttributedStringRef>())) |
| : std::string(); |
| std::move(callback).Run(string, baseline_point); |
| }, |
| std::move(result_callback))); |
| } |
| |
| } // namespace content |
| |
| @implementation RenderWidgetHostViewCocoaSwizzler |
| |
| - (void)showDefinitionForAttributedString:(NSAttributedString*)attrString |
| atPoint:(NSPoint)textBaselineOrigin { |
| content::RenderWidgetHostViewCocoaObserver::GetSwizzler( |
| content::RenderWidgetHostViewCocoaObserver:: |
| kShowDefinitionForAttributedString) |
| ->InvokeOriginal<void, NSAttributedString*, NSPoint>( |
| self, _cmd, attrString, textBaselineOrigin); |
| |
| auto* rwhv_mac = content::GetRenderWidgetHostViewMac(self); |
| |
| auto* observer = content::RenderWidgetHostViewCocoaObserver::GetObserver( |
| rwhv_mac->GetWebContents()); |
| |
| if (!observer) { |
| return; |
| } |
| observer->OnShowDefinitionForAttributedString( |
| base::SysNSStringToUTF8(attrString.string)); |
| } |
| |
| @end |