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