blob: 8e8270ab19937bac6a1cf6c8a923b5c368c869ce [file] [log] [blame]
// Copyright 2013 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/common/input/synthetic_gesture_controller.h"
#include <utility>
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/scoped_refptr.h"
#include "base/task/sequenced_task_runner.h"
#include "base/trace_event/trace_event.h"
#include "content/common/input/synthetic_gesture_target.h"
#include "third_party/perfetto/include/perfetto/tracing/track.h"
namespace content {
SyntheticGestureController::SyntheticGestureController(
Delegate* delegate,
std::unique_ptr<SyntheticGestureTarget> gesture_target,
scoped_refptr<base::SequencedTaskRunner> task_runner)
: delegate_(delegate), gesture_target_(std::move(gesture_target)) {
DCHECK(delegate_);
dispatch_timer_.SetTaskRunner(task_runner);
}
SyntheticGestureController::~SyntheticGestureController() {
while (!pending_gesture_queue_.IsEmpty()) {
pending_gesture_queue_.FrontCallback().Run(
SyntheticGesture::GESTURE_FINISHED);
pending_gesture_queue_.Pop();
}
}
void SyntheticGestureController::EnsureRendererInitialized(
base::OnceClosure on_completed) {
if (renderer_known_to_be_initialized_)
return;
base::OnceClosure wrapper = base::BindOnce(
[](base::WeakPtr<SyntheticGestureController> weak_ptr,
base::OnceClosure on_completed) {
if (weak_ptr)
weak_ptr->renderer_known_to_be_initialized_ = true;
std::move(on_completed).Run();
},
weak_ptr_factory_.GetWeakPtr(), std::move(on_completed));
// TODO(bokan): This will wait for the renderer to produce a frame and the
// GPU to present it but we should really be waiting for hit testing data to
// have been updated in the browser. https://crbug.com/985374.
gesture_target_->WaitForTargetAck(
SyntheticGestureParams::WAIT_FOR_INPUT_PROCESSED,
content::mojom::GestureSourceType::kDefaultInput, std::move(wrapper));
}
bool SyntheticGestureController::IsHiddenAndNeedsVisible() const {
CHECK(!pending_gesture_queue_.IsEmpty());
// If the gesture is from DevTools it'll be injected directly to the correct
// renderer so it doesn't need to be visible.
const SyntheticGesture& gesture = *pending_gesture_queue_.FrontGesture();
if (gesture.IsFromDevToolsDebugger()) {
return false;
}
// Other gestures inject events at the UI layer so require the source
// RenderWidgetHost to be visible to be correctly targeted.
return delegate_->IsHidden();
}
void SyntheticGestureController::StartIfNeeded() {
if (!deferred_start_) {
return;
}
deferred_start_ = false;
CHECK(!pending_gesture_queue_.IsEmpty());
CHECK(!dispatch_timer_.IsRunning());
StartGesture();
}
void SyntheticGestureController::QueueSyntheticGesture(
std::unique_ptr<SyntheticGesture> synthetic_gesture,
OnGestureCompleteCallback completion_callback) {
QueueSyntheticGesture(std::move(synthetic_gesture),
std::move(completion_callback), false);
}
void SyntheticGestureController::QueueSyntheticGestureCompleteImmediately(
std::unique_ptr<SyntheticGesture> synthetic_gesture) {
QueueSyntheticGesture(std::move(synthetic_gesture), base::DoNothing(), true);
}
void SyntheticGestureController::QueueSyntheticGesture(
std::unique_ptr<SyntheticGesture> synthetic_gesture,
OnGestureCompleteCallback completion_callback,
bool complete_immediately) {
DCHECK(synthetic_gesture);
bool was_empty = pending_gesture_queue_.IsEmpty();
SyntheticGesture* raw_gesture = synthetic_gesture.get();
pending_gesture_queue_.Push(std::move(synthetic_gesture),
std::move(completion_callback),
complete_immediately);
raw_gesture->DidQueue(weak_ptr_factory_.GetWeakPtr());
if (was_empty) {
StartGesture();
}
}
void SyntheticGestureController::StartOrUpdateTimer() {
base::TimeTicks vsync_timebase;
base::TimeDelta vsync_interval;
const SyntheticGesture& gesture = *pending_gesture_queue_.FrontGesture();
gesture_target_->GetVSyncParameters(vsync_timebase, vsync_interval);
event_timebase_ =
vsync_timebase + base::Milliseconds(gesture.GetVsyncOffsetMs());
switch (gesture.InputEventPattern()) {
case content::mojom::InputEventPattern::kDefaultPattern:
event_interval_ =
vsync_interval * (gesture.AllowHighFrequencyDispatch() ? 0.5f : 1.0f);
break;
case content::mojom::InputEventPattern::kOnePerVsync:
event_interval_ = vsync_interval;
break;
case content::mojom::InputEventPattern::kTwoPerVsync:
event_interval_ = vsync_interval * 0.5f;
break;
case content::mojom::InputEventPattern::kEveryOtherVsync:
event_interval_ = vsync_interval * 2.0f;
break;
default:
NOTREACHED();
}
if (dispatch_timer_.IsRunning()) {
dispatch_timer_.Stop();
}
// The next deadline is scheduled at the next aligned time which is at least
// `interval_ / 2` after now. `interval_ / 2` is added to avoid playing
// "catch-up" if wake ups are late.
base::TimeTicks deadline =
(base::TimeTicks::Now() + event_interval_ / 2)
.SnappedToNextTick(event_timebase_, event_interval_);
dispatch_timer_.Start(
FROM_HERE, deadline,
base::BindRepeating(
[](base::WeakPtr<SyntheticGestureController> weak_ptr) {
if (weak_ptr)
weak_ptr->DispatchNextEvent(base::TimeTicks::Now());
},
weak_ptr_factory_.GetWeakPtr()),
base::subtle::DelayPolicy::kPrecise);
}
bool SyntheticGestureController::DispatchNextEvent(base::TimeTicks timestamp) {
TRACE_EVENT0("input", "SyntheticGestureController::Flush");
if (pending_gesture_queue_.IsEmpty()) {
return false;
}
if (IsHiddenAndNeedsVisible()) {
dispatch_timer_.Stop();
GestureCompleted(SyntheticGesture::GESTURE_ABORT);
return false;
}
if (!pending_gesture_queue_.is_current_gesture_complete()) {
SyntheticGesture::Result result =
pending_gesture_queue_.FrontGesture()->ForwardInputEvents(
timestamp, gesture_target_.get());
if (result == SyntheticGesture::GESTURE_ABORT) {
// This means we've been destroyed from the call to ForwardInputEvents,
// return immediately.
return false;
} else if (result == SyntheticGesture::GESTURE_RUNNING) {
StartOrUpdateTimer();
return true;
}
pending_gesture_queue_.mark_current_gesture_complete(result);
}
if (!pending_gesture_queue_.CompleteCurrentGestureImmediately() &&
!delegate_->HasGestureStopped()) {
StartOrUpdateTimer();
return true;
}
StopGesture(*pending_gesture_queue_.FrontGesture(),
pending_gesture_queue_.current_gesture_result(),
pending_gesture_queue_.CompleteCurrentGestureImmediately());
return !pending_gesture_queue_.IsEmpty();
}
void SyntheticGestureController::StartGesture() {
if (!renderer_known_to_be_initialized_) {
base::OnceClosure on_initialized = base::BindOnce(
[](base::WeakPtr<SyntheticGestureController> weak_ptr) {
if (!weak_ptr)
return;
// The renderer_known_to_be_initialized_ bit should be flipped before
// this callback is invoked in EnsureRendererInitialized so we don't
// call EnsureRendererInitialized again.
DCHECK(weak_ptr->renderer_known_to_be_initialized_);
weak_ptr->StartGesture();
},
weak_ptr_factory_.GetWeakPtr());
// We don't yet know whether the renderer is ready for input. Force it to
// produce a compositor frame and once it does we'll callback into this
// function to start the gesture.
EnsureRendererInitialized(std::move(on_initialized));
return;
}
if (IsHiddenAndNeedsVisible()) {
// If the gesture is started while the host is hidden, wait until the host
// becomes visible. Once the gesture is running, hiding the host will
// abort the gesture but starting one while hidden will queue it so that
// tests don't race with the visibility signal.
deferred_start_ = true;
return;
}
{
DCHECK(!pending_gesture_queue_.IsEmpty());
TRACE_EVENT_BEGIN(
"input,benchmark", "SyntheticGestureController::running",
perfetto::Track::FromPointer(pending_gesture_queue_.FrontGesture()));
StartOrUpdateTimer();
}
}
void SyntheticGestureController::StopGesture(const SyntheticGesture& gesture,
SyntheticGesture::Result result,
bool complete_immediately) {
DCHECK_NE(result, SyntheticGesture::GESTURE_RUNNING);
TRACE_EVENT_END("input,benchmark", perfetto::Track::FromPointer(&gesture));
dispatch_timer_.Stop();
if (result != SyntheticGesture::GESTURE_FINISHED || complete_immediately) {
GestureCompleted(result);
return;
}
// If the gesture finished successfully, wait until all the input has been
// propagated throughout the entire input pipeline before we resolve the
// completion callback. This ensures all the effects of this gesture are
// visible to subsequent input (e.g. OOPIF hit testing).
gesture.WaitForTargetAck(
base::BindOnce(&SyntheticGestureController::GestureCompleted,
weak_ptr_factory_.GetWeakPtr(),
SyntheticGesture::GESTURE_FINISHED),
gesture_target_.get());
}
void SyntheticGestureController::GestureCompleted(
SyntheticGesture::Result result) {
pending_gesture_queue_.FrontCallback().Run(result);
pending_gesture_queue_.Pop();
if (!pending_gesture_queue_.IsEmpty())
StartGesture();
}
SyntheticGestureController::GestureAndCallbackQueue::GestureAndCallbackQueue() {
}
SyntheticGestureController::GestureAndCallbackQueue::
~GestureAndCallbackQueue() {
}
} // namespace content