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.
4
5 #include "chrome/browser/extensions/api/tab_capture/tab_capture_registry.h"
6
7 #include "base/lazy_instance.h"
8 #include "chrome/browser/chrome_notification_types.h"
9 #include "chrome/browser/extensions/extension_system.h"
10 #include "chrome/browser/profiles/profile.h"
11 #include "chrome/browser/ui/fullscreen/fullscreen_controller.h"
12 #include "components/browser_context_keyed_service/browser_context_dependency_manager.h"
13 #include "content/public/browser/browser_thread.h"
14 #include "content/public/browser/notification_details.h"
15 #include "content/public/browser/notification_service.h"
16 #include "content/public/browser/notification_source.h"
17 #include "content/public/browser/render_view_host.h"
18 #include "content/public/browser/web_contents.h"
19 #include "content/public/browser/web_contents_observer.h"
20 #include "extensions/browser/event_router.h"
21 #include "extensions/common/extension.h"
22
23 using content::BrowserThread;
24 using extensions::TabCaptureRegistry;
25 using extensions::tab_capture::TabCaptureState;
26
27 namespace extensions {
28
29 namespace tab_capture = api::tab_capture;
30
31 class FullscreenObserver : public content::WebContentsObserver {
32 public:
33 FullscreenObserver(TabCaptureRequest* request,
34 const TabCaptureRegistry* registry);
~FullscreenObserver()35 virtual ~FullscreenObserver() {}
36
37 private:
38 // content::WebContentsObserver implementation.
39 virtual void DidShowFullscreenWidget(int routing_id) OVERRIDE;
40 virtual void DidDestroyFullscreenWidget(int routing_id) OVERRIDE;
41
42 TabCaptureRequest* request_;
43 const TabCaptureRegistry* registry_;
44
45 DISALLOW_COPY_AND_ASSIGN(FullscreenObserver);
46 };
47
48 // Holds all the state related to a tab capture stream.
49 struct TabCaptureRequest {
50 TabCaptureRequest(int render_process_id,
51 int render_view_id,
52 const std::string& extension_id,
53 int tab_id,
54 TabCaptureState status);
55 ~TabCaptureRequest();
56
57 const int render_process_id;
58 const int render_view_id;
59 const std::string extension_id;
60 const int tab_id;
61 TabCaptureState status;
62 TabCaptureState last_status;
63 bool fullscreen;
64 scoped_ptr<FullscreenObserver> fullscreen_observer;
65 };
66
FullscreenObserver(TabCaptureRequest * request,const TabCaptureRegistry * registry)67 FullscreenObserver::FullscreenObserver(
68 TabCaptureRequest* request,
69 const TabCaptureRegistry* registry)
70 : request_(request),
71 registry_(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);
76 }
77
DidShowFullscreenWidget(int routing_id)78 void FullscreenObserver::DidShowFullscreenWidget(
79 int routing_id) {
80 request_->fullscreen = true;
81 registry_->DispatchStatusChangeEvent(request_);
82 }
83
DidDestroyFullscreenWidget(int routing_id)84 void FullscreenObserver::DidDestroyFullscreenWidget(
85 int routing_id) {
86 request_->fullscreen = false;
87 registry_->DispatchStatusChangeEvent(request_);
88 }
89
TabCaptureRequest(int render_process_id,int render_view_id,const std::string & extension_id,const int tab_id,TabCaptureState status)90 TabCaptureRequest::TabCaptureRequest(
91 int render_process_id,
92 int render_view_id,
93 const std::string& extension_id,
94 const int tab_id,
95 TabCaptureState status)
96 : render_process_id(render_process_id),
97 render_view_id(render_view_id),
98 extension_id(extension_id),
99 tab_id(tab_id),
100 status(status),
101 last_status(status),
102 fullscreen(false) {
103 }
104
~TabCaptureRequest()105 TabCaptureRequest::~TabCaptureRequest() {
106 }
107
TabCaptureRegistry(Profile * profile)108 TabCaptureRegistry::TabCaptureRegistry(Profile* profile)
109 : profile_(profile) {
110 MediaCaptureDevicesDispatcher::GetInstance()->AddObserver(this);
111 registrar_.Add(this,
112 chrome::NOTIFICATION_EXTENSION_UNLOADED,
113 content::Source<Profile>(profile_));
114 registrar_.Add(this,
115 chrome::NOTIFICATION_FULLSCREEN_CHANGED,
116 content::NotificationService::AllSources());
117 }
118
~TabCaptureRegistry()119 TabCaptureRegistry::~TabCaptureRegistry() {
120 MediaCaptureDevicesDispatcher::GetInstance()->RemoveObserver(this);
121 }
122
123 // static
Get(Profile * profile)124 TabCaptureRegistry* TabCaptureRegistry::Get(Profile* profile) {
125 return ProfileKeyedAPIFactory<TabCaptureRegistry>::GetForProfile(profile);
126 }
127
128 static base::LazyInstance<ProfileKeyedAPIFactory<TabCaptureRegistry> >
129 g_factory = LAZY_INSTANCE_INITIALIZER;
130
131 // static
132 ProfileKeyedAPIFactory<TabCaptureRegistry>*
GetFactoryInstance()133 TabCaptureRegistry::GetFactoryInstance() {
134 return &g_factory.Get();
135 }
136
137 const TabCaptureRegistry::RegistryCaptureInfo
GetCapturedTabs(const std::string & extension_id) const138 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));
145 }
146 }
147 return list;
148 }
149
Observe(int type,const content::NotificationSource & source,const content::NotificationDetails & details)150 void TabCaptureRegistry::Observe(int type,
151 const content::NotificationSource& source,
152 const content::NotificationDetails& details) {
153 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
154 switch (type) {
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)->
159 extension->id();
160 for (ScopedVector<TabCaptureRequest>::iterator it = requests_.begin();
161 it != requests_.end();) {
162 if ((*it)->extension_id == extension_id) {
163 it = requests_.erase(it);
164 } else {
165 ++it;
166 }
167 }
168 break;
169 }
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);
184 break;
185 }
186 continue;
187 }
188
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);
198 break;
199 }
200 }
201 break;
202 }
203 }
204 }
205
AddRequest(int render_process_id,int render_view_id,const std::string & extension_id,int tab_id,TabCaptureState status)206 bool TabCaptureRegistry::AddRequest(int render_process_id,
207 int render_view_id,
208 const std::string& extension_id,
209 int tab_id,
210 TabCaptureState status) {
211 TabCaptureRequest* request = FindCaptureRequest(render_process_id,
212 render_view_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) {
217 return false;
218 } else {
219 DeleteCaptureRequest(render_process_id, render_view_id);
220 }
221 }
222
223 requests_.push_back(new TabCaptureRequest(render_process_id,
224 render_view_id,
225 extension_id,
226 tab_id,
227 status));
228 return true;
229 }
230
VerifyRequest(int render_process_id,int render_view_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);
238 }
239
OnRequestUpdate(int render_process_id,int render_view_id,const content::MediaStreamDevice & device,const content::MediaRequestState new_state)240 void TabCaptureRegistry::OnRequestUpdate(
241 int render_process_id,
242 int render_view_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) {
248 return;
249 }
250
251 TabCaptureRequest* request = FindCaptureRequest(render_process_id,
252 render_view_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.";
257 return;
258 }
259
260 bool opening_stream = false;
261 bool stopping_stream = false;
262
263 TabCaptureState next_state = tab_capture::TAB_CAPTURE_STATE_NONE;
264 switch (new_state) {
265 case content::MEDIA_REQUEST_STATE_PENDING_APPROVAL:
266 next_state = tab_capture::TAB_CAPTURE_STATE_PENDING;
267 break;
268 case content::MEDIA_REQUEST_STATE_DONE:
269 opening_stream = true;
270 next_state = tab_capture::TAB_CAPTURE_STATE_ACTIVE;
271 break;
272 case content::MEDIA_REQUEST_STATE_CLOSING:
273 stopping_stream = true;
274 next_state = tab_capture::TAB_CAPTURE_STATE_STOPPED;
275 break;
276 case content::MEDIA_REQUEST_STATE_ERROR:
277 stopping_stream = true;
278 next_state = tab_capture::TAB_CAPTURE_STATE_ERROR;
279 break;
280 case content::MEDIA_REQUEST_STATE_OPENING:
281 return;
282 case content::MEDIA_REQUEST_STATE_REQUESTED:
283 case content::MEDIA_REQUEST_STATE_NOT_REQUESTED:
284 NOTREACHED();
285 return;
286 }
287
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.";
296 return;
297 }
298
299 if (opening_stream) {
300 request->fullscreen_observer.reset(new FullscreenObserver(request, this));
301 }
302
303 if (stopping_stream) {
304 request->fullscreen_observer.reset();
305 }
306
307 request->last_status = request->status;
308 request->status = next_state;
309
310 // We will get duplicate events if we requested both audio and video, so only
311 // send new events.
312 if (request->last_status != request->status) {
313 DispatchStatusChangeEvent(request);
314 }
315 }
316
DispatchStatusChangeEvent(const TabCaptureRequest * request) const317 void TabCaptureRegistry::DispatchStatusChangeEvent(
318 const TabCaptureRequest* request) const {
319 EventRouter* router = profile_ ?
320 extensions::ExtensionSystem::Get(profile_)->event_router() : NULL;
321 if (!router)
322 return;
323
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;
328
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,
332 args.Pass()));
333 event->restrict_to_browser_context = profile_;
334
335 router->DispatchEventToExtension(request->extension_id, event.Pass());
336 }
337
FindCaptureRequest(int render_process_id,int render_view_id) const338 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) {
344 return *it;
345 }
346 }
347 return NULL;
348 }
349
DeleteCaptureRequest(int render_process_id,int 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) {
356 requests_.erase(it);
357 return;
358 }
359 }
360 }
361
362 } // namespace extensions
363