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