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