Home | History | Annotate | Download | only in download
      1 // Copyright (c) 2011 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/download/download_request_limiter.h"
      6 
      7 #include "base/stl_util-inl.h"
      8 #include "chrome/browser/download/download_request_infobar_delegate.h"
      9 #include "chrome/browser/tab_contents/tab_util.h"
     10 #include "content/browser/browser_thread.h"
     11 #include "content/browser/tab_contents/navigation_controller.h"
     12 #include "content/browser/tab_contents/navigation_entry.h"
     13 #include "content/browser/tab_contents/tab_contents.h"
     14 #include "content/browser/tab_contents/tab_contents_delegate.h"
     15 #include "content/common/notification_source.h"
     16 
     17 // TabDownloadState ------------------------------------------------------------
     18 
     19 DownloadRequestLimiter::TabDownloadState::TabDownloadState(
     20     DownloadRequestLimiter* host,
     21     NavigationController* controller,
     22     NavigationController* originating_controller)
     23     : host_(host),
     24       controller_(controller),
     25       status_(DownloadRequestLimiter::ALLOW_ONE_DOWNLOAD),
     26       download_count_(0),
     27       infobar_(NULL) {
     28   Source<NavigationController> notification_source(controller);
     29   registrar_.Add(this, NotificationType::NAV_ENTRY_PENDING,
     30                  notification_source);
     31   registrar_.Add(this, NotificationType::TAB_CLOSED, notification_source);
     32 
     33   NavigationEntry* active_entry = originating_controller ?
     34       originating_controller->GetActiveEntry() : controller->GetActiveEntry();
     35   if (active_entry)
     36     initial_page_host_ = active_entry->url().host();
     37 }
     38 
     39 DownloadRequestLimiter::TabDownloadState::~TabDownloadState() {
     40   // We should only be destroyed after the callbacks have been notified.
     41   DCHECK(callbacks_.empty());
     42 
     43   // And we should have closed the infobar.
     44   DCHECK(!infobar_);
     45 }
     46 
     47 void DownloadRequestLimiter::TabDownloadState::OnUserGesture() {
     48   if (is_showing_prompt()) {
     49     // Don't change the state if the user clicks on the page some where.
     50     return;
     51   }
     52 
     53   if (status_ != DownloadRequestLimiter::ALLOW_ALL_DOWNLOADS &&
     54       status_ != DownloadRequestLimiter::DOWNLOADS_NOT_ALLOWED) {
     55     // Revert to default status.
     56     host_->Remove(this);
     57     // WARNING: We've been deleted.
     58     return;
     59   }
     60 }
     61 
     62 void DownloadRequestLimiter::TabDownloadState::PromptUserForDownload(
     63     TabContents* tab,
     64     DownloadRequestLimiter::Callback* callback) {
     65   callbacks_.push_back(callback);
     66 
     67   if (is_showing_prompt())
     68     return;  // Already showing prompt.
     69 
     70   if (DownloadRequestLimiter::delegate_) {
     71     NotifyCallbacks(DownloadRequestLimiter::delegate_->ShouldAllowDownload());
     72   } else {
     73     infobar_ = new DownloadRequestInfoBarDelegate(tab, this);
     74     tab->AddInfoBar(infobar_);
     75   }
     76 }
     77 
     78 void DownloadRequestLimiter::TabDownloadState::Cancel() {
     79   NotifyCallbacks(false);
     80 }
     81 
     82 void DownloadRequestLimiter::TabDownloadState::Accept() {
     83   NotifyCallbacks(true);
     84 }
     85 
     86 void DownloadRequestLimiter::TabDownloadState::Observe(
     87     NotificationType type,
     88     const NotificationSource& source,
     89     const NotificationDetails& details) {
     90   if ((type != NotificationType::NAV_ENTRY_PENDING &&
     91        type != NotificationType::TAB_CLOSED) ||
     92       Source<NavigationController>(source).ptr() != controller_) {
     93     NOTREACHED();
     94     return;
     95   }
     96 
     97   switch (type.value) {
     98     case NotificationType::NAV_ENTRY_PENDING: {
     99       // NOTE: resetting state on a pending navigate isn't ideal. In particular
    100       // it is possible that queued up downloads for the page before the
    101       // pending navigate will be delivered to us after we process this
    102       // request. If this happens we may let a download through that we
    103       // shouldn't have. But this is rather rare, and it is difficult to get
    104       // 100% right, so we don't deal with it.
    105       NavigationEntry* entry = controller_->pending_entry();
    106       if (!entry)
    107         return;
    108 
    109       if (PageTransition::IsRedirect(entry->transition_type())) {
    110         // Redirects don't count.
    111         return;
    112       }
    113 
    114       if (status_ == DownloadRequestLimiter::ALLOW_ALL_DOWNLOADS ||
    115           status_ == DownloadRequestLimiter::DOWNLOADS_NOT_ALLOWED) {
    116         // User has either allowed all downloads or canceled all downloads. Only
    117         // reset the download state if the user is navigating to a different
    118         // host (or host is empty).
    119         if (!initial_page_host_.empty() && !entry->url().host().empty() &&
    120             entry->url().host() == initial_page_host_) {
    121           return;
    122         }
    123       }
    124       break;
    125     }
    126 
    127     case NotificationType::TAB_CLOSED:
    128       // Tab closed, no need to handle closing the dialog as it's owned by the
    129       // TabContents, break so that we get deleted after switch.
    130       break;
    131 
    132     default:
    133       NOTREACHED();
    134   }
    135 
    136   NotifyCallbacks(false);
    137   host_->Remove(this);
    138 }
    139 
    140 void DownloadRequestLimiter::TabDownloadState::NotifyCallbacks(bool allow) {
    141   status_ = allow ?
    142       DownloadRequestLimiter::ALLOW_ALL_DOWNLOADS :
    143       DownloadRequestLimiter::DOWNLOADS_NOT_ALLOWED;
    144   std::vector<DownloadRequestLimiter::Callback*> callbacks;
    145   bool change_status = false;
    146 
    147   // Selectively send first few notifications only if number of downloads exceed
    148   // kMaxDownloadsAtOnce. In that case, we also retain the infobar instance and
    149   // don't close it. If allow is false, we send all the notifications to cancel
    150   // all remaining downloads and close the infobar.
    151   if (!allow || (callbacks_.size() < kMaxDownloadsAtOnce)) {
    152     if (infobar_) {
    153       // Reset the delegate so we don't get notified again.
    154       infobar_->set_host(NULL);
    155       infobar_ = NULL;
    156     }
    157     callbacks.swap(callbacks_);
    158   } else {
    159     std::vector<DownloadRequestLimiter::Callback*>::iterator start, end;
    160     start = callbacks_.begin();
    161     end = callbacks_.begin() + kMaxDownloadsAtOnce;
    162     callbacks.assign(start, end);
    163     callbacks_.erase(start, end);
    164     change_status = true;
    165   }
    166 
    167   for (size_t i = 0; i < callbacks.size(); ++i)
    168     host_->ScheduleNotification(callbacks[i], allow);
    169 
    170   if (change_status)
    171     status_ = DownloadRequestLimiter::PROMPT_BEFORE_DOWNLOAD;
    172 }
    173 
    174 // DownloadRequestLimiter ------------------------------------------------------
    175 
    176 DownloadRequestLimiter::DownloadRequestLimiter() {
    177 }
    178 
    179 DownloadRequestLimiter::~DownloadRequestLimiter() {
    180   // All the tabs should have closed before us, which sends notification and
    181   // removes from state_map_. As such, there should be no pending callbacks.
    182   DCHECK(state_map_.empty());
    183 }
    184 
    185 DownloadRequestLimiter::DownloadStatus
    186     DownloadRequestLimiter::GetDownloadStatus(TabContents* tab) {
    187   TabDownloadState* state = GetDownloadState(&tab->controller(), NULL, false);
    188   return state ? state->download_status() : ALLOW_ONE_DOWNLOAD;
    189 }
    190 
    191 void DownloadRequestLimiter::CanDownloadOnIOThread(int render_process_host_id,
    192                                                    int render_view_id,
    193                                                    int request_id,
    194                                                    Callback* callback) {
    195   // This is invoked on the IO thread. Schedule the task to run on the UI
    196   // thread so that we can query UI state.
    197   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    198   BrowserThread::PostTask(
    199       BrowserThread::UI, FROM_HERE,
    200       NewRunnableMethod(this, &DownloadRequestLimiter::CanDownload,
    201                         render_process_host_id, render_view_id, request_id,
    202                         callback));
    203 }
    204 
    205 void DownloadRequestLimiter::OnUserGesture(TabContents* tab) {
    206   TabDownloadState* state = GetDownloadState(&tab->controller(), NULL, false);
    207   if (!state)
    208     return;
    209 
    210   state->OnUserGesture();
    211 }
    212 
    213 // static
    214 void DownloadRequestLimiter::SetTestingDelegate(TestingDelegate* delegate) {
    215   delegate_ = delegate;
    216 }
    217 
    218 DownloadRequestLimiter::TabDownloadState* DownloadRequestLimiter::
    219     GetDownloadState(NavigationController* controller,
    220                      NavigationController* originating_controller,
    221                      bool create) {
    222   DCHECK(controller);
    223   StateMap::iterator i = state_map_.find(controller);
    224   if (i != state_map_.end())
    225     return i->second;
    226 
    227   if (!create)
    228     return NULL;
    229 
    230   TabDownloadState* state =
    231       new TabDownloadState(this, controller, originating_controller);
    232   state_map_[controller] = state;
    233   return state;
    234 }
    235 
    236 void DownloadRequestLimiter::CanDownload(int render_process_host_id,
    237                                          int render_view_id,
    238                                          int request_id,
    239                                          Callback* callback) {
    240   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    241 
    242   TabContents* originating_tab =
    243       tab_util::GetTabContentsByID(render_process_host_id, render_view_id);
    244   if (!originating_tab) {
    245     // The tab was closed, don't allow the download.
    246     ScheduleNotification(callback, false);
    247     return;
    248   }
    249   CanDownloadImpl(originating_tab, request_id, callback);
    250 }
    251 
    252 void DownloadRequestLimiter::CanDownloadImpl(
    253     TabContents* originating_tab,
    254     int request_id,
    255     Callback* callback) {
    256   // FYI: Chrome Frame overrides CanDownload in ExternalTabContainer in order
    257   // to cancel the download operation in chrome and let the host browser
    258   // take care of it.
    259   if (!originating_tab->CanDownload(request_id)) {
    260     ScheduleNotification(callback, false);
    261     return;
    262   }
    263 
    264   // If the tab requesting the download is a constrained popup that is not
    265   // shown, treat the request as if it came from the parent.
    266   TabContents* effective_tab = originating_tab;
    267   if (effective_tab->delegate()) {
    268     effective_tab =
    269         effective_tab->delegate()->GetConstrainingContents(effective_tab);
    270   }
    271 
    272   TabDownloadState* state = GetDownloadState(
    273       &effective_tab->controller(), &originating_tab->controller(), true);
    274   switch (state->download_status()) {
    275     case ALLOW_ALL_DOWNLOADS:
    276       if (state->download_count() && !(state->download_count() %
    277             DownloadRequestLimiter::kMaxDownloadsAtOnce))
    278         state->set_download_status(PROMPT_BEFORE_DOWNLOAD);
    279       ScheduleNotification(callback, true);
    280       state->increment_download_count();
    281       break;
    282 
    283     case ALLOW_ONE_DOWNLOAD:
    284       state->set_download_status(PROMPT_BEFORE_DOWNLOAD);
    285       ScheduleNotification(callback, true);
    286       break;
    287 
    288     case DOWNLOADS_NOT_ALLOWED:
    289       ScheduleNotification(callback, false);
    290       break;
    291 
    292     case PROMPT_BEFORE_DOWNLOAD:
    293       state->PromptUserForDownload(effective_tab, callback);
    294       state->increment_download_count();
    295       break;
    296 
    297     default:
    298       NOTREACHED();
    299   }
    300 }
    301 
    302 void DownloadRequestLimiter::ScheduleNotification(Callback* callback,
    303                                                   bool allow) {
    304   BrowserThread::PostTask(
    305       BrowserThread::IO, FROM_HERE,
    306       NewRunnableMethod(
    307           this, &DownloadRequestLimiter::NotifyCallback, callback, allow));
    308 }
    309 
    310 void DownloadRequestLimiter::NotifyCallback(Callback* callback, bool allow) {
    311   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    312   if (allow)
    313     callback->ContinueDownload();
    314   else
    315     callback->CancelDownload();
    316 }
    317 
    318 void DownloadRequestLimiter::Remove(TabDownloadState* state) {
    319   DCHECK(ContainsKey(state_map_, state->controller()));
    320   state_map_.erase(state->controller());
    321   delete state;
    322 }
    323 
    324 // static
    325 DownloadRequestLimiter::TestingDelegate* DownloadRequestLimiter::delegate_ =
    326     NULL;
    327