| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/extensions/api/tab_capture/tab_capture_registry.h" |
| |
| #include "base/lazy_instance.h" |
| #include "chrome/browser/chrome_notification_types.h" |
| #include "chrome/browser/extensions/extension_system.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/ui/fullscreen/fullscreen_controller.h" |
| #include "components/browser_context_keyed_service/browser_context_dependency_manager.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/notification_details.h" |
| #include "content/public/browser/notification_service.h" |
| #include "content/public/browser/notification_source.h" |
| #include "content/public/browser/render_view_host.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/browser/web_contents_observer.h" |
| #include "extensions/browser/event_router.h" |
| #include "extensions/common/extension.h" |
| |
| using content::BrowserThread; |
| using extensions::TabCaptureRegistry; |
| using extensions::tab_capture::TabCaptureState; |
| |
| namespace extensions { |
| |
| namespace tab_capture = api::tab_capture; |
| |
| class FullscreenObserver : public content::WebContentsObserver { |
| public: |
| FullscreenObserver(TabCaptureRequest* request, |
| const TabCaptureRegistry* registry); |
| virtual ~FullscreenObserver() {} |
| |
| private: |
| // content::WebContentsObserver implementation. |
| virtual void DidShowFullscreenWidget(int routing_id) OVERRIDE; |
| virtual void DidDestroyFullscreenWidget(int routing_id) OVERRIDE; |
| |
| TabCaptureRequest* request_; |
| const TabCaptureRegistry* registry_; |
| |
| DISALLOW_COPY_AND_ASSIGN(FullscreenObserver); |
| }; |
| |
| // Holds all the state related to a tab capture stream. |
| struct TabCaptureRequest { |
| TabCaptureRequest(int render_process_id, |
| int render_view_id, |
| const std::string& extension_id, |
| int tab_id, |
| TabCaptureState status); |
| ~TabCaptureRequest(); |
| |
| const int render_process_id; |
| const int render_view_id; |
| const std::string extension_id; |
| const int tab_id; |
| TabCaptureState status; |
| TabCaptureState last_status; |
| bool fullscreen; |
| scoped_ptr<FullscreenObserver> fullscreen_observer; |
| }; |
| |
| FullscreenObserver::FullscreenObserver( |
| TabCaptureRequest* request, |
| const TabCaptureRegistry* registry) |
| : request_(request), |
| registry_(registry) { |
| content::RenderViewHost* const rvh = |
| content::RenderViewHost::FromID(request->render_process_id, |
| request->render_view_id); |
| Observe(rvh ? content::WebContents::FromRenderViewHost(rvh) : NULL); |
| } |
| |
| void FullscreenObserver::DidShowFullscreenWidget( |
| int routing_id) { |
| request_->fullscreen = true; |
| registry_->DispatchStatusChangeEvent(request_); |
| } |
| |
| void FullscreenObserver::DidDestroyFullscreenWidget( |
| int routing_id) { |
| request_->fullscreen = false; |
| registry_->DispatchStatusChangeEvent(request_); |
| } |
| |
| TabCaptureRequest::TabCaptureRequest( |
| int render_process_id, |
| int render_view_id, |
| const std::string& extension_id, |
| const int tab_id, |
| TabCaptureState status) |
| : render_process_id(render_process_id), |
| render_view_id(render_view_id), |
| extension_id(extension_id), |
| tab_id(tab_id), |
| status(status), |
| last_status(status), |
| fullscreen(false) { |
| } |
| |
| TabCaptureRequest::~TabCaptureRequest() { |
| } |
| |
| TabCaptureRegistry::TabCaptureRegistry(Profile* profile) |
| : profile_(profile) { |
| MediaCaptureDevicesDispatcher::GetInstance()->AddObserver(this); |
| registrar_.Add(this, |
| chrome::NOTIFICATION_EXTENSION_UNLOADED, |
| content::Source<Profile>(profile_)); |
| registrar_.Add(this, |
| chrome::NOTIFICATION_FULLSCREEN_CHANGED, |
| content::NotificationService::AllSources()); |
| } |
| |
| TabCaptureRegistry::~TabCaptureRegistry() { |
| MediaCaptureDevicesDispatcher::GetInstance()->RemoveObserver(this); |
| } |
| |
| // static |
| TabCaptureRegistry* TabCaptureRegistry::Get(Profile* profile) { |
| return ProfileKeyedAPIFactory<TabCaptureRegistry>::GetForProfile(profile); |
| } |
| |
| static base::LazyInstance<ProfileKeyedAPIFactory<TabCaptureRegistry> > |
| g_factory = LAZY_INSTANCE_INITIALIZER; |
| |
| // static |
| ProfileKeyedAPIFactory<TabCaptureRegistry>* |
| TabCaptureRegistry::GetFactoryInstance() { |
| return &g_factory.Get(); |
| } |
| |
| const TabCaptureRegistry::RegistryCaptureInfo |
| TabCaptureRegistry::GetCapturedTabs(const std::string& extension_id) const { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| RegistryCaptureInfo list; |
| for (ScopedVector<TabCaptureRequest>::const_iterator it = requests_.begin(); |
| it != requests_.end(); ++it) { |
| if ((*it)->extension_id == extension_id) { |
| list.push_back(std::make_pair((*it)->tab_id, (*it)->status)); |
| } |
| } |
| return list; |
| } |
| |
| void TabCaptureRegistry::Observe(int type, |
| const content::NotificationSource& source, |
| const content::NotificationDetails& details) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| switch (type) { |
| case chrome::NOTIFICATION_EXTENSION_UNLOADED: { |
| // Cleanup all the requested media streams for this extension. |
| const std::string& extension_id = |
| content::Details<extensions::UnloadedExtensionInfo>(details)-> |
| extension->id(); |
| for (ScopedVector<TabCaptureRequest>::iterator it = requests_.begin(); |
| it != requests_.end();) { |
| if ((*it)->extension_id == extension_id) { |
| it = requests_.erase(it); |
| } else { |
| ++it; |
| } |
| } |
| break; |
| } |
| case chrome::NOTIFICATION_FULLSCREEN_CHANGED: { |
| FullscreenController* fullscreen_controller = |
| content::Source<FullscreenController>(source).ptr(); |
| const bool is_fullscreen = *content::Details<bool>(details).ptr(); |
| for (ScopedVector<TabCaptureRequest>::iterator it = requests_.begin(); |
| it != requests_.end(); ++it) { |
| // If we are exiting fullscreen mode, we only need to check if any of |
| // the requests had the fullscreen flag toggled previously. The |
| // fullscreen controller no longer has the reference to the fullscreen |
| // web_contents here. |
| if (!is_fullscreen) { |
| if ((*it)->fullscreen) { |
| (*it)->fullscreen = false; |
| DispatchStatusChangeEvent(*it); |
| break; |
| } |
| continue; |
| } |
| |
| // If we are entering fullscreen mode, find whether the web_contents we |
| // are capturing entered fullscreen mode. |
| content::RenderViewHost* const rvh = |
| content::RenderViewHost::FromID((*it)->render_process_id, |
| (*it)->render_view_id); |
| if (rvh && fullscreen_controller->IsFullscreenForTabOrPending( |
| content::WebContents::FromRenderViewHost(rvh))) { |
| (*it)->fullscreen = true; |
| DispatchStatusChangeEvent(*it); |
| break; |
| } |
| } |
| break; |
| } |
| } |
| } |
| |
| bool TabCaptureRegistry::AddRequest(int render_process_id, |
| int render_view_id, |
| const std::string& extension_id, |
| int tab_id, |
| TabCaptureState status) { |
| TabCaptureRequest* request = FindCaptureRequest(render_process_id, |
| render_view_id); |
| // Currently, we do not allow multiple active captures for same tab. |
| if (request != NULL) { |
| if (request->status != tab_capture::TAB_CAPTURE_STATE_STOPPED && |
| request->status != tab_capture::TAB_CAPTURE_STATE_ERROR) { |
| return false; |
| } else { |
| DeleteCaptureRequest(render_process_id, render_view_id); |
| } |
| } |
| |
| requests_.push_back(new TabCaptureRequest(render_process_id, |
| render_view_id, |
| extension_id, |
| tab_id, |
| status)); |
| return true; |
| } |
| |
| bool TabCaptureRegistry::VerifyRequest(int render_process_id, |
| int render_view_id) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DVLOG(1) << "Verifying tabCapture request for " |
| << render_process_id << ":" << render_view_id; |
| // TODO(justinlin): Verify extension too. |
| return (FindCaptureRequest(render_process_id, render_view_id) != NULL); |
| } |
| |
| void TabCaptureRegistry::OnRequestUpdate( |
| int render_process_id, |
| int render_view_id, |
| const content::MediaStreamDevice& device, |
| const content::MediaRequestState new_state) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| if (device.type != content::MEDIA_TAB_VIDEO_CAPTURE && |
| device.type != content::MEDIA_TAB_AUDIO_CAPTURE) { |
| return; |
| } |
| |
| TabCaptureRequest* request = FindCaptureRequest(render_process_id, |
| render_view_id); |
| if (request == NULL) { |
| // TODO(justinlin): This can happen because the extension's renderer does |
| // not seem to always cleanup streams correctly. |
| LOG(ERROR) << "Receiving updates for deleted capture request."; |
| return; |
| } |
| |
| bool opening_stream = false; |
| bool stopping_stream = false; |
| |
| TabCaptureState next_state = tab_capture::TAB_CAPTURE_STATE_NONE; |
| switch (new_state) { |
| case content::MEDIA_REQUEST_STATE_PENDING_APPROVAL: |
| next_state = tab_capture::TAB_CAPTURE_STATE_PENDING; |
| break; |
| case content::MEDIA_REQUEST_STATE_DONE: |
| opening_stream = true; |
| next_state = tab_capture::TAB_CAPTURE_STATE_ACTIVE; |
| break; |
| case content::MEDIA_REQUEST_STATE_CLOSING: |
| stopping_stream = true; |
| next_state = tab_capture::TAB_CAPTURE_STATE_STOPPED; |
| break; |
| case content::MEDIA_REQUEST_STATE_ERROR: |
| stopping_stream = true; |
| next_state = tab_capture::TAB_CAPTURE_STATE_ERROR; |
| break; |
| case content::MEDIA_REQUEST_STATE_OPENING: |
| return; |
| case content::MEDIA_REQUEST_STATE_REQUESTED: |
| case content::MEDIA_REQUEST_STATE_NOT_REQUESTED: |
| NOTREACHED(); |
| return; |
| } |
| |
| if (next_state == tab_capture::TAB_CAPTURE_STATE_PENDING && |
| request->status != tab_capture::TAB_CAPTURE_STATE_PENDING && |
| request->status != tab_capture::TAB_CAPTURE_STATE_NONE && |
| request->status != tab_capture::TAB_CAPTURE_STATE_STOPPED && |
| request->status != tab_capture::TAB_CAPTURE_STATE_ERROR) { |
| // If we end up trying to grab a new stream while the previous one was never |
| // terminated, then something fishy is going on. |
| NOTREACHED() << "Trying to capture tab with existing stream."; |
| return; |
| } |
| |
| if (opening_stream) { |
| request->fullscreen_observer.reset(new FullscreenObserver(request, this)); |
| } |
| |
| if (stopping_stream) { |
| request->fullscreen_observer.reset(); |
| } |
| |
| request->last_status = request->status; |
| request->status = next_state; |
| |
| // We will get duplicate events if we requested both audio and video, so only |
| // send new events. |
| if (request->last_status != request->status) { |
| DispatchStatusChangeEvent(request); |
| } |
| } |
| |
| void TabCaptureRegistry::DispatchStatusChangeEvent( |
| const TabCaptureRequest* request) const { |
| EventRouter* router = profile_ ? |
| extensions::ExtensionSystem::Get(profile_)->event_router() : NULL; |
| if (!router) |
| return; |
| |
| scoped_ptr<tab_capture::CaptureInfo> info(new tab_capture::CaptureInfo()); |
| info->tab_id = request->tab_id; |
| info->status = request->status; |
| info->fullscreen = request->fullscreen; |
| |
| scoped_ptr<base::ListValue> args(new base::ListValue()); |
| args->Append(info->ToValue().release()); |
| scoped_ptr<Event> event(new Event(tab_capture::OnStatusChanged::kEventName, |
| args.Pass())); |
| event->restrict_to_browser_context = profile_; |
| |
| router->DispatchEventToExtension(request->extension_id, event.Pass()); |
| } |
| |
| TabCaptureRequest* TabCaptureRegistry::FindCaptureRequest( |
| int render_process_id, int render_view_id) const { |
| for (ScopedVector<TabCaptureRequest>::const_iterator it = requests_.begin(); |
| it != requests_.end(); ++it) { |
| if ((*it)->render_process_id == render_process_id && |
| (*it)->render_view_id == render_view_id) { |
| return *it; |
| } |
| } |
| return NULL; |
| } |
| |
| void TabCaptureRegistry::DeleteCaptureRequest(int render_process_id, |
| int render_view_id) { |
| for (ScopedVector<TabCaptureRequest>::iterator it = requests_.begin(); |
| it != requests_.end(); ++it) { |
| if ((*it)->render_process_id == render_process_id && |
| (*it)->render_view_id == render_view_id) { |
| requests_.erase(it); |
| return; |
| } |
| } |
| } |
| |
| } // namespace extensions |