tab_capture_registry.cc revision 116680a4aac90f2aa7413d9095a592090648e557
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 "base/values.h"
9#include "chrome/browser/sessions/session_id.h"
10#include "components/keyed_service/content/browser_context_dependency_manager.h"
11#include "content/public/browser/browser_thread.h"
12#include "content/public/browser/render_frame_host.h"
13#include "content/public/browser/web_contents.h"
14#include "content/public/browser/web_contents_observer.h"
15#include "extensions/browser/event_router.h"
16#include "extensions/browser/extension_registry.h"
17#include "extensions/common/extension.h"
18
19using content::BrowserThread;
20using extensions::tab_capture::TabCaptureState;
21
22namespace extensions {
23
24namespace tab_capture = api::tab_capture;
25
26// Stores values associated with a tab capture request, maintains lifecycle
27// state, and monitors WebContents for fullscreen transition events and
28// destruction.
29class TabCaptureRegistry::LiveRequest : public content::WebContentsObserver {
30 public:
31  LiveRequest(content::WebContents* target_contents,
32              const std::string& extension_id,
33              TabCaptureRegistry* registry)
34      : content::WebContentsObserver(target_contents),
35        extension_id_(extension_id),
36        registry_(registry),
37        capture_state_(tab_capture::TAB_CAPTURE_STATE_NONE),
38        is_verified_(false),
39        // TODO(miu): This initial value for |is_fullscreened_| is a faulty
40        // assumption.  http://crbug.com/350491
41        is_fullscreened_(false),
42        render_process_id_(-1),
43        render_frame_id_(-1) {
44    DCHECK(web_contents());
45    DCHECK(registry_);
46  }
47
48  virtual ~LiveRequest() {}
49
50  // Accessors.
51  const content::WebContents* target_contents() const {
52    return content::WebContentsObserver::web_contents();
53  }
54  const std::string& extension_id() const {
55    return extension_id_;
56  }
57  TabCaptureState capture_state() const {
58    return capture_state_;
59  }
60  bool is_verified() const {
61    return is_verified_;
62  }
63
64  void SetIsVerified() {
65    DCHECK(!is_verified_);
66    is_verified_ = true;
67  }
68
69  // TODO(miu): See TODO(miu) in VerifyRequest() below.
70  void SetOriginallyTargettedRenderFrameID(int render_process_id,
71                                           int render_frame_id) {
72    DCHECK_GT(render_frame_id, 0);
73    DCHECK_EQ(render_frame_id_, -1);  // Setting ID only once.
74    render_process_id_ = render_process_id;
75    render_frame_id_ = render_frame_id;
76  }
77
78  bool WasOriginallyTargettingRenderFrameID(int render_process_id,
79                                            int render_frame_id) const {
80    return render_process_id_ == render_process_id &&
81        render_frame_id_ == render_frame_id;
82  }
83
84  void UpdateCaptureState(TabCaptureState next_capture_state) {
85    // This method can get duplicate calls if both audio and video were
86    // requested, so return early to avoid duplicate dispatching of status
87    // change events.
88    if (capture_state_ == next_capture_state)
89      return;
90
91    capture_state_ = next_capture_state;
92    registry_->DispatchStatusChangeEvent(this);
93  }
94
95  void GetCaptureInfo(tab_capture::CaptureInfo* info) const {
96    info->tab_id = SessionID::IdForTab(web_contents());
97    info->status = capture_state_;
98    info->fullscreen = is_fullscreened_;
99  }
100
101 protected:
102  virtual void DidShowFullscreenWidget(int routing_id) OVERRIDE {
103    is_fullscreened_ = true;
104    if (capture_state_ == tab_capture::TAB_CAPTURE_STATE_ACTIVE)
105      registry_->DispatchStatusChangeEvent(this);
106  }
107
108  virtual void DidDestroyFullscreenWidget(int routing_id) OVERRIDE {
109    is_fullscreened_ = false;
110    if (capture_state_ == tab_capture::TAB_CAPTURE_STATE_ACTIVE)
111      registry_->DispatchStatusChangeEvent(this);
112  }
113
114  virtual void DidToggleFullscreenModeForTab(bool entered_fullscreen) OVERRIDE {
115    is_fullscreened_ = entered_fullscreen;
116    if (capture_state_ == tab_capture::TAB_CAPTURE_STATE_ACTIVE)
117      registry_->DispatchStatusChangeEvent(this);
118  }
119
120  virtual void WebContentsDestroyed() OVERRIDE {
121    registry_->KillRequest(this);  // Deletes |this|.
122  }
123
124 private:
125  const std::string extension_id_;
126  TabCaptureRegistry* const registry_;
127  TabCaptureState capture_state_;
128  bool is_verified_;
129  bool is_fullscreened_;
130
131  // These reference the originally targetted RenderFrameHost by its ID.  The
132  // RenderFrameHost may have gone away long before a LiveRequest closes, but
133  // calls to OnRequestUpdate() will always refer to this request by this ID.
134  int render_process_id_;
135  int render_frame_id_;
136
137  DISALLOW_COPY_AND_ASSIGN(LiveRequest);
138};
139
140TabCaptureRegistry::TabCaptureRegistry(content::BrowserContext* context)
141    : browser_context_(context), extension_registry_observer_(this) {
142  MediaCaptureDevicesDispatcher::GetInstance()->AddObserver(this);
143  extension_registry_observer_.Add(ExtensionRegistry::Get(browser_context_));
144}
145
146TabCaptureRegistry::~TabCaptureRegistry() {
147  MediaCaptureDevicesDispatcher::GetInstance()->RemoveObserver(this);
148}
149
150// static
151TabCaptureRegistry* TabCaptureRegistry::Get(content::BrowserContext* context) {
152  return BrowserContextKeyedAPIFactory<TabCaptureRegistry>::Get(context);
153}
154
155static base::LazyInstance<BrowserContextKeyedAPIFactory<TabCaptureRegistry> >
156    g_factory = LAZY_INSTANCE_INITIALIZER;
157
158// static
159BrowserContextKeyedAPIFactory<TabCaptureRegistry>*
160TabCaptureRegistry::GetFactoryInstance() {
161  return g_factory.Pointer();
162}
163
164void TabCaptureRegistry::GetCapturedTabs(
165    const std::string& extension_id,
166    base::ListValue* list_of_capture_info) const {
167  DCHECK_CURRENTLY_ON(BrowserThread::UI);
168  DCHECK(list_of_capture_info);
169  list_of_capture_info->Clear();
170  for (ScopedVector<LiveRequest>::const_iterator it = requests_.begin();
171       it != requests_.end(); ++it) {
172    if ((*it)->is_verified() && (*it)->extension_id() == extension_id) {
173      tab_capture::CaptureInfo info;
174      (*it)->GetCaptureInfo(&info);
175      list_of_capture_info->Append(info.ToValue().release());
176    }
177  }
178}
179
180void TabCaptureRegistry::OnExtensionUnloaded(
181    content::BrowserContext* browser_context,
182    const Extension* extension,
183    UnloadedExtensionInfo::Reason reason) {
184  // Cleanup all the requested media streams for this extension.
185  for (ScopedVector<LiveRequest>::iterator it = requests_.begin();
186       it != requests_.end();) {
187    if ((*it)->extension_id() == extension->id()) {
188      it = requests_.erase(it);
189    } else {
190      ++it;
191    }
192  }
193}
194
195bool TabCaptureRegistry::AddRequest(content::WebContents* target_contents,
196                                    const std::string& extension_id) {
197  LiveRequest* const request = FindRequest(target_contents);
198
199  // Currently, we do not allow multiple active captures for same tab.
200  if (request != NULL) {
201    if (request->capture_state() != tab_capture::TAB_CAPTURE_STATE_STOPPED &&
202        request->capture_state() != tab_capture::TAB_CAPTURE_STATE_ERROR) {
203      return false;
204    } else {
205      // Delete the request before creating its replacement (below).
206      KillRequest(request);
207    }
208  }
209
210  requests_.push_back(new LiveRequest(target_contents, extension_id, this));
211  return true;
212}
213
214bool TabCaptureRegistry::VerifyRequest(
215    int render_process_id,
216    int render_frame_id,
217    const std::string& extension_id) {
218  DCHECK_CURRENTLY_ON(BrowserThread::UI);
219
220  LiveRequest* const request = FindRequest(
221      content::WebContents::FromRenderFrameHost(
222          content::RenderFrameHost::FromID(
223              render_process_id, render_frame_id)));
224  if (!request)
225    return false;  // Unknown RenderFrameHost ID, or frame has gone away.
226
227  // TODO(miu): We should probably also verify the origin URL, like the desktop
228  // capture API.  http://crbug.com/163100
229  if (request->is_verified() ||
230      request->extension_id() != extension_id ||
231      (request->capture_state() != tab_capture::TAB_CAPTURE_STATE_NONE &&
232       request->capture_state() != tab_capture::TAB_CAPTURE_STATE_PENDING))
233    return false;
234
235  // TODO(miu): The RenderFrameHost IDs should be set when LiveRequest is
236  // constructed, but ExtensionFunction does not yet support use of
237  // render_frame_host() to determine the exact RenderFrameHost for the call to
238  // AddRequest() above.  Fix tab_capture_api.cc, and then fix this ugly hack.
239  // http://crbug.com/304341
240  request->SetOriginallyTargettedRenderFrameID(
241      render_process_id, render_frame_id);
242
243  request->SetIsVerified();
244  return true;
245}
246
247void TabCaptureRegistry::OnRequestUpdate(
248    int original_target_render_process_id,
249    int original_target_render_frame_id,
250    content::MediaStreamType stream_type,
251    const content::MediaRequestState new_state) {
252  DCHECK_CURRENTLY_ON(BrowserThread::UI);
253  if (stream_type != content::MEDIA_TAB_VIDEO_CAPTURE &&
254      stream_type != content::MEDIA_TAB_AUDIO_CAPTURE) {
255    return;
256  }
257
258  LiveRequest* request = FindRequest(original_target_render_process_id,
259                                     original_target_render_frame_id);
260  if (!request) {
261    // Fall-back: Search again using WebContents since this method may have been
262    // called before VerifyRequest() set the RenderFrameHost ID.  If the
263    // RenderFrameHost has gone away, that's okay since the upcoming call to
264    // VerifyRequest() will fail, and that means the tracking of request updates
265    // doesn't matter anymore.
266    request = FindRequest(content::WebContents::FromRenderFrameHost(
267        content::RenderFrameHost::FromID(original_target_render_process_id,
268                                         original_target_render_frame_id)));
269    if (!request)
270      return;  // Stale or invalid request update.
271  }
272
273  TabCaptureState next_state = tab_capture::TAB_CAPTURE_STATE_NONE;
274  switch (new_state) {
275    case content::MEDIA_REQUEST_STATE_PENDING_APPROVAL:
276      next_state = tab_capture::TAB_CAPTURE_STATE_PENDING;
277      break;
278    case content::MEDIA_REQUEST_STATE_DONE:
279      next_state = tab_capture::TAB_CAPTURE_STATE_ACTIVE;
280      break;
281    case content::MEDIA_REQUEST_STATE_CLOSING:
282      next_state = tab_capture::TAB_CAPTURE_STATE_STOPPED;
283      break;
284    case content::MEDIA_REQUEST_STATE_ERROR:
285      next_state = tab_capture::TAB_CAPTURE_STATE_ERROR;
286      break;
287    case content::MEDIA_REQUEST_STATE_OPENING:
288      return;
289    case content::MEDIA_REQUEST_STATE_REQUESTED:
290    case content::MEDIA_REQUEST_STATE_NOT_REQUESTED:
291      NOTREACHED();
292      return;
293  }
294
295  if (next_state == tab_capture::TAB_CAPTURE_STATE_PENDING &&
296      request->capture_state() != tab_capture::TAB_CAPTURE_STATE_PENDING &&
297      request->capture_state() != tab_capture::TAB_CAPTURE_STATE_NONE &&
298      request->capture_state() != tab_capture::TAB_CAPTURE_STATE_STOPPED &&
299      request->capture_state() != tab_capture::TAB_CAPTURE_STATE_ERROR) {
300    // If we end up trying to grab a new stream while the previous one was never
301    // terminated, then something fishy is going on.
302    NOTREACHED() << "Trying to capture tab with existing stream.";
303    return;
304  }
305
306  request->UpdateCaptureState(next_state);
307}
308
309void TabCaptureRegistry::DispatchStatusChangeEvent(
310    const LiveRequest* request) const {
311  EventRouter* router = EventRouter::Get(browser_context_);
312  if (!router)
313    return;
314
315  scoped_ptr<base::ListValue> args(new base::ListValue());
316  tab_capture::CaptureInfo info;
317  request->GetCaptureInfo(&info);
318  args->Append(info.ToValue().release());
319  scoped_ptr<Event> event(new Event(tab_capture::OnStatusChanged::kEventName,
320      args.Pass()));
321  event->restrict_to_browser_context = browser_context_;
322
323  router->DispatchEventToExtension(request->extension_id(), event.Pass());
324}
325
326TabCaptureRegistry::LiveRequest* TabCaptureRegistry::FindRequest(
327    const content::WebContents* target_contents) const {
328  for (ScopedVector<LiveRequest>::const_iterator it = requests_.begin();
329       it != requests_.end(); ++it) {
330    if ((*it)->target_contents() == target_contents)
331      return *it;
332  }
333  return NULL;
334}
335
336TabCaptureRegistry::LiveRequest* TabCaptureRegistry::FindRequest(
337    int original_target_render_process_id,
338    int original_target_render_frame_id) const {
339  for (ScopedVector<LiveRequest>::const_iterator it = requests_.begin();
340       it != requests_.end(); ++it) {
341    if ((*it)->WasOriginallyTargettingRenderFrameID(
342            original_target_render_process_id,
343            original_target_render_frame_id))
344      return *it;
345  }
346  return NULL;
347}
348
349void TabCaptureRegistry::KillRequest(LiveRequest* request) {
350  for (ScopedVector<LiveRequest>::iterator it = requests_.begin();
351       it != requests_.end(); ++it) {
352    if ((*it) == request) {
353      requests_.erase(it);
354      return;
355    }
356  }
357  NOTREACHED();
358}
359
360}  // namespace extensions
361