Index: content/browser/devtools/protocol/emulation_handler.cc |
diff --git a/content/browser/devtools/protocol/emulation_handler.cc b/content/browser/devtools/protocol/emulation_handler.cc |
index a2f4cbd8082f9f2add3bce35701c566bbb2a45e0..e8916e4208ca2e6c2ba366ced7b1c17d918e3d80 100644 |
--- a/content/browser/devtools/protocol/emulation_handler.cc |
+++ b/content/browser/devtools/protocol/emulation_handler.cc |
@@ -6,15 +6,25 @@ |
#include <utility> |
+#include "base/bind.h" |
+#include "base/guid.h" |
+#include "base/memory/ptr_util.h" |
#include "base/strings/string_number_conversions.h" |
+#include "base/trace_event/trace_event.h" |
#include "build/build_config.h" |
+#include "cc/output/begin_frame_args.h" |
+#include "cc/surfaces/surface_id_allocator.h" |
+#include "cc/surfaces/surface_manager.h" |
+#include "content/browser/compositor/surface_utils.h" |
#include "content/browser/frame_host/render_frame_host_impl.h" |
#include "content/browser/renderer_host/render_widget_host_impl.h" |
#include "content/browser/web_contents/web_contents_impl.h" |
+#include "content/browser/web_contents/web_contents_view.h" |
#include "content/common/view_messages.h" |
#include "content/public/common/url_constants.h" |
#include "device/geolocation/geolocation_service_context.h" |
#include "device/geolocation/geoposition.h" |
+#include "ui/compositor/compositor.h" |
namespace content { |
namespace devtools { |
@@ -56,10 +66,67 @@ ui::GestureProviderConfigType TouchEmulationConfigurationToType( |
} // namespace |
+class EmulatedBeginFrameSource : public cc::ExternalBeginFrameSource { |
+ public: |
+ explicit EmulatedBeginFrameSource(cc::ExternalBeginFrameSourceClient* client) |
+ : cc::ExternalBeginFrameSource(client) {} |
+ ~EmulatedBeginFrameSource() override {} |
+ |
+ void DidFinishFrame(cc::BeginFrameObserver* obs, |
+ size_t remaining_frames) override { |
+ cc::ExternalBeginFrameSource::DidFinishFrame(obs, remaining_frames); |
+ finished_observers_.insert(obs); |
+ CheckFinished(); |
+ } |
+ |
+ void AddObserver(cc::BeginFrameObserver* obs) override { |
+ cc::ExternalBeginFrameSource::AddObserver(obs); |
+ sorted_observers_.insert(obs); |
+ } |
+ |
+ void RemoveObserver(cc::BeginFrameObserver* obs) override { |
+ cc::ExternalBeginFrameSource::RemoveObserver(obs); |
+ sorted_observers_.erase(obs); |
+ finished_observers_.erase(obs); |
+ CheckFinished(); |
+ } |
+ |
+ void SendBeginFrame(const cc::BeginFrameArgs& args) { |
+ finished_observers_.clear(); |
+ OnBeginFrame(args); |
+ |
+ for (cc::BeginFrameObserver* obs : sorted_observers_) { |
+ // TODO(eseckler): Need sequence numbers in args to distinguish emulated |
+ // args with equal timestamps. |
+ if (obs->LastUsedBeginFrameArgs().frame_time != args.frame_time) |
+ finished_observers_.insert(obs); |
+ } |
+ CheckFinished(); |
+ } |
+ |
+ void WhenFinished(base::Callback<void()> callback) { callback_ = callback; } |
+ |
+ private: |
+ void CheckFinished() { |
+ if (!base::STLIncludes(finished_observers_, sorted_observers_)) |
+ return; |
+ |
+ if (callback_) |
+ callback_.Run(); |
+ } |
+ |
+ base::Callback<void()> callback_; |
+ std::set<cc::BeginFrameObserver*> sorted_observers_; |
+ std::set<cc::BeginFrameObserver*> finished_observers_; |
+}; |
+ |
EmulationHandler::EmulationHandler() |
: touch_emulation_enabled_(false), |
device_emulation_enabled_(false), |
- host_(nullptr) { |
+ begin_frame_source_(nullptr), |
+ needs_begin_frame_(false), |
+ host_(nullptr), |
+ weak_factory_(this) { |
} |
EmulationHandler::~EmulationHandler() { |
@@ -69,16 +136,23 @@ void EmulationHandler::SetRenderFrameHost(RenderFrameHostImpl* host) { |
if (host_ == host) |
return; |
+ DisableBeginFrameControl(); |
+ |
host_ = host; |
UpdateTouchEventEmulationState(); |
UpdateDeviceEmulationState(); |
} |
+void EmulationHandler::SetClient(std::unique_ptr<Client> client) { |
+ client_.swap(client); |
+} |
+ |
void EmulationHandler::Detached() { |
touch_emulation_enabled_ = false; |
device_emulation_enabled_ = false; |
UpdateTouchEventEmulationState(); |
UpdateDeviceEmulationState(); |
+ DisableBeginFrameControl(); |
} |
Response EmulationHandler::SetGeolocationOverride( |
@@ -290,6 +364,142 @@ Response EmulationHandler::SetVirtualTimePolicy( |
return Response::FallThrough(); |
} |
+#if defined(USE_AURA) |
+Response EmulationHandler::EnableBeginFrameControl() { |
+ if (begin_frame_source_) |
+ return Response::OK(); // already enabled. |
+ |
+ WebContentsImpl* web_contents = GetWebContents(); |
+ if (!web_contents) |
+ return Response::InternalError("Could not connect to view"); |
+ |
+ ui::Compositor* compositor = web_contents->GetView()->GetCompositor(); |
+ if (!compositor) |
+ return Response::InternalError("Could not obtain Compositor for view"); |
+ |
+ TRACE_EVENT_INSTANT2("cc", "EnableBeginFrameControl", |
+ TRACE_EVENT_SCOPE_THREAD, "WebContents RoutingID", |
+ web_contents->GetRoutingID(), "Compositor FrameSinkId", |
+ compositor->frame_sink_id().ToString()); |
+ |
+ // TODO(eseckler): Support that Display of WebContents may change (and be |
+ // destroyed) while BFC is enabled. |
+ |
+ // Replace original BeginFrameSource by our DevTools-controlled one. |
+ std::unique_ptr<cc::BeginFrameSource> begin_frame_source = |
+ base::MakeUnique<EmulatedBeginFrameSource>(this); |
+ begin_frame_source_ = |
+ static_cast<EmulatedBeginFrameSource*>(begin_frame_source.get()); |
+ compositor->SwapBeginFrameSource(&begin_frame_source); |
+ original_begin_frame_source_.swap(begin_frame_source); |
+ |
+ return Response::OK(); |
+} |
+ |
+Response EmulationHandler::DisableBeginFrameControl() { |
+ if (!begin_frame_source_) |
+ return Response::OK(); // already disabled. |
+ |
+ WebContentsImpl* web_contents = GetWebContents(); |
+ if (!web_contents) |
+ return Response::InternalError("Could not connect to view"); |
+ |
+ ui::Compositor* compositor = web_contents->GetView()->GetCompositor(); |
+ DCHECK(compositor); |
+ |
+ compositor->SwapBeginFrameSource(&original_begin_frame_source_); |
+ original_begin_frame_source_.reset(); |
+ begin_frame_source_ = nullptr; |
+ needs_begin_frame_ = false; |
+ return Response::OK(); |
+} |
+ |
+Response EmulationHandler::SendBeginFrame(double interval, |
+ const double* frame_time, |
+ const double* deadline, |
+ std::string* out_frame_id) { |
+ if (!begin_frame_source_) |
+ return Response::ServerError("BeginFrameControl not enabled"); |
+ |
+ RenderWidgetHostImpl* widget_host = |
+ host_ ? host_->GetRenderWidgetHost() : nullptr; |
+ if (!widget_host) |
+ return Response::ServerError("Target does not support SendBeginFrame"); |
+ |
+ ui::Compositor* compositor = GetWebContents()->GetView()->GetCompositor(); |
+ DCHECK(compositor); |
+ |
+ base::TimeTicks frame_time_ticks = |
+ frame_time |
+ ? base::TimeTicks::Now() |
+ : base::TimeTicks() + base::TimeDelta::FromMicroseconds(*frame_time); |
+ base::TimeDelta interval_delta = base::TimeDelta::FromMicroseconds(interval); |
+ base::TimeTicks deadline_ticks = |
+ deadline |
+ ? (base::TimeTicks() + base::TimeDelta::FromMicroseconds(*deadline)) |
+ : (frame_time_ticks + interval_delta); |
+ auto args = cc::BeginFrameArgs::Create( |
+ BEGINFRAME_FROM_HERE, frame_time_ticks, deadline_ticks, interval_delta, |
+ cc::BeginFrameArgs::NORMAL); |
+ |
+ // TODO(eseckler): Expose via DevTools? |
+ // Ensure that we give the renderer main thread(s) a chance to answer this |
+ // BeginFrame (disables Begin(Main)Frame skipping). |
+ args.allow_latency_optimizations = false; |
+ |
+ *out_frame_id = base::GenerateGUID(); |
+ |
+ begin_frame_source_->WhenFinished( |
+ base::Bind(&EmulationHandler::ClientFrameCommitted, |
+ base::Unretained(this), *out_frame_id)); |
+ begin_frame_source_->SendBeginFrame(args); |
+ return Response::OK(); |
+} |
+ |
+#else |
+Response EmulationHandler::EnableBeginFrameControl() { |
+ return Response::InternalError("Only supported on Aura."); |
+} |
+ |
+Response EmulationHandler::DisableBeginFrameControl() { |
+ return Response::InternalError("Only supported on Aura."); |
+} |
+ |
+Response EmulationHandler::SendBeginFrame(double interval, |
+ const double* frame_time, |
+ const double* deadline, |
+ std::string* out_frame_id) { |
+ return Response::InternalError("Only supported on Aura."); |
+} |
+#endif |
+ |
+void EmulationHandler::OnNeedsBeginFrames(bool needs_begin_frames) { |
+ if (needs_begin_frame_ == needs_begin_frames) |
+ return; |
+ |
+ needs_begin_frame_ = needs_begin_frames; |
+ ClientSetNeedsBeginFrame(needs_begin_frame_); |
+ |
+ // TODO(eseckler): Figure out if we need to capture needsBeginFrame updates |
+ // elsewhere to avoid indicating (sole) presence of browser-level consumers. |
+} |
+ |
+void EmulationHandler::ClientSetNeedsBeginFrame(bool needs_begin_frame) { |
+ if (!client_ || !begin_frame_source_) |
+ return; |
+ |
+ client_->SetNeedsBeginFrame(SetNeedsBeginFrameParams::Create() |
+ ->set_needs_begin_frame(needs_begin_frame)); |
+} |
+ |
+void EmulationHandler::ClientFrameCommitted(const std::string& frame_id) { |
+ if (!client_ || !begin_frame_source_) |
+ return; |
+ |
+ client_->FrameCommitted( |
+ FrameCommittedParams::Create()->set_frame_id(frame_id)); |
+} |
+ |
WebContentsImpl* EmulationHandler::GetWebContents() { |
return host_ ? |
static_cast<WebContentsImpl*>(WebContents::FromRenderFrameHost(host_)) : |