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/extensions/extension_system.h" 10 #include "chrome/browser/profiles/profile.h" 11 #include "chrome/browser/ui/fullscreen/fullscreen_controller.h" 12 #include "components/browser_context_keyed_service/browser_context_dependency_manager.h" 13 #include "content/public/browser/browser_thread.h" 14 #include "content/public/browser/notification_details.h" 15 #include "content/public/browser/notification_service.h" 16 #include "content/public/browser/notification_source.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 #include "extensions/browser/event_router.h" 21 #include "extensions/common/extension.h" 22 23 using content::BrowserThread; 24 using extensions::TabCaptureRegistry; 25 using extensions::tab_capture::TabCaptureState; 26 27 namespace extensions { 28 29 namespace tab_capture = api::tab_capture; 30 31 class 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. 49 struct 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 67 FullscreenObserver::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 78 void FullscreenObserver::DidShowFullscreenWidget( 79 int routing_id) { 80 request_->fullscreen = true; 81 registry_->DispatchStatusChangeEvent(request_); 82 } 83 84 void FullscreenObserver::DidDestroyFullscreenWidget( 85 int routing_id) { 86 request_->fullscreen = false; 87 registry_->DispatchStatusChangeEvent(request_); 88 } 89 90 TabCaptureRequest::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 105 TabCaptureRequest::~TabCaptureRequest() { 106 } 107 108 TabCaptureRegistry::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 119 TabCaptureRegistry::~TabCaptureRegistry() { 120 MediaCaptureDevicesDispatcher::GetInstance()->RemoveObserver(this); 121 } 122 123 // static 124 TabCaptureRegistry* TabCaptureRegistry::Get(Profile* profile) { 125 return ProfileKeyedAPIFactory<TabCaptureRegistry>::GetForProfile(profile); 126 } 127 128 static base::LazyInstance<ProfileKeyedAPIFactory<TabCaptureRegistry> > 129 g_factory = LAZY_INSTANCE_INITIALIZER; 130 131 // static 132 ProfileKeyedAPIFactory<TabCaptureRegistry>* 133 TabCaptureRegistry::GetFactoryInstance() { 134 return &g_factory.Get(); 135 } 136 137 const TabCaptureRegistry::RegistryCaptureInfo 138 TabCaptureRegistry::GetCapturedTabs(const std::string& extension_id) const { 139 DCHECK(BrowserThread::CurrentlyOn(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 150 void TabCaptureRegistry::Observe(int type, 151 const content::NotificationSource& source, 152 const content::NotificationDetails& details) { 153 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 154 switch (type) { 155 case chrome::NOTIFICATION_EXTENSION_UNLOADED: { 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 206 bool 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 231 bool TabCaptureRegistry::VerifyRequest(int render_process_id, 232 int render_view_id) { 233 DCHECK(BrowserThread::CurrentlyOn(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 240 void 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(BrowserThread::CurrentlyOn(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 317 void 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 338 TabCaptureRequest* 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 350 void 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