1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/extensions/api/tab_capture/tab_capture_registry.h"
7 #include "base/lazy_instance.h"
8 #include "chrome/browser/chrome_notification_types.h"
9 #include "chrome/browser/profiles/profile.h"
10 #include "chrome/browser/ui/fullscreen/fullscreen_controller.h"
11 #include "components/keyed_service/content/browser_context_dependency_manager.h"
12 #include "content/public/browser/browser_thread.h"
13 #include "content/public/browser/notification_details.h"
14 #include "content/public/browser/notification_service.h"
15 #include "content/public/browser/notification_source.h"
16 #include "content/public/browser/render_view_host.h"
17 #include "content/public/browser/web_contents.h"
18 #include "content/public/browser/web_contents_observer.h"
19 #include "extensions/browser/event_router.h"
20 #include "extensions/common/extension.h"
22 using content::BrowserThread
;
23 using extensions::TabCaptureRegistry
;
24 using extensions::tab_capture::TabCaptureState
;
26 namespace extensions
{
28 namespace tab_capture
= api::tab_capture
;
30 class FullscreenObserver
: public content::WebContentsObserver
{
32 FullscreenObserver(TabCaptureRequest
* request
,
33 const TabCaptureRegistry
* registry
);
34 virtual ~FullscreenObserver() {}
37 // content::WebContentsObserver implementation.
38 virtual void DidShowFullscreenWidget(int routing_id
) OVERRIDE
;
39 virtual void DidDestroyFullscreenWidget(int routing_id
) OVERRIDE
;
41 TabCaptureRequest
* request_
;
42 const TabCaptureRegistry
* registry_
;
44 DISALLOW_COPY_AND_ASSIGN(FullscreenObserver
);
47 // Holds all the state related to a tab capture stream.
48 struct TabCaptureRequest
{
49 TabCaptureRequest(int render_process_id
,
51 const std::string
& extension_id
,
53 TabCaptureState status
);
56 const int render_process_id
;
57 const int render_view_id
;
58 const std::string extension_id
;
60 TabCaptureState status
;
61 TabCaptureState last_status
;
63 scoped_ptr
<FullscreenObserver
> fullscreen_observer
;
66 FullscreenObserver::FullscreenObserver(
67 TabCaptureRequest
* request
,
68 const TabCaptureRegistry
* registry
)
71 content::RenderViewHost
* const rvh
=
72 content::RenderViewHost::FromID(request
->render_process_id
,
73 request
->render_view_id
);
74 Observe(rvh
? content::WebContents::FromRenderViewHost(rvh
) : NULL
);
77 void FullscreenObserver::DidShowFullscreenWidget(
79 request_
->fullscreen
= true;
80 registry_
->DispatchStatusChangeEvent(request_
);
83 void FullscreenObserver::DidDestroyFullscreenWidget(
85 request_
->fullscreen
= false;
86 registry_
->DispatchStatusChangeEvent(request_
);
89 TabCaptureRequest::TabCaptureRequest(
90 int render_process_id
,
92 const std::string
& extension_id
,
94 TabCaptureState status
)
95 : render_process_id(render_process_id
),
96 render_view_id(render_view_id
),
97 extension_id(extension_id
),
104 TabCaptureRequest::~TabCaptureRequest() {
107 TabCaptureRegistry::TabCaptureRegistry(content::BrowserContext
* context
)
108 : profile_(Profile::FromBrowserContext(context
)) {
109 MediaCaptureDevicesDispatcher::GetInstance()->AddObserver(this);
111 chrome::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED
,
112 content::Source
<Profile
>(profile_
));
114 chrome::NOTIFICATION_FULLSCREEN_CHANGED
,
115 content::NotificationService::AllSources());
118 TabCaptureRegistry::~TabCaptureRegistry() {
119 MediaCaptureDevicesDispatcher::GetInstance()->RemoveObserver(this);
123 TabCaptureRegistry
* TabCaptureRegistry::Get(content::BrowserContext
* context
) {
124 return BrowserContextKeyedAPIFactory
<TabCaptureRegistry
>::Get(context
);
127 static base::LazyInstance
<BrowserContextKeyedAPIFactory
<TabCaptureRegistry
> >
128 g_factory
= LAZY_INSTANCE_INITIALIZER
;
131 BrowserContextKeyedAPIFactory
<TabCaptureRegistry
>*
132 TabCaptureRegistry::GetFactoryInstance() {
133 return g_factory
.Pointer();
136 const TabCaptureRegistry::RegistryCaptureInfo
137 TabCaptureRegistry::GetCapturedTabs(const std::string
& extension_id
) const {
138 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
139 RegistryCaptureInfo list
;
140 for (ScopedVector
<TabCaptureRequest
>::const_iterator it
= requests_
.begin();
141 it
!= requests_
.end(); ++it
) {
142 if ((*it
)->extension_id
== extension_id
) {
143 list
.push_back(std::make_pair((*it
)->tab_id
, (*it
)->status
));
149 void TabCaptureRegistry::Observe(int type
,
150 const content::NotificationSource
& source
,
151 const content::NotificationDetails
& details
) {
152 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
154 case chrome::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED
: {
155 // Cleanup all the requested media streams for this extension.
156 const std::string
& extension_id
=
157 content::Details
<extensions::UnloadedExtensionInfo
>(details
)->
159 for (ScopedVector
<TabCaptureRequest
>::iterator it
= requests_
.begin();
160 it
!= requests_
.end();) {
161 if ((*it
)->extension_id
== extension_id
) {
162 it
= requests_
.erase(it
);
169 case chrome::NOTIFICATION_FULLSCREEN_CHANGED
: {
170 FullscreenController
* fullscreen_controller
=
171 content::Source
<FullscreenController
>(source
).ptr();
172 const bool is_fullscreen
= *content::Details
<bool>(details
).ptr();
173 for (ScopedVector
<TabCaptureRequest
>::iterator it
= requests_
.begin();
174 it
!= requests_
.end(); ++it
) {
175 // If we are exiting fullscreen mode, we only need to check if any of
176 // the requests had the fullscreen flag toggled previously. The
177 // fullscreen controller no longer has the reference to the fullscreen
178 // web_contents here.
179 if (!is_fullscreen
) {
180 if ((*it
)->fullscreen
) {
181 (*it
)->fullscreen
= false;
182 DispatchStatusChangeEvent(*it
);
188 // If we are entering fullscreen mode, find whether the web_contents we
189 // are capturing entered fullscreen mode.
190 content::RenderViewHost
* const rvh
=
191 content::RenderViewHost::FromID((*it
)->render_process_id
,
192 (*it
)->render_view_id
);
193 if (rvh
&& fullscreen_controller
->IsFullscreenForTabOrPending(
194 content::WebContents::FromRenderViewHost(rvh
))) {
195 (*it
)->fullscreen
= true;
196 DispatchStatusChangeEvent(*it
);
205 bool TabCaptureRegistry::AddRequest(int render_process_id
,
207 const std::string
& extension_id
,
209 TabCaptureState status
) {
210 TabCaptureRequest
* request
= FindCaptureRequest(render_process_id
,
212 // Currently, we do not allow multiple active captures for same tab.
213 if (request
!= NULL
) {
214 if (request
->status
!= tab_capture::TAB_CAPTURE_STATE_STOPPED
&&
215 request
->status
!= tab_capture::TAB_CAPTURE_STATE_ERROR
) {
218 DeleteCaptureRequest(render_process_id
, render_view_id
);
222 requests_
.push_back(new TabCaptureRequest(render_process_id
,
230 bool TabCaptureRegistry::VerifyRequest(int render_process_id
,
231 int render_view_id
) {
232 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
233 DVLOG(1) << "Verifying tabCapture request for "
234 << render_process_id
<< ":" << render_view_id
;
235 // TODO(justinlin): Verify extension too.
236 return (FindCaptureRequest(render_process_id
, render_view_id
) != NULL
);
239 void TabCaptureRegistry::OnRequestUpdate(
240 int render_process_id
,
242 const content::MediaStreamDevice
& device
,
243 const content::MediaRequestState new_state
) {
244 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
245 if (device
.type
!= content::MEDIA_TAB_VIDEO_CAPTURE
&&
246 device
.type
!= content::MEDIA_TAB_AUDIO_CAPTURE
) {
250 TabCaptureRequest
* request
= FindCaptureRequest(render_process_id
,
252 if (request
== NULL
) {
253 // TODO(justinlin): This can happen because the extension's renderer does
254 // not seem to always cleanup streams correctly.
255 LOG(ERROR
) << "Receiving updates for deleted capture request.";
259 bool opening_stream
= false;
260 bool stopping_stream
= false;
262 TabCaptureState next_state
= tab_capture::TAB_CAPTURE_STATE_NONE
;
264 case content::MEDIA_REQUEST_STATE_PENDING_APPROVAL
:
265 next_state
= tab_capture::TAB_CAPTURE_STATE_PENDING
;
267 case content::MEDIA_REQUEST_STATE_DONE
:
268 opening_stream
= true;
269 next_state
= tab_capture::TAB_CAPTURE_STATE_ACTIVE
;
271 case content::MEDIA_REQUEST_STATE_CLOSING
:
272 stopping_stream
= true;
273 next_state
= tab_capture::TAB_CAPTURE_STATE_STOPPED
;
275 case content::MEDIA_REQUEST_STATE_ERROR
:
276 stopping_stream
= true;
277 next_state
= tab_capture::TAB_CAPTURE_STATE_ERROR
;
279 case content::MEDIA_REQUEST_STATE_OPENING
:
281 case content::MEDIA_REQUEST_STATE_REQUESTED
:
282 case content::MEDIA_REQUEST_STATE_NOT_REQUESTED
:
287 if (next_state
== tab_capture::TAB_CAPTURE_STATE_PENDING
&&
288 request
->status
!= tab_capture::TAB_CAPTURE_STATE_PENDING
&&
289 request
->status
!= tab_capture::TAB_CAPTURE_STATE_NONE
&&
290 request
->status
!= tab_capture::TAB_CAPTURE_STATE_STOPPED
&&
291 request
->status
!= tab_capture::TAB_CAPTURE_STATE_ERROR
) {
292 // If we end up trying to grab a new stream while the previous one was never
293 // terminated, then something fishy is going on.
294 NOTREACHED() << "Trying to capture tab with existing stream.";
298 if (opening_stream
) {
299 request
->fullscreen_observer
.reset(new FullscreenObserver(request
, this));
302 if (stopping_stream
) {
303 request
->fullscreen_observer
.reset();
306 request
->last_status
= request
->status
;
307 request
->status
= next_state
;
309 // We will get duplicate events if we requested both audio and video, so only
311 if (request
->last_status
!= request
->status
) {
312 DispatchStatusChangeEvent(request
);
316 void TabCaptureRegistry::DispatchStatusChangeEvent(
317 const TabCaptureRequest
* request
) const {
318 EventRouter
* router
= profile_
? EventRouter::Get(profile_
) : NULL
;
322 scoped_ptr
<tab_capture::CaptureInfo
> info(new tab_capture::CaptureInfo());
323 info
->tab_id
= request
->tab_id
;
324 info
->status
= request
->status
;
325 info
->fullscreen
= request
->fullscreen
;
327 scoped_ptr
<base::ListValue
> args(new base::ListValue());
328 args
->Append(info
->ToValue().release());
329 scoped_ptr
<Event
> event(new Event(tab_capture::OnStatusChanged::kEventName
,
331 event
->restrict_to_browser_context
= profile_
;
333 router
->DispatchEventToExtension(request
->extension_id
, event
.Pass());
336 TabCaptureRequest
* TabCaptureRegistry::FindCaptureRequest(
337 int render_process_id
, int render_view_id
) const {
338 for (ScopedVector
<TabCaptureRequest
>::const_iterator it
= requests_
.begin();
339 it
!= requests_
.end(); ++it
) {
340 if ((*it
)->render_process_id
== render_process_id
&&
341 (*it
)->render_view_id
== render_view_id
) {
348 void TabCaptureRegistry::DeleteCaptureRequest(int render_process_id
,
349 int render_view_id
) {
350 for (ScopedVector
<TabCaptureRequest
>::iterator it
= requests_
.begin();
351 it
!= requests_
.end(); ++it
) {
352 if ((*it
)->render_process_id
== render_process_id
&&
353 (*it
)->render_view_id
== render_view_id
) {
360 } // namespace extensions