blob: 06ae46fde184727eb00838bf14bbde55a78f33a2 [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/public/test/hit_test_region_observer.h"
#include <algorithm>
#include "base/test/test_timeouts.h"
#include "components/viz/common/features.h"
#include "components/viz/host/hit_test/hit_test_query.h"
#include "components/viz/host/host_frame_sink_manager.h"
#include "components/viz/service/frame_sinks/frame_sink_manager_impl.h"
#include "components/viz/service/surfaces/surface_manager.h"
#include "content/browser/compositor/surface_utils.h"
#include "content/browser/frame_host/cross_process_frame_connector.h"
#include "content/browser/frame_host/render_frame_host_impl.h"
#include "content/browser/renderer_host/frame_connector_delegate.h"
#include "content/browser/renderer_host/render_widget_host_impl.h"
#include "content/browser/renderer_host/render_widget_host_view_base.h"
#include "content/browser/renderer_host/render_widget_host_view_child_frame.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test_utils.h"
namespace content {
namespace {
class SurfaceHitTestReadyNotifier {
public:
explicit SurfaceHitTestReadyNotifier(RenderWidgetHostViewBase* target_view);
~SurfaceHitTestReadyNotifier() {}
void WaitForSurfaceReady(RenderWidgetHostViewBase* root_container);
private:
bool ContainsSurfaceId(const viz::SurfaceId& container_surface_id);
viz::SurfaceManager* surface_manager_;
RenderWidgetHostViewBase* target_view_;
DISALLOW_COPY_AND_ASSIGN(SurfaceHitTestReadyNotifier);
};
SurfaceHitTestReadyNotifier::SurfaceHitTestReadyNotifier(
RenderWidgetHostViewBase* target_view)
: target_view_(target_view) {
surface_manager_ = GetFrameSinkManager()->surface_manager();
}
void SurfaceHitTestReadyNotifier::WaitForSurfaceReady(
RenderWidgetHostViewBase* root_view) {
while (!ContainsSurfaceId(root_view->GetCurrentSurfaceId())) {
// TODO(kenrb): Need a better way to do this. Needs investigation on
// whether we can add a callback through RenderWidgetHostViewBaseObserver
// from OnSwapCompositorFrame and avoid this busy waiting. A callback on
// every compositor frame might be generally undesirable for performance,
// however.
base::RunLoop run_loop;
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE, run_loop.QuitClosure(), TestTimeouts::tiny_timeout());
run_loop.Run();
}
}
bool SurfaceHitTestReadyNotifier::ContainsSurfaceId(
const viz::SurfaceId& container_surface_id) {
if (!container_surface_id.is_valid())
return false;
viz::Surface* container_surface =
surface_manager_->GetSurfaceForId(container_surface_id);
if (!container_surface ||
container_surface->active_referenced_surfaces().empty()) {
return false;
}
for (const viz::SurfaceId& id :
container_surface->active_referenced_surfaces()) {
if (id == target_view_->GetCurrentSurfaceId() || ContainsSurfaceId(id))
return true;
}
return false;
}
// Waits until the cc::Surface associated with a guest/cross-process-iframe
// has been drawn for the first time. Once this method returns it should be
// safe to assume that events sent to the top-level RenderWidgetHostView can
// be expected to properly hit-test to this surface, if appropriate.
void WaitForGuestSurfaceReady(content::WebContents* guest_web_contents) {
RenderWidgetHostViewChildFrame* child_view =
static_cast<RenderWidgetHostViewChildFrame*>(
guest_web_contents->GetRenderWidgetHostView());
RenderWidgetHostViewBase* root_view = static_cast<RenderWidgetHostViewBase*>(
static_cast<content::WebContentsImpl*>(guest_web_contents)
->GetOuterWebContents()
->GetRenderWidgetHostView());
SurfaceHitTestReadyNotifier notifier(child_view);
notifier.WaitForSurfaceReady(root_view);
}
// To wait for frame submission see RenderFrameSubmissionObserver.
// Waits until the cc::Surface associated with a cross-process child frame
// has been drawn for the first time. Once this method returns it should be
// safe to assume that events sent to the top-level RenderWidgetHostView can
// be expected to properly hit-test to this surface, if appropriate.
void WaitForChildFrameSurfaceReady(content::RenderFrameHost* child_frame) {
RenderWidgetHostViewBase* child_view =
static_cast<RenderFrameHostImpl*>(child_frame)
->GetRenderWidgetHost()
->GetView();
if (!child_view || !child_view->IsRenderWidgetHostViewChildFrame())
return;
RenderWidgetHostViewBase* root_view =
static_cast<CrossProcessFrameConnector*>(
static_cast<RenderWidgetHostViewChildFrame*>(child_view)
->FrameConnectorForTesting())
->GetRootRenderWidgetHostViewForTesting();
SurfaceHitTestReadyNotifier notifier(child_view);
notifier.WaitForSurfaceReady(root_view);
}
// Returns a transform from root to |frame_sink_id|. If no HitTestQuery contains
// |frame_sink_id| then this will return an empty optional.
base::Optional<gfx::Transform> GetRootToTargetTransform(
const viz::FrameSinkId& frame_sink_id) {
for (auto& it : GetHostFrameSinkManager()->display_hit_test_query()) {
if (it.second->ContainsActiveFrameSinkId(frame_sink_id)) {
base::Optional<gfx::Transform> transform(base::in_place);
it.second->GetTransformToTarget(frame_sink_id, &transform.value());
return transform;
}
}
return base::nullopt;
}
} // namespace
void WaitForHitTestDataOrChildSurfaceReady(RenderFrameHost* child_frame) {
RenderWidgetHostViewBase* child_view =
static_cast<RenderFrameHostImpl*>(child_frame)
->GetRenderWidgetHost()
->GetView();
if (features::IsVizHitTestingEnabled()) {
HitTestRegionObserver observer(child_view->GetFrameSinkId());
observer.WaitForHitTestData();
return;
}
WaitForChildFrameSurfaceReady(child_frame);
}
void WaitForHitTestDataOrGuestSurfaceReady(WebContents* guest_web_contents) {
DCHECK(static_cast<RenderWidgetHostViewBase*>(
guest_web_contents->GetRenderWidgetHostView())
->IsRenderWidgetHostViewChildFrame());
RenderWidgetHostViewChildFrame* child_view =
static_cast<RenderWidgetHostViewChildFrame*>(
guest_web_contents->GetRenderWidgetHostView());
if (features::IsVizHitTestingEnabled()) {
HitTestRegionObserver observer(child_view->GetFrameSinkId());
observer.WaitForHitTestData();
return;
}
WaitForGuestSurfaceReady(guest_web_contents);
}
HitTestRegionObserver::HitTestRegionObserver(
const viz::FrameSinkId& frame_sink_id)
: frame_sink_id_(frame_sink_id) {
CHECK(frame_sink_id.is_valid());
GetHostFrameSinkManager()->AddHitTestRegionObserver(this);
}
HitTestRegionObserver::~HitTestRegionObserver() {
GetHostFrameSinkManager()->RemoveHitTestRegionObserver(this);
}
void HitTestRegionObserver::WaitForHitTestData() {
for (auto& it : GetHostFrameSinkManager()->display_hit_test_query()) {
if (it.second->ContainsActiveFrameSinkId(frame_sink_id_)) {
return;
}
}
run_loop_ = std::make_unique<base::RunLoop>();
run_loop_->Run();
run_loop_.reset();
}
void HitTestRegionObserver::OnAggregatedHitTestRegionListUpdated(
const viz::FrameSinkId& frame_sink_id,
const std::vector<viz::AggregatedHitTestRegion>& hit_test_data) {
if (!run_loop_)
return;
for (auto& it : hit_test_data) {
if (it.frame_sink_id == frame_sink_id_ &&
!(it.flags & viz::HitTestRegionFlags::kHitTestNotActive)) {
run_loop_->Quit();
return;
}
}
}
const std::vector<viz::AggregatedHitTestRegion>&
HitTestRegionObserver::GetHitTestData() {
const auto& hit_test_query_map =
GetHostFrameSinkManager()->display_hit_test_query();
const auto iter = hit_test_query_map.find(frame_sink_id_);
DCHECK(iter != hit_test_query_map.end());
return iter->second.get()->hit_test_data_;
}
HitTestTransformChangeObserver::HitTestTransformChangeObserver(
const viz::FrameSinkId& frame_sink_id)
: target_frame_sink_id_(frame_sink_id),
cached_transform_(GetRootToTargetTransform(frame_sink_id)) {
DCHECK(frame_sink_id.is_valid());
}
HitTestTransformChangeObserver::~HitTestTransformChangeObserver() = default;
void HitTestTransformChangeObserver::WaitForHitTestDataChange() {
DCHECK(!run_loop_);
// TODO(kylechar): Remove when VizHitTesting is enabled everywhere.
if (!features::IsVizHitTestingEnabled())
return;
// If the transform has already changed then don't run RunLoop.
base::Optional<gfx::Transform> transform =
GetRootToTargetTransform(target_frame_sink_id_);
if (transform != cached_transform_) {
cached_transform_ = transform;
return;
}
GetHostFrameSinkManager()->AddHitTestRegionObserver(this);
run_loop_ = std::make_unique<base::RunLoop>();
run_loop_->Run();
run_loop_.reset();
GetHostFrameSinkManager()->RemoveHitTestRegionObserver(this);
}
void HitTestTransformChangeObserver::OnAggregatedHitTestRegionListUpdated(
const viz::FrameSinkId& frame_sink_id,
const std::vector<viz::AggregatedHitTestRegion>& hit_test_data) {
// Check if the transform has changed since it was cached.
base::Optional<gfx::Transform> transform =
GetRootToTargetTransform(target_frame_sink_id_);
if (transform != cached_transform_) {
cached_transform_ = transform;
run_loop_->Quit();
}
}
} // namespace content