tab_capture_registry.cc revision 116680a4aac90f2aa7413d9095a592090648e557
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 "base/values.h" 9#include "chrome/browser/sessions/session_id.h" 10#include "components/keyed_service/content/browser_context_dependency_manager.h" 11#include "content/public/browser/browser_thread.h" 12#include "content/public/browser/render_frame_host.h" 13#include "content/public/browser/web_contents.h" 14#include "content/public/browser/web_contents_observer.h" 15#include "extensions/browser/event_router.h" 16#include "extensions/browser/extension_registry.h" 17#include "extensions/common/extension.h" 18 19using content::BrowserThread; 20using extensions::tab_capture::TabCaptureState; 21 22namespace extensions { 23 24namespace tab_capture = api::tab_capture; 25 26// Stores values associated with a tab capture request, maintains lifecycle 27// state, and monitors WebContents for fullscreen transition events and 28// destruction. 29class TabCaptureRegistry::LiveRequest : public content::WebContentsObserver { 30 public: 31 LiveRequest(content::WebContents* target_contents, 32 const std::string& extension_id, 33 TabCaptureRegistry* registry) 34 : content::WebContentsObserver(target_contents), 35 extension_id_(extension_id), 36 registry_(registry), 37 capture_state_(tab_capture::TAB_CAPTURE_STATE_NONE), 38 is_verified_(false), 39 // TODO(miu): This initial value for |is_fullscreened_| is a faulty 40 // assumption. http://crbug.com/350491 41 is_fullscreened_(false), 42 render_process_id_(-1), 43 render_frame_id_(-1) { 44 DCHECK(web_contents()); 45 DCHECK(registry_); 46 } 47 48 virtual ~LiveRequest() {} 49 50 // Accessors. 51 const content::WebContents* target_contents() const { 52 return content::WebContentsObserver::web_contents(); 53 } 54 const std::string& extension_id() const { 55 return extension_id_; 56 } 57 TabCaptureState capture_state() const { 58 return capture_state_; 59 } 60 bool is_verified() const { 61 return is_verified_; 62 } 63 64 void SetIsVerified() { 65 DCHECK(!is_verified_); 66 is_verified_ = true; 67 } 68 69 // TODO(miu): See TODO(miu) in VerifyRequest() below. 70 void SetOriginallyTargettedRenderFrameID(int render_process_id, 71 int render_frame_id) { 72 DCHECK_GT(render_frame_id, 0); 73 DCHECK_EQ(render_frame_id_, -1); // Setting ID only once. 74 render_process_id_ = render_process_id; 75 render_frame_id_ = render_frame_id; 76 } 77 78 bool WasOriginallyTargettingRenderFrameID(int render_process_id, 79 int render_frame_id) const { 80 return render_process_id_ == render_process_id && 81 render_frame_id_ == render_frame_id; 82 } 83 84 void UpdateCaptureState(TabCaptureState next_capture_state) { 85 // This method can get duplicate calls if both audio and video were 86 // requested, so return early to avoid duplicate dispatching of status 87 // change events. 88 if (capture_state_ == next_capture_state) 89 return; 90 91 capture_state_ = next_capture_state; 92 registry_->DispatchStatusChangeEvent(this); 93 } 94 95 void GetCaptureInfo(tab_capture::CaptureInfo* info) const { 96 info->tab_id = SessionID::IdForTab(web_contents()); 97 info->status = capture_state_; 98 info->fullscreen = is_fullscreened_; 99 } 100 101 protected: 102 virtual void DidShowFullscreenWidget(int routing_id) OVERRIDE { 103 is_fullscreened_ = true; 104 if (capture_state_ == tab_capture::TAB_CAPTURE_STATE_ACTIVE) 105 registry_->DispatchStatusChangeEvent(this); 106 } 107 108 virtual void DidDestroyFullscreenWidget(int routing_id) OVERRIDE { 109 is_fullscreened_ = false; 110 if (capture_state_ == tab_capture::TAB_CAPTURE_STATE_ACTIVE) 111 registry_->DispatchStatusChangeEvent(this); 112 } 113 114 virtual void DidToggleFullscreenModeForTab(bool entered_fullscreen) OVERRIDE { 115 is_fullscreened_ = entered_fullscreen; 116 if (capture_state_ == tab_capture::TAB_CAPTURE_STATE_ACTIVE) 117 registry_->DispatchStatusChangeEvent(this); 118 } 119 120 virtual void WebContentsDestroyed() OVERRIDE { 121 registry_->KillRequest(this); // Deletes |this|. 122 } 123 124 private: 125 const std::string extension_id_; 126 TabCaptureRegistry* const registry_; 127 TabCaptureState capture_state_; 128 bool is_verified_; 129 bool is_fullscreened_; 130 131 // These reference the originally targetted RenderFrameHost by its ID. The 132 // RenderFrameHost may have gone away long before a LiveRequest closes, but 133 // calls to OnRequestUpdate() will always refer to this request by this ID. 134 int render_process_id_; 135 int render_frame_id_; 136 137 DISALLOW_COPY_AND_ASSIGN(LiveRequest); 138}; 139 140TabCaptureRegistry::TabCaptureRegistry(content::BrowserContext* context) 141 : browser_context_(context), extension_registry_observer_(this) { 142 MediaCaptureDevicesDispatcher::GetInstance()->AddObserver(this); 143 extension_registry_observer_.Add(ExtensionRegistry::Get(browser_context_)); 144} 145 146TabCaptureRegistry::~TabCaptureRegistry() { 147 MediaCaptureDevicesDispatcher::GetInstance()->RemoveObserver(this); 148} 149 150// static 151TabCaptureRegistry* TabCaptureRegistry::Get(content::BrowserContext* context) { 152 return BrowserContextKeyedAPIFactory<TabCaptureRegistry>::Get(context); 153} 154 155static base::LazyInstance<BrowserContextKeyedAPIFactory<TabCaptureRegistry> > 156 g_factory = LAZY_INSTANCE_INITIALIZER; 157 158// static 159BrowserContextKeyedAPIFactory<TabCaptureRegistry>* 160TabCaptureRegistry::GetFactoryInstance() { 161 return g_factory.Pointer(); 162} 163 164void TabCaptureRegistry::GetCapturedTabs( 165 const std::string& extension_id, 166 base::ListValue* list_of_capture_info) const { 167 DCHECK_CURRENTLY_ON(BrowserThread::UI); 168 DCHECK(list_of_capture_info); 169 list_of_capture_info->Clear(); 170 for (ScopedVector<LiveRequest>::const_iterator it = requests_.begin(); 171 it != requests_.end(); ++it) { 172 if ((*it)->is_verified() && (*it)->extension_id() == extension_id) { 173 tab_capture::CaptureInfo info; 174 (*it)->GetCaptureInfo(&info); 175 list_of_capture_info->Append(info.ToValue().release()); 176 } 177 } 178} 179 180void TabCaptureRegistry::OnExtensionUnloaded( 181 content::BrowserContext* browser_context, 182 const Extension* extension, 183 UnloadedExtensionInfo::Reason reason) { 184 // Cleanup all the requested media streams for this extension. 185 for (ScopedVector<LiveRequest>::iterator it = requests_.begin(); 186 it != requests_.end();) { 187 if ((*it)->extension_id() == extension->id()) { 188 it = requests_.erase(it); 189 } else { 190 ++it; 191 } 192 } 193} 194 195bool TabCaptureRegistry::AddRequest(content::WebContents* target_contents, 196 const std::string& extension_id) { 197 LiveRequest* const request = FindRequest(target_contents); 198 199 // Currently, we do not allow multiple active captures for same tab. 200 if (request != NULL) { 201 if (request->capture_state() != tab_capture::TAB_CAPTURE_STATE_STOPPED && 202 request->capture_state() != tab_capture::TAB_CAPTURE_STATE_ERROR) { 203 return false; 204 } else { 205 // Delete the request before creating its replacement (below). 206 KillRequest(request); 207 } 208 } 209 210 requests_.push_back(new LiveRequest(target_contents, extension_id, this)); 211 return true; 212} 213 214bool TabCaptureRegistry::VerifyRequest( 215 int render_process_id, 216 int render_frame_id, 217 const std::string& extension_id) { 218 DCHECK_CURRENTLY_ON(BrowserThread::UI); 219 220 LiveRequest* const request = FindRequest( 221 content::WebContents::FromRenderFrameHost( 222 content::RenderFrameHost::FromID( 223 render_process_id, render_frame_id))); 224 if (!request) 225 return false; // Unknown RenderFrameHost ID, or frame has gone away. 226 227 // TODO(miu): We should probably also verify the origin URL, like the desktop 228 // capture API. http://crbug.com/163100 229 if (request->is_verified() || 230 request->extension_id() != extension_id || 231 (request->capture_state() != tab_capture::TAB_CAPTURE_STATE_NONE && 232 request->capture_state() != tab_capture::TAB_CAPTURE_STATE_PENDING)) 233 return false; 234 235 // TODO(miu): The RenderFrameHost IDs should be set when LiveRequest is 236 // constructed, but ExtensionFunction does not yet support use of 237 // render_frame_host() to determine the exact RenderFrameHost for the call to 238 // AddRequest() above. Fix tab_capture_api.cc, and then fix this ugly hack. 239 // http://crbug.com/304341 240 request->SetOriginallyTargettedRenderFrameID( 241 render_process_id, render_frame_id); 242 243 request->SetIsVerified(); 244 return true; 245} 246 247void TabCaptureRegistry::OnRequestUpdate( 248 int original_target_render_process_id, 249 int original_target_render_frame_id, 250 content::MediaStreamType stream_type, 251 const content::MediaRequestState new_state) { 252 DCHECK_CURRENTLY_ON(BrowserThread::UI); 253 if (stream_type != content::MEDIA_TAB_VIDEO_CAPTURE && 254 stream_type != content::MEDIA_TAB_AUDIO_CAPTURE) { 255 return; 256 } 257 258 LiveRequest* request = FindRequest(original_target_render_process_id, 259 original_target_render_frame_id); 260 if (!request) { 261 // Fall-back: Search again using WebContents since this method may have been 262 // called before VerifyRequest() set the RenderFrameHost ID. If the 263 // RenderFrameHost has gone away, that's okay since the upcoming call to 264 // VerifyRequest() will fail, and that means the tracking of request updates 265 // doesn't matter anymore. 266 request = FindRequest(content::WebContents::FromRenderFrameHost( 267 content::RenderFrameHost::FromID(original_target_render_process_id, 268 original_target_render_frame_id))); 269 if (!request) 270 return; // Stale or invalid request update. 271 } 272 273 TabCaptureState next_state = tab_capture::TAB_CAPTURE_STATE_NONE; 274 switch (new_state) { 275 case content::MEDIA_REQUEST_STATE_PENDING_APPROVAL: 276 next_state = tab_capture::TAB_CAPTURE_STATE_PENDING; 277 break; 278 case content::MEDIA_REQUEST_STATE_DONE: 279 next_state = tab_capture::TAB_CAPTURE_STATE_ACTIVE; 280 break; 281 case content::MEDIA_REQUEST_STATE_CLOSING: 282 next_state = tab_capture::TAB_CAPTURE_STATE_STOPPED; 283 break; 284 case content::MEDIA_REQUEST_STATE_ERROR: 285 next_state = tab_capture::TAB_CAPTURE_STATE_ERROR; 286 break; 287 case content::MEDIA_REQUEST_STATE_OPENING: 288 return; 289 case content::MEDIA_REQUEST_STATE_REQUESTED: 290 case content::MEDIA_REQUEST_STATE_NOT_REQUESTED: 291 NOTREACHED(); 292 return; 293 } 294 295 if (next_state == tab_capture::TAB_CAPTURE_STATE_PENDING && 296 request->capture_state() != tab_capture::TAB_CAPTURE_STATE_PENDING && 297 request->capture_state() != tab_capture::TAB_CAPTURE_STATE_NONE && 298 request->capture_state() != tab_capture::TAB_CAPTURE_STATE_STOPPED && 299 request->capture_state() != tab_capture::TAB_CAPTURE_STATE_ERROR) { 300 // If we end up trying to grab a new stream while the previous one was never 301 // terminated, then something fishy is going on. 302 NOTREACHED() << "Trying to capture tab with existing stream."; 303 return; 304 } 305 306 request->UpdateCaptureState(next_state); 307} 308 309void TabCaptureRegistry::DispatchStatusChangeEvent( 310 const LiveRequest* request) const { 311 EventRouter* router = EventRouter::Get(browser_context_); 312 if (!router) 313 return; 314 315 scoped_ptr<base::ListValue> args(new base::ListValue()); 316 tab_capture::CaptureInfo info; 317 request->GetCaptureInfo(&info); 318 args->Append(info.ToValue().release()); 319 scoped_ptr<Event> event(new Event(tab_capture::OnStatusChanged::kEventName, 320 args.Pass())); 321 event->restrict_to_browser_context = browser_context_; 322 323 router->DispatchEventToExtension(request->extension_id(), event.Pass()); 324} 325 326TabCaptureRegistry::LiveRequest* TabCaptureRegistry::FindRequest( 327 const content::WebContents* target_contents) const { 328 for (ScopedVector<LiveRequest>::const_iterator it = requests_.begin(); 329 it != requests_.end(); ++it) { 330 if ((*it)->target_contents() == target_contents) 331 return *it; 332 } 333 return NULL; 334} 335 336TabCaptureRegistry::LiveRequest* TabCaptureRegistry::FindRequest( 337 int original_target_render_process_id, 338 int original_target_render_frame_id) const { 339 for (ScopedVector<LiveRequest>::const_iterator it = requests_.begin(); 340 it != requests_.end(); ++it) { 341 if ((*it)->WasOriginallyTargettingRenderFrameID( 342 original_target_render_process_id, 343 original_target_render_frame_id)) 344 return *it; 345 } 346 return NULL; 347} 348 349void TabCaptureRegistry::KillRequest(LiveRequest* request) { 350 for (ScopedVector<LiveRequest>::iterator it = requests_.begin(); 351 it != requests_.end(); ++it) { 352 if ((*it) == request) { 353 requests_.erase(it); 354 return; 355 } 356 } 357 NOTREACHED(); 358} 359 360} // namespace extensions 361