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