| // 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/web_contents/web_contents_view_android.h" |
| |
| #include <memory> |
| #include <optional> |
| #include <utility> |
| |
| #include "base/android/jni_android.h" |
| #include "base/android/jni_string.h" |
| #include "base/check.h" |
| #include "base/feature_list.h" |
| #include "base/files/file_path.h" |
| #include "base/notimplemented.h" |
| #include "cc/layers/layer.h" |
| #include "cc/slim/layer.h" |
| #include "components/input/features.h" |
| #include "content/browser/accessibility/browser_accessibility_manager_android.h" |
| #include "content/browser/android/content_ui_event_handler.h" |
| #include "content/browser/android/drop_data_android.h" |
| #include "content/browser/android/gesture_listener_manager.h" |
| #include "content/browser/android/select_popup.h" |
| #include "content/browser/android/selection/selection_popup_controller.h" |
| #include "content/browser/navigation_transitions/back_forward_transition_animation_manager_android.h" |
| #include "content/browser/renderer_host/render_view_host_factory.h" |
| #include "content/browser/renderer_host/render_view_host_impl.h" |
| #include "content/browser/renderer_host/render_widget_host_view_android.h" |
| #include "content/browser/web_contents/web_contents_impl.h" |
| #include "content/common/features.h" |
| #include "content/public/browser/android/synchronous_compositor.h" |
| #include "content/public/browser/render_widget_host.h" |
| #include "content/public/browser/web_contents_delegate.h" |
| #include "content/public/common/content_client.h" |
| #include "content/public/common/content_features.h" |
| #include "content/public/common/drop_data.h" |
| #include "net/base/mime_util.h" |
| #include "ui/android/overscroll_refresh_handler.h" |
| #include "ui/base/clipboard/clipboard.h" |
| #include "ui/base/clipboard/clipboard_constants.h" |
| #include "ui/base/clipboard/file_info.h" |
| #include "ui/base/dragdrop/mojom/drag_drop_types.mojom.h" |
| #include "ui/base/ui_base_features.h" |
| #include "ui/display/display_util.h" |
| #include "ui/display/screen.h" |
| #include "ui/events/android/drag_event_android.h" |
| #include "ui/events/android/gesture_event_android.h" |
| #include "ui/events/android/key_event_android.h" |
| #include "ui/events/android/motion_event_android.h" |
| #include "ui/gfx/android/java_bitmap.h" |
| #include "ui/gfx/android/view_configuration.h" |
| #include "ui/gfx/image/image_skia.h" |
| |
| // Must come after all headers that specialize FromJniType() / ToJniType(). |
| #include "content/public/android/jar_jni/DragEvent_jni.h" |
| |
| using base::android::AppendJavaStringArrayToStringVector; |
| using base::android::AttachCurrentThread; |
| using base::android::ConvertJavaStringToUTF16; |
| using base::android::ScopedJavaLocalRef; |
| |
| namespace content { |
| |
| namespace { |
| |
| WebContentsViewAndroid::RenderWidgetHostViewCreateFunction |
| g_create_render_widget_host_view = nullptr; |
| |
| // Returns the minimum distance in DIPs, for drag event being considered as an |
| // intentional drag. |
| int DragMovementThresholdDip() { |
| static int radius = features::kTouchDragMovementThresholdDip.Get(); |
| return radius; |
| } |
| |
| // True if we want to disable Android native event batching and use |
| // compositor event queue. |
| bool ShouldRequestUnbufferedDispatch() { |
| static bool should_request_unbuffered_dispatch = |
| !GetContentClient()->UsingSynchronousCompositing() && |
| !base::FeatureList::IsEnabled( |
| input::features::kUseAndroidBufferedInputDispatch); |
| return should_request_unbuffered_dispatch; |
| } |
| |
| bool IsDragAndDropEnabled() { |
| // Cache the feature flag value so it isn't queried on every drag start. |
| static const bool drag_feature_enabled = |
| base::FeatureList::IsEnabled(features::kTouchDragAndContextMenu); |
| return drag_feature_enabled; |
| } |
| |
| bool IsDragEnabledForDropData(const DropData& drop_data) { |
| return IsDragAndDropEnabled() || drop_data.text.has_value(); |
| } |
| } |
| |
| // static |
| void SynchronousCompositor::SetClientForWebContents( |
| WebContents* contents, |
| SynchronousCompositorClient* client) { |
| DCHECK(contents); |
| DCHECK(client); |
| WebContentsViewAndroid* wcva = static_cast<WebContentsViewAndroid*>( |
| static_cast<WebContentsImpl*>(contents)->GetView()); |
| DCHECK(!wcva->synchronous_compositor_client()); |
| wcva->set_synchronous_compositor_client(client); |
| RenderWidgetHostViewAndroid* rwhv = static_cast<RenderWidgetHostViewAndroid*>( |
| contents->GetRenderWidgetHostView()); |
| if (rwhv) |
| rwhv->SetSynchronousCompositorClient(client); |
| } |
| |
| std::unique_ptr<WebContentsView> CreateWebContentsView( |
| WebContentsImpl* web_contents, |
| std::unique_ptr<WebContentsViewDelegate> delegate, |
| raw_ptr<RenderViewHostDelegateView>* render_view_host_delegate_view) { |
| auto rv = std::make_unique<WebContentsViewAndroid>(web_contents, |
| std::move(delegate)); |
| *render_view_host_delegate_view = rv.get(); |
| return rv; |
| } |
| |
| // static |
| void WebContentsViewAndroid::InstallCreateHookForTests( |
| RenderWidgetHostViewCreateFunction create_render_widget_host_view) { |
| CHECK_EQ(nullptr, g_create_render_widget_host_view); |
| g_create_render_widget_host_view = create_render_widget_host_view; |
| } |
| |
| WebContentsViewAndroid::WebContentsViewAndroid( |
| WebContentsImpl* web_contents, |
| std::unique_ptr<WebContentsViewDelegate> delegate) |
| : web_contents_(web_contents), |
| delegate_(std::move(delegate)), |
| view_(ui::ViewAndroid::LayoutType::kNormal), |
| synchronous_compositor_client_(nullptr) { |
| view_.SetLayer(cc::slim::Layer::Create()); |
| view_.set_event_handler(this); |
| |
| // `rwhva_parent_` is a child layer of `view_`. |
| parent_for_web_page_widgets_ = cc::slim::Layer::Create(); |
| view_.GetLayer()->AddChild(parent_for_web_page_widgets_); |
| |
| if (base::FeatureList::IsEnabled(blink::features::kBackForwardTransitions)) { |
| back_forward_animation_manager_ = |
| std::make_unique<BackForwardTransitionAnimationManagerAndroid>( |
| this, &web_contents_->GetController()); |
| } |
| |
| drag_drop_oopif_enabled_ = |
| base::FeatureList::IsEnabled(features::kAndroidDragDropOopif); |
| } |
| |
| WebContentsViewAndroid::~WebContentsViewAndroid() { |
| // The animation manager holds a reference to `parent_for_web_page_widgets_`. |
| // Explicitly destroy the animation manager before resetting |
| // `parent_for_web_page_widgets_`. |
| if (back_forward_animation_manager_) { |
| back_forward_animation_manager_.reset(); |
| } |
| |
| // Opposite to the construction order - disconnect the child first. |
| parent_for_web_page_widgets_->RemoveFromParent(); |
| parent_for_web_page_widgets_.reset(); |
| |
| if (view_.GetLayer()) |
| view_.GetLayer()->RemoveFromParent(); |
| view_.set_event_handler(nullptr); |
| } |
| |
| void WebContentsViewAndroid::SetContentUiEventHandler( |
| std::unique_ptr<ContentUiEventHandler> handler) { |
| content_ui_event_handler_ = std::move(handler); |
| } |
| |
| void WebContentsViewAndroid::SetOverscrollRefreshHandler( |
| std::unique_ptr<ui::OverscrollRefreshHandler> overscroll_refresh_handler) { |
| overscroll_refresh_handler_ = std::move(overscroll_refresh_handler); |
| auto* rwhv = web_contents_->GetRenderWidgetHostView(); |
| if (rwhv) { |
| static_cast<RenderWidgetHostViewAndroid*>(rwhv) |
| ->OnOverscrollRefreshHandlerAvailable(); |
| } |
| } |
| |
| ui::OverscrollRefreshHandler* |
| WebContentsViewAndroid::GetOverscrollRefreshHandler() const { |
| return overscroll_refresh_handler_.get(); |
| } |
| |
| gfx::NativeView WebContentsViewAndroid::GetNativeView() const { |
| return const_cast<gfx::NativeView>(&view_); |
| } |
| |
| gfx::NativeView WebContentsViewAndroid::GetContentNativeView() const { |
| RenderWidgetHostView* rwhv = web_contents_->GetRenderWidgetHostView(); |
| if (rwhv) |
| return rwhv->GetNativeView(); |
| |
| // TODO(sievers): This should return null. |
| return GetNativeView(); |
| } |
| |
| RenderWidgetHostViewAndroid* |
| WebContentsViewAndroid::GetRenderWidgetHostViewAndroid() { |
| RenderWidgetHostView* rwhv = nullptr; |
| rwhv = web_contents_->GetRenderWidgetHostView(); |
| return static_cast<RenderWidgetHostViewAndroid*>(rwhv); |
| } |
| |
| gfx::NativeWindow WebContentsViewAndroid::GetTopLevelNativeWindow() const { |
| return view_.GetWindowAndroid(); |
| } |
| |
| gfx::Rect WebContentsViewAndroid::GetContainerBounds() const { |
| return GetViewBounds(); |
| } |
| |
| void WebContentsViewAndroid::SetPageTitle(const std::u16string& title) { |
| // Do nothing. |
| } |
| |
| void WebContentsViewAndroid::Focus() { |
| auto* rwhv = web_contents_->GetRenderWidgetHostView(); |
| if (rwhv) |
| static_cast<RenderWidgetHostViewAndroid*>(rwhv)->Focus(); |
| } |
| |
| void WebContentsViewAndroid::SetInitialFocus() { |
| if (web_contents_->FocusLocationBarByDefault()) |
| web_contents_->SetFocusToLocationBar(); |
| else |
| Focus(); |
| } |
| |
| void WebContentsViewAndroid::StoreFocus() { |
| NOTIMPLEMENTED(); |
| } |
| |
| void WebContentsViewAndroid::RestoreFocus() { |
| NOTIMPLEMENTED(); |
| } |
| |
| void WebContentsViewAndroid::FocusThroughTabTraversal(bool reverse) { |
| web_contents_->GetRenderViewHost()->SetInitialFocus(reverse); |
| } |
| |
| DropData* WebContentsViewAndroid::GetDropData() const { |
| return drop_data_.get(); |
| } |
| |
| gfx::Rect WebContentsViewAndroid::GetViewBounds() const { |
| return gfx::Rect(view_.GetSizeDIPs()); |
| } |
| |
| void WebContentsViewAndroid::CreateView(gfx::NativeView context) {} |
| |
| RenderWidgetHostViewBase* WebContentsViewAndroid::CreateViewForWidget( |
| RenderWidgetHost* render_widget_host) { |
| if (render_widget_host->GetView()) { |
| // During testing, the view will already be set up in most cases to the |
| // test view, so we don't want to clobber it with a real one. To verify that |
| // this actually is happening (and somebody isn't accidentally creating the |
| // view twice), we check for the RVH Factory, which will be set when we're |
| // making special ones (which go along with the special views). |
| DCHECK(RenderViewHostFactory::has_factory()); |
| return static_cast<RenderWidgetHostViewBase*>( |
| render_widget_host->GetView()); |
| } |
| // Note that while this instructs the render widget host to reference |
| // |native_view_|, this has no effect without also instructing the |
| // native view (i.e. ContentView) how to obtain a reference to this widget in |
| // order to paint it. |
| RenderWidgetHostImpl* rwhi = RenderWidgetHostImpl::From(render_widget_host); |
| auto* rwhv = g_create_render_widget_host_view |
| ? g_create_render_widget_host_view( |
| rwhi, &view_, parent_for_web_page_widgets_.get()) |
| : new RenderWidgetHostViewAndroid( |
| rwhi, &view_, parent_for_web_page_widgets_.get()); |
| rwhv->SetSynchronousCompositorClient(synchronous_compositor_client_); |
| return rwhv; |
| } |
| |
| RenderWidgetHostViewBase* WebContentsViewAndroid::CreateViewForChildWidget( |
| RenderWidgetHost* render_widget_host) { |
| RenderWidgetHostImpl* rwhi = RenderWidgetHostImpl::From(render_widget_host); |
| return new RenderWidgetHostViewAndroid(rwhi, /*parent_native_view=*/nullptr, |
| /*parent_layer=*/nullptr); |
| } |
| |
| void WebContentsViewAndroid::RenderViewReady() { |
| if (device_orientation_ == 0) |
| return; |
| auto* rwhva = GetRenderWidgetHostViewAndroid(); |
| if (rwhva) |
| rwhva->UpdateScreenInfo(); |
| |
| web_contents_->OnScreenOrientationChange(); |
| } |
| |
| void WebContentsViewAndroid::RenderViewHostChanged(RenderViewHost* old_host, |
| RenderViewHost* new_host) { |
| if (old_host) { |
| auto* rwhv = old_host->GetWidget()->GetView(); |
| if (rwhv && rwhv->GetNativeView()) { |
| auto* rwhva = static_cast<RenderWidgetHostViewAndroid*>(rwhv); |
| rwhva->UpdateNativeViewTree(/*parent_native_view=*/nullptr, |
| /*parent_layer=*/nullptr); |
| rwhva->UpdateTooltip(std::u16string()); |
| } |
| } |
| |
| auto* rwhv = new_host->GetWidget()->GetView(); |
| if (rwhv && rwhv->GetNativeView()) { |
| static_cast<RenderWidgetHostViewAndroid*>(rwhv)->UpdateNativeViewTree( |
| &view_, parent_for_web_page_widgets_.get()); |
| SetFocus(view_.HasFocus()); |
| } |
| } |
| |
| void WebContentsViewAndroid::SetFocus(bool focused) { |
| auto* rwhva = GetRenderWidgetHostViewAndroid(); |
| if (!rwhva) |
| return; |
| if (focused) |
| rwhva->GotFocus(); |
| else |
| rwhva->LostFocus(); |
| } |
| |
| void WebContentsViewAndroid::SetOverscrollControllerEnabled(bool enabled) { |
| } |
| |
| void WebContentsViewAndroid::OnCapturerCountChanged() {} |
| |
| void WebContentsViewAndroid::FullscreenStateChanged(bool is_fullscreen) { |
| if (is_fullscreen && select_popup_) |
| select_popup_->HideMenu(); |
| } |
| |
| BackForwardTransitionAnimationManager* |
| WebContentsViewAndroid::GetBackForwardTransitionAnimationManager() { |
| return back_forward_animation_manager_.get(); |
| } |
| |
| void WebContentsViewAndroid::DestroyBackForwardTransitionAnimationManager() { |
| back_forward_animation_manager_.reset(); |
| } |
| |
| void WebContentsViewAndroid::ShowContextMenu(RenderFrameHost& render_frame_host, |
| const ContextMenuParams& params) { |
| if (is_active_drag_ && drag_exceeded_movement_threshold_) |
| return; |
| |
| auto* rwhv = static_cast<RenderWidgetHostViewAndroid*>( |
| web_contents_->GetRenderWidgetHostView()); |
| |
| // See if context menu is handled by SelectionController as a selection menu. |
| // If not, use the delegate to show it. |
| if (rwhv && rwhv->ShowSelectionMenu(&render_frame_host, params)) |
| return; |
| |
| if (delegate_) |
| delegate_->ShowContextMenu(render_frame_host, params); |
| } |
| |
| SelectPopup* WebContentsViewAndroid::GetSelectPopup() { |
| if (!select_popup_) |
| select_popup_ = std::make_unique<SelectPopup>(web_contents_); |
| return select_popup_.get(); |
| } |
| |
| SelectionPopupController* |
| WebContentsViewAndroid::GetSelectionPopupController() { |
| SelectionPopupController* controller = nullptr; |
| if (auto* rwhva = GetRenderWidgetHostViewAndroid()) { |
| controller = rwhva->selection_popup_controller(); |
| } |
| return controller; |
| } |
| |
| void WebContentsViewAndroid::ShowPopupMenu( |
| RenderFrameHost* render_frame_host, |
| mojo::PendingRemote<blink::mojom::PopupMenuClient> popup_client, |
| const gfx::Rect& bounds, |
| double item_font_size, |
| int selected_item, |
| std::vector<blink::mojom::MenuItemPtr> menu_items, |
| bool right_aligned, |
| bool allow_multiple_selection) { |
| GetSelectPopup()->ShowMenu(std::move(popup_client), bounds, |
| std::move(menu_items), selected_item, |
| allow_multiple_selection, right_aligned); |
| } |
| |
| void WebContentsViewAndroid::StartDragging( |
| const DropData& drop_data, |
| const url::Origin& source_origin, |
| blink::DragOperationsMask allowed_ops, |
| const gfx::ImageSkia& image, |
| const gfx::Vector2d& cursor_offset, |
| const gfx::Rect& drag_obj_rect, |
| const blink::mojom::DragEventSourceInfo& event_info, |
| RenderWidgetHostImpl* source_rwh) { |
| current_source_rwh_for_drag_ = source_rwh->GetWeakPtr(); |
| if (!IsDragEnabledForDropData(drop_data)) { |
| // Need to clear drag and drop state in blink. |
| OnSystemDragEnded(source_rwh); |
| return; |
| } |
| |
| gfx::NativeView native_view = GetNativeView(); |
| if (!native_view) { |
| // Need to clear drag and drop state in blink. |
| OnSystemDragEnded(source_rwh); |
| return; |
| } |
| |
| if (drag_drop_oopif_enabled_) { |
| drag_security_info_.OnDragInitiated(source_rwh, drop_data); |
| } |
| |
| const SkBitmap* bitmap = image.bitmap(); |
| SkBitmap dummy_bitmap; |
| |
| if (image.size().IsEmpty()) { |
| // An empty drag image is possible if the Javascript sets an empty drag |
| // image on purpose. |
| // Create a dummy 1x1 pixel image to avoid crashes when converting to java |
| // bitmap. |
| dummy_bitmap.allocN32Pixels(1, 1); |
| dummy_bitmap.eraseColor(0); |
| bitmap = &dummy_bitmap; |
| } |
| |
| // TODO(crbug.com/40886472): Consolidate cursor_offset and drag_obj_rect with |
| // drop_data. |
| |
| ScopedJavaLocalRef<jobject> jdrop_data = ToJavaDropData(drop_data); |
| if (!native_view->StartDragAndDrop( |
| gfx::ConvertToJavaBitmap(*bitmap), jdrop_data, cursor_offset.x(), |
| cursor_offset.y(), drag_obj_rect.width(), drag_obj_rect.height())) { |
| // Need to clear drag and drop state in blink. |
| OnSystemDragEnded(source_rwh); |
| return; |
| } |
| |
| if (auto* selection_popup_controller = GetSelectionPopupController()) { |
| selection_popup_controller->HidePopupsAndPreserveSelection(); |
| // Hide the handles temporarily. |
| auto* rwhva = GetRenderWidgetHostViewAndroid(); |
| if (rwhva) |
| rwhva->SetTextHandlesTemporarilyHidden(true); |
| } |
| } |
| |
| void WebContentsViewAndroid::UpdateDragOperation( |
| ui::mojom::DragOperation op, |
| bool document_is_handling_drag) { |
| // Intentional not storing `op` because Android does not support drag and |
| // drop cursor yet. |
| document_is_handling_drag_ = document_is_handling_drag; |
| } |
| |
| // Pass events to the renderer. In order to support OOPIF, we need to call |
| // WebContents::GetRenderWidgetHostAtPointAsynchronously() with the location of |
| // the event to determine which process to send the event to. This function |
| // seems to always return synchronously in this context, but has the potential |
| // to be async if there are pending events queued. |
| // GetRenderWidgetHostAtPointAsynchronously() is called for DRAG_LOCATION |
| // and DROP, but not for DRAG_ENTERED, DRAG_EXITED, or DRAG_ENDED since they do |
| // not contain a location. This creates a potential for events to arrive out of |
| // order, but testing with blink shows that it handles this ok. |
| // |
| // As the mouse moves across a page, if we detect that the RenderWidgetHost |
| // changes, we resend the entered event before sending the update or drop. |
| bool WebContentsViewAndroid::OnDragEvent(const ui::DragEventAndroid& event) { |
| switch (event.action()) { |
| case JNI_DragEvent::ACTION_DRAG_ENTERED: { |
| drag_metadata_.clear(); |
| for (const std::u16string& mime_type : event.mime_types()) { |
| if (mime_type == ui::kMimeTypePlainText16 || |
| mime_type == ui::kMimeTypeHtml16 || |
| mime_type == ui::kMimeTypeMozillaUrl16) { |
| drag_metadata_.push_back(DropData::Metadata::CreateForMimeType( |
| DropData::Kind::STRING, mime_type)); |
| } else { |
| // Create a file extension from the mime type. |
| std::string ext = base::UTF16ToUTF8(mime_type); |
| if (!net::GetPreferredExtensionForMimeType(ext, &ext)) { |
| // Use mime subtype as a fallback. |
| net::ParseMimeTypeWithoutParameter(ext, nullptr, &ext); |
| } |
| drag_metadata_.push_back(DropData::Metadata::CreateForFilePath( |
| base::FilePath("file." + ext))); |
| } |
| } |
| OnDragEntered(event.location(), event.screen_location()); |
| break; |
| } |
| case JNI_DragEvent::ACTION_DRAG_LOCATION: |
| OnDragUpdated(event.location(), event.screen_location()); |
| break; |
| case JNI_DragEvent::ACTION_DROP: { |
| drop_data_ = std::make_unique<DropData>(); |
| drop_data_->did_originate_from_renderer = false; |
| drop_data_->document_is_handling_drag = document_is_handling_drag_; |
| JNIEnv* env = AttachCurrentThread(); |
| std::vector<std::vector<std::string>> filenames; |
| base::android::Java2dStringArrayTo2dStringVector( |
| env, event.GetJavaFilenames(), &filenames); |
| for (const auto& info : filenames) { |
| CHECK_EQ(info.size(), 2u); |
| drop_data_->filenames.push_back( |
| ui::FileInfo(base::FilePath(info[0]), base::FilePath(info[1]))); |
| } |
| if (!event.GetJavaText().is_null()) { |
| drop_data_->text = ConvertJavaStringToUTF16(env, event.GetJavaText()); |
| } |
| if (!event.GetJavaHtml().is_null()) { |
| drop_data_->html = ConvertJavaStringToUTF16(env, event.GetJavaHtml()); |
| } |
| if (!event.GetJavaUrl().is_null()) { |
| drop_data_->url = |
| GURL(ConvertJavaStringToUTF16(env, event.GetJavaUrl())); |
| } |
| |
| OnPerformDrop(event.location(), event.screen_location()); |
| break; |
| } |
| case JNI_DragEvent::ACTION_DRAG_EXITED: |
| OnDragExited(); |
| break; |
| case JNI_DragEvent::ACTION_DRAG_ENDED: |
| OnDragEnded(); |
| break; |
| case JNI_DragEvent::ACTION_DRAG_STARTED: |
| // Nothing meaningful to do. |
| break; |
| } |
| return true; |
| } |
| |
| void WebContentsViewAndroid::OnDragEntered( |
| const gfx::PointF& location, |
| const gfx::PointF& screen_location) { |
| if (drag_drop_oopif_enabled_) { |
| // Android does not pass a valid location for ACTION_DRAG_STARTED, so do not |
| // try to find GetRenderWidgetHostAtPointAsynchronously(). |
| DragEnteredCallback(location, screen_location, |
| static_cast<RenderWidgetHostViewBase*>( |
| web_contents_->GetRenderWidgetHostView()) |
| ->GetWeakPtr()); |
| return; |
| } |
| |
| blink::DragOperationsMask allowed_ops = |
| static_cast<blink::DragOperationsMask>(blink::kDragOperationCopy | |
| blink::kDragOperationMove); |
| web_contents_->GetRenderViewHost() |
| ->GetWidget() |
| ->DragTargetDragEnterWithMetaData(drag_metadata_, location, |
| screen_location, allowed_ops, 0, |
| base::DoNothing()); |
| } |
| |
| void WebContentsViewAndroid::DragEnteredCallback( |
| const gfx::PointF& location, |
| const gfx::PointF& screen_location, |
| base::WeakPtr<RenderWidgetHostViewBase> target) { |
| if (!target) { |
| return; |
| } |
| |
| RenderWidgetHostImpl* target_rwh = |
| RenderWidgetHostImpl::From(target->GetRenderWidgetHost()); |
| if (!drag_security_info_.IsValidDragTarget(target_rwh)) { |
| return; |
| } |
| |
| current_target_rwh_for_drag_ = target_rwh->GetWeakPtr(); |
| |
| blink::DragOperationsMask allowed_ops = |
| static_cast<blink::DragOperationsMask>(blink::kDragOperationCopy | |
| blink::kDragOperationMove); |
| current_target_rwh_for_drag_->DragTargetDragEnterWithMetaData( |
| drag_metadata_, location, screen_location, allowed_ops, 0, |
| base::DoNothing()); |
| } |
| |
| void WebContentsViewAndroid::OnDragUpdated(const gfx::PointF& location, |
| const gfx::PointF& screen_location) { |
| drag_location_ = location; |
| drag_screen_location_ = screen_location; |
| |
| // When drag and drop is enabled, attempt to dismiss the context menu if drag |
| // leaves start location. |
| if (IsDragAndDropEnabled()) { |
| // On Android DragEvent.ACTION_DRAG_ENTER does not have a valid location. |
| // See |
| // https://developer.android.com/develop/ui/views/touch-and-input/drag-drop/concepts#table2. |
| if (!is_active_drag_) { |
| is_active_drag_ = true; |
| drag_entered_location_ = location; |
| } else if (!drag_exceeded_movement_threshold_) { |
| int radius = DragMovementThresholdDip(); |
| if (!drag_location_.IsWithinDistance(drag_entered_location_, radius)) { |
| drag_exceeded_movement_threshold_ = true; |
| if (delegate_) |
| delegate_->DismissContextMenu(); |
| } |
| } |
| } |
| |
| if (drag_drop_oopif_enabled_) { |
| web_contents_->GetRenderWidgetHostAtPointAsynchronously( |
| static_cast<RenderWidgetHostViewBase*>( |
| web_contents_->GetRenderWidgetHostView()), |
| location, |
| base::BindOnce(&WebContentsViewAndroid::DragUpdatedCallback, |
| weak_ptr_factory_.GetWeakPtr(), location, |
| screen_location)); |
| return; |
| } |
| |
| blink::DragOperationsMask allowed_ops = |
| static_cast<blink::DragOperationsMask>(blink::kDragOperationCopy | |
| blink::kDragOperationMove); |
| web_contents_->GetRenderViewHost()->GetWidget()->DragTargetDragOver( |
| location, screen_location, allowed_ops, 0, base::DoNothing()); |
| } |
| |
| void WebContentsViewAndroid::DragUpdatedCallback( |
| const gfx::PointF& location, |
| const gfx::PointF& screen_location, |
| base::WeakPtr<RenderWidgetHostViewBase> target, |
| std::optional<gfx::PointF> transformed_pt) { |
| if (!target) { |
| return; |
| } |
| RenderWidgetHostImpl* target_rwh = |
| RenderWidgetHostImpl::From(target->GetRenderWidgetHost()); |
| if (!drag_security_info_.IsValidDragTarget(target_rwh)) { |
| return; |
| } |
| |
| if (target_rwh != current_target_rwh_for_drag_.get()) { |
| if (current_target_rwh_for_drag_) { |
| gfx::PointF transformed_leave_point = location; |
| static_cast<RenderWidgetHostViewBase*>( |
| web_contents_->GetRenderWidgetHostView()) |
| ->TransformPointToCoordSpaceForView( |
| location, |
| static_cast<RenderWidgetHostViewBase*>( |
| current_target_rwh_for_drag_->GetView()), |
| &transformed_leave_point); |
| current_target_rwh_for_drag_->DragTargetDragLeave(transformed_leave_point, |
| screen_location); |
| } |
| DragEnteredCallback(location, screen_location, target); |
| } |
| |
| blink::DragOperationsMask allowed_ops = |
| static_cast<blink::DragOperationsMask>(blink::kDragOperationCopy | |
| blink::kDragOperationMove); |
| target_rwh->DragTargetDragOver(transformed_pt.value(), drag_screen_location_, |
| allowed_ops, 0, base::DoNothing()); |
| } |
| |
| void WebContentsViewAndroid::OnDragExited() { |
| if (drag_drop_oopif_enabled_) { |
| if (current_target_rwh_for_drag_) { |
| current_target_rwh_for_drag_->DragTargetDragLeave(gfx::PointF(), |
| gfx::PointF()); |
| } |
| } else { |
| web_contents_->GetRenderViewHost()->GetWidget()->DragTargetDragLeave( |
| gfx::PointF(), gfx::PointF()); |
| } |
| } |
| |
| void WebContentsViewAndroid::OnPerformDrop(const gfx::PointF& location, |
| const gfx::PointF& screen_location) { |
| if (drag_drop_oopif_enabled_) { |
| web_contents_->GetRenderWidgetHostAtPointAsynchronously( |
| static_cast<RenderWidgetHostViewBase*>( |
| web_contents_->GetRenderWidgetHostView()), |
| location, |
| base::BindOnce(&WebContentsViewAndroid::PerformDropCallback, |
| weak_ptr_factory_.GetWeakPtr(), location, |
| screen_location)); |
| return; |
| } |
| |
| web_contents_->Focus(); |
| web_contents_->GetRenderViewHost()->GetWidget()->FilterDropData( |
| drop_data_.get()); |
| web_contents_->GetRenderViewHost()->GetWidget()->DragTargetDrop( |
| *drop_data_, location, screen_location, 0, base::DoNothing()); |
| } |
| |
| void WebContentsViewAndroid::PerformDropCallback( |
| const gfx::PointF& location, |
| const gfx::PointF& screen_location, |
| base::WeakPtr<RenderWidgetHostViewBase> target, |
| std::optional<gfx::PointF> transformed_pt) { |
| if (!target) { |
| return; |
| } |
| RenderWidgetHostImpl* target_rwh = |
| RenderWidgetHostImpl::From(target->GetRenderWidgetHost()); |
| if (!drag_security_info_.IsValidDragTarget(target_rwh)) { |
| return; |
| } |
| |
| if (target_rwh != current_target_rwh_for_drag_.get()) { |
| if (current_target_rwh_for_drag_) { |
| current_target_rwh_for_drag_->DragTargetDragLeave(*transformed_pt, |
| screen_location); |
| } |
| DragEnteredCallback(location, screen_location, target); |
| } |
| |
| web_contents_->Focus(); |
| target_rwh->FilterDropData(drop_data_.get()); |
| target_rwh->DragTargetDrop(*drop_data_, *transformed_pt, screen_location, 0, |
| base::DoNothing()); |
| } |
| |
| void WebContentsViewAndroid::OnSystemDragEnded(RenderWidgetHost* source_rwh) { |
| if (drag_drop_oopif_enabled_) { |
| web_contents_->SystemDragEnded(source_rwh); |
| } else { |
| web_contents_->GetRenderViewHost() |
| ->GetWidget() |
| ->DragSourceSystemDragEnded(); |
| } |
| |
| // Restore the selection popups and the text handles if necessary. |
| if (auto* selection_popup_controller = GetSelectionPopupController()) { |
| selection_popup_controller->RestoreSelectionPopupsIfNecessary(); |
| auto* rwhva = GetRenderWidgetHostViewAndroid(); |
| if (rwhva) |
| rwhva->SetTextHandlesTemporarilyHidden(false); |
| } |
| } |
| |
| void WebContentsViewAndroid::OnDragEnded() { |
| if (drag_drop_oopif_enabled_) { |
| if (current_source_rwh_for_drag_) { |
| web_contents_->DragSourceEndedAt( |
| drag_location_.x(), drag_location_.y(), drag_screen_location_.x(), |
| drag_screen_location_.y(), ui::mojom::DragOperation::kNone, |
| current_source_rwh_for_drag_.get()); |
| OnSystemDragEnded(current_source_rwh_for_drag_.get()); |
| } |
| drag_security_info_.OnDragEnded(); |
| } else { |
| web_contents_->GetRenderViewHost()->GetWidget()->DragSourceEndedAt( |
| drag_location_, drag_screen_location_, ui::mojom::DragOperation::kNone, |
| base::DoNothing()); |
| OnSystemDragEnded(web_contents_->GetRenderViewHost()->GetWidget()); |
| } |
| |
| drag_metadata_.clear(); |
| current_source_rwh_for_drag_.reset(); |
| current_target_rwh_for_drag_.reset(); |
| is_active_drag_ = false; |
| drag_exceeded_movement_threshold_ = false; |
| drag_entered_location_ = gfx::PointF(); |
| drag_location_ = gfx::PointF(); |
| drag_screen_location_ = gfx::PointF(); |
| } |
| |
| void WebContentsViewAndroid::GotFocus( |
| RenderWidgetHostImpl* render_widget_host) { |
| web_contents_->NotifyWebContentsFocused(render_widget_host); |
| } |
| |
| void WebContentsViewAndroid::LostFocus( |
| RenderWidgetHostImpl* render_widget_host) { |
| web_contents_->NotifyWebContentsLostFocus(render_widget_host); |
| } |
| |
| // This is called when we the renderer asks us to take focus back (i.e., it has |
| // iterated past the last focusable element on the page). |
| void WebContentsViewAndroid::TakeFocus(bool reverse) { |
| if (web_contents_->GetDelegate() && |
| web_contents_->GetDelegate()->TakeFocus(web_contents_, reverse)) |
| return; |
| web_contents_->GetRenderWidgetHostView()->Focus(); |
| } |
| |
| int WebContentsViewAndroid::GetTopControlsHeight() const { |
| auto* delegate = web_contents_->GetDelegate(); |
| return delegate ? delegate->GetTopControlsHeight() : 0; |
| } |
| |
| int WebContentsViewAndroid::GetTopControlsMinHeight() const { |
| auto* delegate = web_contents_->GetDelegate(); |
| return delegate ? delegate->GetTopControlsMinHeight() : 0; |
| } |
| |
| int WebContentsViewAndroid::GetBottomControlsHeight() const { |
| auto* delegate = web_contents_->GetDelegate(); |
| return delegate ? delegate->GetBottomControlsHeight() : 0; |
| } |
| |
| int WebContentsViewAndroid::GetBottomControlsMinHeight() const { |
| auto* delegate = web_contents_->GetDelegate(); |
| return delegate ? delegate->GetBottomControlsMinHeight() : 0; |
| } |
| |
| bool WebContentsViewAndroid::ShouldAnimateBrowserControlsHeightChanges() const { |
| auto* delegate = web_contents_->GetDelegate(); |
| return delegate && delegate->ShouldAnimateBrowserControlsHeightChanges(); |
| } |
| |
| bool WebContentsViewAndroid::DoBrowserControlsShrinkRendererSize() const { |
| auto* delegate = web_contents_->GetDelegate(); |
| return delegate && |
| delegate->DoBrowserControlsShrinkRendererSize(web_contents_); |
| } |
| |
| bool WebContentsViewAndroid::OnlyExpandTopControlsAtPageTop() const { |
| auto* delegate = web_contents_->GetDelegate(); |
| return delegate && delegate->OnlyExpandTopControlsAtPageTop(); |
| } |
| |
| bool WebContentsViewAndroid::OnTouchEvent(const ui::MotionEventAndroid& event) { |
| if (event.GetAction() == ui::MotionEventAndroid::Action::DOWN && |
| ShouldRequestUnbufferedDispatch()) { |
| view_.RequestUnbufferedDispatch(event); |
| } |
| return false; // let the children handle the actual event. |
| } |
| |
| bool WebContentsViewAndroid::OnMouseEvent(const ui::MotionEventAndroid& event) { |
| // Hover events can be intercepted when in accessibility mode. |
| auto action = event.GetAction(); |
| if (action != ui::MotionEventAndroid::Action::HOVER_ENTER && |
| action != ui::MotionEventAndroid::Action::HOVER_EXIT && |
| action != ui::MotionEventAndroid::Action::HOVER_MOVE) |
| return false; |
| |
| auto* manager = static_cast<BrowserAccessibilityManagerAndroid*>( |
| web_contents_->GetRootBrowserAccessibilityManager()); |
| return manager && manager->OnHoverEvent(event); |
| } |
| |
| bool WebContentsViewAndroid::OnGenericMotionEvent( |
| const ui::MotionEventAndroid& event) { |
| if (content_ui_event_handler_) |
| return content_ui_event_handler_->OnGenericMotionEvent(event); |
| return false; |
| } |
| |
| bool WebContentsViewAndroid::OnKeyUp(const ui::KeyEventAndroid& event) { |
| if (content_ui_event_handler_) |
| return content_ui_event_handler_->OnKeyUp(event); |
| return false; |
| } |
| |
| bool WebContentsViewAndroid::DispatchKeyEvent( |
| const ui::KeyEventAndroid& event) { |
| if (content_ui_event_handler_) |
| return content_ui_event_handler_->DispatchKeyEvent(event); |
| return false; |
| } |
| |
| bool WebContentsViewAndroid::ScrollBy(float delta_x, float delta_y) { |
| if (content_ui_event_handler_) |
| content_ui_event_handler_->ScrollBy(delta_x, delta_y); |
| return false; |
| } |
| |
| bool WebContentsViewAndroid::ScrollTo(float x, float y) { |
| if (content_ui_event_handler_) |
| content_ui_event_handler_->ScrollTo(x, y); |
| return false; |
| } |
| |
| void WebContentsViewAndroid::OnSizeChanged() { |
| auto* rwhv = GetRenderWidgetHostViewAndroid(); |
| if (rwhv) { |
| web_contents_->SendScreenRects(); |
| rwhv->SynchronizeVisualProperties(cc::DeadlinePolicy::UseDefaultDeadline(), |
| std::nullopt); |
| } |
| } |
| |
| void WebContentsViewAndroid::OnPhysicalBackingSizeChanged( |
| std::optional<base::TimeDelta> deadline_override) { |
| if (back_forward_animation_manager_) { |
| back_forward_animation_manager_->OnPhysicalBackingSizeChanged(); |
| } |
| if (web_contents_->GetRenderWidgetHostView()) |
| web_contents_->SendScreenRects(); |
| } |
| |
| void WebContentsViewAndroid::OnBrowserControlsHeightChanged() { |
| auto* rwhv = GetRenderWidgetHostViewAndroid(); |
| if (rwhv) |
| rwhv->SynchronizeVisualProperties(cc::DeadlinePolicy::UseDefaultDeadline(), |
| std::nullopt); |
| } |
| |
| void WebContentsViewAndroid::OnControlsResizeViewChanged() { |
| auto* rwhv = GetRenderWidgetHostViewAndroid(); |
| if (rwhv) |
| rwhv->SynchronizeVisualProperties(cc::DeadlinePolicy::UseDefaultDeadline(), |
| std::nullopt); |
| } |
| |
| void WebContentsViewAndroid::NotifyVirtualKeyboardOverlayRect( |
| const gfx::Rect& keyboard_rect) { |
| auto* rwhv = GetRenderWidgetHostViewAndroid(); |
| if (rwhv) |
| rwhv->NotifyVirtualKeyboardOverlayRect(keyboard_rect); |
| } |
| |
| void WebContentsViewAndroid::ShowInterestInElement(int nodeID) { |
| auto* rwhv = GetRenderWidgetHostViewAndroid(); |
| if (rwhv) { |
| rwhv->ShowInterestInElement(nodeID); |
| } |
| } |
| |
| } // namespace content |