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