Index: content/browser/media/capture/web_contents_video_capture_device.cc |
diff --git a/content/browser/media/capture/web_contents_video_capture_device.cc b/content/browser/media/capture/web_contents_video_capture_device.cc |
index 654b8dcc0ff4ed89486fe74539b8e88971e8183c..3bd7906b18b3730fca3ecc1305b144f80f3cc7a3 100644 |
--- a/content/browser/media/capture/web_contents_video_capture_device.cc |
+++ b/content/browser/media/capture/web_contents_video_capture_device.cc |
@@ -13,7 +13,7 @@ |
// video encoder -- is the performance bottleneck, and that the rate of |
// frame capture should be throttled back. |
// |
-// 2. Capture: A bitmap is snapshotted/copied from the RenderView's backing |
+// 2. Capture: A bitmap is snapshotted/copied from the RenderWidget's backing |
// store. This is initiated on the UI BrowserThread, and often occurs |
// asynchronously. Where supported, the GPU scales and color converts |
// frames to our desired size, and the readback happens directly into the |
@@ -65,16 +65,18 @@ |
#include "content/browser/media/capture/content_video_capture_device_core.h" |
#include "content/browser/media/capture/video_capture_oracle.h" |
#include "content/browser/media/capture/web_contents_capture_util.h" |
+#include "content/browser/media/capture/web_contents_tracker.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/web_contents/web_contents_impl.h" |
#include "content/public/browser/browser_thread.h" |
+#include "content/public/browser/notification_observer.h" |
+#include "content/public/browser/notification_registrar.h" |
#include "content/public/browser/notification_source.h" |
#include "content/public/browser/notification_types.h" |
-#include "content/public/browser/render_view_host.h" |
+#include "content/public/browser/render_process_host.h" |
#include "content/public/browser/render_widget_host_view.h" |
#include "content/public/browser/render_widget_host_view_frame_subscriber.h" |
-#include "content/public/browser/web_contents_observer.h" |
+#include "content/public/browser/web_contents.h" |
#include "media/base/video_util.h" |
#include "media/video/capture/video_capture_types.h" |
#include "skia/ext/image_operations.h" |
@@ -193,8 +195,11 @@ class ContentCaptureSubscription : public content::NotificationObserver { |
private: |
void OnTimer(); |
+ // Maintain a weak reference to the RenderWidgetHost (via its routing ID), |
+ // since the instance could be destroyed externally during the lifetime of |
+ // |this|. |
const int render_process_id_; |
- const int render_view_id_; |
+ const int render_widget_id_; |
VideoFrameDeliveryLog delivery_log_; |
FrameSubscriber paint_subscriber_; |
@@ -219,16 +224,9 @@ void RenderVideoFrame(const SkBitmap& input, |
const scoped_refptr<media::VideoFrame>& output, |
const base::Callback<void(bool)>& done_cb); |
-// Keeps track of the RenderView to be sourced, and executes copying of the |
-// backing store on the UI BrowserThread. |
-// |
-// TODO(nick): It would be nice to merge this with WebContentsTracker, but its |
-// implementation is currently asynchronous -- in our case, the "rvh changed" |
-// notification would get posted back to the UI thread and processed later, and |
-// this seems disadvantageous. |
-class WebContentsCaptureMachine |
- : public VideoCaptureMachine, |
- public WebContentsObserver { |
+// Renews capture subscriptions based on feedback from WebContentsTracker, and |
+// also executes copying of the backing store on the UI BrowserThread. |
+class WebContentsCaptureMachine : public VideoCaptureMachine { |
public: |
WebContentsCaptureMachine(int render_process_id, int main_render_frame_id); |
virtual ~WebContentsCaptureMachine(); |
@@ -248,44 +246,12 @@ class WebContentsCaptureMachine |
const RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback& |
deliver_frame_cb); |
- // content::WebContentsObserver implementation. |
- virtual void DidShowFullscreenWidget(int routing_id) OVERRIDE { |
- fullscreen_widget_id_ = routing_id; |
- RenewFrameSubscription(); |
- } |
- |
- virtual void DidDestroyFullscreenWidget(int routing_id) OVERRIDE { |
- DCHECK_EQ(fullscreen_widget_id_, routing_id); |
- fullscreen_widget_id_ = MSG_ROUTING_NONE; |
- RenewFrameSubscription(); |
- } |
- |
- virtual void RenderViewReady() OVERRIDE { |
- RenewFrameSubscription(); |
- } |
- |
- virtual void AboutToNavigateRenderView(RenderViewHost* rvh) OVERRIDE { |
- RenewFrameSubscription(); |
- } |
- |
- virtual void DidNavigateMainFrame( |
- const LoadCommittedDetails& details, |
- const FrameNavigateParams& params) OVERRIDE { |
- RenewFrameSubscription(); |
- } |
- |
- virtual void WebContentsDestroyed() OVERRIDE; |
- |
private: |
+ bool IsStarted() const; |
+ |
// Computes the preferred size of the target RenderWidget for optimal capture. |
gfx::Size ComputeOptimalTargetSize() const; |
- // Starts observing the web contents, returning false if lookup fails. |
- bool StartObservingWebContents(); |
- |
- // Helper function to determine the view that we are currently tracking. |
- RenderWidgetHost* GetTarget() const; |
- |
// Response callback for RenderWidgetHost::CopyFromBackingStore(). |
void DidCopyFromBackingStore( |
const base::TimeTicks& start_time, |
@@ -302,15 +268,17 @@ class WebContentsCaptureMachine |
deliver_frame_cb, |
bool success); |
- // Remove the old subscription, and start a new one. This should be called |
- // after any change to the WebContents that affects the RenderWidgetHost or |
- // attached views. |
- void RenewFrameSubscription(); |
+ // Remove the old subscription, and start a new one if |rwh| is not NULL. |
+ void RenewFrameSubscription(RenderWidgetHost* rwh); |
// Parameters saved in constructor. |
const int initial_render_process_id_; |
const int initial_main_render_frame_id_; |
+ // Tracks events and calls back to RenewFrameSubscription() to maintain |
+ // capture on the correct RenderWidgetHost. |
+ const scoped_refptr<WebContentsTracker> tracker_; |
+ |
// A dedicated worker thread on which SkBitmap->VideoFrame conversion will |
// occur. Only used when this activity cannot be done on the GPU. |
scoped_ptr<base::Thread> render_thread_; |
@@ -321,10 +289,6 @@ class WebContentsCaptureMachine |
// Video capture parameters that this machine is started with. |
media::VideoCaptureParams capture_params_; |
- // Routing ID of any active fullscreen render widget or MSG_ROUTING_NONE |
- // otherwise. |
- int fullscreen_widget_id_; |
- |
// Last known RenderView size. |
gfx::Size last_view_size_; |
@@ -363,7 +327,7 @@ ContentCaptureSubscription::ContentCaptureSubscription( |
const scoped_refptr<ThreadSafeCaptureOracle>& oracle_proxy, |
const CaptureCallback& capture_callback) |
: render_process_id_(source.GetProcess()->GetID()), |
- render_view_id_(source.GetRoutingID()), |
+ render_widget_id_(source.GetRoutingID()), |
delivery_log_(), |
paint_subscriber_(VideoCaptureOracle::kSoftwarePaint, oracle_proxy, |
&delivery_log_), |
@@ -373,8 +337,7 @@ ContentCaptureSubscription::ContentCaptureSubscription( |
timer_(true, true) { |
DCHECK_CURRENTLY_ON(BrowserThread::UI); |
- RenderWidgetHostViewBase* view = static_cast<RenderWidgetHostViewBase*>( |
- source.GetView()); |
+ RenderWidgetHostView* const view = source.GetView(); |
// Subscribe to accelerated presents. These will be serviced directly by the |
// oracle. |
@@ -407,14 +370,11 @@ ContentCaptureSubscription::~ContentCaptureSubscription() { |
DCHECK_CURRENTLY_ON(BrowserThread::UI); |
if (kAcceleratedSubscriberIsSupported) { |
- RenderViewHost* source = RenderViewHost::FromID(render_process_id_, |
- render_view_id_); |
- if (source) { |
- RenderWidgetHostViewBase* view = static_cast<RenderWidgetHostViewBase*>( |
- source->GetView()); |
- if (view) |
- view->EndFrameSubscription(); |
- } |
+ RenderWidgetHost* const source = |
+ RenderWidgetHost::FromID(render_process_id_, render_widget_id_); |
+ RenderWidgetHostView* const view = source ? source->GetView() : NULL; |
+ if (view) |
+ view->EndFrameSubscription(); |
} |
} |
@@ -578,16 +538,21 @@ WebContentsCaptureMachine::WebContentsCaptureMachine(int render_process_id, |
int main_render_frame_id) |
: initial_render_process_id_(render_process_id), |
initial_main_render_frame_id_(main_render_frame_id), |
- fullscreen_widget_id_(MSG_ROUTING_NONE), |
+ tracker_(new WebContentsTracker(true)), |
weak_ptr_factory_(this) {} |
WebContentsCaptureMachine::~WebContentsCaptureMachine() {} |
+bool WebContentsCaptureMachine::IsStarted() const { |
+ DCHECK_CURRENTLY_ON(BrowserThread::UI); |
+ return weak_ptr_factory_.HasWeakPtrs(); |
+} |
+ |
bool WebContentsCaptureMachine::Start( |
const scoped_refptr<ThreadSafeCaptureOracle>& oracle_proxy, |
const media::VideoCaptureParams& params) { |
DCHECK_CURRENTLY_ON(BrowserThread::UI); |
- DCHECK(!weak_ptr_factory_.HasWeakPtrs()); // Should not be started. |
+ DCHECK(!IsStarted()); |
DCHECK(oracle_proxy.get()); |
oracle_proxy_ = oracle_proxy; |
@@ -600,27 +565,32 @@ bool WebContentsCaptureMachine::Start( |
return false; |
} |
- if (!StartObservingWebContents()) { |
- DVLOG(1) << "Failed to observe web contents."; |
- render_thread_.reset(); |
- return false; |
- } |
+ // Note: Creation of the first WeakPtr in the following statement will cause |
+ // IsStarted() to return true from now on. |
+ tracker_->Start(initial_render_process_id_, initial_main_render_frame_id_, |
+ base::Bind(&WebContentsCaptureMachine::RenewFrameSubscription, |
+ weak_ptr_factory_.GetWeakPtr())); |
return true; |
} |
void WebContentsCaptureMachine::Stop(const base::Closure& callback) { |
DCHECK_CURRENTLY_ON(BrowserThread::UI); |
- subscription_.reset(); |
- if (web_contents()) { |
- web_contents()->DecrementCapturerCount(); |
- Observe(NULL); |
+ |
+ if (!IsStarted()) { |
+ callback.Run(); |
+ return; |
} |
- // Any callback that intend to use render_thread_ will not work after it is |
- // passed. |
+ // The following cancels any outstanding callbacks and causes IsStarted() to |
+ // return false from here onward. |
weak_ptr_factory_.InvalidateWeakPtrs(); |
+ // Note: RenewFrameSubscription() must be called before stopping |tracker_| so |
+ // the web_contents() can be notified that the capturing is ending. |
+ RenewFrameSubscription(NULL); |
+ tracker_->Stop(); |
+ |
// The render thread cannot be stopped on the UI thread, so post a message |
// to the thread pool used for blocking operations. |
if (render_thread_.get()) { |
@@ -638,10 +608,10 @@ void WebContentsCaptureMachine::Capture( |
deliver_frame_cb) { |
DCHECK_CURRENTLY_ON(BrowserThread::UI); |
- RenderWidgetHost* rwh = GetTarget(); |
+ RenderWidgetHost* rwh = tracker_->GetTargetRenderWidgetHost(); |
RenderWidgetHostViewBase* view = |
rwh ? static_cast<RenderWidgetHostViewBase*>(rwh->GetView()) : NULL; |
- if (!view || !rwh) { |
+ if (!view) { |
deliver_frame_cb.Run(base::TimeTicks(), false); |
return; |
} |
@@ -692,7 +662,7 @@ gfx::Size WebContentsCaptureMachine::ComputeOptimalTargetSize() const { |
// render widget to the "preferred size," the widget will be physically |
// rendered at the exact capture size, thereby eliminating unnecessary scaling |
// operations in the graphics pipeline. |
- RenderWidgetHost* const rwh = GetTarget(); |
+ RenderWidgetHost* const rwh = tracker_->GetTargetRenderWidgetHost(); |
RenderWidgetHostView* const rwhv = rwh ? rwh->GetView() : NULL; |
if (rwhv) { |
const gfx::NativeView view = rwhv->GetNativeView(); |
@@ -713,60 +683,6 @@ gfx::Size WebContentsCaptureMachine::ComputeOptimalTargetSize() const { |
return optimal_size; |
} |
-bool WebContentsCaptureMachine::StartObservingWebContents() { |
- DCHECK_CURRENTLY_ON(BrowserThread::UI); |
- |
- // Look-up the RenderFrameHost and, from that, the WebContents that wraps it. |
- // If successful, begin observing the WebContents instance. |
- // |
- // Why this can be unsuccessful: The request for mirroring originates in a |
- // render process, and this request is based on the current main RenderFrame |
- // associated with a tab. However, by the time we get up-and-running here, |
- // there have been multiple back-and-forth IPCs between processes, as well as |
- // a bit of indirection across threads. It's easily possible that, in the |
- // meantime, the original RenderFrame may have gone away. |
- Observe(WebContents::FromRenderFrameHost(RenderFrameHost::FromID( |
- initial_render_process_id_, initial_main_render_frame_id_))); |
- DVLOG_IF(1, !web_contents()) |
- << "Could not find WebContents associated with main RenderFrameHost " |
- << "referenced by render_process_id=" << initial_render_process_id_ |
- << ", routing_id=" << initial_main_render_frame_id_; |
- |
- WebContentsImpl* contents = static_cast<WebContentsImpl*>(web_contents()); |
- if (contents) { |
- contents->IncrementCapturerCount(ComputeOptimalTargetSize()); |
- fullscreen_widget_id_ = contents->GetFullscreenWidgetRoutingID(); |
- RenewFrameSubscription(); |
- return true; |
- } |
- return false; |
-} |
- |
-void WebContentsCaptureMachine::WebContentsDestroyed() { |
- DCHECK_CURRENTLY_ON(BrowserThread::UI); |
- |
- subscription_.reset(); |
- web_contents()->DecrementCapturerCount(); |
- oracle_proxy_->ReportError("WebContentsDestroyed()"); |
-} |
- |
-RenderWidgetHost* WebContentsCaptureMachine::GetTarget() const { |
- DCHECK_CURRENTLY_ON(BrowserThread::UI); |
- if (!web_contents()) |
- return NULL; |
- |
- RenderWidgetHost* rwh = NULL; |
- if (fullscreen_widget_id_ != MSG_ROUTING_NONE) { |
- RenderProcessHost* process = web_contents()->GetRenderProcessHost(); |
- if (process) |
- rwh = RenderWidgetHost::FromID(process->GetID(), fullscreen_widget_id_); |
- } else { |
- rwh = web_contents()->GetRenderViewHost(); |
- } |
- |
- return rwh; |
-} |
- |
void WebContentsCaptureMachine::DidCopyFromBackingStore( |
const base::TimeTicks& start_time, |
const scoped_refptr<media::VideoFrame>& target, |
@@ -809,15 +725,31 @@ void WebContentsCaptureMachine::DidCopyFromCompositingSurfaceToVideoFrame( |
deliver_frame_cb.Run(start_time, success); |
} |
-void WebContentsCaptureMachine::RenewFrameSubscription() { |
+void WebContentsCaptureMachine::RenewFrameSubscription(RenderWidgetHost* rwh) { |
DCHECK_CURRENTLY_ON(BrowserThread::UI); |
// Always destroy the old subscription before creating a new one. |
+ const bool had_subscription = !!subscription_; |
subscription_.reset(); |
- RenderWidgetHost* rwh = GetTarget(); |
- if (!rwh || !rwh->GetView()) |
+ DVLOG(1) << "Renewing frame subscription to RWH@" << rwh |
+ << ", had_subscription=" << had_subscription; |
+ |
+ if (!rwh) { |
+ if (had_subscription && tracker_->web_contents()) |
+ tracker_->web_contents()->DecrementCapturerCount(); |
+ if (IsStarted()) { |
+ // Tracking of WebContents and/or its main frame has failed before Stop() |
+ // was called, so report this as an error: |
+ oracle_proxy_->ReportError("WebContents and/or main frame are gone."); |
+ } |
return; |
+ } |
+ |
+ if (!had_subscription && tracker_->web_contents()) { |
+ tracker_->web_contents()->IncrementCapturerCount( |
+ ComputeOptimalTargetSize()); |
+ } |
subscription_.reset(new ContentCaptureSubscription(*rwh, oracle_proxy_, |
base::Bind(&WebContentsCaptureMachine::Capture, |