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 <utility>
      8 
      9 #include "chrome/browser/chrome_notification_types.h"
     10 #include "chrome/browser/extensions/event_names.h"
     11 #include "chrome/browser/extensions/event_router.h"
     12 #include "chrome/browser/extensions/extension_system.h"
     13 #include "chrome/browser/profiles/profile.h"
     14 #include "chrome/browser/ui/fullscreen/fullscreen_controller.h"
     15 #include "chrome/common/extensions/extension.h"
     16 #include "components/browser_context_keyed_service/browser_context_dependency_manager.h"
     17 #include "content/public/browser/browser_thread.h"
     18 #include "content/public/browser/notification_details.h"
     19 #include "content/public/browser/notification_service.h"
     20 #include "content/public/browser/notification_source.h"
     21 #include "content/public/browser/render_view_host.h"
     22 #include "content/public/browser/web_contents.h"
     23 #include "content/public/browser/web_contents_observer.h"
     24 
     25 using content::BrowserThread;
     26 using extensions::TabCaptureRegistry;
     27 using extensions::tab_capture::TabCaptureState;
     28 
     29 namespace extensions {
     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 const TabCaptureRegistry::RegistryCaptureInfo
    124     TabCaptureRegistry::GetCapturedTabs(const std::string& extension_id) const {
    125   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    126   RegistryCaptureInfo list;
    127   for (ScopedVector<TabCaptureRequest>::const_iterator it = requests_.begin();
    128        it != requests_.end(); ++it) {
    129     if ((*it)->extension_id == extension_id) {
    130       list.push_back(std::make_pair((*it)->tab_id, (*it)->status));
    131     }
    132   }
    133   return list;
    134 }
    135 
    136 void TabCaptureRegistry::Observe(int type,
    137                                  const content::NotificationSource& source,
    138                                  const content::NotificationDetails& details) {
    139   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    140   switch (type) {
    141     case chrome::NOTIFICATION_EXTENSION_UNLOADED: {
    142       // Cleanup all the requested media streams for this extension.
    143       const std::string& extension_id =
    144           content::Details<extensions::UnloadedExtensionInfo>(details)->
    145               extension->id();
    146       for (ScopedVector<TabCaptureRequest>::iterator it = requests_.begin();
    147            it != requests_.end();) {
    148         if ((*it)->extension_id == extension_id) {
    149           it = requests_.erase(it);
    150         } else {
    151           ++it;
    152         }
    153       }
    154       break;
    155     }
    156     case chrome::NOTIFICATION_FULLSCREEN_CHANGED: {
    157       FullscreenController* fullscreen_controller =
    158           content::Source<FullscreenController>(source).ptr();
    159       const bool is_fullscreen = *content::Details<bool>(details).ptr();
    160       for (ScopedVector<TabCaptureRequest>::iterator it = requests_.begin();
    161            it != requests_.end(); ++it) {
    162         // If we are exiting fullscreen mode, we only need to check if any of
    163         // the requests had the fullscreen flag toggled previously. The
    164         // fullscreen controller no longer has the reference to the fullscreen
    165         // web_contents here.
    166         if (!is_fullscreen) {
    167           if ((*it)->fullscreen) {
    168             (*it)->fullscreen = false;
    169             DispatchStatusChangeEvent(*it);
    170             break;
    171           }
    172           continue;
    173         }
    174 
    175         // If we are entering fullscreen mode, find whether the web_contents we
    176         // are capturing entered fullscreen mode.
    177         content::RenderViewHost* const rvh =
    178             content::RenderViewHost::FromID((*it)->render_process_id,
    179                                             (*it)->render_view_id);
    180         if (rvh && fullscreen_controller->IsFullscreenForTabOrPending(
    181                 content::WebContents::FromRenderViewHost(rvh))) {
    182           (*it)->fullscreen = true;
    183           DispatchStatusChangeEvent(*it);
    184           break;
    185         }
    186       }
    187       break;
    188     }
    189   }
    190 }
    191 
    192 bool TabCaptureRegistry::AddRequest(int render_process_id,
    193                                     int render_view_id,
    194                                     const std::string& extension_id,
    195                                     int tab_id,
    196                                     TabCaptureState status) {
    197   TabCaptureRequest* request = FindCaptureRequest(render_process_id,
    198                                                   render_view_id);
    199   // Currently, we do not allow multiple active captures for same tab.
    200   if (request != NULL) {
    201     if (request->status != tab_capture::TAB_CAPTURE_STATE_STOPPED &&
    202         request->status != tab_capture::TAB_CAPTURE_STATE_ERROR) {
    203       return false;
    204     } else {
    205       DeleteCaptureRequest(render_process_id, render_view_id);
    206     }
    207   }
    208 
    209   requests_.push_back(new TabCaptureRequest(render_process_id,
    210                                             render_view_id,
    211                                             extension_id,
    212                                             tab_id,
    213                                             status));
    214   return true;
    215 }
    216 
    217 bool TabCaptureRegistry::VerifyRequest(int render_process_id,
    218                                        int render_view_id) {
    219   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    220   DVLOG(1) << "Verifying tabCapture request for "
    221            << render_process_id << ":" << render_view_id;
    222   // TODO(justinlin): Verify extension too.
    223   return (FindCaptureRequest(render_process_id, render_view_id) != NULL);
    224 }
    225 
    226 void TabCaptureRegistry::OnRequestUpdate(
    227     int render_process_id,
    228     int render_view_id,
    229     const content::MediaStreamDevice& device,
    230     const content::MediaRequestState new_state) {
    231   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    232   if (device.type != content::MEDIA_TAB_VIDEO_CAPTURE &&
    233       device.type != content::MEDIA_TAB_AUDIO_CAPTURE) {
    234     return;
    235   }
    236 
    237   TabCaptureRequest* request = FindCaptureRequest(render_process_id,
    238                                                   render_view_id);
    239   if (request == NULL) {
    240     // TODO(justinlin): This can happen because the extension's renderer does
    241     // not seem to always cleanup streams correctly.
    242     LOG(ERROR) << "Receiving updates for deleted capture request.";
    243     return;
    244   }
    245 
    246   bool opening_stream = false;
    247   bool stopping_stream = false;
    248 
    249   TabCaptureState next_state = tab_capture::TAB_CAPTURE_STATE_NONE;
    250   switch (new_state) {
    251     case content::MEDIA_REQUEST_STATE_PENDING_APPROVAL:
    252       next_state = tab_capture::TAB_CAPTURE_STATE_PENDING;
    253       break;
    254     case content::MEDIA_REQUEST_STATE_DONE:
    255       opening_stream = true;
    256       next_state = tab_capture::TAB_CAPTURE_STATE_ACTIVE;
    257       break;
    258     case content::MEDIA_REQUEST_STATE_CLOSING:
    259       stopping_stream = true;
    260       next_state = tab_capture::TAB_CAPTURE_STATE_STOPPED;
    261       break;
    262     case content::MEDIA_REQUEST_STATE_ERROR:
    263       stopping_stream = true;
    264       next_state = tab_capture::TAB_CAPTURE_STATE_ERROR;
    265       break;
    266     case content::MEDIA_REQUEST_STATE_OPENING:
    267       return;
    268     case content::MEDIA_REQUEST_STATE_REQUESTED:
    269     case content::MEDIA_REQUEST_STATE_NOT_REQUESTED:
    270       NOTREACHED();
    271       return;
    272   }
    273 
    274   if (next_state == tab_capture::TAB_CAPTURE_STATE_PENDING &&
    275       request->status != tab_capture::TAB_CAPTURE_STATE_PENDING &&
    276       request->status != tab_capture::TAB_CAPTURE_STATE_NONE &&
    277       request->status != tab_capture::TAB_CAPTURE_STATE_STOPPED &&
    278       request->status != tab_capture::TAB_CAPTURE_STATE_ERROR) {
    279     // If we end up trying to grab a new stream while the previous one was never
    280     // terminated, then something fishy is going on.
    281     NOTREACHED() << "Trying to capture tab with existing stream.";
    282     return;
    283   }
    284 
    285   if (opening_stream) {
    286     request->fullscreen_observer.reset(new FullscreenObserver(request, this));
    287   }
    288 
    289   if (stopping_stream) {
    290     request->fullscreen_observer.reset();
    291   }
    292 
    293   request->last_status = request->status;
    294   request->status = next_state;
    295 
    296   // We will get duplicate events if we requested both audio and video, so only
    297   // send new events.
    298   if (request->last_status != request->status) {
    299     DispatchStatusChangeEvent(request);
    300   }
    301 }
    302 
    303 void TabCaptureRegistry::DispatchStatusChangeEvent(
    304     const TabCaptureRequest* request) const {
    305   EventRouter* router = profile_ ?
    306       extensions::ExtensionSystem::Get(profile_)->event_router() : NULL;
    307   if (!router)
    308     return;
    309 
    310   scoped_ptr<tab_capture::CaptureInfo> info(new tab_capture::CaptureInfo());
    311   info->tab_id = request->tab_id;
    312   info->status = request->status;
    313   info->fullscreen = request->fullscreen;
    314 
    315   scoped_ptr<base::ListValue> args(new base::ListValue());
    316   args->Append(info->ToValue().release());
    317   scoped_ptr<Event> event(new Event(
    318       extensions::event_names::kOnTabCaptureStatusChanged, args.Pass()));
    319   event->restrict_to_profile = profile_;
    320 
    321   router->DispatchEventToExtension(request->extension_id, event.Pass());
    322 }
    323 
    324 TabCaptureRequest* TabCaptureRegistry::FindCaptureRequest(
    325     int render_process_id, int render_view_id) const {
    326   for (ScopedVector<TabCaptureRequest>::const_iterator it = requests_.begin();
    327        it != requests_.end(); ++it) {
    328     if ((*it)->render_process_id == render_process_id &&
    329         (*it)->render_view_id == render_view_id) {
    330       return *it;
    331     }
    332   }
    333   return NULL;
    334 }
    335 
    336 void TabCaptureRegistry::DeleteCaptureRequest(int render_process_id,
    337                                               int render_view_id) {
    338   for (ScopedVector<TabCaptureRequest>::iterator it = requests_.begin();
    339        it != requests_.end(); ++it) {
    340     if ((*it)->render_process_id == render_process_id &&
    341         (*it)->render_view_id == render_view_id) {
    342       requests_.erase(it);
    343       return;
    344     }
    345   }
    346 }
    347 
    348 }  // namespace extensions
    349