tab_capture_registry.cc revision 5821806d5e7f356e8fa4b058a389a808ea183019
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 "content/public/browser/browser_thread.h"
8#include "chrome/browser/extensions/event_names.h"
9#include "chrome/browser/extensions/event_router.h"
10#include "chrome/browser/extensions/extension_system.h"
11#include "chrome/browser/media/media_internals.h"
12#include "chrome/browser/profiles/profile.h"
13#include "chrome/browser/profiles/profile_dependency_manager.h"
14#include "chrome/common/chrome_notification_types.h"
15#include "chrome/common/extensions/extension.h"
16#include "content/public/browser/notification_details.h"
17#include "content/public/browser/notification_source.h"
18
19namespace events = extensions::event_names;
20using content::BrowserThread;
21
22namespace extensions {
23
24TabCaptureRegistry::TabCaptureRegistry(Profile* profile)
25    : proxy_(new MediaObserverProxy()), profile_(profile) {
26  proxy_->Attach(this);
27  registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED,
28                 content::Source<Profile>(profile_));
29}
30
31TabCaptureRegistry::~TabCaptureRegistry() {
32  proxy_->Detach();
33}
34
35void TabCaptureRegistry::HandleRequestUpdateOnUIThread(
36    const content::MediaStreamDevice& device,
37    const content::MediaRequestState new_state) {
38  EventRouter* router = profile_ ?
39      extensions::ExtensionSystem::Get(profile_)->event_router() : NULL;
40  if (!router)
41    return;
42
43  if (requests_.find(device.device_id) == requests_.end())
44    return;
45
46  tab_capture::TabCaptureState state =
47      tab_capture::TAB_CAPTURE_TAB_CAPTURE_STATE_NONE;
48  switch (new_state) {
49    case content::MEDIA_REQUEST_STATE_REQUESTED:
50      state = tab_capture::TAB_CAPTURE_TAB_CAPTURE_STATE_REQUESTED;
51      break;
52    case content::MEDIA_REQUEST_STATE_PENDING_APPROVAL:
53      state = tab_capture::TAB_CAPTURE_TAB_CAPTURE_STATE_PENDING;
54      break;
55    case content::MEDIA_REQUEST_STATE_DONE:
56      state = tab_capture::TAB_CAPTURE_TAB_CAPTURE_STATE_ACTIVE;
57      break;
58    case content::MEDIA_REQUEST_STATE_CLOSING:
59      state = tab_capture::TAB_CAPTURE_TAB_CAPTURE_STATE_STOPPED;
60      break;
61    case content::MEDIA_REQUEST_STATE_ERROR:
62      state = tab_capture::TAB_CAPTURE_TAB_CAPTURE_STATE_ERROR;
63      break;
64    default:
65      // TODO(justinlin): Implement muted state notification.
66      break;
67  }
68
69  if (state == tab_capture::TAB_CAPTURE_TAB_CAPTURE_STATE_NONE) {
70    // This is a state we don't handle.
71    return;
72  }
73
74  TabCaptureRegistry::TabCaptureRequest& request_info =
75      requests_[device.device_id];
76  request_info.status = state;
77
78  scoped_ptr<tab_capture::CaptureInfo> info(new tab_capture::CaptureInfo());
79  info->tab_id = request_info.tab_id;
80  info->status = request_info.status;
81
82  scoped_ptr<base::ListValue> args(new ListValue());
83  args->Append(info->ToValue().release());
84  router->DispatchEventToExtension(request_info.extension_id,
85      events::kOnTabCaptureStatusChanged, args.Pass(), profile_, GURL());
86}
87
88const TabCaptureRegistry::CaptureRequestList
89    TabCaptureRegistry::GetCapturedTabs(const std::string& extension_id) {
90  CaptureRequestList list;
91  for (DeviceCaptureRequestMap::iterator it = requests_.begin();
92       it != requests_.end(); ++it) {
93    if (it->second.extension_id == extension_id) {
94      list.push_back(it->second);
95    }
96  }
97  return list;
98}
99
100void TabCaptureRegistry::Observe(int type,
101                                 const content::NotificationSource& source,
102                                 const content::NotificationDetails& details) {
103  switch (type) {
104    case chrome::NOTIFICATION_EXTENSION_UNLOADED: {
105      // Cleanup all the requested media streams for this extension. We might
106      // accumulate too many requests left in the closed state otherwise.
107      std::string extension_id =
108          content::Details<extensions::UnloadedExtensionInfo>(details)->
109              extension->id();
110      for (DeviceCaptureRequestMap::iterator it = requests_.begin();
111           it != requests_.end();) {
112        if (it->second.extension_id == extension_id) {
113          requests_.erase(it++);
114        } else {
115          ++it;
116        }
117      }
118      break;
119    }
120  }
121}
122
123bool TabCaptureRegistry::AddRequest(
124    const std::string& key, const TabCaptureRequest& request) {
125  // Currently, we do not allow multiple active captures for same tab.
126  DCHECK(!key.empty());
127  if (requests_.find(key) != requests_.end())
128    if (requests_[key].status !=
129        tab_capture::TAB_CAPTURE_TAB_CAPTURE_STATE_STOPPED &&
130        requests_[key].status !=
131        tab_capture::TAB_CAPTURE_TAB_CAPTURE_STATE_ERROR)
132      return false;
133  requests_[key] = request;
134  return true;
135}
136
137bool TabCaptureRegistry::VerifyRequest(const std::string& key) {
138  return requests_.find(key) != requests_.end();
139}
140
141void TabCaptureRegistry::MediaObserverProxy::Attach(
142    TabCaptureRegistry* request_handler) {
143  handler_ = request_handler;
144  BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
145      base::Bind(&TabCaptureRegistry::MediaObserverProxy::
146                 RegisterAsMediaObserverOnIOThread, this, false));
147}
148
149void TabCaptureRegistry::MediaObserverProxy::Detach() {
150  handler_ = NULL;
151  BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
152      base::Bind(&TabCaptureRegistry::MediaObserverProxy::
153                 RegisterAsMediaObserverOnIOThread, this, true));
154}
155
156void TabCaptureRegistry::MediaObserverProxy::RegisterAsMediaObserverOnIOThread(
157      bool unregister) {
158  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
159  if (MediaInternals::GetInstance()) {
160    if (!unregister)
161      MediaInternals::GetInstance()->AddObserver(this);
162    else
163      MediaInternals::GetInstance()->RemoveObserver(this);
164  }
165}
166
167void TabCaptureRegistry::MediaObserverProxy::OnRequestUpdate(
168    const content::MediaStreamDevice& device,
169    const content::MediaRequestState new_state) {
170  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
171
172  // TODO(justinlin): We drop audio device events since they will occur in
173  // parallel with the video device events (we would get duplicate events). When
174  // audio mirroring is implemented, we will want to grab those events when
175  // video is not requested.
176  if (device.type != content::MEDIA_TAB_VIDEO_CAPTURE)
177    return;
178
179  BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
180      base::Bind(&TabCaptureRegistry::MediaObserverProxy::UpdateOnUIThread,
181          this, device, new_state));
182}
183
184void TabCaptureRegistry::MediaObserverProxy::UpdateOnUIThread(
185    const content::MediaStreamDevice& device,
186    const content::MediaRequestState new_state) {
187  if (handler_)
188    handler_->HandleRequestUpdateOnUIThread(device, new_state);
189}
190
191}  // namespace extensions
192