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/ui/fullscreen/fullscreen_controller.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/notification_details.h"
13 #include "content/public/browser/notification_service.h"
14 #include "content/public/browser/notification_source.h"
15 #include "content/public/browser/render_view_host.h"
16 #include "content/public/browser/web_contents.h"
17 #include "content/public/browser/web_contents_observer.h"
18 #include "extensions/browser/event_router.h"
19 #include "extensions/browser/extension_registry.h"
20 #include "extensions/common/extension.h"
21
22 using content::BrowserThread;
23 using extensions::tab_capture::TabCaptureState;
24
25 namespace extensions {
26
27 namespace tab_capture = api::tab_capture;
28
29 class FullscreenObserver : public content::WebContentsObserver {
30 public:
31 FullscreenObserver(TabCaptureRequest* request,
32 const TabCaptureRegistry* registry);
~FullscreenObserver()33 virtual ~FullscreenObserver() {}
34
35 private:
36 // content::WebContentsObserver implementation.
37 virtual void DidShowFullscreenWidget(int routing_id) OVERRIDE;
38 virtual void DidDestroyFullscreenWidget(int routing_id) OVERRIDE;
39
40 TabCaptureRequest* request_;
41 const TabCaptureRegistry* registry_;
42
43 DISALLOW_COPY_AND_ASSIGN(FullscreenObserver);
44 };
45
46 // Holds all the state related to a tab capture stream.
47 struct TabCaptureRequest {
48 TabCaptureRequest(int render_process_id,
49 int render_view_id,
50 const std::string& extension_id,
51 int tab_id,
52 TabCaptureState status);
53 ~TabCaptureRequest();
54
55 const int render_process_id;
56 const int render_view_id;
57 const std::string extension_id;
58 const int tab_id;
59 TabCaptureState status;
60 TabCaptureState last_status;
61 bool fullscreen;
62 scoped_ptr<FullscreenObserver> fullscreen_observer;
63 };
64
FullscreenObserver(TabCaptureRequest * request,const TabCaptureRegistry * registry)65 FullscreenObserver::FullscreenObserver(
66 TabCaptureRequest* request,
67 const TabCaptureRegistry* registry)
68 : request_(request),
69 registry_(registry) {
70 content::RenderViewHost* const rvh =
71 content::RenderViewHost::FromID(request->render_process_id,
72 request->render_view_id);
73 Observe(rvh ? content::WebContents::FromRenderViewHost(rvh) : NULL);
74 }
75
DidShowFullscreenWidget(int routing_id)76 void FullscreenObserver::DidShowFullscreenWidget(
77 int routing_id) {
78 request_->fullscreen = true;
79 registry_->DispatchStatusChangeEvent(request_);
80 }
81
DidDestroyFullscreenWidget(int routing_id)82 void FullscreenObserver::DidDestroyFullscreenWidget(
83 int routing_id) {
84 request_->fullscreen = false;
85 registry_->DispatchStatusChangeEvent(request_);
86 }
87
TabCaptureRequest(int render_process_id,int render_view_id,const std::string & extension_id,const int tab_id,TabCaptureState status)88 TabCaptureRequest::TabCaptureRequest(
89 int render_process_id,
90 int render_view_id,
91 const std::string& extension_id,
92 const int tab_id,
93 TabCaptureState status)
94 : render_process_id(render_process_id),
95 render_view_id(render_view_id),
96 extension_id(extension_id),
97 tab_id(tab_id),
98 status(status),
99 last_status(status),
100 fullscreen(false) {
101 }
102
~TabCaptureRequest()103 TabCaptureRequest::~TabCaptureRequest() {
104 }
105
TabCaptureRegistry(content::BrowserContext * context)106 TabCaptureRegistry::TabCaptureRegistry(content::BrowserContext* context)
107 : browser_context_(context), extension_registry_observer_(this) {
108 MediaCaptureDevicesDispatcher::GetInstance()->AddObserver(this);
109 registrar_.Add(this,
110 chrome::NOTIFICATION_FULLSCREEN_CHANGED,
111 content::NotificationService::AllSources());
112 extension_registry_observer_.Add(ExtensionRegistry::Get(browser_context_));
113 }
114
~TabCaptureRegistry()115 TabCaptureRegistry::~TabCaptureRegistry() {
116 MediaCaptureDevicesDispatcher::GetInstance()->RemoveObserver(this);
117 }
118
119 // static
Get(content::BrowserContext * context)120 TabCaptureRegistry* TabCaptureRegistry::Get(content::BrowserContext* context) {
121 return BrowserContextKeyedAPIFactory<TabCaptureRegistry>::Get(context);
122 }
123
124 static base::LazyInstance<BrowserContextKeyedAPIFactory<TabCaptureRegistry> >
125 g_factory = LAZY_INSTANCE_INITIALIZER;
126
127 // static
128 BrowserContextKeyedAPIFactory<TabCaptureRegistry>*
GetFactoryInstance()129 TabCaptureRegistry::GetFactoryInstance() {
130 return g_factory.Pointer();
131 }
132
133 const TabCaptureRegistry::RegistryCaptureInfo
GetCapturedTabs(const std::string & extension_id) const134 TabCaptureRegistry::GetCapturedTabs(const std::string& extension_id) const {
135 DCHECK_CURRENTLY_ON(BrowserThread::UI);
136 RegistryCaptureInfo list;
137 for (ScopedVector<TabCaptureRequest>::const_iterator it = requests_.begin();
138 it != requests_.end(); ++it) {
139 if ((*it)->extension_id == extension_id) {
140 list.push_back(std::make_pair((*it)->tab_id, (*it)->status));
141 }
142 }
143 return list;
144 }
145
Observe(int type,const content::NotificationSource & source,const content::NotificationDetails & details)146 void TabCaptureRegistry::Observe(int type,
147 const content::NotificationSource& source,
148 const content::NotificationDetails& details) {
149 DCHECK_CURRENTLY_ON(BrowserThread::UI);
150 DCHECK_EQ(chrome::NOTIFICATION_FULLSCREEN_CHANGED, type);
151 FullscreenController* fullscreen_controller =
152 content::Source<FullscreenController>(source).ptr();
153 const bool is_fullscreen = *content::Details<bool>(details).ptr();
154 for (ScopedVector<TabCaptureRequest>::iterator it = requests_.begin();
155 it != requests_.end();
156 ++it) {
157 // If we are exiting fullscreen mode, we only need to check if any of
158 // the requests had the fullscreen flag toggled previously. The
159 // fullscreen controller no longer has the reference to the fullscreen
160 // web_contents here.
161 if (!is_fullscreen) {
162 if ((*it)->fullscreen) {
163 (*it)->fullscreen = false;
164 DispatchStatusChangeEvent(*it);
165 break;
166 }
167 continue;
168 }
169
170 // If we are entering fullscreen mode, find whether the web_contents we
171 // are capturing entered fullscreen mode.
172 content::RenderViewHost* const rvh = content::RenderViewHost::FromID(
173 (*it)->render_process_id, (*it)->render_view_id);
174 if (rvh &&
175 fullscreen_controller->IsFullscreenForTabOrPending(
176 content::WebContents::FromRenderViewHost(rvh))) {
177 (*it)->fullscreen = true;
178 DispatchStatusChangeEvent(*it);
179 break;
180 }
181 }
182 }
183
OnExtensionUnloaded(content::BrowserContext * browser_context,const Extension * extension,UnloadedExtensionInfo::Reason reason)184 void TabCaptureRegistry::OnExtensionUnloaded(
185 content::BrowserContext* browser_context,
186 const Extension* extension,
187 UnloadedExtensionInfo::Reason reason) {
188 // Cleanup all the requested media streams for this extension.
189 for (ScopedVector<TabCaptureRequest>::iterator it = requests_.begin();
190 it != requests_.end();) {
191 if ((*it)->extension_id == extension->id()) {
192 it = requests_.erase(it);
193 } else {
194 ++it;
195 }
196 }
197 }
198
AddRequest(int render_process_id,int render_view_id,const std::string & extension_id,int tab_id,TabCaptureState status)199 bool TabCaptureRegistry::AddRequest(int render_process_id,
200 int render_view_id,
201 const std::string& extension_id,
202 int tab_id,
203 TabCaptureState status) {
204 TabCaptureRequest* request = FindCaptureRequest(render_process_id,
205 render_view_id);
206 // Currently, we do not allow multiple active captures for same tab.
207 if (request != NULL) {
208 if (request->status != tab_capture::TAB_CAPTURE_STATE_STOPPED &&
209 request->status != tab_capture::TAB_CAPTURE_STATE_ERROR) {
210 return false;
211 } else {
212 DeleteCaptureRequest(render_process_id, render_view_id);
213 }
214 }
215
216 requests_.push_back(new TabCaptureRequest(render_process_id,
217 render_view_id,
218 extension_id,
219 tab_id,
220 status));
221 return true;
222 }
223
VerifyRequest(int render_process_id,int render_view_id)224 bool TabCaptureRegistry::VerifyRequest(int render_process_id,
225 int render_view_id) {
226 DCHECK_CURRENTLY_ON(BrowserThread::UI);
227 DVLOG(1) << "Verifying tabCapture request for "
228 << render_process_id << ":" << render_view_id;
229 // TODO(justinlin): Verify extension too.
230 return (FindCaptureRequest(render_process_id, render_view_id) != NULL);
231 }
232
OnRequestUpdate(int render_process_id,int render_view_id,const content::MediaStreamDevice & device,const content::MediaRequestState new_state)233 void TabCaptureRegistry::OnRequestUpdate(
234 int render_process_id,
235 int render_view_id,
236 const content::MediaStreamDevice& device,
237 const content::MediaRequestState new_state) {
238 DCHECK_CURRENTLY_ON(BrowserThread::UI);
239 if (device.type != content::MEDIA_TAB_VIDEO_CAPTURE &&
240 device.type != content::MEDIA_TAB_AUDIO_CAPTURE) {
241 return;
242 }
243
244 TabCaptureRequest* request = FindCaptureRequest(render_process_id,
245 render_view_id);
246 if (request == NULL) {
247 // TODO(justinlin): This can happen because the extension's renderer does
248 // not seem to always cleanup streams correctly.
249 LOG(ERROR) << "Receiving updates for deleted capture request.";
250 return;
251 }
252
253 bool opening_stream = false;
254 bool stopping_stream = false;
255
256 TabCaptureState next_state = tab_capture::TAB_CAPTURE_STATE_NONE;
257 switch (new_state) {
258 case content::MEDIA_REQUEST_STATE_PENDING_APPROVAL:
259 next_state = tab_capture::TAB_CAPTURE_STATE_PENDING;
260 break;
261 case content::MEDIA_REQUEST_STATE_DONE:
262 opening_stream = true;
263 next_state = tab_capture::TAB_CAPTURE_STATE_ACTIVE;
264 break;
265 case content::MEDIA_REQUEST_STATE_CLOSING:
266 stopping_stream = true;
267 next_state = tab_capture::TAB_CAPTURE_STATE_STOPPED;
268 break;
269 case content::MEDIA_REQUEST_STATE_ERROR:
270 stopping_stream = true;
271 next_state = tab_capture::TAB_CAPTURE_STATE_ERROR;
272 break;
273 case content::MEDIA_REQUEST_STATE_OPENING:
274 return;
275 case content::MEDIA_REQUEST_STATE_REQUESTED:
276 case content::MEDIA_REQUEST_STATE_NOT_REQUESTED:
277 NOTREACHED();
278 return;
279 }
280
281 if (next_state == tab_capture::TAB_CAPTURE_STATE_PENDING &&
282 request->status != tab_capture::TAB_CAPTURE_STATE_PENDING &&
283 request->status != tab_capture::TAB_CAPTURE_STATE_NONE &&
284 request->status != tab_capture::TAB_CAPTURE_STATE_STOPPED &&
285 request->status != tab_capture::TAB_CAPTURE_STATE_ERROR) {
286 // If we end up trying to grab a new stream while the previous one was never
287 // terminated, then something fishy is going on.
288 NOTREACHED() << "Trying to capture tab with existing stream.";
289 return;
290 }
291
292 if (opening_stream) {
293 request->fullscreen_observer.reset(new FullscreenObserver(request, this));
294 }
295
296 if (stopping_stream) {
297 request->fullscreen_observer.reset();
298 }
299
300 request->last_status = request->status;
301 request->status = next_state;
302
303 // We will get duplicate events if we requested both audio and video, so only
304 // send new events.
305 if (request->last_status != request->status) {
306 DispatchStatusChangeEvent(request);
307 }
308 }
309
DispatchStatusChangeEvent(const TabCaptureRequest * request) const310 void TabCaptureRegistry::DispatchStatusChangeEvent(
311 const TabCaptureRequest* request) const {
312 EventRouter* router = EventRouter::Get(browser_context_);
313 if (!router)
314 return;
315
316 scoped_ptr<tab_capture::CaptureInfo> info(new tab_capture::CaptureInfo());
317 info->tab_id = request->tab_id;
318 info->status = request->status;
319 info->fullscreen = request->fullscreen;
320
321 scoped_ptr<base::ListValue> args(new base::ListValue());
322 args->Append(info->ToValue().release());
323 scoped_ptr<Event> event(new Event(tab_capture::OnStatusChanged::kEventName,
324 args.Pass()));
325 event->restrict_to_browser_context = browser_context_;
326
327 router->DispatchEventToExtension(request->extension_id, event.Pass());
328 }
329
FindCaptureRequest(int render_process_id,int render_view_id) const330 TabCaptureRequest* TabCaptureRegistry::FindCaptureRequest(
331 int render_process_id, int render_view_id) const {
332 for (ScopedVector<TabCaptureRequest>::const_iterator it = requests_.begin();
333 it != requests_.end(); ++it) {
334 if ((*it)->render_process_id == render_process_id &&
335 (*it)->render_view_id == render_view_id) {
336 return *it;
337 }
338 }
339 return NULL;
340 }
341
DeleteCaptureRequest(int render_process_id,int render_view_id)342 void TabCaptureRegistry::DeleteCaptureRequest(int render_process_id,
343 int render_view_id) {
344 for (ScopedVector<TabCaptureRequest>::iterator it = requests_.begin();
345 it != requests_.end(); ++it) {
346 if ((*it)->render_process_id == render_process_id &&
347 (*it)->render_view_id == render_view_id) {
348 requests_.erase(it);
349 return;
350 }
351 }
352 }
353
354 } // namespace extensions
355