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