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/extensions/event_router.h"
10 #include "chrome/browser/extensions/extension_system.h"
11 #include "chrome/browser/profiles/profile.h"
12 #include "chrome/browser/ui/fullscreen/fullscreen_controller.h"
13 #include "chrome/common/extensions/extension.h"
14 #include "components/browser_context_keyed_service/browser_context_dependency_manager.h"
15 #include "content/public/browser/browser_thread.h"
16 #include "content/public/browser/notification_details.h"
17 #include "content/public/browser/notification_service.h"
18 #include "content/public/browser/notification_source.h"
19 #include "content/public/browser/render_view_host.h"
20 #include "content/public/browser/web_contents.h"
21 #include "content/public/browser/web_contents_observer.h"
23 using content::BrowserThread;
24 using extensions::TabCaptureRegistry;
25 using extensions::tab_capture::TabCaptureState;
27 namespace extensions {
29 namespace tab_capture = api::tab_capture;
31 class FullscreenObserver : public content::WebContentsObserver {
33 FullscreenObserver(TabCaptureRequest* request,
34 const TabCaptureRegistry* registry);
35 virtual ~FullscreenObserver() {}
38 // content::WebContentsObserver implementation.
39 virtual void DidShowFullscreenWidget(int routing_id) OVERRIDE;
40 virtual void DidDestroyFullscreenWidget(int routing_id) OVERRIDE;
42 TabCaptureRequest* request_;
43 const TabCaptureRegistry* registry_;
45 DISALLOW_COPY_AND_ASSIGN(FullscreenObserver);
48 // Holds all the state related to a tab capture stream.
49 struct TabCaptureRequest {
50 TabCaptureRequest(int render_process_id,
52 const std::string& extension_id,
54 TabCaptureState status);
57 const int render_process_id;
58 const int render_view_id;
59 const std::string extension_id;
61 TabCaptureState status;
62 TabCaptureState last_status;
64 scoped_ptr<FullscreenObserver> fullscreen_observer;
67 FullscreenObserver::FullscreenObserver(
68 TabCaptureRequest* request,
69 const TabCaptureRegistry* registry)
72 content::RenderViewHost* const rvh =
73 content::RenderViewHost::FromID(request->render_process_id,
74 request->render_view_id);
75 Observe(rvh ? content::WebContents::FromRenderViewHost(rvh) : NULL);
78 void FullscreenObserver::DidShowFullscreenWidget(
80 request_->fullscreen = true;
81 registry_->DispatchStatusChangeEvent(request_);
84 void FullscreenObserver::DidDestroyFullscreenWidget(
86 request_->fullscreen = false;
87 registry_->DispatchStatusChangeEvent(request_);
90 TabCaptureRequest::TabCaptureRequest(
91 int render_process_id,
93 const std::string& extension_id,
95 TabCaptureState status)
96 : render_process_id(render_process_id),
97 render_view_id(render_view_id),
98 extension_id(extension_id),
105 TabCaptureRequest::~TabCaptureRequest() {
108 TabCaptureRegistry::TabCaptureRegistry(Profile* profile)
109 : profile_(profile) {
110 MediaCaptureDevicesDispatcher::GetInstance()->AddObserver(this);
112 chrome::NOTIFICATION_EXTENSION_UNLOADED,
113 content::Source<Profile>(profile_));
115 chrome::NOTIFICATION_FULLSCREEN_CHANGED,
116 content::NotificationService::AllSources());
119 TabCaptureRegistry::~TabCaptureRegistry() {
120 MediaCaptureDevicesDispatcher::GetInstance()->RemoveObserver(this);
124 TabCaptureRegistry* TabCaptureRegistry::Get(Profile* profile) {
125 return ProfileKeyedAPIFactory<TabCaptureRegistry>::GetForProfile(profile);
128 static base::LazyInstance<ProfileKeyedAPIFactory<TabCaptureRegistry> >
129 g_factory = LAZY_INSTANCE_INITIALIZER;
132 ProfileKeyedAPIFactory<TabCaptureRegistry>*
133 TabCaptureRegistry::GetFactoryInstance() {
134 return &g_factory.Get();
137 const TabCaptureRegistry::RegistryCaptureInfo
138 TabCaptureRegistry::GetCapturedTabs(const std::string& extension_id) const {
139 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
140 RegistryCaptureInfo list;
141 for (ScopedVector<TabCaptureRequest>::const_iterator it = requests_.begin();
142 it != requests_.end(); ++it) {
143 if ((*it)->extension_id == extension_id) {
144 list.push_back(std::make_pair((*it)->tab_id, (*it)->status));
150 void TabCaptureRegistry::Observe(int type,
151 const content::NotificationSource& source,
152 const content::NotificationDetails& details) {
153 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
155 case chrome::NOTIFICATION_EXTENSION_UNLOADED: {
156 // Cleanup all the requested media streams for this extension.
157 const std::string& extension_id =
158 content::Details<extensions::UnloadedExtensionInfo>(details)->
160 for (ScopedVector<TabCaptureRequest>::iterator it = requests_.begin();
161 it != requests_.end();) {
162 if ((*it)->extension_id == extension_id) {
163 it = requests_.erase(it);
170 case chrome::NOTIFICATION_FULLSCREEN_CHANGED: {
171 FullscreenController* fullscreen_controller =
172 content::Source<FullscreenController>(source).ptr();
173 const bool is_fullscreen = *content::Details<bool>(details).ptr();
174 for (ScopedVector<TabCaptureRequest>::iterator it = requests_.begin();
175 it != requests_.end(); ++it) {
176 // If we are exiting fullscreen mode, we only need to check if any of
177 // the requests had the fullscreen flag toggled previously. The
178 // fullscreen controller no longer has the reference to the fullscreen
179 // web_contents here.
180 if (!is_fullscreen) {
181 if ((*it)->fullscreen) {
182 (*it)->fullscreen = false;
183 DispatchStatusChangeEvent(*it);
189 // If we are entering fullscreen mode, find whether the web_contents we
190 // are capturing entered fullscreen mode.
191 content::RenderViewHost* const rvh =
192 content::RenderViewHost::FromID((*it)->render_process_id,
193 (*it)->render_view_id);
194 if (rvh && fullscreen_controller->IsFullscreenForTabOrPending(
195 content::WebContents::FromRenderViewHost(rvh))) {
196 (*it)->fullscreen = true;
197 DispatchStatusChangeEvent(*it);
206 bool TabCaptureRegistry::AddRequest(int render_process_id,
208 const std::string& extension_id,
210 TabCaptureState status) {
211 TabCaptureRequest* request = FindCaptureRequest(render_process_id,
213 // Currently, we do not allow multiple active captures for same tab.
214 if (request != NULL) {
215 if (request->status != tab_capture::TAB_CAPTURE_STATE_STOPPED &&
216 request->status != tab_capture::TAB_CAPTURE_STATE_ERROR) {
219 DeleteCaptureRequest(render_process_id, render_view_id);
223 requests_.push_back(new TabCaptureRequest(render_process_id,
231 bool TabCaptureRegistry::VerifyRequest(int render_process_id,
232 int render_view_id) {
233 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
234 DVLOG(1) << "Verifying tabCapture request for "
235 << render_process_id << ":" << render_view_id;
236 // TODO(justinlin): Verify extension too.
237 return (FindCaptureRequest(render_process_id, render_view_id) != NULL);
240 void TabCaptureRegistry::OnRequestUpdate(
241 int render_process_id,
243 const content::MediaStreamDevice& device,
244 const content::MediaRequestState new_state) {
245 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
246 if (device.type != content::MEDIA_TAB_VIDEO_CAPTURE &&
247 device.type != content::MEDIA_TAB_AUDIO_CAPTURE) {
251 TabCaptureRequest* request = FindCaptureRequest(render_process_id,
253 if (request == NULL) {
254 // TODO(justinlin): This can happen because the extension's renderer does
255 // not seem to always cleanup streams correctly.
256 LOG(ERROR) << "Receiving updates for deleted capture request.";
260 bool opening_stream = false;
261 bool stopping_stream = false;
263 TabCaptureState next_state = tab_capture::TAB_CAPTURE_STATE_NONE;
265 case content::MEDIA_REQUEST_STATE_PENDING_APPROVAL:
266 next_state = tab_capture::TAB_CAPTURE_STATE_PENDING;
268 case content::MEDIA_REQUEST_STATE_DONE:
269 opening_stream = true;
270 next_state = tab_capture::TAB_CAPTURE_STATE_ACTIVE;
272 case content::MEDIA_REQUEST_STATE_CLOSING:
273 stopping_stream = true;
274 next_state = tab_capture::TAB_CAPTURE_STATE_STOPPED;
276 case content::MEDIA_REQUEST_STATE_ERROR:
277 stopping_stream = true;
278 next_state = tab_capture::TAB_CAPTURE_STATE_ERROR;
280 case content::MEDIA_REQUEST_STATE_OPENING:
282 case content::MEDIA_REQUEST_STATE_REQUESTED:
283 case content::MEDIA_REQUEST_STATE_NOT_REQUESTED:
288 if (next_state == tab_capture::TAB_CAPTURE_STATE_PENDING &&
289 request->status != tab_capture::TAB_CAPTURE_STATE_PENDING &&
290 request->status != tab_capture::TAB_CAPTURE_STATE_NONE &&
291 request->status != tab_capture::TAB_CAPTURE_STATE_STOPPED &&
292 request->status != tab_capture::TAB_CAPTURE_STATE_ERROR) {
293 // If we end up trying to grab a new stream while the previous one was never
294 // terminated, then something fishy is going on.
295 NOTREACHED() << "Trying to capture tab with existing stream.";
299 if (opening_stream) {
300 request->fullscreen_observer.reset(new FullscreenObserver(request, this));
303 if (stopping_stream) {
304 request->fullscreen_observer.reset();
307 request->last_status = request->status;
308 request->status = next_state;
310 // We will get duplicate events if we requested both audio and video, so only
312 if (request->last_status != request->status) {
313 DispatchStatusChangeEvent(request);
317 void TabCaptureRegistry::DispatchStatusChangeEvent(
318 const TabCaptureRequest* request) const {
319 EventRouter* router = profile_ ?
320 extensions::ExtensionSystem::Get(profile_)->event_router() : NULL;
324 scoped_ptr<tab_capture::CaptureInfo> info(new tab_capture::CaptureInfo());
325 info->tab_id = request->tab_id;
326 info->status = request->status;
327 info->fullscreen = request->fullscreen;
329 scoped_ptr<base::ListValue> args(new base::ListValue());
330 args->Append(info->ToValue().release());
331 scoped_ptr<Event> event(new Event(tab_capture::OnStatusChanged::kEventName,
333 event->restrict_to_profile = profile_;
335 router->DispatchEventToExtension(request->extension_id, event.Pass());
338 TabCaptureRequest* TabCaptureRegistry::FindCaptureRequest(
339 int render_process_id, int render_view_id) const {
340 for (ScopedVector<TabCaptureRequest>::const_iterator it = requests_.begin();
341 it != requests_.end(); ++it) {
342 if ((*it)->render_process_id == render_process_id &&
343 (*it)->render_view_id == render_view_id) {
350 void TabCaptureRegistry::DeleteCaptureRequest(int render_process_id,
351 int render_view_id) {
352 for (ScopedVector<TabCaptureRequest>::iterator it = requests_.begin();
353 it != requests_.end(); ++it) {
354 if ((*it)->render_process_id == render_process_id &&
355 (*it)->render_view_id == render_view_id) {
362 } // namespace extensions