// 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/browser/renderer_host/render_widget_host_view_mac.h"
#import <Carbon/Carbon.h>
#include <limits>
#include <memory>
#include <optional>
#include <tuple>
#include <utility>
#include "base/apple/owned_objc.h"
#include "base/apple/scoped_cftyperef.h"
#include "base/auto_reset.h"
#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/mac/mac_util.h"
#include "base/strings/sys_string_conversions.h"
#include "base/strings/utf_string_conversions.h"
#import "base/task/single_thread_task_runner.h"
#include "base/time/time.h"
#include "components/device_event_log/device_event_log.h"
#include "components/input/cursor_manager.h"
#include "components/input/native_web_keyboard_event.h"
#include "components/input/render_widget_host_input_event_router.h"
#include "components/input/web_input_event_builders_mac.h"
#include "components/remote_cocoa/browser/ns_view_ids.h"
#include "components/remote_cocoa/common/application.mojom.h"
#include "components/viz/common/features.h"
#include "components/viz/common/switches.h"
#import "content/app_shim_remote_cocoa/render_widget_host_ns_view_bridge.h"
#import "content/app_shim_remote_cocoa/render_widget_host_view_cocoa.h"
#include "content/browser/renderer_host/input/motion_event_web.h"
#import "content/browser/renderer_host/input/synthetic_gesture_target_mac.h"
#include "content/browser/renderer_host/render_view_host_delegate.h"
#include "content/browser/renderer_host/render_view_host_impl.h"
#include "content/browser/renderer_host/render_widget_helper.h"
#import "content/browser/renderer_host/text_input_client_mac.h"
#include "content/browser/renderer_host/visible_time_request_trigger.h"
#import "content/common/input/events_helper.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_plugin_guest_manager.h"
#include "content/public/browser/render_widget_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/page_visibility_state.h"
#include "media/base/media_switches.h"
#include "skia/ext/platform_canvas.h"
#include "skia/ext/skia_utils_mac.h"
#include "third_party/blink/public/common/input/web_input_event.h"
#include "third_party/blink/public/mojom/input/input_handler.mojom.h"
#include "third_party/blink/public/mojom/widget/record_content_to_visible_time_request.mojom.h"
#import "ui/accessibility/platform/browser_accessibility_cocoa.h"
#import "ui/accessibility/platform/browser_accessibility_mac.h"
#include "ui/accessibility/platform/browser_accessibility_manager_mac.h"
#import "ui/base/clipboard/clipboard_util_mac.h"
#include "ui/base/cocoa/animation_utils.h"
#include "ui/base/cocoa/cocoa_base_utils.h"
#include "ui/base/cocoa/cursor_accessibility_scale_factor.h"
#include "ui/base/cocoa/remote_accessibility_api.h"
#import "ui/base/cocoa/secure_password_input.h"
#include "ui/base/cocoa/text_services_context_menu.h"
#include "ui/base/cursor/cursor.h"
#include "ui/base/ime/mojom/text_input_state.mojom.h"
#include "ui/base/mojom/attributed_string.mojom.h"
#include "ui/base/ui_base_features.h"
#include "ui/display/display.h"
#include "ui/display/display_util.h"
#include "ui/display/screen.h"
#include "ui/events/blink/did_overscroll_params.h"
#include "ui/events/cocoa/cocoa_event_utils.h"
#include "ui/events/gesture_detection/gesture_provider_config_helper.h"
#include "ui/events/keycodes/dom/dom_code.h"
#include "ui/events/keycodes/dom/dom_keyboard_layout_map.h"
#include "ui/gfx/geometry/dip_util.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/mac/coordinate_conversion.h"
using blink::WebInputEvent;
using blink::WebMouseEvent;
using blink::WebGestureEvent;
using blink::WebTouchEvent;
namespace content {
namespace {
// If enabled, when the text input state changes `[NSApp updateWindows]` is
// called after a delay. This is done as `updateWindows` can be quite
// costly, and if the text input state is changing rapidly there is no need to
// update it immediately.
BASE_FEATURE(kDelayUpdateWindowsAfterTextInputStateChanged,
"DelayUpdateWindowsAfterTextInputStateChanged",
base::FEATURE_ENABLED_BY_DEFAULT);
} // namespace
////////////////////////////////////////////////////////////////////////////////
// BrowserCompositorMacClient, public:
SkColor RenderWidgetHostViewMac::BrowserCompositorMacGetGutterColor() const {
// When making an element on the page fullscreen the element's background
// may not match the page's, so use black as the gutter color to avoid
// flashes of brighter colors during the transition.
if (host()->delegate() && host()->delegate()->IsFullscreen()) {
return SK_ColorBLACK;
}
return last_frame_root_background_color_;
}
void RenderWidgetHostViewMac::OnFrameTokenChanged(
uint32_t frame_token,
base::TimeTicks activation_time) {
OnFrameTokenChangedForView(frame_token, activation_time);
}
void RenderWidgetHostViewMac::DestroyCompositorForShutdown() {
// When RenderWidgetHostViewMac was owned by an NSView, this function was
// necessary to ensure that the ui::Compositor did not outlive the
// infrastructure that was needed to support it.
// https://crbug.com/805726
Destroy();
}
bool RenderWidgetHostViewMac::OnBrowserCompositorSurfaceIdChanged() {
return host()->SynchronizeVisualProperties();
}
std::vector<viz::SurfaceId>
RenderWidgetHostViewMac::CollectSurfaceIdsForEviction() {
return host()->CollectSurfaceIdsForEviction();
}
display::ScreenInfo RenderWidgetHostViewMac::GetCurrentScreenInfo() const {
return screen_infos_.current();
}
void RenderWidgetHostViewMac::SetCurrentDeviceScaleFactor(
float device_scale_factor) {
// TODO(crbug.com/40229152): does this need to be upscaled by
// scale_override_for_capture_ for HiDPI capture mode?
screen_infos_.mutable_current().device_scale_factor = device_scale_factor;
}
////////////////////////////////////////////////////////////////////////////////
// AcceleratedWidgetMacNSView, public:
void RenderWidgetHostViewMac::AcceleratedWidgetCALayerParamsUpdated() {
// Set the background color for the root layer from the frame that just
// swapped. See RenderWidgetHostViewAura for more details. Note that this is
// done only after the swap has completed, so that the background is not set
// before the frame is up.
SetBackgroundLayerColor(last_frame_root_background_color_);
// Update the contents that the NSView is displaying.
const gfx::CALayerParams* ca_layer_params =
browser_compositor_->GetLastCALayerParams();
if (ca_layer_params)
ns_view_->SetCALayerParams(*ca_layer_params);
}
////////////////////////////////////////////////////////////////////////////////
// views::AccessibilityFocusOverrider::Client:
id RenderWidgetHostViewMac::GetAccessibilityFocusedUIElement() {
// If content is overlayed with a focused popup from native UI code, this
// getter must return the current menu item as the focused element, rather
// than the focus within the content. An example of this occurs with the
// Autofill feature, where focus is actually still in the textbox although
// the UX acts as if focus is in the popup.
gfx::NativeViewAccessible popup_focus_override =
ui::AXPlatformNode::GetPopupFocusOverride();
if (popup_focus_override)
return popup_focus_override;
ui::BrowserAccessibilityManager* manager =
host()->GetRootBrowserAccessibilityManager();
if (manager) {
ui::BrowserAccessibility* focused_item = manager->GetFocus();
DCHECK(focused_item);
if (focused_item) {
BrowserAccessibilityCocoa* focused_item_cocoa =
focused_item->GetNativeViewAccessible();
DCHECK(focused_item_cocoa);
if (focused_item_cocoa)
return focused_item_cocoa;
}
}
return nil;
}
///////////////////////////////////////////////////////////////////////////////
// RenderWidgetHostViewMac, public:
RenderWidgetHostViewMac::RenderWidgetHostViewMac(RenderWidgetHost* widget)
: RenderWidgetHostViewBase(widget),
page_at_minimum_scale_(true),
mouse_wheel_phase_handler_(this),
is_loading_(false),
popup_parent_host_view_(nullptr),
popup_child_host_view_(nullptr),
gesture_provider_(ui::GetGestureProviderConfig(
ui::GestureProviderConfigType::CURRENT_PLATFORM),
this),
accessibility_focus_overrider_(this),
ns_view_id_(remote_cocoa::GetNewNSViewId()),
weak_factory_(this) {
// The NSView is on the other side of |ns_view_|.
in_process_ns_view_bridge_ =
std::make_unique<remote_cocoa::RenderWidgetHostNSViewBridge>(this, this,
ns_view_id_);
ns_view_ = in_process_ns_view_bridge_.get();
// Guess that the initial screen we will be on is the screen of the current
// window (since that's the best guess that we have, and is usually right).
// https://crbug.com/357443
auto* screen = display::Screen::GetScreen();
screen_infos_ = screen->GetScreenInfosNearestDisplay(
screen->GetDisplayNearestWindow([NSApp keyWindow]).id());
original_screen_infos_ = screen_infos_;
viz::FrameSinkId frame_sink_id = host()->GetFrameSinkId();
browser_compositor_ = std::make_unique<BrowserCompositorMac>(
this, this, host()->is_hidden(), frame_sink_id);
DCHECK(![GetInProcessNSView() window]);
host()->SetView(this);
RenderWidgetHostOwnerDelegate* owner_delegate = host()->owner_delegate();
if (owner_delegate) {
// TODO(mostynb): actually use prefs. Landing this as a separate CL
// first to rebaseline some unreliable web tests.
// NOTE: This will not be run for child frame widgets, which do not have
// an owner delegate and won't get a RenderViewHost here.
std::ignore = owner_delegate->GetWebkitPreferencesForWidget();
}
cursor_manager_ = std::make_unique<input::CursorManager>(this);
// Start observing changes to the system's cursor accessibility scale factor,
// and when it changes, notify the renderers that there is a new value to
// synchronize.
cursor_scale_observer_ =
[CursorAccessibilityScaleFactorNotifier.sharedNotifier addObserver:^{
host()->SynchronizeVisualProperties();
}];
if (GetTextInputManager()) {
GetTextInputManager()->AddObserver(this);
}
host()->render_frame_metadata_provider()->AddObserver(this);
}
RenderWidgetHostViewMac::~RenderWidgetHostViewMac() {
if (popup_parent_host_view_) {
DCHECK(!popup_parent_host_view_->popup_child_host_view_ ||
popup_parent_host_view_->popup_child_host_view_ == this);
popup_parent_host_view_->popup_child_host_view_ = nullptr;
}
if (popup_child_host_view_) {
DCHECK(!popup_child_host_view_->popup_parent_host_view_ ||
popup_child_host_view_->popup_parent_host_view_ == this);
popup_child_host_view_->popup_parent_host_view_ = nullptr;
}
[CursorAccessibilityScaleFactorNotifier.sharedNotifier
removeObserver:cursor_scale_observer_];
}
void RenderWidgetHostViewMac::MigrateNSViewBridge(
remote_cocoa::mojom::Application* remote_cocoa_application,
uint64_t parent_ns_view_id) {
// Destroy the previous remote accessibility element.
remote_window_accessible_ = nil;
// Reset `ns_view_` before resetting `remote_ns_view_` to avoid dangling
// pointers. `ns_view_` gets reinitialized later in this method.
ns_view_ = nullptr;
// Disconnect from the previous bridge (this will have the effect of
// destroying the associated bridge), and close the receiver (to allow it
// to be re-bound). Note that |in_process_ns_view_bridge_| remains valid.
remote_ns_view_client_receiver_.reset();
if (remote_ns_view_)
remote_ns_view_->Destroy();
remote_ns_view_.reset();
// Enable accessibility focus overriding for remote NSViews.
accessibility_focus_overrider_.SetAppIsRemote(remote_cocoa_application !=
nullptr);
// If no host is specified, then use the locally hosted NSView.
if (!remote_cocoa_application) {
ns_view_ = in_process_ns_view_bridge_.get();
// Observe local Screen info, to correspond with the locally hosted NSView.
// This condition is triggered during init within a locally hosted NSView,
// and when a remote view is migrated into a locally hosted NSView. Since
// the bridge adds itself as an observer during construction, it may already
// be an observer, and calling AddObserver here would cause a CHECK to fail.
// To workaround that case, this code removes the observer first, which is a
// safe no-op if the bridge is already not an observer.
// TODO(crbug.com/40179941): Maybe recreate `in_process_ns_view_bridge_`?
display::Screen::GetScreen()->RemoveObserver(
in_process_ns_view_bridge_.get());
display::Screen::GetScreen()->AddObserver(in_process_ns_view_bridge_.get());
return;
}
mojo::PendingAssociatedRemote<remote_cocoa::mojom::RenderWidgetHostNSViewHost>
client = remote_ns_view_client_receiver_.BindNewEndpointAndPassRemote();
mojo::PendingAssociatedReceiver<remote_cocoa::mojom::RenderWidgetHostNSView>
view_receiver = remote_ns_view_.BindNewEndpointAndPassReceiver();
// Cast from PendingAssociatedRemote<mojom::RenderWidgetHostNSViewHost> and
// mojo::PendingAssociatedReceiver<mojom::RenderWidgetHostNSView> 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_client(
client.PassHandle(), 0);
mojo::PendingAssociatedReceiver<remote_cocoa::mojom::StubInterface>
stub_bridge_receiver(view_receiver.PassHandle());
remote_cocoa_application->CreateRenderWidgetHostNSView(
ns_view_id_, std::move(stub_client), std::move(stub_bridge_receiver));
ns_view_ = remote_ns_view_.get();
// New remote NSViews start out as visible, make sure we hide it if it is
// supposed to be hidden already.
if (!is_visible_) {
remote_ns_view_->SetVisible(false);
}
// End local display::Screen observation via `in_process_ns_view_bridge_`;
// the remote NSWindow's display::Screen information will be sent by Mojo.
// TODO(crbug.com/40179941): Maybe just destroy `in_process_ns_view_bridge_`?
display::Screen::GetScreen()->RemoveObserver(
in_process_ns_view_bridge_.get());
// Popup windows will specify an invalid |parent_ns_view_id|, because popups
// have their own NSWindows (of which they are the content NSView).
if (parent_ns_view_id != remote_cocoa::kInvalidNSViewId)
remote_ns_view_->SetParentWebContentsNSView(parent_ns_view_id);
}
void RenderWidgetHostViewMac::SetParentUiLayer(ui::Layer* parent_ui_layer) {
if (parent_ui_layer) {
// The first time that we display using a parent ui::Layer, permanently
// switch from drawing using Cocoa to only drawing using ui::Views. Erase
// the existing content being drawn by Cocoa (which may have been set due
// to races, e.g, in https://crbug.com/845807). Note that this transition
// must be done lazily because not all code has been updated to use
// ui::Views (e.g, content_shell). Also note that this call must be done
// every time the RenderWidgetHostNSViewBridge that `ns_view` points to
// changes (e.g, due to MigrateNSViewBridge), see
// https://crbug.com/1222976#c49.
ns_view_->DisableDisplay();
}
if (browser_compositor_)
browser_compositor_->SetParentUiLayer(parent_ui_layer);
}
void RenderWidgetHostViewMac::SetParentAccessibilityElement(
id parent_accessibility_element) {
[GetInProcessNSView()
setAccessibilityParentElement:parent_accessibility_element];
}
RenderWidgetHostViewCocoa* RenderWidgetHostViewMac::GetInProcessNSView() const {
if (in_process_ns_view_bridge_)
return in_process_ns_view_bridge_->GetNSView();
return nullptr;
}
void RenderWidgetHostViewMac::SetDelegate(
NSObject<RenderWidgetHostViewMacDelegate>* delegate) {
[GetInProcessNSView() setResponderDelegate:delegate];
}
ui::TextInputType RenderWidgetHostViewMac::GetTextInputType() {
if (!GetActiveWidget())
return ui::TEXT_INPUT_TYPE_NONE;
return text_input_manager_->GetTextInputState()->type;
}
RenderWidgetHostImpl* RenderWidgetHostViewMac::GetActiveWidget() {
return text_input_manager_ ? text_input_manager_->GetActiveWidget() : nullptr;
}
const TextInputManager::CompositionRangeInfo*
RenderWidgetHostViewMac::GetCompositionRangeInfo() {
return text_input_manager_ ? text_input_manager_->GetCompositionRangeInfo()
: nullptr;
}
const TextInputManager::TextSelection*
RenderWidgetHostViewMac::GetTextSelection() {
return text_input_manager_ ? text_input_manager_->GetTextSelection(
GetFocusedViewForTextSelection())
: nullptr;
}
///////////////////////////////////////////////////////////////////////////////
// RenderWidgetHostViewMac, RenderWidgetHostView implementation:
void RenderWidgetHostViewMac::InitAsChild(gfx::NativeView parent_view) {
DCHECK_EQ(widget_type_, WidgetType::kFrame);
}
void RenderWidgetHostViewMac::InitAsPopup(
RenderWidgetHostView* parent_host_view,
const gfx::Rect& pos,
const gfx::Rect& anchor_rect) {
DCHECK_EQ(widget_type_, WidgetType::kPopup);
popup_parent_host_view_ =
static_cast<RenderWidgetHostViewMac*>(parent_host_view);
RenderWidgetHostViewMac* old_child =
popup_parent_host_view_->popup_child_host_view_;
if (old_child) {
DCHECK(old_child->popup_parent_host_view_ == popup_parent_host_view_);
old_child->popup_parent_host_view_ = nullptr;
}
popup_parent_host_view_->popup_child_host_view_ = this;
// Use transparent background color for the popup in order to avoid flashing
// the white background on popup open when dark color-scheme is used.
SetContentBackgroundColor(SK_ColorTRANSPARENT);
// If HiDPI capture mode is active for the parent, propagate the scale
// override to the popup window also. Its content was created assuming
// that the new window will share the parent window's scale. See
// https://crbug.com/1354703 .
scale_override_for_capture_ =
popup_parent_host_view_->GetScaleOverrideForCapture();
// This path is used by the time/date picker.
ns_view_->InitAsPopup(pos, popup_parent_host_view_->ns_view_id_);
Show();
}
RenderWidgetHostViewBase*
RenderWidgetHostViewMac::GetFocusedViewForTextSelection() {
// We obtain the TextSelection from focused RWH which is obtained from the
// frame tree.
return GetFocusedWidget() ? GetFocusedWidget()->GetView() : nullptr;
}
RenderWidgetHostDelegate*
RenderWidgetHostViewMac::GetFocusedRenderWidgetHostDelegate() {
if (auto* focused_widget = GetFocusedWidget())
return focused_widget->delegate();
return host()->delegate();
}
RenderWidgetHostImpl* RenderWidgetHostViewMac::GetWidgetForKeyboardEvent() {
DCHECK(in_keyboard_event_);
return RenderWidgetHostImpl::FromID(keyboard_event_widget_process_id_,
keyboard_event_widget_routing_id_);
}
RenderWidgetHostImpl* RenderWidgetHostViewMac::GetWidgetForIme() {
if (in_keyboard_event_)
return GetWidgetForKeyboardEvent();
return GetActiveWidget();
}
void RenderWidgetHostViewMac::ShowWithVisibility(
PageVisibilityState page_visibility) {
is_visible_ = true;
ns_view_->SetVisible(is_visible_);
browser_compositor_->SetViewVisible(is_visible_);
OnShowWithPageVisibility(page_visibility);
}
void RenderWidgetHostViewMac::Hide() {
is_visible_ = false;
ns_view_->SetVisible(is_visible_);
browser_compositor_->SetViewVisible(is_visible_);
WasOccluded();
if (base::FeatureList::IsEnabled(::features::kHideDelegatedFrameHostMac)) {
browser_compositor_->GetDelegatedFrameHost()->WasHidden(
DelegatedFrameHost::HiddenCause::kOther);
}
}
void RenderWidgetHostViewMac::WasUnOccluded() {
OnShowWithPageVisibility(PageVisibilityState::kVisible);
}
void RenderWidgetHostViewMac::NotifyHostAndDelegateOnWasShown(
blink::mojom::RecordContentToVisibleTimeRequestPtr tab_switch_start_state) {
DCHECK(host_->is_hidden());
// SetRenderWidgetHostIsHidden may cause a state transition that switches to
// a new instance of DelegatedFrameHost and calls WasShown, which causes
// HasSavedFrame to always return true. So cache the HasSavedFrame result
// before the transition, and do not save this DelegatedFrameHost* locally.
const bool has_saved_frame =
browser_compositor_->GetDelegatedFrameHost()->HasSavedFrame();
browser_compositor_->SetRenderWidgetHostIsHidden(false);
const bool renderer_should_record_presentation_time = !has_saved_frame;
host()->WasShown(renderer_should_record_presentation_time
? tab_switch_start_state.Clone()
: blink::mojom::RecordContentToVisibleTimeRequestPtr());
// If the frame for the renderer is already available, then the
// tab-switching time is the presentation time for the browser-compositor.
// SetRenderWidgetHostIsHidden above will show the DelegatedFrameHost
// in this state, but doesn't include the presentation time request.
if (has_saved_frame && tab_switch_start_state) {
browser_compositor_->GetDelegatedFrameHost()
->RequestSuccessfulPresentationTimeForNextFrame(
std::move(tab_switch_start_state));
}
}
void RenderWidgetHostViewMac::
RequestSuccessfulPresentationTimeFromHostOrDelegate(
blink::mojom::RecordContentToVisibleTimeRequestPtr
visible_time_request) {
DCHECK(!host_->is_hidden());
DCHECK(visible_time_request);
// No state transition here so don't use
// has_saved_frame_before_state_transition.
if (browser_compositor_->GetDelegatedFrameHost()->HasSavedFrame()) {
// If the frame for the renderer is already available, then the
// tab-switching time is the presentation time for the browser-compositor.
browser_compositor_->GetDelegatedFrameHost()
->RequestSuccessfulPresentationTimeForNextFrame(
std::move(visible_time_request));
} else {
host()->RequestSuccessfulPresentationTimeForNextFrame(
std::move(visible_time_request));
}
}
void RenderWidgetHostViewMac::
CancelSuccessfulPresentationTimeRequestForHostAndDelegate() {
DCHECK(!host_->is_hidden());
host()->CancelSuccessfulPresentationTimeRequest();
browser_compositor_->GetDelegatedFrameHost()
->CancelSuccessfulPresentationTimeRequest();
}
void RenderWidgetHostViewMac::WasOccluded() {
if (host()->is_hidden())
return;
host()->WasHidden();
browser_compositor_->SetRenderWidgetHostIsHidden(true);
}
void RenderWidgetHostViewMac::SetSize(const gfx::Size& size) {
gfx::Rect rect = GetViewBounds();
rect.set_size(size);
SetBounds(rect);
}
void RenderWidgetHostViewMac::SetBounds(const gfx::Rect& rect) {
ns_view_->SetBounds(rect);
}
gfx::NativeView RenderWidgetHostViewMac::GetNativeView() {
return GetInProcessNSView();
}
gfx::NativeViewAccessible RenderWidgetHostViewMac::GetNativeViewAccessible() {
return GetInProcessNSView();
}
void RenderWidgetHostViewMac::Focus() {
// Ignore redundant calls, as they can cause unending loops of focus-setting.
// crbug.com/998123, crbug.com/804184.
if (is_first_responder_ || is_getting_focus_)
return;
base::AutoReset<bool> is_getting_focus_bit(&is_getting_focus_, true);
ns_view_->MakeFirstResponder();
}
bool RenderWidgetHostViewMac::HasFocus() {
return is_first_responder_;
}
bool RenderWidgetHostViewMac::IsSurfaceAvailableForCopy() {
return browser_compositor_->GetDelegatedFrameHost()
->CanCopyFromCompositingSurface();
}
bool RenderWidgetHostViewMac::IsShowing() {
return is_visible_;
}
gfx::Rect RenderWidgetHostViewMac::GetViewBounds() {
return view_bounds_in_window_dip_ +
window_frame_in_screen_dip_.OffsetFromOrigin();
}
bool RenderWidgetHostViewMac::IsPointerLocked() {
return pointer_locked_;
}
void RenderWidgetHostViewMac::UpdateCursor(const ui::Cursor& cursor) {
GetCursorManager()->UpdateCursor(this, cursor);
}
void RenderWidgetHostViewMac::DisplayCursor(const ui::Cursor& cursor) {
ns_view_->DisplayCursor(cursor);
}
input::CursorManager* RenderWidgetHostViewMac::GetCursorManager() {
return cursor_manager_.get();
}
void RenderWidgetHostViewMac::OnOldViewDidNavigatePreCommit() {
if (base::FeatureList::IsEnabled(
features::kInvalidateLocalSurfaceIdPreCommit)) {
CHECK(browser_compositor_) << "Shouldn't be called during destruction!";
browser_compositor_->DidNavigateMainFramePreCommit();
}
}
void RenderWidgetHostViewMac::OnNewViewDidNavigatePostCommit() {
gesture_provider_.ResetDetection();
}
void RenderWidgetHostViewMac::DidEnterBackForwardCache() {
CHECK(browser_compositor_) << "Shouldn't be called during destruction!";
browser_compositor_->DidEnterBackForwardCache();
// If we have the fallback content timer running, force it to stop. Else, when
// the page is restored the timer could also fire, setting whatever
// `DelegatedFrameHost::first_local_surface_id_after_navigation_` as the
// fallback to our Surfacelayer.
//
// This is safe for BFCache restore because we will supply specific fallback
// surfaces for BFCache.
//
// We do not want to call this in `RWHImpl::WasHidden()` because in the case
// of `Visibility::OCCLUDED` we still want to keep the timer running.
//
// Called after to prevent prematurely evict the BFCached surface.
host()->ForceFirstFrameAfterNavigationTimeout();
}
void RenderWidgetHostViewMac::SetIsLoading(bool is_loading) {
is_loading_ = is_loading;
// If we ever decide to show the waiting cursor while the page is loading
// like Chrome does on Windows, call |UpdateCursor()| here.
}
void RenderWidgetHostViewMac::OnUpdateTextInputStateCalled(
TextInputManager* text_input_manager,
RenderWidgetHostViewBase* updated_view,
bool did_update_state) {
if (!did_update_state)
return;
const ui::mojom::TextInputState* state =
text_input_manager->GetTextInputState();
if (state)
ns_view_->SetTextInputState(state->type, state->flags);
else
ns_view_->SetTextInputState(ui::TEXT_INPUT_TYPE_NONE, 0);
// |updated_view| is the last view to change its TextInputState which can be
// used to start/stop monitoring composition info when it has a focused
// editable text input field.
RenderWidgetHostImpl* widget_host =
RenderWidgetHostImpl::From(updated_view->GetRenderWidgetHost());
// We might end up here when |updated_view| has had active TextInputState and
// then got destroyed. In that case, |updated_view->GetRenderWidgetHost()|
// returns nullptr.
if (!widget_host)
return;
// Set the monitor state based on the text input focus state.
const bool has_focus = HasFocus();
bool need_monitor_composition =
has_focus && state && state->type != ui::TEXT_INPUT_TYPE_NONE;
widget_host->RequestCompositionUpdates(false /* immediate_request */,
need_monitor_composition);
if (has_focus) {
SetTextInputActive(true);
// Let AppKit cache the new input context to make IMEs happy.
// See http://crbug.com/73039.
if (base::FeatureList::IsEnabled(
kDelayUpdateWindowsAfterTextInputStateChanged)) {
update_windows_timer_.Start(FROM_HERE, base::Milliseconds(100), this,
&RenderWidgetHostViewMac::UpdateWindowsNow);
} else {
[NSApp updateWindows];
}
}
}
void RenderWidgetHostViewMac::OnImeCancelComposition(
TextInputManager* text_input_manager,
RenderWidgetHostViewBase* updated_view) {
ns_view_->CancelComposition();
}
void RenderWidgetHostViewMac::OnImeCompositionRangeChanged(
TextInputManager* text_input_manager,
RenderWidgetHostViewBase* updated_view,
bool character_bounds_changed,
const std::optional<std::vector<gfx::Rect>>& line_bounds) {
const TextInputManager::CompositionRangeInfo* info =
GetCompositionRangeInfo();
if (!info)
return;
// The RangeChanged message is only sent with valid values. The current
// caret position (start == end) will be sent if there is no IME range.
ns_view_->SetCompositionRangeInfo(info->range);
}
void RenderWidgetHostViewMac::OnSelectionBoundsChanged(
TextInputManager* text_input_manager,
RenderWidgetHostViewBase* updated_view) {
DCHECK_EQ(GetTextInputManager(), text_input_manager);
// The rest of the code is to support the Mac Zoom feature tracking the
// text caret; we can skip it if that feature is not currently enabled.
if (!UAZoomEnabled())
return;
RenderWidgetHostViewBase* focused_view = GetFocusedViewForTextSelection();
if (!focused_view)
return;
const TextInputManager::SelectionRegion* region =
GetTextInputManager()->GetSelectionRegion(focused_view);
if (!region)
return;
// Create a rectangle for the edge of the selection focus, which will be
// the same as the caret position if the selection is collapsed. That's
// what we want to try to keep centered on-screen if possible.
gfx::Rect gfx_caret_rect(region->focus.edge_start_rounded().x(),
region->focus.edge_start_rounded().y(), 1,
region->focus.GetHeight());
gfx_caret_rect += view_bounds_in_window_dip_.OffsetFromOrigin();
gfx_caret_rect += window_frame_in_screen_dip_.OffsetFromOrigin();
// Note that UAZoomChangeFocus wants unflipped screen coordinates.
NSRect caret_rect = NSRectFromCGRect(gfx_caret_rect.ToCGRect());
UAZoomChangeFocus(&caret_rect, &caret_rect, kUAZoomFocusTypeInsertionPoint);
}
void RenderWidgetHostViewMac::OnTextSelectionChanged(
TextInputManager* text_input_manager,
RenderWidgetHostViewBase* updated_view) {
DCHECK_EQ(GetTextInputManager(), text_input_manager);
const TextInputManager::TextSelection* selection = GetTextSelection();
if (!selection)
return;
ns_view_->SetTextSelection(selection->text(), selection->offset(),
selection->range());
}
void RenderWidgetHostViewMac::OnGestureEvent(
const ui::GestureEventData& gesture) {
blink::WebGestureEvent web_gesture =
ui::CreateWebGestureEventFromGestureEventData(gesture);
ui::LatencyInfo latency_info;
if (ShouldRouteEvents()) {
blink::WebGestureEvent gesture_event(web_gesture);
host()->delegate()->GetInputEventRouter()->RouteGestureEvent(
this, &gesture_event, latency_info);
} else {
host()->GetRenderInputRouter()->ForwardGestureEventWithLatencyInfo(
web_gesture, latency_info);
}
}
void RenderWidgetHostViewMac::OnRenderFrameMetadataChangedAfterActivation(
base::TimeTicks activation_time) {
// TODO(crbug.com/40219248): Remove toSkColor and make all SkColor4f.
last_frame_root_background_color_ = host()
->render_frame_metadata_provider()
->LastRenderFrameMetadata()
.root_background_color.toSkColor();
}
void RenderWidgetHostViewMac::RenderProcessGone() {
Destroy();
}
void RenderWidgetHostViewMac::Destroy() {
host()->render_frame_metadata_provider()->RemoveObserver(this);
// Unlock the mouse in the NSView's process before destroying our bridge to
// it.
if (pointer_locked_) {
pointer_locked_ = false;
ns_view_->SetCursorLocked(false);
}
// Destroy the local and remote bridges to the NSView. Note that the NSView on
// the other side of |ns_view_| may outlive us due to other retains.
ns_view_ = nullptr;
in_process_ns_view_bridge_.reset();
remote_ns_view_client_receiver_.reset();
if (remote_ns_view_)
remote_ns_view_->Destroy();
remote_ns_view_.reset();
// Delete the delegated frame state, which will reach back into
// host().
browser_compositor_.reset();
// Make sure none of our observers send events for us to process after
// we release host().
NotifyObserversAboutShutdown();
if (text_input_manager_)
text_input_manager_->RemoveObserver(this);
mouse_wheel_phase_handler_.IgnorePendingWheelEndEvent();
// The call to the base class will set host() to nullptr.
RenderWidgetHostViewBase::Destroy();
delete this;
}
void RenderWidgetHostViewMac::UpdateTooltipUnderCursor(
const std::u16string& tooltip_text) {
if (GetCursorManager()->IsViewUnderCursor(this))
UpdateTooltip(tooltip_text);
}
void RenderWidgetHostViewMac::UpdateTooltip(
const std::u16string& tooltip_text) {
SetTooltipText(tooltip_text);
}
void RenderWidgetHostViewMac::UpdateScreenInfo() {
// Update the size, scale factor, color profile, and any other properties of
// the NSView or pertinent NSScreens. Propagate these to the
// RenderWidgetHostImpl as well.
// During auto-resize it is the responsibility of the caller to ensure that
// the NSView and RenderWidgetHostImpl are kept in sync.
if (host()->auto_resize_enabled())
return;
if (host()->delegate())
host()->delegate()->SendScreenRects();
else
host()->SendScreenRects();
// Update with the latest display list from the remote process if needed.
bool current_display_changed = false;
bool any_display_changed = false;
if (new_screen_infos_from_shim_.has_value()) {
current_display_changed =
new_screen_infos_from_shim_->current() != screen_infos_.current();
any_display_changed = new_screen_infos_from_shim_.value() != screen_infos_;
screen_infos_ = new_screen_infos_from_shim_.value();
original_screen_infos_ = screen_infos_;
new_screen_infos_from_shim_.reset();
}
if (base::FeatureList::IsEnabled(media::kWebContentsCaptureHiDpi)) {
// If HiDPI capture mode is active, adjust the device scale factor to
// increase the rendered pixel count. |new_screen_infos| always contains
// the unmodified original values for the display, and a copy of it is
// saved in |screen_infos_|, with a modification applied if applicable.
// When HiDPI mode is turned off (the scale override is 1.0), the original
// |new_screen_infos| value gets copied unchanged to |screen_infos_|.
display::ScreenInfos new_screen_infos = original_screen_infos_;
const float old_device_scale_factor =
new_screen_infos.current().device_scale_factor;
new_screen_infos.mutable_current().device_scale_factor =
old_device_scale_factor * scale_override_for_capture_;
if (screen_infos_ != new_screen_infos) {
DVLOG(1) << __func__ << ": Overriding device_scale_factor from "
<< old_device_scale_factor << " to "
<< new_screen_infos.current().device_scale_factor
<< " for capture.";
any_display_changed = true;
current_display_changed |=
new_screen_infos.current() != screen_infos_.current();
screen_infos_ = new_screen_infos;
}
}
bool dip_size_changed = view_bounds_in_window_dip_.size() !=
browser_compositor_->GetRendererSize();
if (dip_size_changed || current_display_changed) {
browser_compositor_->UpdateSurfaceFromNSView(
view_bounds_in_window_dip_.size());
}
// TODO(crbug.com/40165361): Unify display info caching and change detection.
// Notify the associated RenderWidgetHostImpl when screen info has changed.
// That will synchronize visual properties needed for frame tree rendering
// and for web platform APIs that expose screen and window info and events.
// RenderWidgetHostImpl will query BrowserCompositorMac for the dimensions
// to send to the renderer, so BrowserCompositorMac must be updated first.
if (dip_size_changed || any_display_changed)
host()->NotifyScreenInfoChanged();
}
viz::ScopedSurfaceIdAllocator
RenderWidgetHostViewMac::DidUpdateVisualProperties(
const cc::RenderFrameMetadata& metadata) {
base::OnceCallback<void()> allocation_task = base::BindOnce(
base::IgnoreResult(
&RenderWidgetHostViewMac::OnDidUpdateVisualPropertiesComplete),
weak_factory_.GetWeakPtr(), metadata);
return browser_compositor_->GetScopedRendererSurfaceIdAllocator(
std::move(allocation_task));
}
void RenderWidgetHostViewMac::DidNavigate() {
browser_compositor_->DidNavigate();
}
gfx::Size RenderWidgetHostViewMac::GetRequestedRendererSize() {
return browser_compositor_->GetRendererSize();
}
namespace {
// A helper function for CombineTextNodesAndMakeCallback() below. It would
// ordinarily be a helper lambda in that class method, but it processes a tree
// and needs to be recursive, and that's crazy difficult to do with a lambda.
// TODO(avi): Move this to be a lambda when P0839R0 lands in C++.
void AddTextNodesToVector(const ui::AXNode* node,
std::vector<std::u16string>* strings) {
if (node->GetRole() == ax::mojom::Role::kStaticText) {
std::u16string value;
if (node->GetString16Attribute(ax::mojom::StringAttribute::kName, &value))
strings->emplace_back(value);
return;
}
for (auto iter = node->UnignoredChildrenBegin();
iter != node->UnignoredChildrenEnd(); ++iter) {
AddTextNodesToVector(iter.get(), strings);
}
}
using SpeechCallback = base::OnceCallback<void(const std::u16string&)>;
void CombineTextNodesAndMakeCallback(SpeechCallback callback,
ui::AXTreeUpdate& update) {
std::vector<std::u16string> text_node_contents;
text_node_contents.reserve(update.nodes.size());
ui::AXTree tree(update);
AddTextNodesToVector(tree.root(), &text_node_contents);
std::move(callback).Run(base::JoinString(text_node_contents, u"\n"));
}
} // namespace
void RenderWidgetHostViewMac::GetPageTextForSpeech(SpeechCallback callback) {
// Note that we are calling WebContents::RequestAXTreeSnapshot() with a limit
// of 5000 nodes returned. For large pages, this call might hit that limit
// (and in practice it may return slightly more than 5000 to ensure a
// well-formed tree).
//
// This is a reasonable limit. The "Start Speaking" call dates back to the
// earliest days of the Mac, before accessibility. It was designed to show off
// the speech capabilities of the Mac, which is fine, but is mostly
// inapplicable nowadays. Is it useful to have the Mac read megabytes of text
// with zero control over positioning, with no fast-forward or rewind? What
// does it even mean to read a Web 2.0 dynamic, AJAXy page aloud from
// beginning to end?
//
// If this is an issue, please file a bug explaining the situation and how the
// limits of this feature affect you in the real world.
GetWebContents()->RequestAXTreeSnapshot(
base::BindOnce(CombineTextNodesAndMakeCallback, std::move(callback)),
ui::AXMode::kWebContents,
/* max_nodes= */ 5000,
/* timeout= */ {}, WebContents::AXTreeSnapshotPolicy::kAll);
}
void RenderWidgetHostViewMac::SpeakSelection() {
const TextInputManager::TextSelection* selection = GetTextSelection();
if (selection && !selection->selected_text().empty()) {
ui::TextServicesContextMenu::SpeakText(selection->selected_text());
return;
}
// With no selection, speak an approximation of the entire contents of the
// page.
GetPageTextForSpeech(base::BindOnce(ui::TextServicesContextMenu::SpeakText));
}
void RenderWidgetHostViewMac::SetWindowFrameInScreen(const gfx::Rect& rect) {
DCHECK(GetInProcessNSView() && ![GetInProcessNSView() window])
<< "This method should only be called in headless browser!";
OnWindowFrameInScreenChanged(rect);
}
//
// RenderWidgetHostViewCocoa uses the stored selection text,
// which implements NSServicesRequests protocol.
//
void RenderWidgetHostViewMac::SetShowingContextMenu(bool showing) {
ns_view_->SetShowingContextMenu(showing);
}
uint32_t RenderWidgetHostViewMac::GetCaptureSequenceNumber() const {
return latest_capture_sequence_number_;
}
void RenderWidgetHostViewMac::CopyFromSurface(
const gfx::Rect& src_subrect,
const gfx::Size& dst_size,
base::OnceCallback<void(const SkBitmap&)> callback) {
base::WeakPtr<RenderWidgetHostImpl> popup_host;
base::WeakPtr<DelegatedFrameHost> popup_frame_host;
if (popup_child_host_view_) {
popup_host = popup_child_host_view_->host()->GetWeakPtr();
popup_frame_host = popup_child_host_view_->BrowserCompositor()
->GetDelegatedFrameHost()
->GetWeakPtr();
}
// TODO(crbug.com/40743791): Resolve potential differences between display
// info caches in RenderWidgetHostViewMac and BrowserCompositorMac.
RenderWidgetHostViewBase::CopyMainAndPopupFromSurface(
host()->GetWeakPtr(),
browser_compositor_->GetDelegatedFrameHost()->GetWeakPtr(), popup_host,
popup_frame_host, src_subrect, dst_size, GetDeviceScaleFactor(),
std::move(callback));
}
void RenderWidgetHostViewMac::EnsureSurfaceSynchronizedForWebTest() {
++latest_capture_sequence_number_;
browser_compositor_->ForceNewSurfaceId();
}
void RenderWidgetHostViewMac::OnDidUpdateVisualPropertiesComplete(
const cc::RenderFrameMetadata& metadata) {
browser_compositor_->UpdateSurfaceFromChild(
host()->auto_resize_enabled(), metadata.device_scale_factor,
metadata.viewport_size_in_pixels,
metadata.local_surface_id.value_or(viz::LocalSurfaceId()));
}
void RenderWidgetHostViewMac::TakeFallbackContentFrom(
RenderWidgetHostView* view) {
DCHECK(!static_cast<RenderWidgetHostViewBase*>(view)
->IsRenderWidgetHostViewChildFrame());
RenderWidgetHostViewMac* view_mac =
static_cast<RenderWidgetHostViewMac*>(view);
ScopedCAActionDisabler disabler;
std::optional<SkColor> color = view_mac->GetBackgroundColor();
if (color)
SetBackgroundColor(*color);
// Make the NSView for |this| display the same content as is being displayed
// in the NSView for |view_mac|.
const gfx::CALayerParams* ca_layer_params =
view_mac->browser_compositor_->GetLastCALayerParams();
if (ca_layer_params)
ns_view_->SetCALayerParams(*ca_layer_params);
browser_compositor_->TakeFallbackContentFrom(
view_mac->browser_compositor_.get());
}
bool RenderWidgetHostViewMac::IsHTMLFormPopup() const {
return !!popup_parent_host_view_;
}
uint64_t RenderWidgetHostViewMac::GetNSViewId() const {
return ns_view_id_;
}
bool RenderWidgetHostViewMac::GetLineBreakIndex(
const std::vector<gfx::Rect>& bounds,
const gfx::Range& range,
size_t* line_break_point) {
DCHECK(line_break_point);
if (range.start() >= bounds.size() || range.is_reversed() || range.is_empty())
return false;
// We can't check line breaking completely from only rectangle array. Thus we
// assume the line breaking as the next character's y offset is larger than
// a threshold. Currently the threshold is determined as minimum y offset plus
// 75% of maximum height.
// TODO(nona): Check the threshold is reliable or not.
// TODO(nona): Bidi support.
const size_t loop_end_idx =
std::min(bounds.size(), static_cast<size_t>(range.end()));
int max_height = 0;
int min_y_offset = std::numeric_limits<int32_t>::max();
for (size_t idx = range.start(); idx < loop_end_idx; ++idx) {
max_height = std::max(max_height, bounds[idx].height());
min_y_offset = std::min(min_y_offset, bounds[idx].y());
}
int line_break_threshold = min_y_offset + (max_height * 3 / 4);
for (size_t idx = range.start(); idx < loop_end_idx; ++idx) {
if (bounds[idx].y() > line_break_threshold) {
*line_break_point = idx;
return true;
}
}
return false;
}
gfx::Rect RenderWidgetHostViewMac::GetFirstRectForCompositionRange(
const gfx::Range& range,
gfx::Range* actual_range) {
TRACE_EVENT1("ime",
"RenderWidgetHostViewMac::GetFirstRectForCompositionRange",
"range", range.ToString());
const TextInputManager::CompositionRangeInfo* composition_info =
GetCompositionRangeInfo();
if (!composition_info)
return gfx::Rect();
DCHECK(actual_range);
DCHECK(!composition_info->character_bounds.empty());
DCHECK(range.start() <= composition_info->character_bounds.size());
DCHECK(range.end() <= composition_info->character_bounds.size());
if (range.is_empty()) {
*actual_range = range;
if (range.start() == composition_info->character_bounds.size()) {
return gfx::Rect(
composition_info->character_bounds[range.start() - 1].right(),
composition_info->character_bounds[range.start() - 1].y(), 0,
composition_info->character_bounds[range.start() - 1].height());
} else {
return gfx::Rect(
composition_info->character_bounds[range.start()].x(),
composition_info->character_bounds[range.start()].y(), 0,
composition_info->character_bounds[range.start()].height());
}
}
size_t end_idx;
if (!GetLineBreakIndex(composition_info->character_bounds, range, &end_idx)) {
end_idx = range.end();
}
*actual_range = gfx::Range(range.start(), end_idx);
gfx::Rect rect = composition_info->character_bounds[range.start()];
for (size_t i = range.start() + 1; i < end_idx; ++i) {
rect.Union(composition_info->character_bounds[i]);
}
return rect;
}
gfx::Range RenderWidgetHostViewMac::ConvertCharacterRangeToCompositionRange(
const gfx::Range& request_range) {
const TextInputManager::CompositionRangeInfo* composition_info =
GetCompositionRangeInfo();
if (!composition_info)
return gfx::Range::InvalidRange();
if (composition_info->range.is_empty())
return gfx::Range::InvalidRange();
if (composition_info->range.is_reversed())
return gfx::Range::InvalidRange();
if (request_range.start() < composition_info->range.start())
return gfx::Range::InvalidRange();
// Heuristic: truncate the request range within the composition range.
uint32_t truncated_request_start =
std::min(request_range.start(), composition_info->range.end());
uint32_t truncated_request_end =
std::min(request_range.end(), composition_info->range.end());
return gfx::Range(truncated_request_start - composition_info->range.start(),
truncated_request_end - composition_info->range.start());
}
WebContents* RenderWidgetHostViewMac::GetWebContents() {
return WebContents::FromRenderViewHost(RenderViewHost::From(host()));
}
bool RenderWidgetHostViewMac::GetCachedFirstRectForCharacterRange(
const gfx::Range& requested_range,
gfx::Rect* rect,
gfx::Range* actual_range) {
if (!GetTextInputManager())
return false;
DCHECK(rect);
// This exists to make IMEs more responsive, see http://crbug.com/115920
TRACE_EVENT1("ime",
"RenderWidgetHostViewMac::GetCachedFirstRectForCharacterRange",
"requested range", requested_range.ToString());
const TextInputManager::TextSelection* selection = GetTextSelection();
if (!selection)
return false;
// If requested range is right after caret, we can just return it.
if (selection->range().is_empty() &&
requested_range.start() == selection->range().end()) {
DCHECK(GetFocusedWidget());
if (actual_range)
*actual_range = requested_range;
// Check selection bounds first (currently populated only for EditContext)
const std::optional<gfx::Rect> text_selection_bound =
GetTextInputManager()->GetTextSelectionBounds();
if (text_selection_bound) {
*rect = text_selection_bound.value();
TRACE_EVENT1(
"ime",
"RenderWidgetHostViewMac::GetCachedFirstRectForCharacterRange",
"GetTextSelectionBounds", rect->ToString());
return true;
}
// If no selection bounds, fall back to use selection region.
*rect = GetTextInputManager()
->GetSelectionRegion(GetFocusedWidget()->GetView())
->caret_rect;
TRACE_EVENT1(
"ime", "RenderWidgetHostViewMac::GetCachedFirstRectForCharacterRange",
"caret_rect", rect->ToString());
return true;
}
const TextInputManager::CompositionRangeInfo* composition_info =
GetCompositionRangeInfo();
if (!composition_info || composition_info->range.is_empty()) {
if (!requested_range.IsBoundedBy(selection->range()))
return false;
DCHECK(GetFocusedWidget());
if (actual_range)
*actual_range = selection->range();
*rect = GetTextInputManager()
->GetSelectionRegion(GetFocusedWidget()->GetView())
->first_selection_rect;
TRACE_EVENT1(
"ime", "RenderWidgetHostViewMac::GetCachedFirstRectForCharacterRange",
"first_selection_rect", rect->ToString());
return true;
}
// If firstRectForCharacterRange in WebFrame is failed in renderer,
// ImeCompositionRangeChanged will be sent with empty vector.
if (!composition_info || composition_info->character_bounds.empty())
return false;
const gfx::Range request_range_in_composition =
ConvertCharacterRangeToCompositionRange(requested_range);
if (request_range_in_composition == gfx::Range::InvalidRange())
return false;
DCHECK_EQ(composition_info->character_bounds.size(),
composition_info->range.length());
gfx::Range ui_actual_range;
*rect = GetFirstRectForCompositionRange(request_range_in_composition,
&ui_actual_range);
TRACE_EVENT1("ime",
"RenderWidgetHostViewMac::GetCachedFirstRectForCharacterRange",
"GetFirstRectForCompositionRange", rect->ToString());
if (actual_range) {
*actual_range =
gfx::Range(composition_info->range.start() + ui_actual_range.start(),
composition_info->range.start() + ui_actual_range.end());
}
return true;
}
void RenderWidgetHostViewMac::FocusedNodeChanged(
bool is_editable_node,
const gfx::Rect& node_bounds_in_screen) {
ns_view_->CancelComposition();
// If the Mac Zoom feature is enabled, update it with the bounds of the
// current focused node so that it can ensure that it's scrolled into view.
// Don't do anything if it's an editable node, as this will be handled by
// OnSelectionBoundsChanged instead.
if (UAZoomEnabled() && !is_editable_node) {
NSRect bounds = NSRectFromCGRect(node_bounds_in_screen.ToCGRect());
UAZoomChangeFocus(&bounds, nullptr, kUAZoomFocusTypeOther);
}
}
void RenderWidgetHostViewMac::ClearFallbackSurfaceForCommitPending() {
browser_compositor_->GetDelegatedFrameHost()
->ClearFallbackSurfaceForCommitPending();
browser_compositor_->InvalidateLocalSurfaceIdOnEviction();
}
void RenderWidgetHostViewMac::ResetFallbackToFirstNavigationSurface() {
browser_compositor_->GetDelegatedFrameHost()
->ResetFallbackToFirstNavigationSurface();
}
bool RenderWidgetHostViewMac::RequestRepaintForTesting() {
return browser_compositor_->ForceNewSurfaceId();
}
void RenderWidgetHostViewMac::TransformPointToRootSurface(gfx::PointF* point) {
browser_compositor_->TransformPointToRootSurface(point);
}
gfx::Rect RenderWidgetHostViewMac::GetBoundsInRootWindow() {
return window_frame_in_screen_dip_;
}
blink::mojom::PointerLockResult RenderWidgetHostViewMac::LockPointer(
bool request_unadjusted_movement) {
if (pointer_locked_) {
return blink::mojom::PointerLockResult::kSuccess;
}
pointer_locked_ = true;
pointer_lock_unadjusted_movement_ = request_unadjusted_movement;
// Lock position of mouse cursor and hide it.
ns_view_->SetCursorLockedUnacceleratedMovement(request_unadjusted_movement);
ns_view_->SetCursorLocked(true);
// Clear the tooltip window.
SetTooltipText(std::u16string());
return blink::mojom::PointerLockResult::kSuccess;
}
blink::mojom::PointerLockResult RenderWidgetHostViewMac::ChangePointerLock(
bool request_unadjusted_movement) {
pointer_lock_unadjusted_movement_ = request_unadjusted_movement;
ns_view_->SetCursorLockedUnacceleratedMovement(request_unadjusted_movement);
return blink::mojom::PointerLockResult::kSuccess;
}
void RenderWidgetHostViewMac::UnlockPointer() {
if (!pointer_locked_) {
return;
}
pointer_locked_ = false;
pointer_lock_unadjusted_movement_ = false;
ns_view_->SetCursorLocked(false);
ns_view_->SetCursorLockedUnacceleratedMovement(false);
if (host())
host()->LostPointerLock();
}
bool RenderWidgetHostViewMac::GetIsPointerLockedUnadjustedMovementForTesting() {
return pointer_locked_ && pointer_lock_unadjusted_movement_;
}
bool RenderWidgetHostViewMac::CanBePointerLocked() {
return HasFocus() && is_window_key_;
}
bool RenderWidgetHostViewMac::AccessibilityHasFocus() {
return HasFocus() && is_window_key_;
}
bool RenderWidgetHostViewMac::LockKeyboard(
std::optional<base::flat_set<ui::DomCode>> dom_codes) {
std::optional<std::vector<uint32_t>> uint_dom_codes;
if (dom_codes) {
uint_dom_codes.emplace();
for (const auto& dom_code : *dom_codes)
uint_dom_codes->push_back(static_cast<uint32_t>(dom_code));
}
is_keyboard_locked_ = true;
ns_view_->LockKeyboard(uint_dom_codes);
return true;
}
void RenderWidgetHostViewMac::UnlockKeyboard() {
if (!is_keyboard_locked_)
return;
is_keyboard_locked_ = false;
ns_view_->UnlockKeyboard();
}
bool RenderWidgetHostViewMac::IsKeyboardLocked() {
return is_keyboard_locked_;
}
base::flat_map<std::string, std::string>
RenderWidgetHostViewMac::GetKeyboardLayoutMap() {
return ui::GenerateDomKeyboardLayoutMap();
}
void RenderWidgetHostViewMac::GestureEventAck(
const WebGestureEvent& event,
blink::mojom::InputEventResultSource ack_source,
blink::mojom::InputEventResultState ack_result) {
ForwardTouchpadZoomEventIfNecessary(event, ack_result);
// Stop flinging if a GSU event with momentum phase is sent to the renderer
// but not consumed.
StopFlingingIfNecessary(event, ack_result);
bool consumed = ack_result == blink::mojom::InputEventResultState::kConsumed;
switch (event.GetType()) {
case WebInputEvent::Type::kGestureScrollBegin:
case WebInputEvent::Type::kGestureScrollUpdate:
case WebInputEvent::Type::kGestureScrollEnd: {
auto input_event = std::make_unique<blink::WebCoalescedInputEvent>(
event.Clone(), std::vector<std::unique_ptr<blink::WebInputEvent>>{},
std::vector<std::unique_ptr<blink::WebInputEvent>>{},
ui::LatencyInfo());
ns_view_->GestureScrollEventAck(std::move(input_event), consumed);
}
return;
default:
break;
}
mouse_wheel_phase_handler_.GestureEventAck(event, ack_result);
}
void RenderWidgetHostViewMac::ProcessAckedTouchEvent(
const input::TouchEventWithLatencyInfo& touch,
blink::mojom::InputEventResultState ack_result) {
const bool event_consumed =
ack_result == blink::mojom::InputEventResultState::kConsumed;
gesture_provider_.OnTouchEventAck(
touch.event.unique_touch_event_id, event_consumed,
InputEventResultStateIsSetBlocking(ack_result));
if (touch.event.touch_start_or_first_touch_move && event_consumed &&
host()->delegate() && host()->delegate()->GetInputEventRouter()) {
host()
->delegate()
->GetInputEventRouter()
->OnHandledTouchStartOrFirstTouchMove(
touch.event.unique_touch_event_id);
}
}
void RenderWidgetHostViewMac::DidOverscroll(
const ui::DidOverscrollParams& params) {
ns_view_->DidOverscroll(blink::mojom::DidOverscrollParams::New(
params.accumulated_overscroll, params.latest_overscroll_delta,
params.current_fling_velocity, params.causal_event_viewport_point,
params.overscroll_behavior));
}
std::unique_ptr<SyntheticGestureTarget>
RenderWidgetHostViewMac::CreateSyntheticGestureTarget() {
RenderWidgetHostImpl* host =
RenderWidgetHostImpl::From(GetRenderWidgetHost());
return std::unique_ptr<SyntheticGestureTarget>(
new SyntheticGestureTargetMac(host, GetInProcessNSView()));
}
const viz::LocalSurfaceId& RenderWidgetHostViewMac::GetLocalSurfaceId() const {
return browser_compositor_->GetRendererLocalSurfaceId();
}
void RenderWidgetHostViewMac::InvalidateLocalSurfaceIdAndAllocationGroup() {
browser_compositor_->InvalidateSurfaceAllocationGroup();
}
void RenderWidgetHostViewMac::UpdateFrameSinkIdRegistration() {
RenderWidgetHostViewBase::UpdateFrameSinkIdRegistration();
browser_compositor_->GetDelegatedFrameHost()->SetIsFrameSinkIdOwner(
is_frame_sink_id_owner());
}
const viz::FrameSinkId& RenderWidgetHostViewMac::GetFrameSinkId() const {
return browser_compositor_->GetDelegatedFrameHost()->frame_sink_id();
}
bool RenderWidgetHostViewMac::ShouldRouteEvents() const {
// Event routing requires a valid frame sink (that is, that we be connected to
// a ui::Compositor), which is not guaranteed to be the case.
// https://crbug.com/844095
if (!browser_compositor_->GetRootFrameSinkId().is_valid())
return false;
return host()->delegate() && host()->delegate()->GetInputEventRouter();
}
void RenderWidgetHostViewMac::SendTouchpadZoomEvent(
const WebGestureEvent* event) {
DCHECK(event->IsTouchpadZoomEvent());
if (ShouldRouteEvents()) {
host()->delegate()->GetInputEventRouter()->RouteGestureEvent(
this, event, ui::LatencyInfo());
return;
}
host()->ForwardGestureEvent(*event);
}
void RenderWidgetHostViewMac::InjectTouchEvent(
const WebTouchEvent& event,
const ui::LatencyInfo& latency_info) {
ui::FilteredGestureProvider::TouchHandlingResult result =
gesture_provider_.OnTouchEvent(MotionEventWeb(event));
if (!result.succeeded)
return;
if (ShouldRouteEvents()) {
WebTouchEvent touch_event(event);
host()->delegate()->GetInputEventRouter()->RouteTouchEvent(
this, &touch_event, latency_info);
} else {
host()->GetRenderInputRouter()->ForwardTouchEventWithLatencyInfo(
event, latency_info);
}
}
bool RenderWidgetHostViewMac::HasFallbackSurface() const {
return browser_compositor_->GetDelegatedFrameHost()->HasFallbackSurface();
}
bool RenderWidgetHostViewMac::TransformPointToCoordSpaceForView(
const gfx::PointF& point,
RenderWidgetHostViewInput* target_view,
gfx::PointF* transformed_point) {
if (target_view == this) {
*transformed_point = point;
return true;
}
return target_view->TransformPointToLocalCoordSpace(
point, GetCurrentSurfaceId(), transformed_point);
}
viz::FrameSinkId RenderWidgetHostViewMac::GetRootFrameSinkId() {
return browser_compositor_->GetRootFrameSinkId();
}
viz::SurfaceId RenderWidgetHostViewMac::GetCurrentSurfaceId() const {
// |browser_compositor_| could be null if this method is called during its
// destruction.
if (!browser_compositor_)
return viz::SurfaceId();
return browser_compositor_->GetDelegatedFrameHost()->GetCurrentSurfaceId();
}
void RenderWidgetHostViewMac::ShutdownHost() {
weak_factory_.InvalidateWeakPtrs();
host()->ShutdownAndDestroyWidget(true);
// Do not touch any members at this point, |this| has been deleted.
}
void RenderWidgetHostViewMac::SetActive(bool active) {
if (host()) {
UpdateActiveState(active);
if (active) {
if (HasFocus())
host()->GotFocus();
} else {
host()->LostFocus();
}
}
if (HasFocus())
SetTextInputActive(active);
if (!active)
UnlockPointer();
}
void RenderWidgetHostViewMac::ShowDefinitionForSelection() {
// This will round-trip to the NSView to determine the selection range.
ns_view_->ShowDictionaryOverlayForSelection();
}
void RenderWidgetHostViewMac::UpdateBackgroundColor() {
// This is called by the embedding code prior to the first frame appearing,
// to set a reasonable color to show before the web content generates its
// first frame. This will be overridden by the web contents.
DCHECK(RenderWidgetHostViewBase::GetBackgroundColor());
SkColor color = *RenderWidgetHostViewBase::GetBackgroundColor();
SetBackgroundLayerColor(color);
browser_compositor_->SetBackgroundColor(color);
}
std::optional<SkColor> RenderWidgetHostViewMac::GetBackgroundColor() {
// This is used to specify a color to temporarily show while waiting for web
// content. This should never return transparent, since that will cause bugs
// where views are initialized as having a transparent background
// inappropriately.
// https://crbug.com/735407
std::optional<SkColor> color = RenderWidgetHostViewBase::GetBackgroundColor();
return (color && *color == SK_ColorTRANSPARENT) ? SK_ColorWHITE : color;
}
viz::SurfaceId RenderWidgetHostViewMac::GetFallbackSurfaceIdForTesting() const {
return browser_compositor_->GetDelegatedFrameHost()
->GetFallbackSurfaceIdForTesting(); // IN-TEST
}
void RenderWidgetHostViewMac::SetBackgroundLayerColor(SkColor color) {
if (color == background_layer_color_)
return;
background_layer_color_ = color;
ns_view_->SetBackgroundColor(color);
}
std::optional<DisplayFeature> RenderWidgetHostViewMac::GetDisplayFeature() {
return display_feature_;
}
void RenderWidgetHostViewMac::SetDisplayFeatureForTesting(
const DisplayFeature* display_feature) {
if (display_feature)
display_feature_ = *display_feature;
else
display_feature_ = std::nullopt;
}
gfx::NativeViewAccessible
RenderWidgetHostViewMac::AccessibilityGetNativeViewAccessible() {
return GetInProcessNSView();
}
gfx::NativeViewAccessible
RenderWidgetHostViewMac::AccessibilityGetNativeViewAccessibleForWindow() {
if (remote_window_accessible_)
return remote_window_accessible_;
return [GetInProcessNSView() window];
}
void RenderWidgetHostViewMac::SetTextInputActive(bool active) {
const bool should_enable_password_input =
active && GetTextInputType() == ui::TEXT_INPUT_TYPE_PASSWORD;
if (should_enable_password_input) {
password_input_enabler_ =
std::make_unique<ui::ScopedPasswordInputEnabler>();
} else {
password_input_enabler_.reset();
}
update_windows_timer_.Stop();
}
MouseWheelPhaseHandler* RenderWidgetHostViewMac::GetMouseWheelPhaseHandler() {
return &mouse_wheel_phase_handler_;
}
void RenderWidgetHostViewMac::ShowSharePicker(
const std::string& title,
const std::string& text,
const std::string& url,
const std::vector<std::string>& file_paths,
blink::mojom::ShareService::ShareCallback callback) {
ns_view_->ShowSharingServicePicker(title, text, url, file_paths,
std::move(callback));
}
///////////////////////////////////////////////////////////////////////////////
// RenderWidgetHostNSViewHostHelper and mojom::RenderWidgetHostNSViewHost
// implementation:
id RenderWidgetHostViewMac::GetAccessibilityElement() {
return GetNativeViewAccessible();
}
id RenderWidgetHostViewMac::GetRootBrowserAccessibilityElement() {
if (auto* manager = host()->GetRootBrowserAccessibilityManager())
return manager->GetBrowserAccessibilityRoot()->GetNativeViewAccessible();
return nil;
}
id RenderWidgetHostViewMac::GetFocusedBrowserAccessibilityElement() {
return GetAccessibilityFocusedUIElement();
}
void RenderWidgetHostViewMac::SetAccessibilityWindow(NSWindow* window) {
// When running in-process, just use the NSView's NSWindow as its own
// accessibility element.
remote_window_accessible_ = nil;
}
bool RenderWidgetHostViewMac::SyncIsWidgetForMainFrame(
bool* is_for_main_frame) {
*is_for_main_frame = !!host()->owner_delegate();
return true;
}
void RenderWidgetHostViewMac::SyncIsWidgetForMainFrame(
SyncIsWidgetForMainFrameCallback callback) {
bool is_for_main_frame;
SyncIsWidgetForMainFrame(&is_for_main_frame);
std::move(callback).Run(is_for_main_frame);
}
void RenderWidgetHostViewMac::RequestShutdown() {
if (!weak_factory_.HasWeakPtrs()) {
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&RenderWidgetHostViewMac::ShutdownHost,
weak_factory_.GetWeakPtr()));
}
}
void RenderWidgetHostViewMac::OnFirstResponderChanged(bool is_first_responder) {
if (is_first_responder_ == is_first_responder)
return;
is_first_responder_ = is_first_responder;
accessibility_focus_overrider_.SetViewIsFirstResponder(is_first_responder_);
if (is_first_responder_) {
host()->GotFocus();
SetTextInputActive(true);
} else {
SetTextInputActive(false);
host()->LostFocus();
}
}
void RenderWidgetHostViewMac::OnWindowIsKeyChanged(bool is_key) {
if (is_window_key_ == is_key)
return;
is_window_key_ = is_key;
accessibility_focus_overrider_.SetWindowIsKey(is_window_key_);
if (is_first_responder_)
SetActive(is_key);
}
void RenderWidgetHostViewMac::OnBoundsInWindowChanged(
const gfx::Rect& view_bounds_in_window_dip,
bool attached_to_window) {
bool view_size_changed =
view_bounds_in_window_dip_.size() != view_bounds_in_window_dip.size();
if (attached_to_window) {
view_bounds_in_window_dip_ = view_bounds_in_window_dip;
} else {
// If not attached to a window, do not update the bounds origin (since it is
// meaningless, and the last value is the best guess at the next meaningful
// value).
view_bounds_in_window_dip_.set_size(view_bounds_in_window_dip.size());
}
if (view_size_changed)
UpdateScreenInfo();
}
void RenderWidgetHostViewMac::OnWindowFrameInScreenChanged(
const gfx::Rect& window_frame_in_screen_dip) {
if (window_frame_in_screen_dip_ == window_frame_in_screen_dip)
return;
window_frame_in_screen_dip_ = window_frame_in_screen_dip;
if (host()->delegate())
host()->delegate()->SendScreenRects();
else
host()->SendScreenRects();
}
void RenderWidgetHostViewMac::OnScreenInfosChanged(
const display::ScreenInfos& screen_infos) {
// Cache the screen infos, which may originate from a remote process that
// hosts the associated NSWindow. The latest display::Screen info observed
// directly in this process may be intermittently out-of-sync with that info.
// Also, BrowserCompositorMac and RenderWidgetHostViewMac do not update their
// cached screen info during auto-resize.
new_screen_infos_from_shim_ = screen_infos;
UpdateScreenInfo();
}
void RenderWidgetHostViewMac::BeginKeyboardEvent() {
DCHECK(!in_keyboard_event_);
in_keyboard_event_ = true;
RenderWidgetHostImpl* widget_host = host();
if (widget_host && widget_host->delegate()) {
widget_host =
widget_host->delegate()->GetFocusedRenderWidgetHost(widget_host);
}
if (widget_host) {
keyboard_event_widget_process_id_ = widget_host->GetProcess()->GetID();
keyboard_event_widget_routing_id_ = widget_host->GetRoutingID();
}
}
void RenderWidgetHostViewMac::EndKeyboardEvent() {
in_keyboard_event_ = false;
keyboard_event_widget_process_id_ = 0;
keyboard_event_widget_routing_id_ = 0;
}
void RenderWidgetHostViewMac::ForwardKeyboardEvent(
const input::NativeWebKeyboardEvent& key_event,
const ui::LatencyInfo& latency_info) {
if (auto* widget_host = GetWidgetForKeyboardEvent()) {
widget_host->ForwardKeyboardEventWithLatencyInfo(key_event, latency_info);
}
}
void RenderWidgetHostViewMac::ForwardKeyboardEventWithCommands(
const input::NativeWebKeyboardEvent& key_event,
const ui::LatencyInfo& latency_info,
std::vector<blink::mojom::EditCommandPtr> commands) {
if (auto* widget_host = GetWidgetForKeyboardEvent()) {
widget_host->ForwardKeyboardEventWithCommands(key_event, latency_info,
std::move(commands));
}
}
void RenderWidgetHostViewMac::RouteOrProcessMouseEvent(
const blink::WebMouseEvent& const_web_event) {
blink::WebMouseEvent web_event = const_web_event;
ui::LatencyInfo latency_info;
latency_info.AddLatencyNumber(ui::INPUT_EVENT_LATENCY_UI_COMPONENT);
if (ShouldRouteEvents()) {
host()->delegate()->GetInputEventRouter()->RouteMouseEvent(this, &web_event,
latency_info);
} else {
ProcessMouseEvent(web_event, latency_info);
}
}
void RenderWidgetHostViewMac::RouteOrProcessTouchEvent(
const blink::WebTouchEvent& const_web_event) {
blink::WebTouchEvent web_event = const_web_event;
ui::FilteredGestureProvider::TouchHandlingResult result =
gesture_provider_.OnTouchEvent(MotionEventWeb(web_event));
if (!result.succeeded)
return;
ui::LatencyInfo latency_info;
latency_info.AddLatencyNumber(ui::INPUT_EVENT_LATENCY_UI_COMPONENT);
if (ShouldRouteEvents()) {
host()->delegate()->GetInputEventRouter()->RouteTouchEvent(this, &web_event,
latency_info);
} else {
ProcessTouchEvent(web_event, latency_info);
}
}
void RenderWidgetHostViewMac::RouteOrProcessWheelEvent(
const blink::WebMouseWheelEvent& const_web_event) {
blink::WebMouseWheelEvent web_event = const_web_event;
ui::LatencyInfo latency_info;
latency_info.AddLatencyNumber(ui::INPUT_EVENT_LATENCY_UI_COMPONENT);
mouse_wheel_phase_handler_.AddPhaseIfNeededAndScheduleEndEvent(
web_event, ShouldRouteEvents());
if (web_event.phase == blink::WebMouseWheelEvent::kPhaseEnded) {
// A wheel end event is scheduled and will get dispatched if momentum
// phase doesn't start in 100ms. Don't sent the wheel end event
// immediately.
return;
}
if (ShouldRouteEvents()) {
host()->delegate()->GetInputEventRouter()->RouteMouseWheelEvent(
this, &web_event, latency_info);
} else {
ProcessMouseWheelEvent(web_event, latency_info);
}
}
void RenderWidgetHostViewMac::ForwardMouseEvent(
const blink::WebMouseEvent& web_event) {
if (host())
host()->ForwardMouseEvent(web_event);
if (web_event.GetType() == WebInputEvent::Type::kMouseLeave)
SetTooltipText(std::u16string());
}
void RenderWidgetHostViewMac::ForwardWheelEvent(
const blink::WebMouseWheelEvent& const_web_event) {
blink::WebMouseWheelEvent web_event = const_web_event;
mouse_wheel_phase_handler_.AddPhaseIfNeededAndScheduleEndEvent(web_event,
false);
}
void RenderWidgetHostViewMac::GestureBegin(blink::WebGestureEvent begin_event,
bool is_synthetically_injected) {
gesture_begin_event_ = std::make_unique<WebGestureEvent>(begin_event);
// If the page is at the minimum zoom level, require a threshold be reached
// before the pinch has an effect. Synthetic pinches are not subject to this
// threshold.
// TODO(crbug.com/40666440): |page_at_minimum_scale_| is always true, should
// it be removed or correctly set based on RenderFrameMetadata?
if (page_at_minimum_scale_) {
pinch_has_reached_zoom_threshold_ = is_synthetically_injected;
pinch_unused_amount_ = 1;
}
}
void RenderWidgetHostViewMac::GestureUpdate(
blink::WebGestureEvent update_event) {
// If, due to nesting of multiple gestures (e.g, from multiple touch
// devices), the beginning of the gesture has been lost, skip the remainder
// of the gesture.
if (!gesture_begin_event_)
return;
if (!pinch_has_reached_zoom_threshold_) {
pinch_unused_amount_ *= update_event.data.pinch_update.scale;
if (pinch_unused_amount_ < 0.667 || pinch_unused_amount_ > 1.5)
pinch_has_reached_zoom_threshold_ = true;
}
// Send a GesturePinchBegin event if none has been sent yet.
if (!gesture_begin_pinch_sent_) {
// Before starting a pinch sequence, send the pending wheel end event to
// finish scrolling.
mouse_wheel_phase_handler_.DispatchPendingWheelEndEvent();
WebGestureEvent begin_event(*gesture_begin_event_);
begin_event.SetType(WebInputEvent::Type::kGesturePinchBegin);
begin_event.SetSourceDevice(blink::WebGestureDevice::kTouchpad);
begin_event.SetNeedsWheelEvent(true);
SendTouchpadZoomEvent(&begin_event);
gesture_begin_pinch_sent_ = YES;
}
// Send a GesturePinchUpdate event.
update_event.data.pinch_update.zoom_disabled =
!pinch_has_reached_zoom_threshold_;
SendTouchpadZoomEvent(&update_event);
}
void RenderWidgetHostViewMac::GestureEnd(blink::WebGestureEvent end_event) {
gesture_begin_event_.reset();
if (gesture_begin_pinch_sent_) {
SendTouchpadZoomEvent(&end_event);
gesture_begin_pinch_sent_ = false;
}
}
void RenderWidgetHostViewMac::SmartMagnify(
const blink::WebGestureEvent& smart_magnify_event) {
SendTouchpadZoomEvent(&smart_magnify_event);
}
void RenderWidgetHostViewMac::ImeSetComposition(
const std::u16string& text,
const std::vector<ui::ImeTextSpan>& ime_text_spans,
const gfx::Range& replacement_range,
int selection_start,
int selection_end) {
if (auto* widget_host = GetWidgetForIme()) {
widget_host->ImeSetComposition(text, ime_text_spans, replacement_range,
selection_start, selection_end);
}
}
void RenderWidgetHostViewMac::ImeCommitText(
const std::u16string& text,
const gfx::Range& replacement_range) {
if (auto* widget_host = GetWidgetForIme()) {
widget_host->ImeCommitText(text, std::vector<ui::ImeTextSpan>(),
replacement_range, 0);
}
}
void RenderWidgetHostViewMac::ImeFinishComposingText() {
if (auto* widget_host = GetWidgetForIme()) {
widget_host->ImeFinishComposingText(false);
}
}
void RenderWidgetHostViewMac::ImeCancelCompositionFromCocoa() {
if (auto* widget_host = GetWidgetForIme()) {
widget_host->ImeCancelComposition();
}
}
void RenderWidgetHostViewMac::LookUpDictionaryOverlayFromRange(
const gfx::Range& range) {
content::RenderWidgetHostViewBase* focused_view =
GetFocusedViewForTextSelection();
if (!focused_view)
return;
RenderWidgetHostImpl* widget_host =
RenderWidgetHostImpl::From(focused_view->GetRenderWidgetHost());
if (!widget_host)
return;
int32_t target_widget_process_id = widget_host->GetProcess()->GetID();
int32_t target_widget_routing_id = widget_host->GetRoutingID();
TextInputClientMac::GetInstance()->GetStringFromRange(
widget_host, range,
base::BindOnce(&RenderWidgetHostViewMac::OnGotStringForDictionaryOverlay,
weak_factory_.GetWeakPtr(), target_widget_process_id,
target_widget_routing_id));
}
void RenderWidgetHostViewMac::LookUpDictionaryOverlayAtPoint(
const gfx::PointF& root_point_in_dips) {
if (!host() || !host()->delegate() ||
!host()->delegate()->GetInputEventRouter())
return;
// With zoom-for-dsf, RenderWidgetHost coordinate system is physical points,
// which means we have to scale the point by device scale factor.
gfx::PointF root_point = root_point_in_dips;
root_point.Scale(GetDeviceScaleFactor());
gfx::PointF transformed_point;
auto* view = host()
->delegate()
->GetInputEventRouter()
->GetRenderWidgetHostViewInputAtPoint(this, root_point,
&transformed_point);
if (!view) {
return;
}
RenderWidgetHostImpl* widget_host = RenderWidgetHostImpl::From(
static_cast<RenderWidgetHostViewBase*>(view)->GetRenderWidgetHost());
if (!widget_host)
return;
// For popups, do not support QuickLook.
if (popup_parent_host_view_)
return;
int32_t target_widget_process_id = widget_host->GetProcess()->GetID();
int32_t target_widget_routing_id = widget_host->GetRoutingID();
TextInputClientMac::GetInstance()->GetStringAtPoint(
widget_host, gfx::ToFlooredPoint(transformed_point),
base::BindOnce(&RenderWidgetHostViewMac::OnGotStringForDictionaryOverlay,
weak_factory_.GetWeakPtr(), target_widget_process_id,
target_widget_routing_id));
}
bool RenderWidgetHostViewMac::SyncGetCharacterIndexAtPoint(
const gfx::PointF& root_point,
uint32_t* index) {
*index = UINT32_MAX;
if (!host() || !host()->delegate() ||
!host()->delegate()->GetInputEventRouter())
return true;
gfx::PointF transformed_point;
auto* view = host()
->delegate()
->GetInputEventRouter()
->GetRenderWidgetHostViewInputAtPoint(this, root_point,
&transformed_point);
if (!view) {
return true;
}
RenderWidgetHostImpl* widget_host = RenderWidgetHostImpl::From(
static_cast<RenderWidgetHostViewBase*>(view)->GetRenderWidgetHost());
if (!widget_host)
return true;
*index = TextInputClientMac::GetInstance()->GetCharacterIndexAtPoint(
widget_host, gfx::ToFlooredPoint(transformed_point));
return true;
}
void RenderWidgetHostViewMac::SyncGetCharacterIndexAtPoint(
const gfx::PointF& root_point,
SyncGetCharacterIndexAtPointCallback callback) {
uint32_t index;
SyncGetCharacterIndexAtPoint(root_point, &index);
std::move(callback).Run(index);
}
bool RenderWidgetHostViewMac::SyncGetFirstRectForRange(
const gfx::Range& requested_range,
gfx::Rect* rect,
gfx::Range* actual_range,
bool* success) {
TRACE_EVENT1("ime", "RenderWidgetHostViewMac::SyncGetFirstRectForRange",
"requested range", requested_range.ToString());
*actual_range = requested_range;
if (!GetFocusedWidget()) {
*success = false;
return true;
}
*success = true;
if (!GetCachedFirstRectForCharacterRange(requested_range, rect,
actual_range)) {
// https://crbug.com/121917
base::ScopedAllowBlocking allow_wait;
// TODO(thakis): Pipe |actualRange| through TextInputClientMac machinery.
gfx::Rect blink_rect =
TextInputClientMac::GetInstance()->GetFirstRectForRange(
GetFocusedWidget(), requested_range);
// With zoom-for-dsf, RenderWidgetHost coordinate system is physical points,
// which means we have to scale the rect by the device scale factor.
*rect = gfx::ScaleToEnclosingRect(blink_rect, 1.f / GetDeviceScaleFactor());
}
return true;
}
void RenderWidgetHostViewMac::SyncGetFirstRectForRange(
const gfx::Range& requested_range,
SyncGetFirstRectForRangeCallback callback) {
gfx::Rect out_rect;
gfx::Range out_actual_range;
bool success;
SyncGetFirstRectForRange(requested_range, &out_rect, &out_actual_range,
&success);
std::move(callback).Run(out_rect, out_actual_range, success);
}
void RenderWidgetHostViewMac::ExecuteEditCommand(const std::string& command) {
if (host()->delegate()) {
host()->delegate()->ExecuteEditCommand(command, std::nullopt);
}
}
void RenderWidgetHostViewMac::Undo() {
if (auto* delegate = GetFocusedRenderWidgetHostDelegate()) {
delegate->Undo();
}
}
void RenderWidgetHostViewMac::Redo() {
if (auto* delegate = GetFocusedRenderWidgetHostDelegate()) {
delegate->Redo();
}
}
void RenderWidgetHostViewMac::Cut() {
if (auto* delegate = GetFocusedRenderWidgetHostDelegate()) {
delegate->Cut();
}
}
void RenderWidgetHostViewMac::Copy() {
if (auto* delegate = GetFocusedRenderWidgetHostDelegate()) {
delegate->Copy();
}
}
void RenderWidgetHostViewMac::CopyToFindPboard() {
WebContents* web_contents = GetWebContents();
if (web_contents)
web_contents->CopyToFindPboard();
}
void RenderWidgetHostViewMac::CenterSelection() {
WebContents* web_contents = GetWebContents();
if (web_contents) {
web_contents->CenterSelection();
}
}
void RenderWidgetHostViewMac::Paste() {
if (auto* delegate = GetFocusedRenderWidgetHostDelegate()) {
delegate->Paste();
}
}
void RenderWidgetHostViewMac::PasteAndMatchStyle() {
if (auto* delegate = GetFocusedRenderWidgetHostDelegate()) {
delegate->PasteAndMatchStyle();
}
}
void RenderWidgetHostViewMac::SelectAll() {
if (auto* delegate = GetFocusedRenderWidgetHostDelegate()) {
delegate->SelectAll();
}
}
bool RenderWidgetHostViewMac::SyncIsSpeaking(bool* is_speaking) {
*is_speaking = ui::TextServicesContextMenu::IsSpeaking();
return true;
}
void RenderWidgetHostViewMac::SyncIsSpeaking(SyncIsSpeakingCallback callback) {
bool is_speaking;
SyncIsSpeaking(&is_speaking);
std::move(callback).Run(is_speaking);
}
void RenderWidgetHostViewMac::StartSpeaking() {
RenderWidgetHostView* target = this;
WebContents* web_contents = GetWebContents();
if (web_contents) {
content::BrowserPluginGuestManager* guest_manager =
web_contents->GetBrowserContext()->GetGuestManager();
if (guest_manager) {
content::WebContents* guest =
guest_manager->GetFullPageGuest(web_contents);
if (guest) {
target = guest->GetRenderWidgetHostView();
}
}
}
target->SpeakSelection();
}
void RenderWidgetHostViewMac::StopSpeaking() {
ui::TextServicesContextMenu::StopSpeaking();
}
void RenderWidgetHostViewMac::GetRenderWidgetAccessibilityToken(
GetRenderWidgetAccessibilityTokenCallback callback) {
base::ProcessId pid = getpid();
id element_id = GetNativeViewAccessible();
std::vector<uint8_t> token =
ui::RemoteAccessibility::GetTokenForLocalElement(element_id);
std::move(callback).Run(pid, token);
}
void RenderWidgetHostViewMac::SetRemoteAccessibilityWindowToken(
const std::vector<uint8_t>& window_token) {
if (window_token.empty()) {
remote_window_accessible_ = nil;
} else {
remote_window_accessible_ =
ui::RemoteAccessibility::GetRemoteElementFromToken(window_token);
}
}
///////////////////////////////////////////////////////////////////////////////
// mojom::RenderWidgetHostNSViewHost functions that translate events and
// forward them to the RenderWidgetHostNSViewHostHelper implementation:
void RenderWidgetHostViewMac::ForwardKeyboardEventWithCommands(
std::unique_ptr<blink::WebCoalescedInputEvent> input_event,
const std::vector<uint8_t>& native_event_data,
bool skip_if_unhandled,
std::vector<blink::mojom::EditCommandPtr> edit_commands) {
if (!input_event || !blink::WebInputEvent::IsKeyboardEventType(
input_event->Event().GetType())) {
DLOG(ERROR) << "Absent or non-KeyboardEventType event.";
return;
}
const blink::WebKeyboardEvent& keyboard_event =
static_cast<const blink::WebKeyboardEvent&>(input_event->Event());
input::NativeWebKeyboardEvent native_event(keyboard_event, nil);
native_event.skip_if_unhandled = skip_if_unhandled;
// The NSEvent constructed from the InputEvent sent over mojo is not even
// close to the original NSEvent, resulting in all sorts of bugs. Use the
// native event serialization to reconstruct the NSEvent.
// https://crbug.com/919167,943197,964052
native_event.os_event =
base::apple::OwnedNSEvent(ui::EventFromData(native_event_data));
ForwardKeyboardEventWithCommands(native_event, input_event->latency_info(),
std::move(edit_commands));
}
void RenderWidgetHostViewMac::RouteOrProcessMouseEvent(
std::unique_ptr<blink::WebCoalescedInputEvent> input_event) {
if (!input_event ||
!blink::WebInputEvent::IsMouseEventType(input_event->Event().GetType())) {
DLOG(ERROR) << "Absent or non-MouseEventType event.";
return;
}
const blink::WebMouseEvent& mouse_event =
static_cast<const blink::WebMouseEvent&>(input_event->Event());
RouteOrProcessMouseEvent(mouse_event);
}
void RenderWidgetHostViewMac::RouteOrProcessTouchEvent(
std::unique_ptr<blink::WebCoalescedInputEvent> input_event) {
if (!input_event ||
!blink::WebInputEvent::IsTouchEventType(input_event->Event().GetType())) {
DLOG(ERROR) << "Absent or non-TouchEventType event.";
return;
}
const blink::WebTouchEvent& touch_event =
static_cast<const blink::WebTouchEvent&>(input_event->Event());
RouteOrProcessTouchEvent(touch_event);
}
void RenderWidgetHostViewMac::RouteOrProcessWheelEvent(
std::unique_ptr<blink::WebCoalescedInputEvent> input_event) {
if (!input_event || input_event->Event().GetType() !=
blink::WebInputEvent::Type::kMouseWheel) {
DLOG(ERROR) << "Absent or non-MouseWheel event.";
return;
}
const blink::WebMouseWheelEvent& wheel_event =
static_cast<const blink::WebMouseWheelEvent&>(input_event->Event());
RouteOrProcessWheelEvent(wheel_event);
}
void RenderWidgetHostViewMac::ForwardMouseEvent(
std::unique_ptr<blink::WebCoalescedInputEvent> input_event) {
if (!input_event ||
!blink::WebInputEvent::IsMouseEventType(input_event->Event().GetType())) {
DLOG(ERROR) << "Absent or non-MouseEventType event.";
return;
}
const blink::WebMouseEvent& mouse_event =
static_cast<const blink::WebMouseEvent&>(input_event->Event());
ForwardMouseEvent(mouse_event);
}
void RenderWidgetHostViewMac::ForwardWheelEvent(
std::unique_ptr<blink::WebCoalescedInputEvent> input_event) {
if (!input_event || input_event->Event().GetType() !=
blink::WebInputEvent::Type::kMouseWheel) {
DLOG(ERROR) << "Absent or non-MouseWheel event.";
return;
}
const blink::WebMouseWheelEvent& wheel_event =
static_cast<const blink::WebMouseWheelEvent&>(input_event->Event());
ForwardWheelEvent(wheel_event);
}
void RenderWidgetHostViewMac::GestureBegin(
std::unique_ptr<blink::WebCoalescedInputEvent> input_event,
bool is_synthetically_injected) {
if (!input_event || !blink::WebInputEvent::IsGestureEventType(
input_event->Event().GetType())) {
DLOG(ERROR) << "Absent or non-GestureEventType event.";
return;
}
blink::WebGestureEvent gesture_event =
static_cast<const blink::WebGestureEvent&>(input_event->Event());
// Strip the gesture type, because it is not known.
gesture_event.SetType(blink::WebInputEvent::Type::kUndefined);
GestureBegin(gesture_event, is_synthetically_injected);
}
void RenderWidgetHostViewMac::GestureUpdate(
std::unique_ptr<blink::WebCoalescedInputEvent> input_event) {
if (!input_event || !blink::WebInputEvent::IsGestureEventType(
input_event->Event().GetType())) {
DLOG(ERROR) << "Absent or non-GestureEventType event.";
return;
}
const blink::WebGestureEvent& gesture_event =
static_cast<const blink::WebGestureEvent&>(input_event->Event());
GestureUpdate(gesture_event);
}
void RenderWidgetHostViewMac::GestureEnd(
std::unique_ptr<blink::WebCoalescedInputEvent> input_event) {
if (!input_event || !blink::WebInputEvent::IsGestureEventType(
input_event->Event().GetType())) {
DLOG(ERROR) << "Absent or non-GestureEventType event.";
return;
}
blink::WebGestureEvent gesture_event =
static_cast<const blink::WebGestureEvent&>(input_event->Event());
GestureEnd(gesture_event);
}
void RenderWidgetHostViewMac::SmartMagnify(
std::unique_ptr<blink::WebCoalescedInputEvent> input_event) {
if (!input_event || !blink::WebInputEvent::IsGestureEventType(
input_event->Event().GetType())) {
DLOG(ERROR) << "Absent or non-GestureEventType event.";
return;
}
const blink::WebGestureEvent& gesture_event =
static_cast<const blink::WebGestureEvent&>(input_event->Event());
SmartMagnify(gesture_event);
}
void RenderWidgetHostViewMac::OnGotStringForDictionaryOverlay(
int32_t target_widget_process_id,
int32_t target_widget_routing_id,
ui::mojom::AttributedStringPtr attributed_string,
const gfx::Point& baseline_point_in_layout_space) {
if (!attributed_string || attributed_string->string.empty()) {
// The PDF plugin does not support getting the attributed string at point.
// Until it does, use NSPerformService(), which opens Dictionary.app.
// TODO(shuchen): Support GetStringAtPoint() & GetStringFromRange() for PDF.
// https://crbug.com/152438
// This often just opens a blank dictionary, not the definition of |string|.
// https://crbug.com/830047
// This path will be taken, inappropriately, when a lookup gesture was
// performed at a location that doesn't have text, but some text is
// selected.
// https://crbug.com/830906
if (auto* selection = GetTextSelection()) {
const std::u16string& selected_text = selection->selected_text();
NSString* ns_selected_text = base::SysUTF16ToNSString(selected_text);
if ([ns_selected_text length] == 0)
return;
scoped_refptr<ui::UniquePasteboard> pasteboard = new ui::UniquePasteboard;
if ([pasteboard->get() writeObjects:@[ ns_selected_text ]]) {
NSPerformService(@"Look Up in Dictionary", pasteboard->get());
}
}
} else {
// By the time we get here |widget_host| might have been destroyed.
// https://crbug.com/737032
auto* widget_host = content::RenderWidgetHost::FromID(
target_widget_process_id, target_widget_routing_id);
gfx::Point updated_baseline_point = baseline_point_in_layout_space;
if (widget_host) {
if (auto* rwhv = widget_host->GetView()) {
updated_baseline_point = rwhv->TransformPointToRootCoordSpace(
baseline_point_in_layout_space);
}
}
// Layout space is physical pixels. Scale
// it to get DIPs, which is what ns_view_ expects.
updated_baseline_point = gfx::ScaleToRoundedPoint(
updated_baseline_point, 1.f / GetDeviceScaleFactor());
ns_view_->ShowDictionaryOverlay(std::move(attributed_string),
updated_baseline_point);
}
}
void RenderWidgetHostViewMac::SetTooltipText(
const std::u16string& tooltip_text) {
ns_view_->SetTooltipText(tooltip_text);
if (tooltip_observer_for_testing_)
tooltip_observer_for_testing_->OnTooltipTextUpdated(tooltip_text);
}
void RenderWidgetHostViewMac::UpdateWindowsNow() {
[NSApp updateWindows];
}
Class GetRenderWidgetHostViewCocoaClassForTesting() {
return [RenderWidgetHostViewCocoa class];
}
} // namespace content