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