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