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