• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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