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