blob: 24be6a2cad1084eca508b610296667eca95550ee [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "content/app_shim_remote_cocoa/render_widget_host_ns_view_bridge.h"
#include "base/mac/scoped_cftyperef.h"
#include "base/strings/sys_string_conversions.h"
#include "components/remote_cocoa/app_shim/ns_view_ids.h"
#include "content/app_shim_remote_cocoa/render_widget_host_ns_view_host_helper.h"
#include "content/common/cursors/webcursor.h"
#import "skia/ext/skia_utils_mac.h"
#include "ui/accelerated_widget_mac/window_resize_helper_mac.h"
#import "ui/base/cocoa/animation_utils.h"
#include "ui/display/screen.h"
#include "ui/events/keycodes/dom/dom_code.h"
#include "ui/gfx/mac/coordinate_conversion.h"
namespace remote_cocoa {
RenderWidgetHostNSViewBridge::RenderWidgetHostNSViewBridge(
mojom::RenderWidgetHostNSViewHost* host,
RenderWidgetHostNSViewHostHelper* host_helper) {
display::Screen::GetScreen()->AddObserver(this);
cocoa_view_.reset([[RenderWidgetHostViewCocoa alloc]
initWithHost:host
withHostHelper:host_helper]);
background_layer_.reset([[CALayer alloc] init]);
display_ca_layer_tree_ =
std::make_unique<ui::DisplayCALayerTree>(background_layer_.get());
[cocoa_view_ setLayer:background_layer_];
[cocoa_view_ setWantsLayer:YES];
}
RenderWidgetHostNSViewBridge::~RenderWidgetHostNSViewBridge() {
[cocoa_view_ setHostDisconnected];
// Do not immediately remove |cocoa_view_| from the NSView heirarchy, because
// the call to -[NSView removeFromSuperview] may cause use to call into the
// RWHVMac during tear-down, via WebContentsImpl::UpdateWebContentsVisibility.
// https://crbug.com/834931
[cocoa_view_ performSelector:@selector(removeFromSuperview)
withObject:nil
afterDelay:0];
cocoa_view_.autorelease();
display::Screen::GetScreen()->RemoveObserver(this);
popup_window_.reset();
}
void RenderWidgetHostNSViewBridge::BindReceiver(
mojo::PendingAssociatedReceiver<mojom::RenderWidgetHostNSView>
bridge_receiver) {
receiver_.Bind(std::move(bridge_receiver),
ui::WindowResizeHelperMac::Get()->task_runner());
}
RenderWidgetHostViewCocoa* RenderWidgetHostNSViewBridge::GetNSView() {
return cocoa_view_;
}
void RenderWidgetHostNSViewBridge::InitAsPopup(const gfx::Rect& content_rect,
bool has_shadow) {
popup_window_ =
std::make_unique<PopupWindowMac>(content_rect, has_shadow, cocoa_view_);
}
void RenderWidgetHostNSViewBridge::SetParentWebContentsNSView(
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);
// Set the frame and autoresizing mask of the RenderWidgetHostViewCocoa as is
// done by WebContentsViewMac.
[cocoa_view_ setFrame:[parent_ns_view bounds]];
[cocoa_view_ setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
// Place the new view below all other views, matching the behavior in
// WebContentsViewMac::CreateViewForWidget.
// https://crbug.com/1017446
[parent_ns_view addSubview:cocoa_view_
positioned:NSWindowBelow
relativeTo:nil];
}
void RenderWidgetHostNSViewBridge::MakeFirstResponder() {
[[cocoa_view_ window] makeFirstResponder:cocoa_view_];
}
void RenderWidgetHostNSViewBridge::DisableDisplay() {
if (display_disabled_)
return;
SetBackgroundColor(SK_ColorTRANSPARENT);
display_ca_layer_tree_.reset();
display_disabled_ = true;
}
void RenderWidgetHostNSViewBridge::SetBounds(const gfx::Rect& rect) {
// |rect.size()| is view coordinates, |rect.origin| is screen coordinates,
// TODO(thakis): fix, http://crbug.com/73362
// During the initial creation of the RenderWidgetHostView in
// WebContentsImpl::CreateRenderViewForRenderManager, SetSize is called with
// an empty size. In the Windows code flow, it is not ignored because
// subsequent sizing calls from the OS flow through TCVW::WasSized which calls
// SetSize() again. On Cocoa, we rely on the Cocoa view struture and resizer
// flags to keep things sized properly. On the other hand, if the size is not
// empty then this is a valid request for a pop-up.
if (rect.size().IsEmpty())
return;
// Ignore the position of |rect| for non-popup rwhvs. This is because
// background tabs do not have a window, but the window is required for the
// coordinate conversions. Popups are always for a visible tab.
//
// Note: If |cocoa_view_| has been removed from the view hierarchy, it's still
// valid for resizing to be requested (e.g., during tab capture, to size the
// view to screen-capture resolution). In this case, simply treat the view as
// relative to the screen.
BOOL isRelativeToScreen =
IsPopup() || ![[cocoa_view_ superview] isKindOfClass:[BaseView class]];
if (isRelativeToScreen) {
// The position of |rect| is screen coordinate system and we have to
// consider Cocoa coordinate system is upside-down and also multi-screen.
NSRect frame = gfx::ScreenRectToNSRect(rect);
if (IsPopup())
[popup_window_->window() setFrame:frame display:YES];
else
[cocoa_view_ setFrame:frame];
} else {
BaseView* superview = static_cast<BaseView*>([cocoa_view_ superview]);
gfx::Rect rect2 = [superview flipNSRectToRect:[cocoa_view_ frame]];
rect2.set_width(rect.width());
rect2.set_height(rect.height());
[cocoa_view_ setFrame:[superview flipRectToNSRect:rect2]];
}
}
void RenderWidgetHostNSViewBridge::SetCALayerParams(
const gfx::CALayerParams& ca_layer_params) {
if (display_disabled_)
return;
display_ca_layer_tree_->UpdateCALayerTree(ca_layer_params);
}
void RenderWidgetHostNSViewBridge::SetBackgroundColor(SkColor color) {
if (display_disabled_)
return;
ScopedCAActionDisabler disabler;
base::ScopedCFTypeRef<CGColorRef> cg_color(
skia::CGColorCreateFromSkColor(color));
[background_layer_ setBackgroundColor:cg_color];
}
void RenderWidgetHostNSViewBridge::SetVisible(bool visible) {
ScopedCAActionDisabler disabler;
[cocoa_view_ setHidden:!visible];
}
void RenderWidgetHostNSViewBridge::SetTooltipText(
const base::string16& tooltip_text) {
// Called from the renderer to tell us what the tooltip text should be. It
// calls us frequently so we need to cache the value to prevent doing a lot
// of repeat work.
if (tooltip_text == tooltip_text_ || ![[cocoa_view_ window] isKeyWindow])
return;
tooltip_text_ = tooltip_text;
// Maximum number of characters we allow in a tooltip.
const size_t kMaxTooltipLength = 1024;
// Clamp the tooltip length to kMaxTooltipLength. It's a DOS issue on
// Windows; we're just trying to be polite. Don't persist the trimmed
// string, as then the comparison above will always fail and we'll try to
// set it again every single time the mouse moves.
base::string16 display_text = tooltip_text_;
if (tooltip_text_.length() > kMaxTooltipLength)
display_text = tooltip_text_.substr(0, kMaxTooltipLength);
NSString* tooltip_nsstring = base::SysUTF16ToNSString(display_text);
[cocoa_view_ setToolTipAtMousePoint:tooltip_nsstring];
}
void RenderWidgetHostNSViewBridge::SetCompositionRangeInfo(
const gfx::Range& range) {
[cocoa_view_ setCompositionRange:range];
[cocoa_view_ setMarkedRange:range.ToNSRange()];
}
void RenderWidgetHostNSViewBridge::CancelComposition() {
[cocoa_view_ cancelComposition];
}
void RenderWidgetHostNSViewBridge::SetTextInputState(
ui::TextInputType text_input_type,
uint32_t flags) {
[cocoa_view_ setTextInputType:text_input_type];
[cocoa_view_ setTextInputFlags:flags];
}
void RenderWidgetHostNSViewBridge::SetTextSelection(const base::string16& text,
uint64_t offset,
const gfx::Range& range) {
[cocoa_view_ setTextSelectionText:text offset:offset range:range];
// Updates markedRange when there is no marked text so that retrieving
// markedRange immediately after calling setMarkdText: returns the current
// caret position.
if (![cocoa_view_ hasMarkedText]) {
[cocoa_view_ setMarkedRange:range.ToNSRange()];
}
}
void RenderWidgetHostNSViewBridge::SetShowingContextMenu(bool showing) {
[cocoa_view_ setShowingContextMenu:showing];
}
void RenderWidgetHostNSViewBridge::OnDisplayMetricsChanged(
const display::Display& display,
uint32_t changed_metrics) {
// Note that -updateScreenProperties is also be called by the notification
// NSWindowDidChangeBackingPropertiesNotification (some of these calls
// will be redundant).
[cocoa_view_ updateScreenProperties];
}
void RenderWidgetHostNSViewBridge::DisplayCursor(
const content::WebCursor& cursor) {
content::WebCursor non_const_cursor(cursor);
[cocoa_view_ updateCursor:non_const_cursor.GetNativeCursor()];
}
void RenderWidgetHostNSViewBridge::SetCursorLocked(bool locked) {
[cocoa_view_ setCursorLocked:locked];
}
void RenderWidgetHostNSViewBridge::ShowDictionaryOverlayForSelection() {
NSRange selection_range = [cocoa_view_ selectedRange];
[cocoa_view_ showLookUpDictionaryOverlayFromRange:selection_range];
}
void RenderWidgetHostNSViewBridge::ShowDictionaryOverlay(
const mac::AttributedStringCoder::EncodedString& encoded_string,
const gfx::Point& baseline_point) {
NSAttributedString* string =
mac::AttributedStringCoder::Decode(&encoded_string);
if ([string length] == 0)
return;
NSPoint flipped_baseline_point = {
baseline_point.x(),
[cocoa_view_ frame].size.height - baseline_point.y(),
};
[cocoa_view_ showDefinitionForAttributedString:string
atPoint:flipped_baseline_point];
}
void RenderWidgetHostNSViewBridge::LockKeyboard(
const base::Optional<std::vector<uint32_t>>& uint_dom_codes) {
base::Optional<base::flat_set<ui::DomCode>> dom_codes;
if (uint_dom_codes) {
dom_codes.emplace();
for (const auto& uint_dom_code : *uint_dom_codes)
dom_codes->insert(static_cast<ui::DomCode>(uint_dom_code));
}
[cocoa_view_ lockKeyboard:std::move(dom_codes)];
}
void RenderWidgetHostNSViewBridge::UnlockKeyboard() {
[cocoa_view_ unlockKeyboard];
}
} // namespace remote_cocoa