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/chrome_notification_types.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/ui/fullscreen/fullscreen_controller.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 25 using content::BrowserThread; 26 using extensions::TabCaptureRegistry; 27 using extensions::tab_capture::TabCaptureState; 28 29 namespace extensions { 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 const 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 136 void 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 192 bool 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 217 bool 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 226 void 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 303 void 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 324 TabCaptureRequest* 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 336 void 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