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 "base/values.h"
9 #include "chrome/browser/sessions/session_tab_helper.h"
10 #include "components/keyed_service/content/browser_context_dependency_manager.h"
11 #include "content/public/browser/browser_thread.h"
12 #include "content/public/browser/render_frame_host.h"
13 #include "content/public/browser/web_contents.h"
14 #include "content/public/browser/web_contents_observer.h"
15 #include "extensions/browser/event_router.h"
16 #include "extensions/browser/extension_registry.h"
17 #include "extensions/common/extension.h"
19 using content::BrowserThread
;
20 using extensions::tab_capture::TabCaptureState
;
22 namespace extensions
{
24 namespace tab_capture
= api::tab_capture
;
26 // Stores values associated with a tab capture request, maintains lifecycle
27 // state, and monitors WebContents for fullscreen transition events and
29 class TabCaptureRegistry::LiveRequest
: public content::WebContentsObserver
{
31 LiveRequest(content::WebContents
* target_contents
,
32 const std::string
& extension_id
,
33 TabCaptureRegistry
* registry
)
34 : content::WebContentsObserver(target_contents
),
35 extension_id_(extension_id
),
37 capture_state_(tab_capture::TAB_CAPTURE_STATE_NONE
),
39 // TODO(miu): This initial value for |is_fullscreened_| is a faulty
40 // assumption. http://crbug.com/350491
41 is_fullscreened_(false),
42 render_process_id_(-1),
43 render_frame_id_(-1) {
44 DCHECK(web_contents());
48 ~LiveRequest() override
{}
51 const std::string
& extension_id() const {
54 TabCaptureState
capture_state() const {
55 return capture_state_
;
57 bool is_verified() const {
61 void SetIsVerified() {
62 DCHECK(!is_verified_
);
66 // TODO(miu): See TODO(miu) in VerifyRequest() below.
67 void SetOriginallyTargettedRenderFrameID(int render_process_id
,
68 int render_frame_id
) {
69 DCHECK_GT(render_frame_id
, 0);
70 DCHECK_EQ(render_frame_id_
, -1); // Setting ID only once.
71 render_process_id_
= render_process_id
;
72 render_frame_id_
= render_frame_id
;
75 bool WasOriginallyTargettingRenderFrameID(int render_process_id
,
76 int render_frame_id
) const {
77 return render_process_id_
== render_process_id
&&
78 render_frame_id_
== render_frame_id
;
81 void UpdateCaptureState(TabCaptureState next_capture_state
) {
82 // This method can get duplicate calls if both audio and video were
83 // requested, so return early to avoid duplicate dispatching of status
85 if (capture_state_
== next_capture_state
)
88 capture_state_
= next_capture_state
;
89 registry_
->DispatchStatusChangeEvent(this);
92 void GetCaptureInfo(tab_capture::CaptureInfo
* info
) const {
93 info
->tab_id
= SessionTabHelper::IdForTab(web_contents());
94 info
->status
= capture_state_
;
95 info
->fullscreen
= is_fullscreened_
;
99 void DidShowFullscreenWidget(int routing_id
) override
{
100 is_fullscreened_
= true;
101 if (capture_state_
== tab_capture::TAB_CAPTURE_STATE_ACTIVE
)
102 registry_
->DispatchStatusChangeEvent(this);
105 void DidDestroyFullscreenWidget(int routing_id
) override
{
106 is_fullscreened_
= false;
107 if (capture_state_
== tab_capture::TAB_CAPTURE_STATE_ACTIVE
)
108 registry_
->DispatchStatusChangeEvent(this);
111 void DidToggleFullscreenModeForTab(bool entered_fullscreen
) override
{
112 is_fullscreened_
= entered_fullscreen
;
113 if (capture_state_
== tab_capture::TAB_CAPTURE_STATE_ACTIVE
)
114 registry_
->DispatchStatusChangeEvent(this);
117 void WebContentsDestroyed() override
{
118 registry_
->KillRequest(this); // Deletes |this|.
122 const std::string extension_id_
;
123 TabCaptureRegistry
* const registry_
;
124 TabCaptureState capture_state_
;
126 bool is_fullscreened_
;
128 // These reference the originally targetted RenderFrameHost by its ID. The
129 // RenderFrameHost may have gone away long before a LiveRequest closes, but
130 // calls to OnRequestUpdate() will always refer to this request by this ID.
131 int render_process_id_
;
132 int render_frame_id_
;
134 DISALLOW_COPY_AND_ASSIGN(LiveRequest
);
137 TabCaptureRegistry::TabCaptureRegistry(content::BrowserContext
* context
)
138 : browser_context_(context
), extension_registry_observer_(this) {
139 MediaCaptureDevicesDispatcher::GetInstance()->AddObserver(this);
140 extension_registry_observer_
.Add(ExtensionRegistry::Get(browser_context_
));
143 TabCaptureRegistry::~TabCaptureRegistry() {
144 MediaCaptureDevicesDispatcher::GetInstance()->RemoveObserver(this);
148 TabCaptureRegistry
* TabCaptureRegistry::Get(content::BrowserContext
* context
) {
149 return BrowserContextKeyedAPIFactory
<TabCaptureRegistry
>::Get(context
);
152 static base::LazyInstance
<BrowserContextKeyedAPIFactory
<TabCaptureRegistry
> >
153 g_factory
= LAZY_INSTANCE_INITIALIZER
;
156 BrowserContextKeyedAPIFactory
<TabCaptureRegistry
>*
157 TabCaptureRegistry::GetFactoryInstance() {
158 return g_factory
.Pointer();
161 void TabCaptureRegistry::GetCapturedTabs(
162 const std::string
& extension_id
,
163 base::ListValue
* list_of_capture_info
) const {
164 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
165 DCHECK(list_of_capture_info
);
166 list_of_capture_info
->Clear();
167 for (ScopedVector
<LiveRequest
>::const_iterator it
= requests_
.begin();
168 it
!= requests_
.end(); ++it
) {
169 if ((*it
)->is_verified() && (*it
)->extension_id() == extension_id
) {
170 tab_capture::CaptureInfo info
;
171 (*it
)->GetCaptureInfo(&info
);
172 list_of_capture_info
->Append(info
.ToValue().release());
177 void TabCaptureRegistry::OnExtensionUnloaded(
178 content::BrowserContext
* browser_context
,
179 const Extension
* extension
,
180 UnloadedExtensionInfo::Reason reason
) {
181 // Cleanup all the requested media streams for this extension.
182 for (ScopedVector
<LiveRequest
>::iterator it
= requests_
.begin();
183 it
!= requests_
.end();) {
184 if ((*it
)->extension_id() == extension
->id()) {
185 it
= requests_
.erase(it
);
192 bool TabCaptureRegistry::AddRequest(content::WebContents
* target_contents
,
193 const std::string
& extension_id
) {
194 LiveRequest
* const request
= FindRequest(target_contents
);
196 // Currently, we do not allow multiple active captures for same tab.
197 if (request
!= NULL
) {
198 if (request
->capture_state() != tab_capture::TAB_CAPTURE_STATE_STOPPED
&&
199 request
->capture_state() != tab_capture::TAB_CAPTURE_STATE_ERROR
) {
202 // Delete the request before creating its replacement (below).
203 KillRequest(request
);
207 requests_
.push_back(new LiveRequest(target_contents
, extension_id
, this));
211 bool TabCaptureRegistry::VerifyRequest(
212 int render_process_id
,
214 const std::string
& extension_id
) {
215 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
217 LiveRequest
* const request
= FindRequest(
218 content::WebContents::FromRenderFrameHost(
219 content::RenderFrameHost::FromID(
220 render_process_id
, render_frame_id
)));
222 return false; // Unknown RenderFrameHost ID, or frame has gone away.
224 // TODO(miu): We should probably also verify the origin URL, like the desktop
225 // capture API. http://crbug.com/163100
226 if (request
->is_verified() ||
227 request
->extension_id() != extension_id
||
228 (request
->capture_state() != tab_capture::TAB_CAPTURE_STATE_NONE
&&
229 request
->capture_state() != tab_capture::TAB_CAPTURE_STATE_PENDING
))
232 // TODO(miu): The RenderFrameHost IDs should be set when LiveRequest is
233 // constructed, but ExtensionFunction does not yet support use of
234 // render_frame_host() to determine the exact RenderFrameHost for the call to
235 // AddRequest() above. Fix tab_capture_api.cc, and then fix this ugly hack.
236 // http://crbug.com/304341
237 request
->SetOriginallyTargettedRenderFrameID(
238 render_process_id
, render_frame_id
);
240 request
->SetIsVerified();
244 void TabCaptureRegistry::OnRequestUpdate(
245 int original_target_render_process_id
,
246 int original_target_render_frame_id
,
247 content::MediaStreamType stream_type
,
248 const content::MediaRequestState new_state
) {
249 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
250 if (stream_type
!= content::MEDIA_TAB_VIDEO_CAPTURE
&&
251 stream_type
!= content::MEDIA_TAB_AUDIO_CAPTURE
) {
255 LiveRequest
* request
= FindRequest(original_target_render_process_id
,
256 original_target_render_frame_id
);
258 // Fall-back: Search again using WebContents since this method may have been
259 // called before VerifyRequest() set the RenderFrameHost ID. If the
260 // RenderFrameHost has gone away, that's okay since the upcoming call to
261 // VerifyRequest() will fail, and that means the tracking of request updates
262 // doesn't matter anymore.
263 request
= FindRequest(content::WebContents::FromRenderFrameHost(
264 content::RenderFrameHost::FromID(original_target_render_process_id
,
265 original_target_render_frame_id
)));
267 return; // Stale or invalid request update.
270 TabCaptureState next_state
= tab_capture::TAB_CAPTURE_STATE_NONE
;
272 case content::MEDIA_REQUEST_STATE_PENDING_APPROVAL
:
273 next_state
= tab_capture::TAB_CAPTURE_STATE_PENDING
;
275 case content::MEDIA_REQUEST_STATE_DONE
:
276 next_state
= tab_capture::TAB_CAPTURE_STATE_ACTIVE
;
278 case content::MEDIA_REQUEST_STATE_CLOSING
:
279 next_state
= tab_capture::TAB_CAPTURE_STATE_STOPPED
;
281 case content::MEDIA_REQUEST_STATE_ERROR
:
282 next_state
= tab_capture::TAB_CAPTURE_STATE_ERROR
;
284 case content::MEDIA_REQUEST_STATE_OPENING
:
286 case content::MEDIA_REQUEST_STATE_REQUESTED
:
287 case content::MEDIA_REQUEST_STATE_NOT_REQUESTED
:
292 if (next_state
== tab_capture::TAB_CAPTURE_STATE_PENDING
&&
293 request
->capture_state() != tab_capture::TAB_CAPTURE_STATE_PENDING
&&
294 request
->capture_state() != tab_capture::TAB_CAPTURE_STATE_NONE
&&
295 request
->capture_state() != tab_capture::TAB_CAPTURE_STATE_STOPPED
&&
296 request
->capture_state() != tab_capture::TAB_CAPTURE_STATE_ERROR
) {
297 // If we end up trying to grab a new stream while the previous one was never
298 // terminated, then something fishy is going on.
299 NOTREACHED() << "Trying to capture tab with existing stream.";
303 request
->UpdateCaptureState(next_state
);
306 void TabCaptureRegistry::DispatchStatusChangeEvent(
307 const LiveRequest
* request
) const {
308 EventRouter
* router
= EventRouter::Get(browser_context_
);
312 scoped_ptr
<base::ListValue
> args(new base::ListValue());
313 tab_capture::CaptureInfo info
;
314 request
->GetCaptureInfo(&info
);
315 args
->Append(info
.ToValue().release());
316 scoped_ptr
<Event
> event(new Event(events::TAB_CAPTURE_ON_STATUS_CHANGED
,
317 tab_capture::OnStatusChanged::kEventName
,
319 event
->restrict_to_browser_context
= browser_context_
;
321 router
->DispatchEventToExtension(request
->extension_id(), event
.Pass());
324 TabCaptureRegistry::LiveRequest
* TabCaptureRegistry::FindRequest(
325 const content::WebContents
* target_contents
) const {
326 for (ScopedVector
<LiveRequest
>::const_iterator it
= requests_
.begin();
327 it
!= requests_
.end(); ++it
) {
328 if ((*it
)->web_contents() == target_contents
)
334 TabCaptureRegistry::LiveRequest
* TabCaptureRegistry::FindRequest(
335 int original_target_render_process_id
,
336 int original_target_render_frame_id
) const {
337 for (ScopedVector
<LiveRequest
>::const_iterator it
= requests_
.begin();
338 it
!= requests_
.end(); ++it
) {
339 if ((*it
)->WasOriginallyTargettingRenderFrameID(
340 original_target_render_process_id
,
341 original_target_render_frame_id
))
347 void TabCaptureRegistry::KillRequest(LiveRequest
* request
) {
348 for (ScopedVector
<LiveRequest
>::iterator it
= requests_
.begin();
349 it
!= requests_
.end(); ++it
) {
350 if ((*it
) == request
) {
358 } // namespace extensions