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