// 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_android.h"
#include <android/bitmap.h>
#include <limits>
#include <utility>
#include "base/android/build_info.h"
#include "base/android/callback_android.h"
#include "base/android/jni_string.h"
#include "base/auto_reset.h"
#include "base/command_line.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/memory/ref_counted.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/utf_string_conversions.h"
#include "base/system/sys_info.h"
#include "base/task/single_thread_task_runner.h"
#include "base/task/thread_pool.h"
#include "base/threading/scoped_blocking_call.h"
#include "cc/base/math_util.h"
#include "cc/input/browser_controls_offset_tags_info.h"
#include "cc/slim/layer.h"
#include "components/input/input_router.h"
#include "components/input/render_widget_host_input_event_router.h"
#include "components/input/web_input_event_builders_android.h"
#include "components/viz/common/features.h"
#include "components/viz/common/quads/compositor_frame.h"
#include "components/viz/common/surfaces/frame_sink_id_allocator.h"
#include "components/viz/common/surfaces/parent_local_surface_id_allocator.h"
#include "content/browser/accessibility/browser_accessibility_manager_android.h"
#include "content/browser/accessibility/web_contents_accessibility_android.h"
#include "content/browser/android/gesture_listener_manager.h"
#include "content/browser/android/ime_adapter_android.h"
#include "content/browser/android/overscroll_controller_android.h"
#include "content/browser/android/selection/selection_popup_controller.h"
#include "content/browser/android/synchronous_compositor_host.h"
#include "content/browser/android/text_suggestion_host_android.h"
#include "content/browser/bad_message.h"
#include "content/browser/compositor/surface_utils.h"
#include "content/browser/devtools/render_frame_devtools_agent_host.h"
#include "content/browser/gpu/gpu_process_host.h"
#include "content/browser/renderer_host/compositor_impl_android.h"
#include "content/browser/renderer_host/delegated_frame_host_client_android.h"
#include "content/browser/renderer_host/input/synthetic_gesture_target_android.h"
#include "content/browser/renderer_host/input/touch_selection_controller_client_manager_android.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/renderer_host/render_process_host_impl.h"
#include "content/browser/renderer_host/render_view_host_delegate_view.h"
#include "content/browser/renderer_host/render_view_host_impl.h"
#include "content/browser/renderer_host/render_widget_host_impl.h"
#include "content/browser/renderer_host/visible_time_request_trigger.h"
#include "content/browser/screen_orientation/screen_orientation_provider.h"
#include "content/common/content_switches_internal.h"
#include "content/common/features.h"
#include "content/common/input/events_helper.h"
#include "content/public/browser/android/compositor.h"
#include "content/public/browser/android/synchronous_compositor_client.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/devtools_agent_host.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_widget_host_iterator.h"
#include "content/public/common/content_client.h"
#include "content/public/common/content_switches.h"
#include "third_party/blink/public/mojom/input/input_handler.mojom-blink.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkColor.h"
#include "third_party/skia/include/core/SkImageInfo.h"
#include "ui/android/view_android_observer.h"
#include "ui/android/window_android.h"
#include "ui/android/window_android_compositor.h"
#include "ui/base/cursor/cursor.h"
#include "ui/base/ui_base_types.h"
#include "ui/display/display_util.h"
#include "ui/events/android/gesture_event_android.h"
#include "ui/events/android/gesture_event_type.h"
#include "ui/events/android/motion_event_android.h"
#include "ui/events/blink/blink_event_util.h"
#include "ui/events/blink/blink_features.h"
#include "ui/events/blink/did_overscroll_params.h"
#include "ui/events/blink/web_input_event_traits.h"
#include "ui/events/event_utils.h"
#include "ui/events/gesture_detection/gesture_provider_config_helper.h"
#include "ui/gfx/android/view_configuration.h"
#include "ui/gfx/codec/jpeg_codec.h"
#include "ui/gfx/geometry/dip_util.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/gfx/geometry/rect_conversions.h"
#include "ui/gfx/geometry/size_conversions.h"
#include "ui/touch_selection/selection_event_type.h"
#include "ui/touch_selection/touch_selection_controller.h"
// Must come after all headers that specialize FromJniType() / ToJniType().
#include "content/public/android/content_jni_headers/RenderWidgetHostViewImpl_jni.h"
namespace content {
namespace {
static const base::TimeDelta kClickCountInterval = base::Seconds(0.5);
static const float kClickCountRadiusSquaredDIP = 25;
static const base::TimeDelta kThrottleTimeout = base::Milliseconds(200);
std::unique_ptr<ui::TouchSelectionController> CreateSelectionController(
ui::TouchSelectionControllerClient* client,
bool has_view_tree) {
DCHECK(client);
DCHECK(has_view_tree);
ui::TouchSelectionController::Config config;
config.max_tap_duration =
base::Milliseconds(gfx::ViewConfiguration::GetLongPressTimeoutInMs());
config.tap_slop = gfx::ViewConfiguration::GetTouchSlopInDips();
config.enable_adaptive_handle_orientation =
base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kEnableAdaptiveSelectionHandleOrientation);
config.enable_longpress_drag_selection =
base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kEnableLongpressDragSelection);
config.hide_active_handle =
base::android::BuildInfo::GetInstance()->sdk_int() >=
base::android::SDK_VERSION_P;
return std::make_unique<ui::TouchSelectionController>(client, config);
}
gfx::RectF GetSelectionRect(const ui::TouchSelectionController& controller) {
// When the touch handles are on the same line, the rect may become simply a
// one-dimensional rect, and still need to union the handle rect to avoid the
// context menu covering the touch handle. See detailed comments in
// TouchSelectionController::GetRectBetweenBounds(). Ensure that the |rect| is
// not empty by adding a pixel width or height to avoid the wrong menu
// position.
gfx::RectF rect = controller.GetVisibleRectBetweenBounds();
if (rect.IsEmpty()) {
gfx::SizeF size = rect.size();
size.SetToMax(gfx::SizeF(1.0f, 1.0f));
rect.set_size(size);
}
rect.Union(controller.GetStartHandleRect());
rect.Union(controller.GetEndHandleRect());
return rect;
}
void RecordToolTypeForActionDown(const ui::MotionEventAndroid& event) {
ui::MotionEventAndroid::Action action = event.GetAction();
if (action == ui::MotionEventAndroid::Action::DOWN ||
action == ui::MotionEventAndroid::Action::POINTER_DOWN ||
action == ui::MotionEventAndroid::Action::BUTTON_PRESS) {
UMA_HISTOGRAM_ENUMERATION(
"Event.AndroidActionDown.ToolType",
static_cast<int>(event.GetToolType(0)),
static_cast<int>(ui::MotionEventAndroid::ToolType::LAST) + 1);
}
}
void WakeUpGpu(GpuProcessHost* host) {
if (host)
host->gpu_service()->WakeUpGpu();
}
std::string CompressAndSaveBitmap(const std::string& dir,
const SkBitmap& bitmap) {
base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
base::BlockingType::WILL_BLOCK);
std::vector<unsigned char> data;
if (!gfx::JPEGCodec::Encode(bitmap, 85, &data)) {
LOG(ERROR) << "Failed to encode bitmap to JPEG";
return std::string();
}
base::FilePath screenshot_dir(dir);
if (!base::DirectoryExists(screenshot_dir)) {
if (!base::CreateDirectory(screenshot_dir)) {
LOG(ERROR) << "Failed to create screenshot directory";
return std::string();
}
}
base::FilePath screenshot_path;
base::ScopedFILE out_file(base::CreateAndOpenTemporaryStreamInDir(
screenshot_dir, &screenshot_path));
if (!out_file) {
LOG(ERROR) << "Failed to create temporary screenshot file";
return std::string();
}
unsigned int bytes_written =
fwrite(reinterpret_cast<const char*>(data.data()), 1, data.size(),
out_file.get());
out_file.reset(); // Explicitly close before a possible Delete below.
// If there were errors, don't leave a partial file around.
if (bytes_written != data.size()) {
base::DeleteFile(screenshot_path);
LOG(ERROR) << "Error writing screenshot file to disk";
return std::string();
}
return screenshot_path.value();
}
blink::mojom::RecordContentToVisibleTimeRequestPtr
TakeContentToVisibleTimeRequest(RenderWidgetHostImpl* host) {
return host->GetVisibleTimeRequestTrigger().TakeRequest();
}
} // namespace
// static
RenderWidgetHostViewAndroid*
RenderWidgetHostViewAndroid::FromRenderWidgetHostView(
RenderWidgetHostView* view) {
if (!view || static_cast<RenderWidgetHostViewBase*>(view)
->IsRenderWidgetHostViewChildFrame()) {
return nullptr;
}
return static_cast<RenderWidgetHostViewAndroid*>(view);
}
RenderWidgetHostViewAndroid::ScreenStateChangeHandler::ScreenStateChangeHandler(
RenderWidgetHostViewAndroid* rwhva)
: rwhva_(rwhva) {}
bool RenderWidgetHostViewAndroid::ScreenStateChangeHandler::
CanSynchronizeVisualProperties() const {
if (pending_screen_state_.is_fullscreen &&
!pending_screen_state_.any_non_rotation_size_changed) {
return false;
}
return true;
}
void RenderWidgetHostViewAndroid::ScreenStateChangeHandler::
OnVisibleViewportSizeChanged(const gfx::Size& visible_viewport_size) {
// RendereWidgetHostImpl::SendScreenRects will send updated sizes to the
// Renderer without waiting for SurfaceSync. In some fullscreen transitions
// we receive neither OnPhysicalBackingChanged nor
// OnSynchronizedDisplayPropertiesChanged. In those cases verify the new
// screen state and cause a SurfaceSync so that the Renderer does not attempt
// to submit new sizes to an old viz::LocalSurfaceId.
pending_screen_state_.visible_viewport_size = visible_viewport_size;
HandleScreenStateChanges(cc::DeadlinePolicy::UseDefaultDeadline());
}
bool RenderWidgetHostViewAndroid::ScreenStateChangeHandler::
OnPhysicalBackingSizeChanged(const gfx::Size& physical_backing_size,
int64_t deadline_in_frames) {
// A fullscreen rotation can include a partial change in height for the
// initial top-controls layout. Before the full layout arrives in a second
// OnPhysicalBackingSizeChanged later.
pending_screen_state_.physical_backing_size = physical_backing_size;
return HandleScreenStateChanges(
cc::DeadlinePolicy::UseSpecifiedDeadline(deadline_in_frames));
}
bool RenderWidgetHostViewAndroid::ScreenStateChangeHandler::OnScreenInfoChanged(
const display::ScreenInfo& screen_info) {
// TODO(crbug.com/13801170): Once the legacy Killswitch path has been
// removed we should consider performing no SurfaceSync while hidden. For
// example multiple conflicting ScreenInfo.rect changes can occur while
// hidden and the Renderer is doing redundant work.
pending_screen_state_.screen_info_size = screen_info.rect.size();
pending_screen_state_.orientation_type = screen_info.orientation_type;
return HandleScreenStateChanges(cc::DeadlinePolicy::UseDefaultDeadline());
}
void RenderWidgetHostViewAndroid::ScreenStateChangeHandler::
EnterFullscreenMode() {
BeginScreenStateChange();
pending_screen_state_.is_fullscreen = true;
HandleScreenStateChanges(cc::DeadlinePolicy::UseDefaultDeadline());
if (throttle_timeout_.IsRunning())
throttle_timeout_.Stop();
throttle_timeout_.Start(
FROM_HERE, kThrottleTimeout,
base::BindOnce(
&RenderWidgetHostViewAndroid::ScreenStateChangeHandler::Unthrottle,
base::Unretained(this)));
}
void RenderWidgetHostViewAndroid::ScreenStateChangeHandler::
ExitFullscreenMode() {
// There is no guarantee that there will be any updates to visual properties
// when exiting fullscreen. So we currently cannot throttle.
// In some rare cases, when we exit fullscreen there is only the update to
// `visible_viewport_rect`. Such as when we are in Fullscreen Landscape, and
// are exiting to Landscape, but with a large enough scroll offset to have
// no top-chrome visible.
// When in split view, there are no changes to visual properties when exiting
// fullscreen mode. Even when there are changes upon entering.
// (crbug.com/1378754)
BeginScreenStateChange();
pending_screen_state_.is_fullscreen = false;
HandleScreenStateChanges(cc::DeadlinePolicy::UseDefaultDeadline());
}
void RenderWidgetHostViewAndroid::ScreenStateChangeHandler::LockOrientation(
device::mojom::ScreenOrientationLockType orientation) {
// Orientation Lock is only supported during fullscreen.
pending_screen_state_.is_expecting_fullscreen_rotation =
!ScreenOrientationProvider::LockMatchesOrientation(
orientation, rwhva_->GetScreenInfo().orientation_type);
HandleScreenStateChanges(cc::DeadlinePolicy::UseDefaultDeadline());
}
void RenderWidgetHostViewAndroid::ScreenStateChangeHandler::
UnlockOrientation() {
BeginScreenStateChange();
pending_screen_state_.is_expecting_fullscreen_rotation = false;
pending_screen_state_.has_unlocked_orientation_lock = true;
// The notification to unlock can occur after the first portion of a rotation
// has begun. We are no longer guaranteed to receive the remainder of that
// rotation, in fact a new one may begin. We stop throttling in these cases
// and sync immediately.
HandleScreenStateChanges(cc::DeadlinePolicy::UseDefaultDeadline());
}
void RenderWidgetHostViewAndroid::ScreenStateChangeHandler::
SetHasPersistentVideo(bool has_persistent_video) {
bool is_fullscreen = current_screen_state_.is_fullscreen;
// Picture-in-Picture requires fullscreen, and stays in fullscreen.
if (has_persistent_video)
pre_picture_in_picture_ = current_screen_state_;
else
is_fullscreen = pending_screen_state_.is_fullscreen;
BeginScreenStateChange();
pending_screen_state_.is_picture_in_picture = has_persistent_video;
pending_screen_state_.is_fullscreen = is_fullscreen;
// TODO(crbug.com/40872802): We should try to re-establish throttling for
// Picture-in-Picture mode. Will need better determination of when we have
// completed entering/exiting.
pending_screen_state_.any_non_rotation_size_changed = true;
HandleScreenStateChanges(cc::DeadlinePolicy::UseDefaultDeadline());
}
void RenderWidgetHostViewAndroid::ScreenStateChangeHandler::WasEvicted() {
// Reset the world upon eviction. We will re-esatblish the world when we next
// become visible and begin embedding content again. This should not call
// HandleScreenStateChanges, as we explicitly to not want to do any syncing
// when we are evicted.
BeginScreenStateChange();
}
void RenderWidgetHostViewAndroid::ScreenStateChangeHandler::
WasShownAfterEviction() {
// The screen state can change while we were evicted. Reset the world for
// future changes.
BeginScreenStateChange();
HandleScreenStateChanges(cc::DeadlinePolicy::UseDefaultDeadline());
}
void RenderWidgetHostViewAndroid::ScreenStateChangeHandler::
BeginScreenStateChange() {
current_screen_state_.visible_viewport_size = rwhva_->view_.GetSize();
current_screen_state_.physical_backing_size =
rwhva_->view_.GetPhysicalBackingSize();
auto screen_info = rwhva_->GetScreenInfo();
current_screen_state_.screen_info_size = screen_info.rect.size();
current_screen_state_.orientation_type = screen_info.orientation_type;
current_screen_state_.local_surface_id =
rwhva_->local_surface_id_allocator_.GetCurrentLocalSurfaceId();
pending_screen_state_ = ScreenState();
}
bool RenderWidgetHostViewAndroid::ScreenStateChangeHandler::
HandleScreenStateChanges(const cc::DeadlinePolicy& deadline_policy,
bool force_fullscreen_sync) {
bool sync_needed =
force_fullscreen_sync && pending_screen_state_.is_fullscreen !=
current_screen_state_.is_fullscreen;
bool start_rotation = false;
bool end_rotation = false;
bool exiting_pip = false;
// When `visible_viewport_size` change is a non-rotation it may be inset
// changes for System UI, or scaling changes for Picture-in-Picture. Stop
// throttling fullscreen transitions now, as we cannot be certain if there
// will be any subsequent updates.
if (!pending_screen_state_.visible_viewport_size.IsEmpty() &&
!ScreenState::IsRotation(current_screen_state_.visible_viewport_size,
pending_screen_state_.visible_viewport_size)) {
pending_screen_state_.any_non_rotation_size_changed = true;
}
// TODO(crbug.com/40242839): We need a pre-Android S detection of
// Picture-in-Picture mode. The `visible_viewport_size` and
// `physical_backing_size` will be shrunk, though it is not guaranteed to be
// simply a scale from the fullscreen size. As sometimes inset changes are
// also applied.
//
// TODO(crbug.com/40872802): We should try to re-establish throttling for
// Picture-in-Picture mode. Will need better determination of when we have
// completed entering/exiting.
if (pending_screen_state_.is_picture_in_picture) {
if (rwhva_->in_rotation_)
end_rotation = true;
else
sync_needed = true;
} else if (pre_picture_in_picture_.IsValid()) {
if (rwhva_->in_rotation_)
end_rotation = true;
else
sync_needed = true;
exiting_pip = true;
} else if (pending_screen_state_.has_unlocked_orientation_lock &&
rwhva_->in_rotation_) {
end_rotation = true;
} else if (!pending_screen_state_.is_fullscreen &&
current_screen_state_.is_fullscreen) {
// PWA and WebView may be created as Fullscreen, without marking the
// WebContents as Fullscreen. In this state the Renderer can still request
// to toggle Fullscreen, which enables the ScreenOrientation APIs. However
// there will be no layout changes occuring.
//
// To account for this trigger a sync now to release the JavaScript Promise,
// and to update our `current_screen_state_`.
sync_needed = true;
} else {
bool physical_backing_rotation = false;
bool screen_info_rotation = false;
if (!pending_screen_state_.physical_backing_size.IsEmpty()) {
// When transitioning to a split view, the physical backing will be
// resized along one single axis. The resize can be significant enough to
// be rotation, however there will be no subsequent rotation of the
// ScreenInfo. So do not treat it as such.
physical_backing_rotation =
ScreenState::IsRotation(
current_screen_state_.physical_backing_size,
pending_screen_state_.physical_backing_size) &&
!ScreenState::IsSingleAxisResize(
current_screen_state_.physical_backing_size,
pending_screen_state_.physical_backing_size);
if (!physical_backing_rotation) {
// Inset changes for System UI, or scaling changes for
// Picture-in-Picture mode.
pending_screen_state_.any_non_rotation_size_changed = true;
// If we are expecting a rotation, start rotation throttle now anyways.
// Otherwise we have no way to know if the transition will ever lead to
// a rotation so just sync.
if (pending_screen_state_.is_expecting_fullscreen_rotation) {
start_rotation = true;
} else if (rwhva_->in_rotation_) {
// TODO(crbug.com/40244577): The legacy killswitch path, combined with
// the legacy kOnShowWithPageVisibility path make it difficult to
// refactor the hidden rotation handling. Once we clear those we
// should consider no SurfaceSync while hidden. Then synchronizing the
// entire world upon OnShowWithPageVisibility. For now detect being
// left in a rotation throttle and ending it here.
end_rotation = true;
} else {
sync_needed = true;
}
}
}
if (!pending_screen_state_.screen_info_size.IsEmpty()) {
screen_info_rotation = ScreenState::ExpectsResizeForOrientationChange(
current_screen_state_.orientation_type,
pending_screen_state_.orientation_type);
if (!screen_info_rotation) {
pending_screen_state_.any_non_rotation_size_changed = true;
// This can occur when there is a "rotation" from a primary to a
// secondary variant of the `orientation_type`. Such as Portrait-Primary
// to Portrait-Secondary. When this occurs we don't need to force a
// sync, just update the `current_screen_state_` to be ready for any
// future comparisons.
if (pending_screen_state_.screen_info_size ==
current_screen_state_.screen_info_size) {
current_screen_state_.orientation_type =
pending_screen_state_.orientation_type;
pending_screen_state_.screen_info_size = gfx::Size();
pending_screen_state_.orientation_type =
display::mojom::ScreenOrientation::kUndefined;
pending_screen_state_.on_sync_display_properties_changed_received =
false;
// If we are expecting a rotation, start rotation throttle now
// anyways.
if (pending_screen_state_.is_expecting_fullscreen_rotation)
start_rotation = true;
}
}
}
if (physical_backing_rotation && screen_info_rotation) {
end_rotation = true;
pending_screen_state_.any_non_rotation_size_changed = true;
} else if (physical_backing_rotation &&
pending_screen_state_.screen_info_size.IsEmpty() &&
!pending_screen_state_.on_physical_backing_changed_received) {
// There can be repeated changes to `visible_viewport_rect` in-between the
// physical backing and screen info updates. So only process the pending
// state once.
pending_screen_state_.on_physical_backing_changed_received = true;
if (ScreenState::IsRotation(pending_screen_state_.physical_backing_size,
current_screen_state_.screen_info_size)) {
start_rotation = true;
} else {
// When transitioning from mixed orientation states, such as Landscape
// Video in a Portrait Picture-in-Picture screen, we confirm the new
// transition matches the current screen info.
sync_needed = true;
}
} else if (screen_info_rotation &&
pending_screen_state_.physical_backing_size.IsEmpty() &&
!pending_screen_state_
.on_sync_display_properties_changed_received) {
// There can be repeated changes to `visible_viewport_rect` in-between the
// physical backing and screen info updates. So only process the pending
// state once.
pending_screen_state_.on_sync_display_properties_changed_received = true;
// ScreenInfo explicitly lists an orientation, we always start a rotation
// when requested. It is possible in split-screen for
// `physical_backing_size` to become in a mixed orientation states, so we
// do not compare to them.
start_rotation = true;
}
}
if (!start_rotation && !end_rotation && !sync_needed)
return false;
if (start_rotation) {
rwhva_->BeginRotationBatching();
return true;
} else if (end_rotation) {
// The rotation timeout is intended to catch edge-cases where Android::View
// code does not give us some aspects of re-layouts. However on slower
// devices the timeout may fire before the final signals arrive. In these
// cases call BeginRotationBatching to properly enqueue the rotation, before
// immediately embedding the new content.
if (!rwhva_->in_rotation_)
rwhva_->BeginRotationBatching();
rwhva_->EndRotationAndSyncIfNecessary();
} else if (sync_needed) {
// If any sync is recorded, disable the fullscreen throttling.
if (pending_screen_state_.is_fullscreen) {
pending_screen_state_.any_non_rotation_size_changed = true;
}
rwhva_->SynchronizeVisualProperties(
deadline_policy, std::nullopt,
/*reuse_current_local_surface_id=*/false,
/*ignore_ack=*/true);
}
current_screen_state_.CopyDefinedAttributes(pending_screen_state_);
current_screen_state_.local_surface_id =
rwhva_->local_surface_id_allocator_.GetCurrentLocalSurfaceId();
pending_screen_state_ = ScreenState();
pending_screen_state_.is_fullscreen = current_screen_state_.is_fullscreen;
pending_screen_state_.is_picture_in_picture =
current_screen_state_.is_picture_in_picture;
pending_screen_state_.any_non_rotation_size_changed =
current_screen_state_.any_non_rotation_size_changed;
// When exiting Picture-in-Picture mode, we can sometimes return to the same
// state. We can sometimes be in the same orientation, but the insets have
// changes. Or we can be in a rotation of the original state. Each is valid
// and a signal we are done the transition.
if (exiting_pip &&
((pre_picture_in_picture_.EqualVisualProperties(current_screen_state_)) ||
(pre_picture_in_picture_.IsValid() && current_screen_state_.IsValid() &&
(pre_picture_in_picture_.EqualOrientations(current_screen_state_) ||
pre_picture_in_picture_.IsRotated(current_screen_state_))))) {
pre_picture_in_picture_ = ScreenState();
}
return true;
}
void RenderWidgetHostViewAndroid::ScreenStateChangeHandler::Unthrottle() {
pending_screen_state_.any_non_rotation_size_changed = true;
HandleScreenStateChanges(cc::DeadlinePolicy::UseDefaultDeadline(),
true /* force_fullscreen_sync */);
}
RenderWidgetHostViewAndroid::RenderWidgetHostViewAndroid(
RenderWidgetHostImpl* widget_host,
gfx::NativeView parent_native_view,
cc::slim::Layer* parent_layer)
: RenderWidgetHostViewBase(widget_host),
is_showing_(!widget_host->is_hidden()),
is_window_visible_(true),
is_window_activity_started_(true),
ime_adapter_android_(nullptr),
selection_popup_controller_(nullptr),
text_suggestion_host_(nullptr),
gesture_listener_manager_(nullptr),
view_(ui::ViewAndroid::LayoutType::MATCH_PARENT),
gesture_provider_(
ui::GetGestureProviderConfig(
ui::GestureProviderConfigType::CURRENT_PLATFORM,
GetUIThreadTaskRunner({BrowserTaskType::kUserInput})),
this),
stylus_text_selector_(this),
using_browser_compositor_(CompositorImpl::IsInitialized()),
synchronous_compositor_client_(nullptr),
observing_root_window_(false),
prev_top_shown_pix_(0.f),
prev_top_controls_pix_(0.f),
prev_top_controls_translate_(0.f),
prev_top_controls_min_height_offset_pix_(0.f),
prev_bottom_shown_pix_(0.f),
prev_bottom_controls_translate_(0.f),
prev_bottom_controls_min_height_offset_pix_(0.f),
page_scale_(1.f),
min_page_scale_(1.f),
max_page_scale_(1.f),
mouse_wheel_phase_handler_(this),
screen_state_change_handler_(this) {
// Set the layer which will hold the content layer for this view. The content
// layer is managed by the DelegatedFrameHost.
view_.SetLayer(cc::slim::Layer::Create());
view_.set_event_handler(this);
// If we're showing at creation time, we won't get a visibility change, so
// generate our initial LocalSurfaceId here.
if (is_showing_)
local_surface_id_allocator_.GenerateId();
delegated_frame_host_client_ =
std::make_unique<DelegatedFrameHostClientAndroid>(this);
delegated_frame_host_ = std::make_unique<ui::DelegatedFrameHostAndroid>(
&view_, GetHostFrameSinkManager(), delegated_frame_host_client_.get(),
host()->GetFrameSinkId());
if (is_showing_) {
delegated_frame_host_->WasShown(
local_surface_id_allocator_.GetCurrentLocalSurfaceId(),
GetCompositorViewportPixelSize(), host()->delegate()->IsFullscreen(),
TakeContentToVisibleTimeRequest(host()));
}
host()->SetView(this);
touch_selection_controller_client_manager_ =
std::make_unique<TouchSelectionControllerClientManagerAndroid>(this);
// `parent_native_view` and `parent_layer` must be null or non-null at the
// same time.
CHECK(!(!!parent_native_view ^ !!parent_layer));
UpdateNativeViewTree(parent_native_view, parent_layer);
// This RWHVA may have been created speculatively. We should give any
// existing RWHVAs priority for receiving input events, otherwise a
// speculative RWHVA could be sent input events intended for the currently
// showing RWHVA.
if (parent_native_view) {
parent_native_view->MoveToBack(&view_);
}
CreateOverscrollControllerIfPossible();
if (GetTextInputManager())
GetTextInputManager()->AddObserver(this);
host()->render_frame_metadata_provider()->AddObserver(this);
}
RenderWidgetHostViewAndroid::~RenderWidgetHostViewAndroid() {
UpdateNativeViewTree(/*parent_native_view=*/nullptr,
/*parent_layer=*/nullptr);
view_.set_event_handler(nullptr);
DCHECK(!ime_adapter_android_);
DCHECK(!delegated_frame_host_);
if (obj_) {
Java_RenderWidgetHostViewImpl_clearNativePtr(
base::android::AttachCurrentThread(), obj_);
obj_.Reset();
}
}
void RenderWidgetHostViewAndroid::AddDestructionObserver(
DestructionObserver* observer) {
destruction_observers_.AddObserver(observer);
}
void RenderWidgetHostViewAndroid::RemoveDestructionObserver(
DestructionObserver* observer) {
destruction_observers_.RemoveObserver(observer);
}
base::CallbackListSubscription
RenderWidgetHostViewAndroid::SubscribeToSurfaceIdChanges(
const SurfaceIdChangedCallback& callback) {
return surface_id_changed_callbacks_.Add(callback);
}
void RenderWidgetHostViewAndroid::OnSurfaceIdChanged() {
surface_id_changed_callbacks_.Notify(GetCurrentSurfaceId());
if (selection_popup_controller_) {
selection_popup_controller_->ChildLocalSurfaceIdChanged();
}
}
void RenderWidgetHostViewAndroid::InitAsChild(gfx::NativeView parent_view) {
NOTIMPLEMENTED();
}
void RenderWidgetHostViewAndroid::InitAsPopup(
RenderWidgetHostView* parent_host_view,
const gfx::Rect& pos,
const gfx::Rect& anchor_rect) {
NOTIMPLEMENTED();
}
void RenderWidgetHostViewAndroid::NotifyVirtualKeyboardOverlayRect(
const gfx::Rect& keyboard_rect) {
RenderFrameHostImpl* frame_host = host()->frame_tree()->GetMainFrame();
if (GetVirtualKeyboardMode() !=
ui::mojom::VirtualKeyboardMode::kOverlaysContent) {
return;
}
gfx::Rect keyboard_rect_with_scale;
if (!keyboard_rect.IsEmpty()) {
// This is necessary because the receiver of this rect in the renderer
// expects the rect to be in device-independnet pixels, but |keyboard_rect|
// is in device pixels. See
// LocalFrameMojoHandler::NotifyVirtualKeyboardOverlayRect.
// To trigger this code, follow the steps in
// .../external/wpt/virtual-keyboard/virtual-keyboard-css-env-manual.html
float scale = 1 / view_.GetDipScale();
keyboard_rect_with_scale = ScaleToEnclosedRect(keyboard_rect, scale);
// Intersect the keyboard rect with the `this` bounds which will be sent
// to the renderer.
keyboard_rect_with_scale.Intersect(GetViewBounds());
}
frame_host->GetPage().NotifyVirtualKeyboardOverlayRect(
keyboard_rect_with_scale);
}
ui::mojom::VirtualKeyboardMode
RenderWidgetHostViewAndroid::GetVirtualKeyboardMode() {
RenderFrameHostImpl* frame_host = host()->frame_tree()->GetMainFrame();
if (!frame_host)
return ui::mojom::VirtualKeyboardMode::kUnset;
return frame_host->GetPage().virtual_keyboard_mode();
}
viz::SurfaceId RenderWidgetHostViewAndroid::GetFallbackSurfaceIdForTesting()
const {
return delegated_frame_host_->GetFallbackSurfaceIdForTesting(); // IN-TEST
}
bool RenderWidgetHostViewAndroid::SynchronizeVisualProperties(
const cc::DeadlinePolicy& deadline_policy,
const std::optional<viz::LocalSurfaceId>& child_local_surface_id,
bool reuse_current_local_surface_id,
bool ignore_ack) {
// Always merge the child_id, even if we cannot sync at this time.
if (child_local_surface_id)
local_surface_id_allocator_.UpdateFromChild(*child_local_surface_id);
if (!CanSynchronizeVisualProperties())
return false;
if (!child_local_surface_id && !reuse_current_local_surface_id)
local_surface_id_allocator_.GenerateId();
// If we still have an invalid viz::LocalSurfaceId, then we are hidden and
// evicted. This will have been triggered by a child acknowledging a previous
// synchronization message via DidUpdateVisualProperties. The child has not
// prompted any further property changes, so we do not need to continue
// syncrhonization. Nor do we want to embed an invalid surface.
if (!local_surface_id_allocator_.HasValidLocalSurfaceId())
return false;
if (delegated_frame_host_) {
delegated_frame_host_->EmbedSurface(
local_surface_id_allocator_.GetCurrentLocalSurfaceId(),
GetCompositorViewportPixelSize(), deadline_policy,
host()->delegate()->IsFullscreen());
}
if (ignore_ack) {
return host()->SynchronizeVisualPropertiesIgnoringPendingAck();
}
return host()->SynchronizeVisualProperties();
}
void RenderWidgetHostViewAndroid::SetSize(const gfx::Size& size) {
// Ignore the given size as only the Java code has the power to
// resize the view on Android.
default_bounds_ = gfx::Rect(default_bounds_.origin(), size);
}
void RenderWidgetHostViewAndroid::SetBounds(const gfx::Rect& rect) {
default_bounds_ = rect;
}
bool RenderWidgetHostViewAndroid::HasValidFrame() const {
if (!delegated_frame_host_)
return false;
if (!view_.parent())
return false;
if (current_surface_size_.IsEmpty())
return false;
return delegated_frame_host_->HasSavedFrame();
}
gfx::NativeView RenderWidgetHostViewAndroid::GetNativeView() {
return &view_;
}
gfx::NativeViewAccessible
RenderWidgetHostViewAndroid::GetNativeViewAccessible() {
NOTIMPLEMENTED();
return NULL;
}
void RenderWidgetHostViewAndroid::GotFocus() {
host()->GotFocus();
OnFocusInternal();
}
void RenderWidgetHostViewAndroid::LostFocus() {
host()->LostFocus();
LostFocusInternal();
}
void RenderWidgetHostViewAndroid::OnRenderFrameMetadataChangedBeforeActivation(
const cc::RenderFrameMetadata& metadata) {
bool is_transparent = metadata.has_transparent_background;
if (!using_browser_compositor_) {
// Android WebView ignores transparent background.
is_transparent = false;
}
gesture_provider_.SetDoubleTapSupportForPageEnabled(
!metadata.is_mobile_optimized);
float dip_scale = view_.GetDipScale();
gfx::SizeF root_layer_size_dip = metadata.root_layer_size;
gfx::SizeF scrollable_viewport_size_dip = metadata.scrollable_viewport_size;
gfx::PointF root_scroll_offset_dip =
metadata.root_scroll_offset.value_or(gfx::PointF());
float pix_to_dip = 1 / dip_scale;
root_layer_size_dip.Scale(pix_to_dip);
scrollable_viewport_size_dip.Scale(pix_to_dip);
root_scroll_offset_dip.Scale(pix_to_dip);
// Note that the height of browser control is not affected by page scale
// factor. Thus, |top_content_offset| in CSS pixels is also in DIPs.
float top_content_offset =
metadata.top_controls_height * metadata.top_controls_shown_ratio;
float top_shown_pix = top_content_offset;
if (ime_adapter_android_) {
ime_adapter_android_->UpdateFrameInfo(metadata.selection.start, dip_scale,
top_shown_pix);
}
if (!gesture_listener_manager_)
return;
UpdateTouchSelectionController(metadata.selection, metadata.page_scale_factor,
metadata.top_controls_height,
metadata.top_controls_shown_ratio,
scrollable_viewport_size_dip);
// ViewAndroid::content_offset() must be in dip.
float top_content_offset_dip = top_content_offset / dip_scale;
view_.UpdateFrameInfo({scrollable_viewport_size_dip, top_content_offset_dip});
bool controls_changed = UpdateControls(
view_.GetDipScale(), metadata.top_controls_height,
metadata.top_controls_shown_ratio,
metadata.top_controls_min_height_offset, metadata.bottom_controls_height,
metadata.bottom_controls_shown_ratio,
metadata.bottom_controls_min_height_offset);
// TODO(crbug.com/40219248): Remove toSkColor and make all SkColor4f.
SetContentBackgroundColor(is_transparent
? SK_ColorTRANSPARENT
: metadata.root_background_color.toSkColor());
if (overscroll_controller_) {
overscroll_controller_->OnFrameMetadataUpdated(
metadata.page_scale_factor, metadata.device_scale_factor,
metadata.scrollable_viewport_size, metadata.root_layer_size,
metadata.root_scroll_offset.value_or(gfx::PointF()),
metadata.root_overflow_y_hidden);
}
// All offsets and sizes except |top_shown_pix| are in dip.
gesture_listener_manager_->UpdateScrollInfo(
root_scroll_offset_dip, metadata.page_scale_factor,
metadata.min_page_scale_factor, metadata.max_page_scale_factor,
root_layer_size_dip, scrollable_viewport_size_dip, top_content_offset_dip,
top_shown_pix, controls_changed);
// This needs to be called after GestureListenerManager::UpdateScrollInfo, as
// it depends on frame info being updated during the UpdateScrollInfo call.
auto* wcax = GetWebContentsAccessibilityAndroid();
if (wcax)
wcax->UpdateFrameInfo(metadata.page_scale_factor);
page_scale_ = metadata.page_scale_factor;
min_page_scale_ = metadata.min_page_scale_factor;
max_page_scale_ = metadata.max_page_scale_factor;
current_surface_size_ = metadata.viewport_size_in_pixels;
// With SurfaceSync we no longer call evict frame on every metadata change. We
// must still call UpdateWebViewBackgroundColorIfNecessary to maintain the
// associated background color changes.
UpdateWebViewBackgroundColorIfNecessary();
if (metadata.new_vertical_scroll_direction !=
viz::VerticalScrollDirection::kNull) {
bool can_scroll = metadata.root_layer_size.height() -
metadata.viewport_size_in_pixels.height() >
std::numeric_limits<float>::epsilon();
float scroll_ratio = 0.f;
if (can_scroll && metadata.root_scroll_offset) {
scroll_ratio = metadata.root_scroll_offset.value().y() /
(metadata.root_layer_size.height() -
metadata.viewport_size_in_pixels.height());
}
view_.OnVerticalScrollDirectionChanged(
metadata.new_vertical_scroll_direction ==
viz::VerticalScrollDirection::kUp,
scroll_ratio);
}
}
base::android::ScopedJavaLocalRef<jobject>
RenderWidgetHostViewAndroid::GetJavaObject() {
if (!obj_) {
JNIEnv* env = base::android::AttachCurrentThread();
obj_.Reset(env, Java_RenderWidgetHostViewImpl_create(
env, reinterpret_cast<intptr_t>(this))
.obj());
}
return base::android::ScopedJavaLocalRef<jobject>(obj_);
}
bool RenderWidgetHostViewAndroid::IsReady(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& obj) {
return HasValidFrame();
}
void RenderWidgetHostViewAndroid::DismissTextHandles(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& obj) {
DismissTextHandles();
}
jint RenderWidgetHostViewAndroid::GetBackgroundColor(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& obj) {
std::optional<SkColor> color =
RenderWidgetHostViewAndroid::GetCachedBackgroundColor();
if (!color)
return SK_ColorTRANSPARENT;
return *color;
}
void RenderWidgetHostViewAndroid::ShowContextMenuAtTouchHandle(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& obj,
jint x,
jint y) {
if (GetTouchSelectionControllerClientManager()) {
GetTouchSelectionControllerClientManager()->ShowContextMenu(
gfx::Point(x, y));
}
}
void RenderWidgetHostViewAndroid::OnViewportInsetBottomChanged(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& obj) {
SynchronizeVisualProperties(cc::DeadlinePolicy::UseDefaultDeadline(),
std::nullopt);
}
void RenderWidgetHostViewAndroid::WriteContentBitmapToDiskAsync(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& obj,
jint width,
jint height,
const base::android::JavaParamRef<jstring>& jpath,
const base::android::JavaParamRef<jobject>& jcallback) {
base::OnceCallback<void(const SkBitmap&)> result_callback = base::BindOnce(
&RenderWidgetHostViewAndroid::OnFinishGetContentBitmap,
weak_ptr_factory_.GetWeakPtr(),
base::android::ScopedJavaGlobalRef<jobject>(env, obj),
base::android::ScopedJavaGlobalRef<jobject>(env, jcallback),
base::android::ConvertJavaStringToUTF8(env, jpath));
CopyFromSurface(gfx::Rect(), gfx::Size(width, height),
std::move(result_callback));
}
void RenderWidgetHostViewAndroid::OnRenderFrameMetadataChangedAfterActivation(
base::TimeTicks activation_time) {
const cc::RenderFrameMetadata& metadata =
host()->render_frame_metadata_provider()->LastRenderFrameMetadata();
auto activated_local_surface_id =
metadata.local_surface_id.value_or(viz::LocalSurfaceId());
if (activated_local_surface_id.is_valid()) {
// We have received content, ensure that any subsequent navigation allocates
// a new surface.
pre_navigation_content_ = true;
while (!rotation_metrics_.empty()) {
auto rotation_target = rotation_metrics_.front();
// Activation from a previous surface before the new rotation has set a
// viz::LocalSurfaceId.
if (!rotation_target.second.is_valid())
break;
// In most cases the viz::LocalSurfaceId will be the same.
//
// However, if there are two cases where this does not occur.
//
// Firstly the Renderer may increment the |child_sequence_number| if it
// needs to also alter visual properties. If so the newer surface would
// denote the first visual update of the rotation. So its activation time
// is correct.
//
// Otherwise there may be two rotations in close proximity, and one takes
// too long to present. When this occurs the initial rotation does not
// display. This newer surface will be the first displayed. Use its
// activation time for the rotation, as the user would have been blocked
// on visual updates for that long.
//
// We want to know of these long tail rotation times.
if (activated_local_surface_id.IsSameOrNewerThan(
rotation_target.second)) {
// The duration for a rotation encompasses two separate spans of time,
// depending on whether or not we were `is_showing_` at the start of
// rotation.
//
// For a visible rotation `rotation_target.first` denotes the start of
// the rotation event handled in BeginRotationBatching.
//
// For a hidden rotation we ignore this initial event, as the Renderer
// can continue to be hidden for a long time. In these cases the
// `rotation_target.first` denotes when ShowInternal is called.
//
// From these, until `activation_time`, we can determine the length of
// time that the Renderer is visible, until the post rotation surface is
// first displayed.
auto duration = activation_time - rotation_target.first;
TRACE_EVENT_NESTABLE_ASYNC_END_WITH_TIMESTAMP1(
"viz", "RenderWidgetHostViewAndroid::RotationEmbed",
TRACE_ID_LOCAL(rotation_target.second.hash()), activation_time,
"duration(ms)", duration.InMillisecondsF());
rotation_metrics_.pop_front();
} else {
// The embedded surface may have updated the
// LocalSurfaceId::child_sequence_number while we were updating the
// parent_sequence_number for `rotation_target`. For example starting
// from (6, 2) the child advances to (6, 3), and the parent advances to
// (7, 2). viz::LocalSurfaceId::IsNewerThan will return false in these
// mixed sequence advancements.
//
// Subsequently we would merge the two into (7, 3) which will become the
// actually submitted surface to Viz.
//
// As such we have now received a surface that is not for our target, so
// we break here and await the next frame from the child.
break;
}
}
if (rotation_metrics_.empty())
in_rotation_ = false;
}
if (ime_adapter_android_) {
// We need to first wait for Blink's viewport size to change such that we
// can correctly scroll to the currently focused input.
// On Clank, only visible viewport size changes and device viewport size or
// viewport_size_in_pixels do not change according to the window/view size
/// change. Only scrollable viewport size changes both for Chrome and
// WebView.
ime_adapter_android_->OnRenderFrameMetadataChangedAfterActivation(
metadata.scrollable_viewport_size);
}
}
void RenderWidgetHostViewAndroid::OnRootScrollOffsetChanged(
const gfx::PointF& root_scroll_offset) {
if (!gesture_listener_manager_)
return;
gfx::PointF root_scroll_offset_dip = root_scroll_offset;
root_scroll_offset_dip.Scale(1 / view_.GetDipScale());
gesture_listener_manager_->OnRootScrollOffsetChanged(root_scroll_offset_dip);
}
void RenderWidgetHostViewAndroid::Focus() {
if (view_.HasFocus())
GotFocus();
else
view_.RequestFocus();
}
void RenderWidgetHostViewAndroid::OnFocusInternal() {
if (overscroll_controller_)
overscroll_controller_->Enable();
}
void RenderWidgetHostViewAndroid::LostFocusInternal() {
if (overscroll_controller_)
overscroll_controller_->Disable();
}
bool RenderWidgetHostViewAndroid::HasFocus() {
return view_.HasFocus();
}
bool RenderWidgetHostViewAndroid::IsSurfaceAvailableForCopy() {
return !using_browser_compositor_ ||
(delegated_frame_host_ &&
delegated_frame_host_->CanCopyFromCompositingSurface());
}
void RenderWidgetHostViewAndroid::ShowWithVisibility(
PageVisibilityState page_visibility) {
// We can transition from `PageVisibilityState::kHiddenButPainting` to
// `PageVisibilityState::kVisible` while `is_showing_`. We only want to
// support updating visibility requests for this transition.
if (page_visibility_ == page_visibility) {
return;
}
page_visibility_ = page_visibility;
is_showing_ = true;
ShowInternal();
}
void RenderWidgetHostViewAndroid::Hide() {
if (!is_showing_)
return;
page_visibility_ = PageVisibilityState::kHidden;
is_showing_ = false;
HideInternal();
}
bool RenderWidgetHostViewAndroid::IsShowing() {
// |view_.parent()| being NULL means that it is not attached
// to the View system yet, so we treat this RWHVA as hidden.
return is_showing_ && view_.parent();
}
void RenderWidgetHostViewAndroid::SelectAroundCaretAck(
int startOffset,
int endOffset,
int surroundingTextLength,
blink::mojom::SelectAroundCaretResultPtr result) {
if (!selection_popup_controller_)
return;
selection_popup_controller_->OnSelectAroundCaretAck(
startOffset, endOffset, surroundingTextLength, std::move(result));
}
gfx::Rect RenderWidgetHostViewAndroid::GetViewBounds() {
if (!view_.parent())
return default_bounds_;
gfx::Size size(view_.GetSize());
return gfx::Rect(size);
}
gfx::Size RenderWidgetHostViewAndroid::GetVisibleViewportSize() {
int pinned_bottom_adjust_dps =
std::max(0, (int)(view_.GetViewportInsetBottom() / view_.GetDipScale()));
gfx::Rect requested_rect(GetRequestedRendererSize());
requested_rect.Inset(gfx::Insets::TLBR(0, 0, pinned_bottom_adjust_dps, 0));
return requested_rect.size();
}
void RenderWidgetHostViewAndroid::SetInsets(const gfx::Insets& insets) {
NOTREACHED_IN_MIGRATION();
}
gfx::Size RenderWidgetHostViewAndroid::GetCompositorViewportPixelSize() {
if (!view_.parent()) {
if (default_bounds_.IsEmpty()) return gfx::Size();
float scale_factor = view_.GetDipScale();
return gfx::Size(default_bounds_.right() * scale_factor,
default_bounds_.bottom() * scale_factor);
}
return view_.GetPhysicalBackingSize();
}
int RenderWidgetHostViewAndroid::GetMouseWheelMinimumGranularity() const {
auto* window = view_.GetWindowAndroid();
if (!window)
return 0;
// On Android, mouse wheel MotionEvents specify the number of ticks and how
// many pixels each tick scrolls. This multiplier is specified by device
// metrics (See WindowAndroid.getMouseWheelScrollFactor) so the minimum
// granularity will be the size of this tick multiplier.
return window->mouse_wheel_scroll_factor() / view_.GetDipScale();
}
void RenderWidgetHostViewAndroid::UpdateCursor(const ui::Cursor& cursor) {
view_.OnCursorChanged(cursor);
}
void RenderWidgetHostViewAndroid::SetIsLoading(bool is_loading) {
// Do nothing. The UI notification is handled through ContentViewClient which
// is TabContentsDelegate.
}
// -----------------------------------------------------------------------------
// TextInputManager::Observer implementations.
void RenderWidgetHostViewAndroid::OnUpdateTextInputStateCalled(
TextInputManager* text_input_manager,
RenderWidgetHostViewBase* updated_view,
bool did_change_state) {
if (!ime_adapter_android_)
return;
DCHECK_EQ(text_input_manager_, text_input_manager);
if (GetTextInputManager()->GetActiveWidget()) {
ime_adapter_android_->UpdateState(
*GetTextInputManager()->GetTextInputState());
} else {
// If there are no active widgets, the TextInputState.type should be
// reported as none.
ime_adapter_android_->UpdateState(ui::mojom::TextInputState());
}
}
void RenderWidgetHostViewAndroid::OnImeCompositionRangeChanged(
TextInputManager* text_input_manager,
RenderWidgetHostViewBase* updated_view,
bool character_bounds_changed,
const std::optional<std::vector<gfx::Rect>>& line_bounds) {
DCHECK_EQ(text_input_manager_, text_input_manager);
// Don't pass data to Java if using the new pipeline.
if (!ime_adapter_android_ ||
base::FeatureList::IsEnabled(
blink::features::kCursorAnchorInfoMojoPipe)) {
return;
}
if (character_bounds_changed) {
const TextInputManager::CompositionRangeInfo* info =
text_input_manager_->GetCompositionRangeInfo();
ime_adapter_android_->SetBounds(
info ? info->character_bounds : std::vector<gfx::Rect>(),
character_bounds_changed, line_bounds);
return;
}
ime_adapter_android_->SetBounds(std::vector<gfx::Rect>(), false, line_bounds);
}
void RenderWidgetHostViewAndroid::OnImeCancelComposition(
TextInputManager* text_input_manager,
RenderWidgetHostViewBase* updated_view) {
DCHECK_EQ(text_input_manager_, text_input_manager);
if (ime_adapter_android_)
ime_adapter_android_->CancelComposition();
}
void RenderWidgetHostViewAndroid::OnTextSelectionChanged(
TextInputManager* text_input_manager,
RenderWidgetHostViewBase* updated_view) {
DCHECK_EQ(text_input_manager_, text_input_manager);
if (!selection_popup_controller_)
return;
RenderWidgetHostImpl* focused_widget = GetFocusedWidget();
if (!focused_widget || !focused_widget->GetView())
return;
const TextInputManager::TextSelection& selection =
*text_input_manager_->GetTextSelection(focused_widget->GetView());
selection_popup_controller_->OnSelectionChanged(
base::UTF16ToUTF8(selection.selected_text()));
}
viz::FrameSinkId RenderWidgetHostViewAndroid::GetRootFrameSinkId() {
if (sync_compositor_)
return sync_compositor_->GetFrameSinkId();
if (view_.GetWindowAndroid() && view_.GetWindowAndroid()->GetCompositor())
return view_.GetWindowAndroid()->GetCompositor()->GetFrameSinkId();
return viz::FrameSinkId();
}
viz::SurfaceId RenderWidgetHostViewAndroid::GetCurrentSurfaceId() const {
if (sync_compositor_)
return sync_compositor_->GetSurfaceId();
return delegated_frame_host_ ? delegated_frame_host_->SurfaceId()
: viz::SurfaceId();
}
bool RenderWidgetHostViewAndroid::TransformPointToCoordSpaceForView(
const gfx::PointF& point,
RenderWidgetHostViewInput* target_view,
gfx::PointF* transformed_point) {
if (target_view == this) {
*transformed_point = point;
return true;
}
viz::SurfaceId surface_id = GetCurrentSurfaceId();
if (!surface_id.is_valid())
return false;
// In TransformPointToLocalCoordSpace() there is a Point-to-Pixel conversion,
// but it is not necessary here because the final target view is responsible
// for converting before computing the final transform.
return target_view->TransformPointToLocalCoordSpace(point, surface_id,
transformed_point);
}
void RenderWidgetHostViewAndroid::SetGestureListenerManager(
GestureListenerManager* manager) {
gesture_listener_manager_ = manager;
UpdateRootScrollOffsetUpdateFrequency();
}
void RenderWidgetHostViewAndroid::UpdateRootScrollOffsetUpdateFrequency() {
if (!host())
return;
host()
->render_frame_metadata_provider()
->UpdateRootScrollOffsetUpdateFrequency(
RootScrollOffsetUpdateFrequency());
}
base::WeakPtr<RenderWidgetHostViewAndroid>
RenderWidgetHostViewAndroid::GetWeakPtrAndroid() {
return weak_ptr_factory_.GetWeakPtr();
}
bool RenderWidgetHostViewAndroid::OnGestureEvent(
const ui::GestureEventAndroid& event) {
std::unique_ptr<blink::WebGestureEvent> web_event;
if (event.scale() < 0.f) {
// Negative scale indicates zoom reset.
float delta = min_page_scale_ / page_scale_;
web_event = ui::CreateWebGestureEventFromGestureEventAndroid(
ui::GestureEventAndroid(event.type(), event.location(),
event.screen_location(), event.time(), delta, 0,
0, 0, 0, /*target_viewport*/ false,
/*synthetic_scroll*/ false,
/*prevent_boosting*/ false));
} else {
web_event = ui::CreateWebGestureEventFromGestureEventAndroid(event);
}
if (!web_event)
return false;
SendGestureEvent(*web_event);
return true;
}
bool RenderWidgetHostViewAndroid::OnTouchEvent(
const ui::MotionEventAndroid& event) {
RecordToolTypeForActionDown(event);
if (event.GetAction() == ui::MotionEventAndroid::Action::DOWN) {
if (base::FeatureList::IsEnabled(
features::kFocusRenderWidgetHostViewAndroidOnActionDown) &&
!HasFocus()) {
// On Android, |this| class should always be focused even when a
// ChildFrame is handling touch.
// TODO(b/340824076): Adding Focus call on ActionDown is a workaround to
// this problem. This line should be removed after this bug is fixed.
Focus();
}
if (ime_adapter_android_)
ime_adapter_android_->UpdateOnTouchDown();
if (gesture_listener_manager_)
gesture_listener_manager_->UpdateOnTouchDown();
}
if (event.for_touch_handle())
return OnTouchHandleEvent(event);
if (!host() || !host()->delegate())
return false;
ComputeEventLatencyOSTouchHistograms(event);
// Receiving any other touch event before the double-tap timeout expires
// cancels opening the spellcheck menu.
if (text_suggestion_host_)
text_suggestion_host_->StopSuggestionMenuTimer();
// If a browser-based widget consumes the touch event, it's critical that
// touch event interception be disabled. This avoids issues with
// double-handling for embedder-detected gestures like side swipe.
if (OnTouchHandleEvent(event)) {
RequestDisallowInterceptTouchEvent();
return true;
}
if (stylus_text_selector_.OnTouchEvent(event)) {
RequestDisallowInterceptTouchEvent();
return true;
}
ui::FilteredGestureProvider::TouchHandlingResult result =
gesture_provider_.OnTouchEvent(event);
if (!result.succeeded)
return false;
blink::WebTouchEvent web_event = ui::CreateWebTouchEventFromMotionEvent(
event, result.moved_beyond_slop_region /* may_cause_scrolling */,
false /* hovering */);
if (web_event.GetType() == blink::WebInputEvent::Type::kUndefined)
return false;
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 {
host()->GetRenderInputRouter()->ForwardTouchEventWithLatencyInfo(
web_event, latency_info);
}
// Send a proactive BeginFrame for this vsync to reduce scroll latency for
// scroll-inducing touch events. Note that Android's Choreographer ensures
// that BeginFrame requests made during Action::MOVE dispatch will be honored
// in the same vsync phase.
if (observing_root_window_ && result.moved_beyond_slop_region) {
if (sync_compositor_)
sync_compositor_->RequestOneBeginFrame();
}
return true;
}
bool RenderWidgetHostViewAndroid::OnTouchHandleEvent(
const ui::MotionEvent& event) {
return touch_selection_controller_ &&
touch_selection_controller_->WillHandleTouchEvent(event);
}
int RenderWidgetHostViewAndroid::GetTouchHandleHeight() {
if (!touch_selection_controller_)
return 0;
return static_cast<int>(touch_selection_controller_->GetTouchHandleHeight());
}
void RenderWidgetHostViewAndroid::ResetGestureDetection() {
const ui::MotionEvent* current_down_event =
gesture_provider_.GetCurrentDownEvent();
if (!current_down_event) {
// A hard reset ensures prevention of any timer-based events that might fire
// after a touch sequence has ended.
gesture_provider_.ResetDetection();
return;
}
std::unique_ptr<ui::MotionEvent> cancel_event = current_down_event->Cancel();
if (gesture_provider_.OnTouchEvent(*cancel_event).succeeded) {
bool causes_scrolling = false;
ui::LatencyInfo latency_info;
latency_info.AddLatencyNumber(ui::INPUT_EVENT_LATENCY_UI_COMPONENT);
blink::WebTouchEvent web_event = ui::CreateWebTouchEventFromMotionEvent(
*cancel_event, causes_scrolling /* may_cause_scrolling */,
false /* hovering */);
if (ShouldRouteEvents()) {
host()->delegate()->GetInputEventRouter()->RouteTouchEvent(
this, &web_event, latency_info);
} else {
host()->GetRenderInputRouter()->ForwardTouchEventWithLatencyInfo(
web_event, latency_info);
}
}
}
void RenderWidgetHostViewAndroid::OnOldViewDidNavigatePreCommit() {
if (delegated_frame_host_) {
delegated_frame_host_->DidNavigateMainFramePreCommit();
}
}
void RenderWidgetHostViewAndroid::OnNewViewDidNavigatePostCommit() {
// Move to front only if we are the primary page (we don't want to receive
// events in the Prerender). GetMainRenderFrameHost() may be null in tests.
if (view_.parent() &&
RenderViewHostImpl::From(host())->GetMainRenderFrameHost() &&
RenderViewHostImpl::From(host())
->GetMainRenderFrameHost()
->GetLifecycleState() ==
RenderFrameHost::LifecycleState::kActive) {
view_.parent()->MoveToFront(&view_);
}
ResetGestureDetection();
}
void RenderWidgetHostViewAndroid::DidEnterBackForwardCache() {
local_surface_id_allocator_.GenerateId();
delegated_frame_host_->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
// `DelegatedFrameHostAndroid::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 RenderWidgetHostViewAndroid::SetDoubleTapSupportEnabled(bool enabled) {
gesture_provider_.SetDoubleTapSupportForPlatformEnabled(enabled);
}
void RenderWidgetHostViewAndroid::SetMultiTouchZoomSupportEnabled(
bool enabled) {
gesture_provider_.SetMultiTouchZoomSupportEnabled(enabled);
}
void RenderWidgetHostViewAndroid::FocusedNodeChanged(
bool is_editable_node,
const gfx::Rect& node_bounds_in_screen) {
if (ime_adapter_android_)
ime_adapter_android_->FocusedNodeChanged(is_editable_node,
node_bounds_in_screen);
}
bool RenderWidgetHostViewAndroid::ShouldInitiateStylusWriting() {
return ime_adapter_android_ &&
ime_adapter_android_->ShouldInitiateStylusWriting();
}
void RenderWidgetHostViewAndroid::NotifyHoverActionStylusWritable(
bool stylus_writable) {
view_.NotifyHoverActionStylusWritable(stylus_writable);
}
void RenderWidgetHostViewAndroid::OnEditElementFocusedForStylusWriting(
const gfx::Rect& focused_edit_bounds,
const gfx::Rect& caret_bounds) {
if (ime_adapter_android_) {
ime_adapter_android_->OnEditElementFocusedForStylusWriting(
focused_edit_bounds, caret_bounds);
}
}
void RenderWidgetHostViewAndroid::RenderProcessGone() {
Destroy();
}
void RenderWidgetHostViewAndroid::Destroy() {
host()->render_frame_metadata_provider()->RemoveObserver(this);
host()->ViewDestroyed();
UpdateNativeViewTree(/*parent_native_view=*/nullptr,
/*parent_layer=*/nullptr);
delegated_frame_host_.reset();
delegated_frame_host_client_.reset();
if (GetTextInputManager() && GetTextInputManager()->HasObserver(this))
GetTextInputManager()->RemoveObserver(this);
for (auto& observer : destruction_observers_)
observer.RenderWidgetHostViewDestroyed(this);
destruction_observers_.Clear();
// Call this before the derived class is destroyed so that virtual function
// calls back into `this` still work.
NotifyObserversAboutShutdown();
RenderWidgetHostViewBase::Destroy();
delete this;
}
void RenderWidgetHostViewAndroid::UpdateTooltipUnderCursor(
const std::u16string& tooltip_text) {
// Tooltips don't make sense on Android.
}
void RenderWidgetHostViewAndroid::UpdateTooltipFromKeyboard(
const std::u16string& tooltip_text,
const gfx::Rect& bounds) {
// Tooltips don't make sense on Android.
}
void RenderWidgetHostViewAndroid::ClearKeyboardTriggeredTooltip() {
// Tooltips don't make sense on Android.
}
void RenderWidgetHostViewAndroid::UpdateFrameSinkIdRegistration() {
RenderWidgetHostViewBase::UpdateFrameSinkIdRegistration();
delegated_frame_host_->SetIsFrameSinkIdOwner(is_frame_sink_id_owner());
}
void RenderWidgetHostViewAndroid::UpdateBackgroundColor() {
DCHECK(RenderWidgetHostViewBase::GetBackgroundColor());
SkColor color = *RenderWidgetHostViewBase::GetBackgroundColor();
view_.OnBackgroundColorChanged(color);
}
bool RenderWidgetHostViewAndroid::HasFallbackSurface() const {
return delegated_frame_host_ && delegated_frame_host_->HasFallbackSurface();
}
void RenderWidgetHostViewAndroid::CopyFromSurface(
const gfx::Rect& src_subrect,
const gfx::Size& output_size,
base::OnceCallback<void(const SkBitmap&)> callback) {
TRACE_EVENT0("cc", "RenderWidgetHostViewAndroid::CopyFromSurface");
if (!IsSurfaceAvailableForCopy()) {
std::move(callback).Run(SkBitmap());
return;
}
if (!using_browser_compositor_) {
SynchronousCopyContents(src_subrect, output_size, std::move(callback));
return;
}
DCHECK(delegated_frame_host_);
delegated_frame_host_->CopyFromCompositingSurface(
src_subrect, output_size,
base::BindOnce(
[](base::OnceCallback<void(const SkBitmap&)> callback,
const SkBitmap& bitmap) {
TRACE_EVENT0(
"cc", "RenderWidgetHostViewAndroid::CopyFromSurface finished");
std::move(callback).Run(bitmap);
},
std::move(callback)),
/*capture_exact_surface_id=*/false);
}
void RenderWidgetHostViewAndroid::CopyFromExactSurface(
const gfx::Rect& src_rect,
const gfx::Size& output_size,
base::OnceCallback<void(const SkBitmap&)> callback) {
CHECK(IsSurfaceAvailableForCopy())
<< "To copy the exact surface, it must be available for copy (embedded "
"via the browser).";
CHECK(using_browser_compositor_);
CHECK(delegated_frame_host_);
delegated_frame_host_->CopyFromCompositingSurface(
src_rect, output_size,
base::BindOnce(
[](base::OnceCallback<void(const SkBitmap&)> callback,
const SkBitmap& bitmap) { std::move(callback).Run(bitmap); },
std::move(callback)),
/*capture_exact_surface_id=*/true);
}
void RenderWidgetHostViewAndroid::EnsureSurfaceSynchronizedForWebTest() {
++latest_capture_sequence_number_;
SynchronizeVisualProperties(cc::DeadlinePolicy::UseInfiniteDeadline(),
std::nullopt);
}
uint32_t RenderWidgetHostViewAndroid::GetCaptureSequenceNumber() const {
return latest_capture_sequence_number_;
}
bool RenderWidgetHostViewAndroid::CanSynchronizeVisualProperties() {
// When a rotation begins, the new visual properties are not all notified to
// RenderWidgetHostViewAndroid at the same time. The process begins when
// OnSynchronizedDisplayPropertiesChanged is called, and ends with
// OnPhysicalBackingSizeChanged.
//
// During this time there can be upwards of three calls to
// SynchronizeVisualProperties. Sending each of these separately to the
// Renderer causes three full re-layouts of the page to occur.
//
// We should instead wait for the full set of new visual properties to be
// available, and deliver them to the Renderer in one single update.
if (in_rotation_) {
return false;
}
return screen_state_change_handler_.CanSynchronizeVisualProperties();
}
std::unique_ptr<SyntheticGestureTarget>
RenderWidgetHostViewAndroid::CreateSyntheticGestureTarget() {
return std::unique_ptr<SyntheticGestureTarget>(
new SyntheticGestureTargetAndroid(host(), &view_));
}
bool RenderWidgetHostViewAndroid::ShouldRouteEvents() const {
DCHECK(host());
return host()->delegate() && host()->delegate()->GetInputEventRouter();
}
void RenderWidgetHostViewAndroid::UpdateWebViewBackgroundColorIfNecessary() {
// Android WebView had a bug the BG color was always set to black when
// fullscreen (see https://crbug.com/961223#c5). As applications came to rely
// on this behavior, preserve it here.
if (!using_browser_compositor_ && host()->delegate()->IsFullscreen()) {
SetContentBackgroundColor(SK_ColorBLACK);
}
}
void RenderWidgetHostViewAndroid::ClearFallbackSurfaceForCommitPending() {
delegated_frame_host_->ClearFallbackSurfaceForCommitPending();
EvictInternal();
}
void RenderWidgetHostViewAndroid::ResetFallbackToFirstNavigationSurface() {
if (delegated_frame_host_)
delegated_frame_host_->ResetFallbackToFirstNavigationSurface();
}
bool RenderWidgetHostViewAndroid::RequestRepaintForTesting() {
return SynchronizeVisualProperties(cc::DeadlinePolicy::UseDefaultDeadline(),
std::nullopt);
}
void RenderWidgetHostViewAndroid::SetSynchronousCompositorClient(
SynchronousCompositorClient* client) {
synchronous_compositor_client_ = client;
MaybeCreateSynchronousCompositor();
}
void RenderWidgetHostViewAndroid::MaybeCreateSynchronousCompositor() {
if (!sync_compositor_ && synchronous_compositor_client_) {
sync_compositor_ = SynchronousCompositorHost::Create(
this, host()->GetFrameSinkId(), GetHostFrameSinkManager());
view_.SetCopyOutputCallback(sync_compositor_->GetCopyViewCallback());
if (renderer_widget_created_)
sync_compositor_->InitMojo();
}
}
void RenderWidgetHostViewAndroid::ResetSynchronousCompositor() {
if (sync_compositor_) {
view_.SetCopyOutputCallback(ui::ViewAndroid::CopyViewCallback());
sync_compositor_.reset();
}
}
void RenderWidgetHostViewAndroid::OnOverscrollRefreshHandlerAvailable() {
DCHECK(!overscroll_controller_);
CreateOverscrollControllerIfPossible();
}
bool RenderWidgetHostViewAndroid::SupportsAnimation() const {
// The synchronous (WebView) compositor does not have a proper browser
// compositor with which to drive animations.
return using_browser_compositor_;
}
void RenderWidgetHostViewAndroid::SetNeedsAnimate() {
DCHECK(view_.GetWindowAndroid());
DCHECK(using_browser_compositor_);
view_.GetWindowAndroid()->SetNeedsAnimate();
}
void RenderWidgetHostViewAndroid::MoveCaret(const gfx::PointF& position) {
MoveCaret(gfx::Point(position.x(), position.y()));
}
void RenderWidgetHostViewAndroid::MoveRangeSelectionExtent(
const gfx::PointF& extent) {
if (!selection_popup_controller_)
return;
selection_popup_controller_->MoveRangeSelectionExtent(extent);
}
void RenderWidgetHostViewAndroid::SelectBetweenCoordinates(
const gfx::PointF& base,
const gfx::PointF& extent) {
if (!selection_popup_controller_)
return;
selection_popup_controller_->SelectBetweenCoordinates(base, extent);
}
void RenderWidgetHostViewAndroid::OnSelectionEvent(
ui::SelectionEventType event) {
if (!selection_popup_controller_)
return;
DCHECK(touch_selection_controller_);
// If a selection drag has started, it has taken over the active touch
// sequence. Immediately cancel gesture detection and any downstream touch
// listeners (e.g., web content) to communicate this transfer.
if (event == ui::SELECTION_HANDLES_SHOWN &&
gesture_provider_.GetCurrentDownEvent()) {
ResetGestureDetection();
}
selection_popup_controller_->OnSelectionEvent(
event, GetSelectionRect(*touch_selection_controller_));
}
void RenderWidgetHostViewAndroid::OnDragUpdate(
const ui::TouchSelectionDraggable::Type type,
const gfx::PointF& position) {
if (!selection_popup_controller_)
return;
selection_popup_controller_->OnDragUpdate(type, position);
}
ui::TouchSelectionControllerClient*
RenderWidgetHostViewAndroid::GetSelectionControllerClientManagerForTesting() {
return touch_selection_controller_client_manager_.get();
}
void RenderWidgetHostViewAndroid::SetSelectionControllerClientForTesting(
std::unique_ptr<ui::TouchSelectionControllerClient> client) {
touch_selection_controller_client_for_test_.swap(client);
touch_selection_controller_ = CreateSelectionController(
touch_selection_controller_client_for_test_.get(), !!view_.parent());
}
std::unique_ptr<ui::TouchHandleDrawable>
RenderWidgetHostViewAndroid::CreateDrawable() {
if (!using_browser_compositor_) {
if (!sync_compositor_)
return nullptr;
return std::unique_ptr<ui::TouchHandleDrawable>(
sync_compositor_->client()->CreateDrawable());
}
if (!selection_popup_controller_)
return nullptr;
return selection_popup_controller_->CreateTouchHandleDrawable(
view_.parent(), view_.GetLayer()->parent());
}
void RenderWidgetHostViewAndroid::DidScroll() {}
void RenderWidgetHostViewAndroid::ShowTouchSelectionContextMenu(
const gfx::Point& location) {
host()->ShowContextMenuAtPoint(location, ui::MENU_SOURCE_TOUCH_HANDLE);
}
void RenderWidgetHostViewAndroid::SynchronousCopyContents(
const gfx::Rect& src_subrect_dip,
const gfx::Size& dst_size_in_pixel,
base::OnceCallback<void(const SkBitmap&)> callback) {
// Note: When |src_subrect| is empty, a conversion from the view size must
// be made instead of using |current_frame_size_|. The latter sometimes also
// includes extra height for the toolbar UI, which is not intended for
// capture.
gfx::Rect valid_src_subrect_in_dips = src_subrect_dip;
if (valid_src_subrect_in_dips.IsEmpty())
valid_src_subrect_in_dips = gfx::Rect(GetVisibleViewportSize());
const gfx::Rect src_subrect_in_pixel = gfx::ToEnclosingRect(
gfx::ConvertRectToPixels(valid_src_subrect_in_dips, view_.GetDipScale()));
// TODO(crbug.com/41305903): [BUG] Current implementation does not support
// read-back of regions that do not originate at (0,0).
const gfx::Size& input_size_in_pixel = src_subrect_in_pixel.size();
DCHECK(!input_size_in_pixel.IsEmpty());
gfx::Size output_size_in_pixel;
if (dst_size_in_pixel.IsEmpty())
output_size_in_pixel = input_size_in_pixel;
else
output_size_in_pixel = dst_size_in_pixel;
int output_width = output_size_in_pixel.width();
int output_height = output_size_in_pixel.height();
if (!sync_compositor_) {
std::move(callback).Run(SkBitmap());
return;
}
SkBitmap bitmap;
bitmap.allocPixels(SkImageInfo::MakeN32Premul(output_width, output_height));
SkCanvas canvas(bitmap);
canvas.scale(
(float)output_width / (float)input_size_in_pixel.width(),
(float)output_height / (float)input_size_in_pixel.height());
sync_compositor_->DemandDrawSw(&canvas, /*software_canvas=*/true);
std::move(callback).Run(bitmap);
}
WebContentsAccessibilityAndroid*
RenderWidgetHostViewAndroid::GetWebContentsAccessibilityAndroid() const {
return web_contents_accessibility_;
}
void RenderWidgetHostViewAndroid::UpdateTouchSelectionController(
const viz::Selection<gfx::SelectionBound>& selection,
float page_scale_factor,
float top_controls_height,
float top_controls_shown_ratio,
const gfx::SizeF& scrollable_viewport_size_dip) {
if (!touch_selection_controller_)
return;
DCHECK(touch_selection_controller_client_manager_);
touch_selection_controller_client_manager_->UpdateClientSelectionBounds(
selection.start, selection.end, this, nullptr);
OnUpdateScopedSelectionHandles();
// Set parameters for adaptive handle orientation.
gfx::SizeF viewport_size(scrollable_viewport_size_dip);
viewport_size.Scale(page_scale_factor);
gfx::RectF viewport_rect(0.0f, top_controls_height * top_controls_shown_ratio,
viewport_size.width(), viewport_size.height());
touch_selection_controller_->OnViewportChanged(viewport_rect);
}
bool RenderWidgetHostViewAndroid::UpdateControls(
float dip_scale,
float top_controls_height,
float top_controls_shown_ratio,
float top_controls_min_height_offset,
float bottom_controls_height,
float bottom_controls_shown_ratio,
float bottom_controls_min_height_offset) {
float top_controls_pix = top_controls_height;
// |top_content_offset| is in physical pixels if --use-zoom-for-dsf is
// enabled. Otherwise, it is in DIPs.
// Note that the height of browser control is not affected by page scale
// factor. Thus, |top_content_offset| in CSS pixels is also in DIPs.
float top_content_offset = top_controls_height * top_controls_shown_ratio;
float top_shown_pix = top_content_offset;
float top_translate = top_shown_pix - top_controls_pix;
bool top_changed =
!cc::MathUtil::IsFloatNearlyTheSame(top_shown_pix, prev_top_shown_pix_);
float top_min_height_offset_pix = top_controls_min_height_offset;
top_changed |= !cc::MathUtil::IsFloatNearlyTheSame(
top_min_height_offset_pix, prev_top_controls_min_height_offset_pix_);
top_changed |= !cc::MathUtil::IsFloatNearlyTheSame(top_controls_pix,
prev_top_controls_pix_);
prev_top_shown_pix_ = top_shown_pix;
prev_top_controls_pix_ = top_controls_pix;
prev_top_controls_translate_ = top_translate;
prev_top_controls_min_height_offset_pix_ = top_min_height_offset_pix;
float bottom_controls_pix = bottom_controls_height;
float bottom_shown_pix = bottom_controls_pix * bottom_controls_shown_ratio;
bool bottom_changed = !cc::MathUtil::IsFloatNearlyTheSame(
bottom_shown_pix, prev_bottom_shown_pix_);
float bottom_translate = bottom_controls_pix - bottom_shown_pix;
float bottom_min_height_offset_pix = bottom_controls_min_height_offset;
bottom_changed |= !cc::MathUtil::IsFloatNearlyTheSame(
bottom_min_height_offset_pix,
prev_bottom_controls_min_height_offset_pix_);
if (top_changed || bottom_changed || !controls_initialized_) {
view_.OnControlsChanged(top_translate, top_shown_pix,
top_min_height_offset_pix, bottom_translate,
bottom_min_height_offset_pix);
}
prev_bottom_shown_pix_ = bottom_shown_pix;
prev_bottom_controls_translate_ = bottom_translate;
prev_bottom_controls_min_height_offset_pix_ = bottom_min_height_offset_pix;
controls_initialized_ = true;
return top_changed || bottom_changed;
}
void RenderWidgetHostViewAndroid::OnDidUpdateVisualPropertiesComplete(
const cc::RenderFrameMetadata& metadata) {
// Eviction and rotation handling has been updated, and is no longer tied to
// child update. No more need to unthrottle here.
SynchronizeVisualProperties(cc::DeadlinePolicy::UseDefaultDeadline(),
metadata.local_surface_id);
if (using_browser_compositor_) {
ui::WindowAndroid* window = view_.GetWindowAndroid();
if (!window) {
return;
}
ui::WindowAndroidCompositor* compositor = window->GetCompositor();
if (!compositor) {
return;
}
static_cast<CompositorImpl*>(compositor)->MaybeCompositeNow();
}
}
void RenderWidgetHostViewAndroid::OnFinishGetContentBitmap(
const base::android::JavaRef<jobject>& obj,
const base::android::JavaRef<jobject>& callback,
const std::string& path,
const SkBitmap& bitmap) {
JNIEnv* env = base::android::AttachCurrentThread();
if (!bitmap.drawsNothing()) {
auto task_runner = base::ThreadPool::CreateSequencedTaskRunner(
{base::MayBlock(), base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN});
task_runner->PostTaskAndReplyWithResult(
FROM_HERE, base::BindOnce(&CompressAndSaveBitmap, path, bitmap),
base::BindOnce(
&base::android::RunStringCallbackAndroid,
base::android::ScopedJavaGlobalRef<jobject>(env, callback.obj())));
return;
}
// If readback failed, call empty callback
base::android::RunStringCallbackAndroid(callback, std::string());
}
void RenderWidgetHostViewAndroid::ShowInternal() {
bool show = is_showing_ && is_window_activity_started_ && is_window_visible_;
if (!show)
return;
OnShowWithPageVisibility(page_visibility_);
}
void RenderWidgetHostViewAndroid::HideInternal() {
DCHECK(!is_showing_ || !is_window_activity_started_ || !is_window_visible_)
<< "Hide called when the widget should be shown.";
// As we stop visual observations, we clear the current fullscreen state. Once
// ShowInternal() is invoked the most up to date visual properties will be
// used.
fullscreen_rotation_ = false;
// If a RWHVA gets hidden and swapped out then gets swapped back in and shown,
// the last known controls offsets may be the same as the latest values we get
// from the renderer. In this case, we would skip pushing the offset to
// `ViewAndroid` assuming there was no change. To prevent this, we should
// reset `controls_initialized_` to make sure the offsets are pushed once the
// RWHVA is shown again.
controls_initialized_ = false;
// Only preserve the frontbuffer if the activity was stopped while the
// window is still visible. This avoids visual artifacts when transitioning
// between activities.
bool hide_frontbuffer = is_window_activity_started_ || !is_window_visible_;
// Only stop observing the root window if the widget has been explicitly
// hidden and the frontbuffer is being cleared. This allows window visibility
// notifications to eventually clear the frontbuffer.
bool stop_observing_root_window = !is_showing_ && hide_frontbuffer;
if (hide_frontbuffer) {
view_.GetLayer()->SetHideLayerAndSubtree(true);
if (delegated_frame_host_)
delegated_frame_host_->WasHidden();
}
if (stop_observing_root_window) {
DCHECK(!is_showing_);
StopObservingRootWindow();
}
if (!host() || host()->is_hidden())
return;
if (overscroll_controller_)
overscroll_controller_->Disable();
// Inform the renderer that we are being hidden so it can reduce its resource
// utilization.
host()->WasHidden();
}
void RenderWidgetHostViewAndroid::StartObservingRootWindow() {
DCHECK(view_.parent());
DCHECK(view_.GetWindowAndroid());
DCHECK(is_showing_);
if (observing_root_window_)
return;
observing_root_window_ = true;
view_.GetWindowAndroid()->AddObserver(this);
ui::WindowAndroidCompositor* compositor =
view_.GetWindowAndroid()->GetCompositor();
if (compositor) {
delegated_frame_host_->AttachToCompositor(compositor);
}
OnUpdateScopedSelectionHandles();
ObserveDevicePosturePlatformProvider();
}
void RenderWidgetHostViewAndroid::StopObservingRootWindow() {
if (!(view_.GetWindowAndroid())) {
DCHECK(!observing_root_window_);
return;
}
if (!observing_root_window_)
return;
// Reset window state variables to their defaults.
is_window_activity_started_ = true;
is_window_visible_ = true;
observing_root_window_ = false;
OnUpdateScopedSelectionHandles();
view_.GetWindowAndroid()->RemoveObserver(this);
// If the DFH has already been destroyed, it will have cleaned itself up.
// This happens in some WebView cases.
if (delegated_frame_host_)
delegated_frame_host_->DetachFromCompositor();
}
bool RenderWidgetHostViewAndroid::Animate(base::TimeTicks frame_time) {
bool needs_animate = false;
if (overscroll_controller_) {
needs_animate |=
overscroll_controller_->Animate(frame_time, view_.GetLayer()->parent());
}
// TODO(wjmaclean): Investigate how animation here does or doesn't affect
// an OOPIF client.
if (touch_selection_controller_)
needs_animate |= touch_selection_controller_->Animate(frame_time);
return needs_animate;
}
void RenderWidgetHostViewAndroid::RequestDisallowInterceptTouchEvent() {
if (view_.parent())
view_.RequestDisallowInterceptTouchEvent();
}
void RenderWidgetHostViewAndroid::TransformPointToRootSurface(
gfx::PointF* point) {
if (!host()->delegate())
return;
RenderViewHostDelegateView* rvh_delegate_view =
host()->delegate()->GetDelegateView();
if (rvh_delegate_view->DoBrowserControlsShrinkRendererSize())
*point += gfx::Vector2d(0, rvh_delegate_view->GetTopControlsHeight());
}
// TODO(jrg): Find out the implications and answer correctly here,
// as we are returning the WebView and not root window bounds.
gfx::Rect RenderWidgetHostViewAndroid::GetBoundsInRootWindow() {
return GetViewBounds();
}
const viz::LocalSurfaceId&
RenderWidgetHostViewAndroid::IncrementSurfaceIdForNavigation() {
local_surface_id_allocator_.GenerateId();
if (delegated_frame_host_) {
delegated_frame_host_->EmbedSurface(
local_surface_id_allocator_.GetCurrentLocalSurfaceId(),
GetCompositorViewportPixelSize(),
cc::DeadlinePolicy::UseDefaultDeadline(),
host()->delegate()->IsFullscreen());
}
return local_surface_id_allocator_.GetCurrentLocalSurfaceId();
}
void RenderWidgetHostViewAndroid::ProcessAckedTouchEvent(
const input::TouchEventWithLatencyInfo& touch,
blink::mojom::InputEventResultState ack_result) {
TRACE_EVENT0("input", "RenderWidgetHostViewAndroid::ProcessAckedTouchEvent");
const bool event_consumed =
ack_result == blink::mojom::InputEventResultState::kConsumed;
// |is_source_touch_event_set_non_blocking| defines a blocking behaviour of
// the future inputs.
const bool is_source_touch_event_set_non_blocking =
InputEventResultStateIsSetBlocking(ack_result);
// |was_touch_blocked| indicates whether the current event was dispatched
// blocking to the Renderer.
const bool was_touch_blocked =
ui::WebInputEventTraits::ShouldBlockEventStream(touch.event);
gesture_provider_.OnTouchEventAck(
touch.event.unique_touch_event_id, event_consumed,
is_source_touch_event_set_non_blocking,
was_touch_blocked
? std::make_optional(touch.event.GetEventLatencyMetadata())
: std::nullopt);
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 RenderWidgetHostViewAndroid::GestureEventAck(
const blink::WebGestureEvent& event,
blink::mojom::InputEventResultSource ack_source,
blink::mojom::InputEventResultState ack_result) {
if (overscroll_controller_)
overscroll_controller_->OnGestureEventAck(event, ack_result);
mouse_wheel_phase_handler_.GestureEventAck(event, 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);
if (gesture_listener_manager_)
gesture_listener_manager_->GestureEventAck(event, ack_result);
HandleSwipeToMoveCursorGestureAck(event);
}
void RenderWidgetHostViewAndroid::ChildDidAckGestureEvent(
const blink::WebGestureEvent& event,
blink::mojom::InputEventResultState ack_result) {
if (gesture_listener_manager_)
gesture_listener_manager_->GestureEventAck(event, ack_result);
}
blink::mojom::InputEventResultState
RenderWidgetHostViewAndroid::FilterInputEvent(
const blink::WebInputEvent& input_event) {
if (overscroll_controller_ &&
blink::WebInputEvent::IsGestureEventType(input_event.GetType())) {
blink::WebGestureEvent gesture_event =
static_cast<const blink::WebGestureEvent&>(input_event);
if (overscroll_controller_->WillHandleGestureEvent(gesture_event)) {
// Terminate an active fling when a GSU generated from the fling progress
// (GSU with inertial state) is consumed by the overscroll_controller_ and
// overscrolling mode is not |OVERSCROLL_NONE|. The early fling
// termination generates a GSE which completes the overscroll action.
if (gesture_event.GetType() ==
blink::WebInputEvent::Type::kGestureScrollUpdate &&
gesture_event.data.scroll_update.inertial_phase ==
blink::WebGestureEvent::InertialPhaseState::kMomentum) {
host_->StopFling();
}
return blink::mojom::InputEventResultState::kConsumed;
}
}
if (gesture_listener_manager_ &&
gesture_listener_manager_->FilterInputEvent(input_event)) {
return blink::mojom::InputEventResultState::kConsumed;
}
if (!host())
return blink::mojom::InputEventResultState::kNotConsumed;
if (input_event.GetType() == blink::WebInputEvent::Type::kTouchStart) {
GpuProcessHost::CallOnUI(FROM_HERE, GPU_PROCESS_KIND_SANDBOXED,
false /* force_create */,
base::BindOnce(&WakeUpGpu));
}
return blink::mojom::InputEventResultState::kNotConsumed;
}
blink::mojom::PointerLockResult RenderWidgetHostViewAndroid::LockPointer(
bool request_unadjusted_movement) {
NOTIMPLEMENTED();
return blink::mojom::PointerLockResult::kUnsupportedOptions;
}
blink::mojom::PointerLockResult RenderWidgetHostViewAndroid::ChangePointerLock(
bool request_unadjusted_movement) {
NOTIMPLEMENTED();
return blink::mojom::PointerLockResult::kUnsupportedOptions;
}
void RenderWidgetHostViewAndroid::UnlockPointer() {
NOTIMPLEMENTED();
}
// Methods called from the host to the render
void RenderWidgetHostViewAndroid::SendKeyEvent(
const input::NativeWebKeyboardEvent& event) {
if (!host())
return;
RenderWidgetHostImpl* target_host = host();
// If there are multiple widgets on the page (such as when there are
// out-of-process iframes), pick the one that should process this event.
if (host()->delegate())
target_host = host()->delegate()->GetFocusedRenderWidgetHost(host());
if (!target_host)
return;
// Receiving a key event before the double-tap timeout expires cancels opening
// the spellcheck menu. If the suggestion menu is open, we close the menu.
if (text_suggestion_host_)
text_suggestion_host_->OnKeyEvent();
ui::LatencyInfo latency_info;
latency_info.AddLatencyNumber(ui::INPUT_EVENT_LATENCY_UI_COMPONENT);
target_host->ForwardKeyboardEventWithLatencyInfo(event, latency_info);
}
void RenderWidgetHostViewAndroid::SendMouseEvent(
const blink::WebMouseEvent& event,
const ui::LatencyInfo& info) {
if (!host() || !host()->delegate())
return;
if (ShouldRouteEvents()) {
host()->delegate()->GetInputEventRouter()->RouteMouseEvent(this, &event,
info);
} else {
host()->ForwardMouseEventWithLatencyInfo(event, info);
}
}
void RenderWidgetHostViewAndroid::UpdateMouseState(int action_button,
float mousedown_x,
float mousedown_y) {
if (action_button != ui::MotionEventAndroid::BUTTON_PRIMARY) {
// Reset state if middle or right button was pressed.
left_click_count_ = 0;
prev_mousedown_timestamp_ = base::TimeTicks();
return;
}
const base::TimeTicks current_time = base::TimeTicks::Now();
const base::TimeDelta time_delay = current_time - prev_mousedown_timestamp_;
const gfx::Point mousedown_point(mousedown_x, mousedown_y);
const float distance_squared =
(mousedown_point - prev_mousedown_point_).LengthSquared();
if (left_click_count_ > 2 || time_delay > kClickCountInterval ||
distance_squared > kClickCountRadiusSquaredDIP) {
left_click_count_ = 0;
}
left_click_count_++;
prev_mousedown_timestamp_ = current_time;
prev_mousedown_point_ = mousedown_point;
}
void RenderWidgetHostViewAndroid::SendMouseWheelEvent(
const blink::WebMouseWheelEvent& event) {
if (!host() || !host()->delegate())
return;
ui::LatencyInfo latency_info;
latency_info.AddLatencyNumber(ui::INPUT_EVENT_LATENCY_UI_COMPONENT);
blink::WebMouseWheelEvent wheel_event(event);
bool should_route_events = ShouldRouteEvents();
mouse_wheel_phase_handler_.AddPhaseIfNeededAndScheduleEndEvent(
wheel_event, should_route_events);
if (should_route_events) {
host()->delegate()->GetInputEventRouter()->RouteMouseWheelEvent(
this, &wheel_event, latency_info);
} else {
host()->ForwardWheelEventWithLatencyInfo(wheel_event, latency_info);
}
}
void RenderWidgetHostViewAndroid::SendGestureEvent(
const blink::WebGestureEvent& event) {
// Sending a gesture that may trigger overscroll should resume the effect.
if (overscroll_controller_)
overscroll_controller_->Enable();
if (!host() || !host()->delegate() ||
event.GetType() == blink::WebInputEvent::Type::kUndefined) {
return;
}
// We let the touch selection controller see gesture events here, since they
// may be routed and not make it to FilterInputEvent().
if (touch_selection_controller_ &&
event.SourceDevice() == blink::WebGestureDevice::kTouchscreen) {
switch (event.GetType()) {
case blink::WebInputEvent::Type::kGestureLongPress:
touch_selection_controller_->HandleLongPressEvent(
event.TimeStamp(), event.PositionInWidget());
break;
case blink::WebInputEvent::Type::kGestureTapDown:
if (event.data.tap_down.tap_down_count == 2) {
touch_selection_controller_->HandleDoublePressEvent(
event.TimeStamp(), event.PositionInWidget());
}
break;
case blink::WebInputEvent::Type::kGestureTap:
touch_selection_controller_->HandleTapEvent(event.PositionInWidget(),
event.data.tap.tap_count);
break;
case blink::WebInputEvent::Type::kGestureScrollBegin:
touch_selection_controller_->OnScrollBeginEvent();
break;
default:
break;
}
}
ui::LatencyInfo latency_info;
if (event.SourceDevice() == blink::WebGestureDevice::kTouchscreen) {
if (event.GetType() == blink::WebInputEvent::Type::kGestureScrollBegin) {
// If there is a current scroll going on and a new scroll that isn't
// wheel based, send a synthetic wheel event with kPhaseEnded to cancel
// the current scroll.
mouse_wheel_phase_handler_.DispatchPendingWheelEndEvent();
} else if (event.GetType() ==
blink::WebInputEvent::Type::kGestureScrollEnd) {
// Make sure that the next wheel event will have phase = |kPhaseBegan|.
// This is for maintaining the correct phase info when some of the wheel
// events get ignored while a touchscreen scroll is going on.
mouse_wheel_phase_handler_.IgnorePendingWheelEndEvent();
}
} else if (event.GetType() ==
blink::WebInputEvent::Type::kGestureFlingStart &&
event.SourceDevice() == blink::WebGestureDevice::kTouchpad) {
// Ignore the pending wheel end event to avoid sending a wheel event with
// kPhaseEnded before a GFS.
mouse_wheel_phase_handler_.IgnorePendingWheelEndEvent();
}
if (ShouldRouteEvents()) {
blink::WebGestureEvent gesture_event(event);
host()->delegate()->GetInputEventRouter()->RouteGestureEvent(
this, &gesture_event, latency_info);
} else {
host()->GetRenderInputRouter()->ForwardGestureEventWithLatencyInfo(
event, latency_info);
}
}
bool RenderWidgetHostViewAndroid::ShowSelectionMenu(
RenderFrameHost* render_frame_host,
const ContextMenuParams& params) {
if (!selection_popup_controller_) {
return false;
}
return selection_popup_controller_->ShowSelectionMenu(
render_frame_host, params, GetTouchHandleHeight());
}
void RenderWidgetHostViewAndroid::MoveCaret(const gfx::Point& point) {
if (host() && host()->delegate())
host()->delegate()->MoveCaret(point);
}
void RenderWidgetHostViewAndroid::DismissTextHandles() {
if (touch_selection_controller_)
touch_selection_controller_->HideAndDisallowShowingAutomatically();
}
void RenderWidgetHostViewAndroid::SetTextHandlesTemporarilyHidden(
bool hide_handles) {
if (!touch_selection_controller_ ||
handles_hidden_by_selection_ui_ == hide_handles)
return;
handles_hidden_by_selection_ui_ = hide_handles;
SetTextHandlesHiddenInternal();
}
std::optional<SkColor> RenderWidgetHostViewAndroid::GetCachedBackgroundColor() {
return RenderWidgetHostViewBase::GetBackgroundColor();
}
void RenderWidgetHostViewAndroid::DidOverscroll(
const ui::DidOverscrollParams& params) {
if (sync_compositor_)
sync_compositor_->DidOverscroll(params);
if (!view_.parent() || !is_showing_)
return;
if (overscroll_controller_)
overscroll_controller_->OnOverscrolled(params);
}
void RenderWidgetHostViewAndroid::DidStopFlinging() {
if (!gesture_listener_manager_)
return;
gesture_listener_manager_->DidStopFlinging();
}
const viz::FrameSinkId& RenderWidgetHostViewAndroid::GetFrameSinkId() const {
if (!delegated_frame_host_)
return viz::FrameSinkIdAllocator::InvalidFrameSinkId();
return delegated_frame_host_->GetFrameSinkId();
}
void RenderWidgetHostViewAndroid::UpdateNativeViewTree(
gfx::NativeView parent_native_view,
cc::slim::Layer* parent_layer) {
// `parent_native_view` and `parent_layer` must be null or non-null at the
// same time.
CHECK(!(!!parent_native_view ^ !!parent_layer));
bool will_build_tree = parent_native_view != nullptr;
bool has_view_tree = view_.parent() != nullptr;
// Allows same parent view to be set again.
DCHECK(!will_build_tree || !has_view_tree ||
parent_native_view == view_.parent());
StopObservingRootWindow();
bool resize = false;
if (will_build_tree != has_view_tree) {
touch_selection_controller_.reset();
if (has_view_tree) {
view_.RemoveObserver(this);
view_.RemoveFromParent();
view_.GetLayer()->RemoveFromParent();
}
if (will_build_tree) {
view_.AddObserver(this);
parent_native_view->AddChild(&view_);
parent_layer->AddChild(view_.GetLayer());
}
// TODO(yusufo) : Get rid of the below conditions and have a better handling
// for resizing after crbug.com/628302 is handled.
bool is_size_initialized = !will_build_tree ||
view_.GetSize().width() != 0 ||
view_.GetSize().height() != 0;
if (has_view_tree || is_size_initialized)
resize = true;
has_view_tree = will_build_tree;
}
if (!has_view_tree) {
ResetSynchronousCompositor();
return;
}
// Parent native view can become null and then later non-null again, if
// WebContents swaps away from this, and then later back to it. Need to
// ensure SynchronousCompositor is recreated in this case.
MaybeCreateSynchronousCompositor();
// Force an initial update of screen infos so the default RWHVBase value
// is not used.
// TODO(enne): figure out a more straightforward init path for screen infos.
UpdateScreenInfo();
if (is_showing_ && view_.GetWindowAndroid())
StartObservingRootWindow();
if (resize) {
SynchronizeVisualProperties(
cc::DeadlinePolicy::UseSpecifiedDeadline(
ui::DelegatedFrameHostAndroid::ResizeTimeoutFrames()),
std::nullopt);
}
if (!touch_selection_controller_) {
ui::TouchSelectionControllerClient* client =
touch_selection_controller_client_manager_.get();
if (touch_selection_controller_client_for_test_)
client = touch_selection_controller_client_for_test_.get();
touch_selection_controller_ = CreateSelectionController(client, true);
}
CreateOverscrollControllerIfPossible();
}
cc::mojom::RootScrollOffsetUpdateFrequency
RenderWidgetHostViewAndroid::RootScrollOffsetUpdateFrequency() {
// In order to provide support for onScrollOffsetOrExtentChanged()
// GestureListenerManager needs root-scroll-offsets. The frequency of the
// updates depends on the needs of the `GestureStateListenerWithScroll`s, if
// any.
if (web_contents_accessibility_ != nullptr) {
return cc::mojom::RootScrollOffsetUpdateFrequency::kAllUpdates;
}
return gesture_listener_manager_
? gesture_listener_manager_->root_scroll_offset_update_frequency()
: cc::mojom::RootScrollOffsetUpdateFrequency::kNone;
}
MouseWheelPhaseHandler*
RenderWidgetHostViewAndroid::GetMouseWheelPhaseHandler() {
return &mouse_wheel_phase_handler_;
}
TouchSelectionControllerClientManager*
RenderWidgetHostViewAndroid::GetTouchSelectionControllerClientManager() {
return touch_selection_controller_client_manager_.get();
}
const viz::LocalSurfaceId& RenderWidgetHostViewAndroid::GetLocalSurfaceId()
const {
return local_surface_id_allocator_.GetCurrentLocalSurfaceId();
}
void RenderWidgetHostViewAndroid::OnRendererWidgetCreated() {
renderer_widget_created_ = true;
if (sync_compositor_)
sync_compositor_->InitMojo();
}
bool RenderWidgetHostViewAndroid::OnMouseEvent(
const ui::MotionEventAndroid& event) {
RecordToolTypeForActionDown(event);
blink::WebInputEvent::Type webMouseEventType =
ui::ToWebMouseEventType(event.GetAction());
if (webMouseEventType == blink::WebInputEvent::Type::kUndefined) {
return false;
}
int action_button = event.GetActionButton();
if (webMouseEventType == blink::WebInputEvent::Type::kMouseDown) {
UpdateMouseState(action_button, event.GetX(0), event.GetY(0));
}
int click_count = 0;
if (webMouseEventType == blink::WebInputEvent::Type::kMouseDown ||
webMouseEventType == blink::WebInputEvent::Type::kMouseUp) {
click_count = (action_button == ui::MotionEventAndroid::BUTTON_PRIMARY)
? left_click_count_
: 1;
}
SendMouseEvent(input::WebMouseEventBuilder::Build(event, webMouseEventType,
click_count, action_button),
ui::LatencyInfo());
return true;
}
bool RenderWidgetHostViewAndroid::OnMouseWheelEvent(
const ui::MotionEventAndroid& event) {
SendMouseWheelEvent(input::WebMouseWheelEventBuilder::Build(event));
return true;
}
void RenderWidgetHostViewAndroid::OnGestureEvent(
const ui::GestureEventData& gesture) {
if ((gesture.type() == ui::EventType::kGesturePinchBegin ||
gesture.type() == ui::EventType::kGesturePinchUpdate ||
gesture.type() == ui::EventType::kGesturePinchEnd) &&
!IsPinchToZoomEnabled()) {
return;
}
blink::WebGestureEvent web_gesture =
ui::CreateWebGestureEventFromGestureEventData(gesture);
// TODO(jdduke): Remove this workaround after Android fixes UiAutomator to
// stop providing shift meta values to synthetic MotionEvents. This prevents
// unintended shift+click interpretation of all accessibility clicks.
// See crbug.com/443247.
if (web_gesture.GetType() == blink::WebInputEvent::Type::kGestureTap &&
web_gesture.GetModifiers() == blink::WebInputEvent::kShiftKey) {
web_gesture.SetModifiers(blink::WebInputEvent::kNoModifiers);
}
SendGestureEvent(web_gesture);
}
bool RenderWidgetHostViewAndroid::RequiresDoubleTapGestureEvents() const {
return true;
}
void RenderWidgetHostViewAndroid::OnSizeChanged() {
screen_state_change_handler_.OnVisibleViewportSizeChanged(view_.GetSize());
// The display feature depends on the view size so we need to recompute it.
ComputeDisplayFeature();
}
void RenderWidgetHostViewAndroid::OnPhysicalBackingSizeChanged(
std::optional<base::TimeDelta> deadline_override) {
// We may need to update the background color to match pre-surface-sync
// behavior of EvictFrameIfNecessary.
UpdateWebViewBackgroundColorIfNecessary();
int64_t deadline_in_frames =
deadline_override ? ui::DelegatedFrameHostAndroid::TimeDeltaToFrames(
deadline_override.value())
: ui::DelegatedFrameHostAndroid::ResizeTimeoutFrames();
if (screen_state_change_handler_.OnPhysicalBackingSizeChanged(
view_.GetPhysicalBackingSize(), deadline_in_frames)) {
return;
}
SynchronizeVisualProperties(
cc::DeadlinePolicy::UseSpecifiedDeadline(deadline_in_frames),
std::nullopt);
}
void RenderWidgetHostViewAndroid::OnRootWindowVisibilityChanged(bool visible) {
TRACE_EVENT1("browser",
"RenderWidgetHostViewAndroid::OnRootWindowVisibilityChanged",
"visible", visible);
DCHECK(observing_root_window_);
// Don't early out if visibility hasn't changed and visible. This is necessary
// as OnDetachedFromWindow() sets |is_window_visible_| to true, so that this
// may be called when ShowInternal() needs to be called.
if (is_window_visible_ == visible && !visible)
return;
is_window_visible_ = visible;
if (visible)
ShowInternal();
else
HideInternal();
}
void RenderWidgetHostViewAndroid::OnAttachedToWindow() {
if (!view_.parent())
return;
UpdateScreenInfo();
if (is_showing_)
StartObservingRootWindow();
DCHECK(view_.GetWindowAndroid());
if (view_.GetWindowAndroid()->GetCompositor())
OnAttachCompositor();
}
void RenderWidgetHostViewAndroid::OnDetachedFromWindow() {
StopObservingRootWindow();
OnDetachCompositor();
}
void RenderWidgetHostViewAndroid::OnAttachCompositor() {
DCHECK(view_.parent());
CreateOverscrollControllerIfPossible();
if (observing_root_window_ && using_browser_compositor_) {
ui::WindowAndroidCompositor* compositor =
view_.GetWindowAndroid()->GetCompositor();
delegated_frame_host_->AttachToCompositor(compositor);
}
}
void RenderWidgetHostViewAndroid::OnDetachCompositor() {
DCHECK(view_.parent());
overscroll_controller_.reset();
if (using_browser_compositor_)
delegated_frame_host_->DetachFromCompositor();
}
void RenderWidgetHostViewAndroid::OnAnimate(base::TimeTicks begin_frame_time) {
if (Animate(begin_frame_time))
SetNeedsAnimate();
}
void RenderWidgetHostViewAndroid::OnUnfoldStarted(
base::TimeTicks unfold_begin_time) {
TRACE_EVENT0("browser", "RenderWidgetHostViewAndroid::OnUnfoldStarted");
host()->RequestSuccessfulPresentationTimeForNextFrame(
blink::mojom::RecordContentToVisibleTimeRequest::New(
unfold_begin_time, /*destination_is_loaded=*/false,
/*show_reason_tab_switching=*/false,
/*show_reason_bfcache_restore=*/false,
/*show_reason_unfolding=*/true));
}
void RenderWidgetHostViewAndroid::OnActivityStopped() {
TRACE_EVENT0("browser", "RenderWidgetHostViewAndroid::OnActivityStopped");
DCHECK(observing_root_window_);
is_window_activity_started_ = false;
HideInternal();
}
void RenderWidgetHostViewAndroid::OnActivityStarted() {
TRACE_EVENT0("browser", "RenderWidgetHostViewAndroid::OnActivityStarted");
DCHECK(observing_root_window_);
is_window_activity_started_ = true;
ShowInternal();
}
void RenderWidgetHostViewAndroid::SetTextHandlesHiddenForDropdownMenu(
bool hide_handles) {
if (!touch_selection_controller_ ||
handles_hidden_by_dropdown_menu_ == hide_handles) {
return;
}
handles_hidden_by_dropdown_menu_ = hide_handles;
SetTextHandlesHiddenInternal();
}
void RenderWidgetHostViewAndroid::SetTextHandlesHiddenForStylus(
bool hide_handles) {
if (!touch_selection_controller_ || handles_hidden_by_stylus_ == hide_handles)
return;
handles_hidden_by_stylus_ = hide_handles;
SetTextHandlesHiddenInternal();
}
void RenderWidgetHostViewAndroid::SetTextHandlesHiddenInternal() {
if (!touch_selection_controller_)
return;
touch_selection_controller_->SetTemporarilyHidden(
handles_hidden_by_dropdown_menu_ || handles_hidden_by_stylus_ ||
handles_hidden_by_selection_ui_);
}
void RenderWidgetHostViewAndroid::OnStylusSelectBegin(float x0,
float y0,
float x1,
float y1) {
SetTextHandlesHiddenForStylus(true);
// TODO(ajith.v) Refactor the event names as this is not really handle drag,
// but currently we use same for long press drag selection as well.
OnSelectionEvent(ui::SELECTION_HANDLE_DRAG_STARTED);
SelectBetweenCoordinates(gfx::PointF(x0, y0), gfx::PointF(x1, y1));
}
void RenderWidgetHostViewAndroid::OnStylusSelectUpdate(float x, float y) {
MoveRangeSelectionExtent(gfx::PointF(x, y));
}
void RenderWidgetHostViewAndroid::OnStylusSelectEnd(float x, float y) {
SetTextHandlesHiddenForStylus(false);
// TODO(ajith.v) Refactor the event names as this is not really handle drag,
// but currently we use same for long press drag selection as well.
OnSelectionEvent(ui::SELECTION_HANDLE_DRAG_STOPPED);
}
void RenderWidgetHostViewAndroid::OnStylusSelectTap(base::TimeTicks time,
float x,
float y) {
// Treat the stylus tap as a long press, activating either a word selection or
// context menu depending on the targetted content.
blink::WebGestureEvent long_press = input::WebGestureEventBuilder::Build(
blink::WebInputEvent::Type::kGestureLongPress, time, x, y);
SendGestureEvent(long_press);
}
void RenderWidgetHostViewAndroid::ComputeEventLatencyOSTouchHistograms(
const ui::MotionEvent& event) {
base::TimeTicks event_time = event.GetEventTime();
base::TimeTicks current_time = base::TimeTicks::Now();
ui::EventType event_type;
switch (event.GetAction()) {
case ui::MotionEvent::Action::DOWN:
case ui::MotionEvent::Action::POINTER_DOWN:
event_type = ui::EventType::kTouchPressed;
break;
case ui::MotionEvent::Action::MOVE:
event_type = ui::EventType::kTouchMoved;
break;
case ui::MotionEvent::Action::UP:
case ui::MotionEvent::Action::POINTER_UP:
event_type = ui::EventType::kTouchReleased;
break;
default:
return;
}
ui::ComputeEventLatencyOS(event_type, event_time, current_time);
}
void RenderWidgetHostViewAndroid::CreateOverscrollControllerIfPossible() {
// an OverscrollController is already set
if (overscroll_controller_)
return;
RenderWidgetHostDelegate* delegate = host()->delegate();
if (!delegate)
return;
RenderViewHostDelegateView* delegate_view = delegate->GetDelegateView();
// render_widget_host_unittest.cc uses an object called
// MockRenderWidgetHostDelegate that does not have a DelegateView
if (!delegate_view)
return;
ui::OverscrollRefreshHandler* overscroll_refresh_handler =
delegate_view->GetOverscrollRefreshHandler();
if (!overscroll_refresh_handler)
return;
if (!view_.parent())
return;
// If window_android is null here, this is bad because we don't listen for it
// being set, so we won't be able to construct the OverscrollController at the
// proper time.
ui::WindowAndroid* window_android = view_.GetWindowAndroid();
if (!window_android)
return;
ui::WindowAndroidCompositor* compositor = window_android->GetCompositor();
if (!compositor)
return;
overscroll_controller_ = std::make_unique<OverscrollControllerAndroid>(
overscroll_refresh_handler, compositor, view_.GetDipScale());
}
void RenderWidgetHostViewAndroid::SetOverscrollControllerForTesting(
ui::OverscrollRefreshHandler* overscroll_refresh_handler) {
overscroll_controller_ = std::make_unique<OverscrollControllerAndroid>(
overscroll_refresh_handler, view_.GetWindowAndroid()->GetCompositor(),
view_.GetDipScale());
}
void RenderWidgetHostViewAndroid::TakeFallbackContentFrom(
RenderWidgetHostView* view) {
DCHECK(!static_cast<RenderWidgetHostViewBase*>(view)
->IsRenderWidgetHostViewChildFrame());
CopyBackgroundColorIfPresentFrom(*view);
RenderWidgetHostViewAndroid* view_android =
static_cast<RenderWidgetHostViewAndroid*>(view);
if (!delegated_frame_host_ || !view_android->delegated_frame_host_)
return;
delegated_frame_host_->TakeFallbackContentFrom(
view_android->delegated_frame_host_.get());
}
void RenderWidgetHostViewAndroid::OnSynchronizedDisplayPropertiesChanged(
bool rotation) {
if (screen_state_change_handler_.OnScreenInfoChanged(GetScreenInfo()))
return;
SynchronizeVisualProperties(cc::DeadlinePolicy::UseDefaultDeadline(),
std::nullopt);
}
std::optional<SkColor> RenderWidgetHostViewAndroid::GetBackgroundColor() {
return default_background_color_;
}
void RenderWidgetHostViewAndroid::DidNavigate() {
if (!delegated_frame_host_) {
RenderWidgetHostViewBase::DidNavigate();
return;
}
if (!is_showing_) {
// Navigating while hidden should not allocate a new LocalSurfaceID. Once
// sizes are ready, or we begin to Show, we can then allocate the new
// LocalSurfaceId.
EvictInternal();
navigation_while_hidden_ = true;
} else {
// TODO(jonross): This was a legacy optimization to not perform too many
// Surface Synchronization iterations for the first navigation. However we
// currently are performing 5 full synchornizations before navigation
// completes anyways. So we need to re-do RWHVA setup.
// (https://crbug.com/1245652)
//
// In the interim we will not allocate a new Surface as long as the Renderer
// has yet to produce any content. If we have existing content always
// allocate a new surface, as the content will be from a pre-navigation
// source.
if (!pre_navigation_content_) {
SynchronizeVisualProperties(cc::DeadlinePolicy::UseExistingDeadline(),
std::nullopt,
/*reuse_current_local_surface_id=*/true);
} else {
SynchronizeVisualProperties(cc::DeadlinePolicy::UseExistingDeadline(),
std::nullopt);
}
// Only notify of navigation once a surface has been embedded.
delegated_frame_host_->DidNavigate();
}
pre_navigation_content_ = true;
}
WebContentsAccessibility*
RenderWidgetHostViewAndroid::GetWebContentsAccessibility() {
return web_contents_accessibility_;
}
viz::ScopedSurfaceIdAllocator
RenderWidgetHostViewAndroid::DidUpdateVisualProperties(
const cc::RenderFrameMetadata& metadata) {
base::OnceCallback<void()> allocation_task = base::BindOnce(
&RenderWidgetHostViewAndroid::OnDidUpdateVisualPropertiesComplete,
weak_ptr_factory_.GetWeakPtr(), metadata);
return viz::ScopedSurfaceIdAllocator(std::move(allocation_task));
}
display::ScreenInfo RenderWidgetHostViewAndroid::GetScreenInfo() const {
bool use_window_wide_color_gamut =
GetContentClient()->browser()->GetWideColorGamutHeuristic() ==
ContentBrowserClient::WideColorGamutHeuristic::kUseWindow;
auto* window = view_.GetWindowAndroid();
if (!window || !use_window_wide_color_gamut) {
return RenderWidgetHostViewBase::GetScreenInfo();
}
display::ScreenInfo screen_info;
display::DisplayUtil::DisplayToScreenInfo(
&screen_info, window->GetDisplayWithWindowColorSpace());
return screen_info;
}
void RenderWidgetHostViewAndroid::ObserveDevicePosturePlatformProvider() {
if (device_posture_observation_.IsObserving()) {
return;
}
DevicePosturePlatformProvider* platform_provider =
GetDevicePosturePlatformProvider();
if (!platform_provider) {
return;
}
device_posture_observation_.Observe(platform_provider);
OnDisplayFeatureBoundsChanged(platform_provider->GetDisplayFeatureBounds());
}
void RenderWidgetHostViewAndroid::OnDisplayFeatureBoundsChanged(
const gfx::Rect& display_feature_bounds) {
if (display_feature_overridden_for_testing_) {
return;
}
display_feature_ = std::nullopt;
display_feature_bounds_ = gfx::Rect();
// On some devices like the Galaxy Fold the display feature has a size of
// 0 (width or height depending on the orientation). IsEmpty() will fail here.
if (display_feature_bounds.size().IsZero()) {
SynchronizeVisualProperties(cc::DeadlinePolicy::UseDefaultDeadline(),
std::nullopt);
return;
}
display_feature_bounds_ = display_feature_bounds;
ComputeDisplayFeature();
SynchronizeVisualProperties(cc::DeadlinePolicy::UseDefaultDeadline(),
std::nullopt);
}
void RenderWidgetHostViewAndroid::SetDisplayFeatureBoundsForTesting(
const gfx::Rect& bounds) {
display_feature_bounds_ = bounds;
ComputeDisplayFeature();
}
void RenderWidgetHostViewAndroid::ComputeDisplayFeature() {
if (display_feature_overridden_for_testing_) {
return;
}
display_feature_ = std::nullopt;
gfx::Size view_size(view_.GetSize());
// On some devices like the Galaxy Fold the display feature has a size of
// 0 (width or height depending on the orientation). IsEmpty() will fail here.
if (display_feature_bounds_.size().IsZero() || view_size.IsEmpty()) {
return;
}
// On Android, the display feature is exposed as a rectangle as a generic
// concept. Here in the content layer, we translate that to a more
// constrained concept, see content::DisplayFeature.
// The display feature and view location are both provided in device pixels,
// relative to the window. Convert this to DIP and view relative coordinates,
// first by applying the scale, converting the display feature to view
// relative coordinates, then intersect with the view bounds rect.
// the convert to view-relative coordinates.
float dip_scale = 1 / view_.GetDipScale();
gfx::Point view_location = view_.GetLocationOfContainerViewInWindow();
view_location = gfx::ScaleToRoundedPoint(view_location, dip_scale);
gfx::Rect transformed_display_feature =
gfx::ScaleToRoundedRect(display_feature_bounds_, dip_scale);
transformed_display_feature.Offset(-view_location.x(), -view_location.y());
transformed_display_feature.InclusiveIntersect(gfx::Rect(view_size));
if (transformed_display_feature.x() == 0 &&
transformed_display_feature.width() == view_size.width()) {
// A horizontal display feature covers the view's width and starts at
// an x-offset of 0.
display_feature_ = {DisplayFeature::Orientation::kHorizontal,
transformed_display_feature.y(),
transformed_display_feature.height()};
} else if (transformed_display_feature.y() == 0 &&
transformed_display_feature.height() == view_size.height()) {
// A vertical display feature covers the view's height and starts at
// a y-offset of 0.
display_feature_ = {DisplayFeature::Orientation::kVertical,
transformed_display_feature.x(),
transformed_display_feature.width()};
}
}
std::optional<DisplayFeature> RenderWidgetHostViewAndroid::GetDisplayFeature() {
return display_feature_;
}
void RenderWidgetHostViewAndroid::SetDisplayFeatureForTesting(
const DisplayFeature* display_feature) {
if (display_feature) {
display_feature_ = *display_feature;
} else {
display_feature_ = std::nullopt;
}
display_feature_overridden_for_testing_ = true;
}
void RenderWidgetHostViewAndroid::NotifyHostAndDelegateOnWasShown(
blink::mojom::RecordContentToVisibleTimeRequestPtr visible_time_request) {
// Whether evicted or not, we stop batching for rotation in order to get
// content ready for the new orientation.
bool rotation_override = in_rotation_;
in_rotation_ = false;
view_.GetLayer()->SetHideLayerAndSubtree(false);
if (overscroll_controller_)
overscroll_controller_->Enable();
bool was_evicted = false;
if ((delegated_frame_host_ &&
delegated_frame_host_->IsPrimarySurfaceEvicted()) ||
!local_surface_id_allocator_.HasValidLocalSurfaceId()) {
was_evicted = true;
ui::WindowAndroidCompositor* compositor =
view_.GetWindowAndroid() ? view_.GetWindowAndroid()->GetCompositor()
: nullptr;
SynchronizeVisualProperties(
compositor && compositor->IsDrawingFirstVisibleFrame()
? cc::DeadlinePolicy::UseSpecifiedDeadline(
ui::DelegatedFrameHostAndroid::FirstFrameTimeoutFrames())
: cc::DeadlinePolicy::UseDefaultDeadline(),
std::nullopt);
// If we navigated while hidden, we need to update the fallback surface only
// after we've completed navigation, and embedded the new surface. The
// |delegated_frame_host_| is always valid when |navigation_while_hidden_|
// is set to true.
if (navigation_while_hidden_) {
navigation_while_hidden_ = false;
delegated_frame_host_->DidNavigate();
}
} else if (rotation_override) {
// If a rotation occurred while this was not visible, we need to allocate a
// new viz::LocalSurfaceId and send the current visual properties to the
// Renderer. Otherwise there will be no content at all to display.
//
// The rotation process will complete after this first surface is displayed.
SynchronizeVisualProperties(cc::DeadlinePolicy::UseDefaultDeadline(),
std::nullopt);
}
// Whenever the page is restored, via back-forward cache, or tab changes,
// record content to visible time.
bool show_reason_bfcache_restore =
visible_time_request ? visible_time_request->show_reason_bfcache_restore
: false;
bool has_saved_frame = delegated_frame_host_->HasSavedFrame();
if (show_reason_bfcache_restore) {
host()->WasShown(visible_time_request.Clone());
} else {
host()->WasShown(has_saved_frame
? blink::mojom::RecordContentToVisibleTimeRequestPtr()
: visible_time_request.Clone());
}
if (delegated_frame_host_) {
delegated_frame_host_->WasShown(
local_surface_id_allocator_.GetCurrentLocalSurfaceId(),
GetCompositorViewportPixelSize(), host()->delegate()->IsFullscreen(),
has_saved_frame ? std::move(visible_time_request)
: blink::mojom::RecordContentToVisibleTimeRequestPtr());
}
if (view_.parent() && view_.GetWindowAndroid()) {
StartObservingRootWindow();
if (sync_compositor_)
sync_compositor_->RequestOneBeginFrame();
}
if (rotation_override) {
// It's possible that several rotations were all enqueued while this view
// has hidden. We skip those and update to just the final state.
size_t skipped_rotations = rotation_metrics_.size() - 1;
if (skipped_rotations) {
rotation_metrics_.erase(rotation_metrics_.begin(),
rotation_metrics_.begin() + skipped_rotations);
}
// If a rotation occurred while we were hidden, we do not want to include
// all of that idle time in the rotation metrics. However we do want to have
// the "RotationBegin" tracing event. So end the tracing event, before
// setting the starting time of the rotation.
EndRotationBatching();
rotation_metrics_.begin()->first = base::TimeTicks::Now();
BeginRotationEmbed();
} else if (!rotation_metrics_.empty()) {
// If we have enqueued `rotation_metrics` but are not completing a rotation,
// then a timeout fired while we were hidden. As no synchronizing has
// previously occurred, set now to be the start of the rotation time.
rotation_metrics_.begin()->first = base::TimeTicks::Now();
}
// TODO(crbug.com/40879074): Ideally we would do no synchronizing at all when
// hidden. We should just amass all the new blink::VisualProperties and send
// them once when becoming visible. However the refactor would be difficult
// right now. We will revisit this once we are satisfied with the rollout of
// content::kSurfaceSyncFullscreenKillswitch.
if (was_evicted)
screen_state_change_handler_.WasShownAfterEviction();
}
void RenderWidgetHostViewAndroid::
RequestSuccessfulPresentationTimeFromHostOrDelegate(
blink::mojom::RecordContentToVisibleTimeRequestPtr
visible_time_request) {
bool has_saved_frame = delegated_frame_host_->HasSavedFrame();
// No need to check for saved frames for the case of bfcache restore.
if (visible_time_request->show_reason_bfcache_restore || !has_saved_frame) {
host()->RequestSuccessfulPresentationTimeForNextFrame(
visible_time_request.Clone());
}
// If the frame for the renderer is already available, then the
// tab-switching time is the presentation time for the browser-compositor.
if (has_saved_frame) {
delegated_frame_host_->RequestSuccessfulPresentationTimeForNextFrame(
std::move(visible_time_request));
}
}
void RenderWidgetHostViewAndroid::
CancelSuccessfulPresentationTimeRequestForHostAndDelegate() {
host()->CancelSuccessfulPresentationTimeRequest();
delegated_frame_host_->CancelSuccessfulPresentationTimeRequest();
}
void RenderWidgetHostViewAndroid::EnterFullscreenMode(
const blink::mojom::FullscreenOptions& options) {
screen_state_change_handler_.EnterFullscreenMode();
}
void RenderWidgetHostViewAndroid::ExitFullscreenMode() {
screen_state_change_handler_.ExitFullscreenMode();
}
void RenderWidgetHostViewAndroid::LockOrientation(
device::mojom::ScreenOrientationLockType orientation) {
screen_state_change_handler_.LockOrientation(orientation);
}
void RenderWidgetHostViewAndroid::UnlockOrientation() {
screen_state_change_handler_.UnlockOrientation();
}
void RenderWidgetHostViewAndroid::SetHasPersistentVideo(
bool has_persistent_video) {
screen_state_change_handler_.SetHasPersistentVideo(has_persistent_video);
}
void RenderWidgetHostViewAndroid::InvalidateLocalSurfaceIdAndAllocationGroup() {
local_surface_id_allocator_.Invalidate(
/*also_invalidate_allocation_group=*/true);
}
void RenderWidgetHostViewAndroid::HandleSwipeToMoveCursorGestureAck(
const blink::WebGestureEvent& event) {
if (!touch_selection_controller_ || !selection_popup_controller_) {
swipe_to_move_cursor_activated_ = false;
return;
}
switch (event.GetType()) {
case blink::WebInputEvent::Type::kGestureScrollBegin: {
if (!event.data.scroll_begin.cursor_control)
break;
swipe_to_move_cursor_activated_ = true;
touch_selection_controller_->OnSwipeToMoveCursorBegin();
OnSelectionEvent(ui::INSERTION_HANDLE_DRAG_STARTED);
break;
}
case blink::WebInputEvent::Type::kGestureScrollUpdate: {
if (!swipe_to_move_cursor_activated_)
break;
gfx::RectF rect = touch_selection_controller_->GetRectBetweenBounds();
// Suppress this when the input is not focused, in which case rect will be
// 0x0.
if (rect.width() != 0.f || rect.height() != 0.f) {
selection_popup_controller_->OnDragUpdate(
ui::TouchSelectionDraggable::Type::kNone,
gfx::PointF(event.PositionInWidget().x(), rect.right_center().y()));
}
break;
}
case blink::WebInputEvent::Type::kGestureScrollEnd: {
if (!swipe_to_move_cursor_activated_)
break;
swipe_to_move_cursor_activated_ = false;
touch_selection_controller_->OnSwipeToMoveCursorEnd();
OnSelectionEvent(ui::INSERTION_HANDLE_DRAG_STOPPED);
break;
}
default:
break;
}
}
void RenderWidgetHostViewAndroid::WasEvicted() {
// Eviction can occur when the CompositorFrameSink has changed. This can
// occur either from a lost connection, as well as from the initial conneciton
// upon creating RenderWidgetHostViewAndroid. When this occurs while visible
// a new LocalSurfaceId should be generated. If eviction occurs while not
// visible, then the new LocalSurfaceId can be allocated upon the next Show.
if (is_showing_) {
local_surface_id_allocator_.GenerateId();
// Guarantee that the new LocalSurfaceId is propagated. Rather than relying
// upon calls to Show() and OnDidUpdateVisualPropertiesComplete(). As there
// is no guarantee that they will occur after the eviction.
SynchronizeVisualProperties(
cc::DeadlinePolicy::UseExistingDeadline(),
local_surface_id_allocator_.GetCurrentLocalSurfaceId());
} else {
EvictInternal();
}
if (sync_compositor_) {
sync_compositor_->WasEvicted();
}
}
void RenderWidgetHostViewAndroid::OnUpdateScopedSelectionHandles() {
if (!observing_root_window_ ||
!touch_selection_controller_client_manager_->has_active_selection()) {
scoped_selection_handles_.reset();
return;
}
if (!scoped_selection_handles_) {
scoped_selection_handles_ =
std::make_unique<ui::WindowAndroid::ScopedSelectionHandles>(
view_.GetWindowAndroid());
}
}
void RenderWidgetHostViewAndroid::SetWebContentsAccessibility(
WebContentsAccessibilityAndroid* web_contents_accessibility) {
web_contents_accessibility_ = web_contents_accessibility;
UpdateRootScrollOffsetUpdateFrequency();
}
void RenderWidgetHostViewAndroid::SetNeedsBeginFrameForFlingProgress() {
if (sync_compositor_)
sync_compositor_->RequestOneBeginFrame();
}
const cc::slim::SurfaceLayer* RenderWidgetHostViewAndroid::GetSurfaceLayer()
const {
if (!delegated_frame_host_) {
return nullptr;
}
return delegated_frame_host_->content_layer();
}
void RenderWidgetHostViewAndroid::RegisterOffsetTags(
const cc::BrowserControlsOffsetTagsInfo& tags_info) {
if (delegated_frame_host_) {
delegated_frame_host_->RegisterOffsetTags(tags_info);
}
}
void RenderWidgetHostViewAndroid::UnregisterOffsetTags(
const cc::BrowserControlsOffsetTagsInfo& tags_info) {
if (delegated_frame_host_) {
delegated_frame_host_->UnregisterOffsetTags(tags_info);
}
}
void RenderWidgetHostViewAndroid::PassImeRenderWidgetHost(
mojo::PendingRemote<blink::mojom::ImeRenderWidgetHost> pending_remote) {
host()->PassImeRenderWidgetHost(std::move(pending_remote));
}
void RenderWidgetHostViewAndroid::BeginRotationBatching() {
in_rotation_ = true;
rotation_metrics_.emplace_back(
std::make_pair(base::TimeTicks::Now(), viz::LocalSurfaceId()));
// When a rotation begins, a series of calls update different aspects of
// visual properties. Completing in EndRotationBatching, where the full new
// set of properties is known. Trace the duration of that.
const auto delta = rotation_metrics_.back().first - base::TimeTicks();
TRACE_EVENT_NESTABLE_ASYNC_BEGIN1(
"viz", "RenderWidgetHostViewAndroid::RotationBegin",
TRACE_ID_LOCAL(delta.InNanoseconds()), "visible", is_showing_);
if (rotation_timeout_.IsRunning())
rotation_timeout_.Stop();
rotation_timeout_.Start(
FROM_HERE, kThrottleTimeout,
base::BindOnce(
&RenderWidgetHostViewAndroid::EndRotationAndSyncIfNecessary,
base::Unretained(this)));
}
void RenderWidgetHostViewAndroid::EndRotationBatching() {
in_rotation_ = false;
// Always clear when ending batching. As WebView can trigger multiple
// OnPhysicalBackingSizeChanged which would re-trigger rotation if we were
// still tracking `fullscreen_rotation_`. crbug.com/1302964
fullscreen_rotation_ = false;
DCHECK(!rotation_metrics_.empty());
const auto delta = rotation_metrics_.back().first - base::TimeTicks();
TRACE_EVENT_NESTABLE_ASYNC_END1(
"viz", "RenderWidgetHostViewAndroid::RotationBegin",
TRACE_ID_LOCAL(delta.InNanoseconds()), "local_surface_id",
local_surface_id_allocator_.GetCurrentLocalSurfaceId().ToString());
if (rotation_timeout_.IsRunning())
rotation_timeout_.Stop();
}
void RenderWidgetHostViewAndroid::BeginRotationEmbed() {
DCHECK(!rotation_metrics_.empty());
rotation_metrics_.back().second =
local_surface_id_allocator_.GetCurrentLocalSurfaceId();
// The full set of visual properties for a rotation is now known. This
// tracks the time it takes until the Renderer successfully submits a frame
// embedding the new viz::LocalSurfaceId. Tracking how long until a user
// sees the complete rotation and layout of the page. This completes in
// OnRenderFrameMetadataChangedAfterActivation.
TRACE_EVENT_NESTABLE_ASYNC_BEGIN_WITH_TIMESTAMP1(
"viz", "RenderWidgetHostViewAndroid::RotationEmbed",
TRACE_ID_LOCAL(
local_surface_id_allocator_.GetCurrentLocalSurfaceId().hash()),
base::TimeTicks::Now(), "LocalSurfaceId",
local_surface_id_allocator_.GetCurrentLocalSurfaceId().ToString());
}
void RenderWidgetHostViewAndroid::EndRotationAndSyncIfNecessary() {
if (!in_rotation_)
return;
EndRotationBatching();
if (is_showing_) {
SynchronizeVisualProperties(cc::DeadlinePolicy::UseDefaultDeadline(),
std::nullopt,
/*reuse_current_local_surface_id=*/false,
/*ignore_ack=*/true);
} else {
// If hidden, generate a new viz::LocalSurfaceId to represent the new set of
// blink::VisualProperties. However do not synchronize them to perform
// layout. The subsequent Show will lead to embedding (crbug.com/1383446)
local_surface_id_allocator_.GenerateId();
}
BeginRotationEmbed();
}
void RenderWidgetHostViewAndroid::EvictInternal() {
screen_state_change_handler_.WasEvicted();
local_surface_id_allocator_.Invalidate();
}
} // namespace content