Home | History | Annotate | Download | only in infobars
      1 // Copyright (c) 2013 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/infobars/infobar_service.h"
      6 
      7 #include "chrome/browser/chrome_notification_types.h"
      8 #include "chrome/browser/infobars/infobar.h"
      9 #include "chrome/browser/infobars/infobar_delegate.h"
     10 #include "chrome/browser/infobars/insecure_content_infobar_delegate.h"
     11 #include "chrome/common/render_messages.h"
     12 #include "content/public/browser/navigation_controller.h"
     13 #include "content/public/browser/notification_service.h"
     14 #include "content/public/browser/web_contents.h"
     15 
     16 
     17 DEFINE_WEB_CONTENTS_USER_DATA_KEY(InfoBarService);
     18 
     19 InfoBarDelegate* InfoBarService::AddInfoBar(
     20     scoped_ptr<InfoBarDelegate> infobar) {
     21   DCHECK(infobar);
     22   if (!infobars_enabled_)
     23     return NULL;
     24 
     25   for (InfoBars::const_iterator i(infobars_.begin()); i != infobars_.end();
     26        ++i) {
     27     if ((*i)->EqualsDelegate(infobar.get())) {
     28       DCHECK_NE(*i, infobar.get());
     29       return NULL;
     30     }
     31   }
     32 
     33   InfoBarDelegate* infobar_ptr = infobar.release();
     34   infobars_.push_back(infobar_ptr);
     35   // TODO(pkasting): Remove InfoBarService arg from delegate constructors and
     36   // instead use a setter from here.
     37 
     38   // Add ourselves as an observer for navigations the first time a delegate is
     39   // added. We use this notification to expire InfoBars that need to expire on
     40   // page transitions.  We must do this before calling Notify() below;
     41   // otherwise, if that call causes a call to RemoveInfoBar(), we'll try to
     42   // unregister for the NAV_ENTRY_COMMITTED notification, which we won't have
     43   // yet registered here, and we'll fail the "was registered" DCHECK in
     44   // NotificationRegistrar::Remove().
     45   if (infobars_.size() == 1) {
     46     registrar_.Add(
     47         this, content::NOTIFICATION_NAV_ENTRY_COMMITTED,
     48         content::Source<content::NavigationController>(
     49             &web_contents()->GetController()));
     50   }
     51 
     52   content::NotificationService::current()->Notify(
     53       chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_ADDED,
     54       content::Source<InfoBarService>(this),
     55       content::Details<InfoBarAddedDetails>(infobar_ptr));
     56   return infobar_ptr;
     57 }
     58 
     59 void InfoBarService::RemoveInfoBar(InfoBarDelegate* infobar) {
     60   RemoveInfoBarInternal(infobar, true);
     61 }
     62 
     63 InfoBarDelegate* InfoBarService::ReplaceInfoBar(
     64     InfoBarDelegate* old_infobar,
     65     scoped_ptr<InfoBarDelegate> new_infobar) {
     66   DCHECK(old_infobar);
     67   if (!infobars_enabled_)
     68     return AddInfoBar(new_infobar.Pass());  // Deletes the delegate.
     69   DCHECK(new_infobar);
     70 
     71   InfoBars::iterator i(std::find(infobars_.begin(), infobars_.end(),
     72                                  old_infobar));
     73   DCHECK(i != infobars_.end());
     74 
     75   InfoBarDelegate* new_infobar_ptr = new_infobar.release();
     76   i = infobars_.insert(i, new_infobar_ptr);
     77   InfoBarReplacedDetails replaced_details(old_infobar, new_infobar_ptr);
     78 
     79   // Remove the old infobar before notifying, so that if any observers call
     80   // back to AddInfoBar() or similar, we don't dupe-check against this infobar.
     81   infobars_.erase(++i);
     82 
     83   old_infobar->clear_owner();
     84   content::NotificationService::current()->Notify(
     85       chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REPLACED,
     86       content::Source<InfoBarService>(this),
     87       content::Details<InfoBarReplacedDetails>(&replaced_details));
     88   return new_infobar_ptr;
     89 }
     90 
     91 InfoBarService::InfoBarService(content::WebContents* web_contents)
     92     : content::WebContentsObserver(web_contents),
     93       infobars_enabled_(true) {
     94   DCHECK(web_contents);
     95   registrar_.Add(this,
     96                  content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
     97                  content::Source<content::WebContents>(web_contents));
     98 }
     99 
    100 InfoBarService::~InfoBarService() {
    101   // Destroy all remaining InfoBars.  It's important to not animate here so that
    102   // we guarantee that we'll delete all delegates before we do anything else.
    103   //
    104   // TODO(pkasting): If there is no InfoBarContainer, this leaks all the
    105   // InfoBarDelegates.  This will be fixed once we call CloseSoon() directly on
    106   // Infobars.
    107   RemoveAllInfoBars(false);
    108 }
    109 
    110 void InfoBarService::RenderProcessGone(base::TerminationStatus status) {
    111   RemoveAllInfoBars(true);
    112 }
    113 
    114 bool InfoBarService::OnMessageReceived(const IPC::Message& message) {
    115   bool handled = true;
    116   IPC_BEGIN_MESSAGE_MAP(InfoBarService, message)
    117     IPC_MESSAGE_HANDLER(ChromeViewHostMsg_DidBlockDisplayingInsecureContent,
    118                         OnDidBlockDisplayingInsecureContent)
    119     IPC_MESSAGE_HANDLER(ChromeViewHostMsg_DidBlockRunningInsecureContent,
    120                         OnDidBlockRunningInsecureContent)
    121     IPC_MESSAGE_UNHANDLED(handled = false)
    122   IPC_END_MESSAGE_MAP()
    123   return handled;
    124 }
    125 
    126 void InfoBarService::Observe(int type,
    127                              const content::NotificationSource& source,
    128                              const content::NotificationDetails& details) {
    129   if (type == content::NOTIFICATION_NAV_ENTRY_COMMITTED) {
    130     DCHECK(&web_contents()->GetController() ==
    131            content::Source<content::NavigationController>(source).ptr());
    132 
    133     content::LoadCommittedDetails& committed_details =
    134         *(content::Details<content::LoadCommittedDetails>(details).ptr());
    135 
    136     // NOTE: It is not safe to change the following code to count upwards or
    137     // use iterators, as the RemoveInfoBar() call synchronously modifies our
    138     // delegate list.
    139     for (size_t i = infobars_.size(); i > 0; --i) {
    140       InfoBarDelegate* infobar = infobars_[i - 1];
    141       if (infobar->ShouldExpire(committed_details))
    142         RemoveInfoBar(infobar);
    143     }
    144 
    145     return;
    146   }
    147 
    148   DCHECK_EQ(type, content::NOTIFICATION_WEB_CONTENTS_DESTROYED);
    149   // The WebContents is going away; be aggressively paranoid and delete
    150   // ourselves lest other parts of the system attempt to add infobars or use
    151   // us otherwise during the destruction.
    152   DCHECK_EQ(web_contents(),
    153             content::Source<content::WebContents>(source).ptr());
    154   web_contents()->RemoveUserData(UserDataKey());
    155   // That was the equivalent of "delete this". This object is now destroyed;
    156   // returning from this function is the only safe thing to do.
    157   return;
    158 }
    159 
    160 void InfoBarService::RemoveInfoBarInternal(InfoBarDelegate* infobar,
    161                                            bool animate) {
    162   DCHECK(infobar);
    163   if (!infobars_enabled_) {
    164     DCHECK(infobars_.empty());
    165     return;
    166   }
    167 
    168   InfoBars::iterator i(std::find(infobars_.begin(), infobars_.end(), infobar));
    169   DCHECK(i != infobars_.end());
    170 
    171   infobar->clear_owner();
    172   // Remove the infobar before notifying, so that if any observers call back to
    173   // AddInfoBar() or similar, we don't dupe-check against this infobar.
    174   infobars_.erase(i);
    175 
    176   // Remove ourselves as an observer if we are tracking no more InfoBars.  We
    177   // must do this before calling Notify() below; otherwise, if that call
    178   // causes a call to AddInfoBar(), we'll try to register for the
    179   // NAV_ENTRY_COMMITTED notification, which we won't have yet unregistered
    180   // here, and we'll fail the "not already registered" DCHECK in
    181   // NotificationRegistrar::Add().
    182   if (infobars_.empty()) {
    183     registrar_.Remove(
    184         this, content::NOTIFICATION_NAV_ENTRY_COMMITTED,
    185         content::Source<content::NavigationController>(
    186             &web_contents()->GetController()));
    187   }
    188 
    189   InfoBarRemovedDetails removed_details(infobar, animate);
    190   content::NotificationService::current()->Notify(
    191       chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED,
    192       content::Source<InfoBarService>(this),
    193       content::Details<InfoBarRemovedDetails>(&removed_details));
    194 }
    195 
    196 void InfoBarService::RemoveAllInfoBars(bool animate) {
    197   while (!infobars_.empty())
    198     RemoveInfoBarInternal(infobars_.back(), animate);
    199 }
    200 
    201 void InfoBarService::OnDidBlockDisplayingInsecureContent() {
    202   InsecureContentInfoBarDelegate::Create(
    203       this, InsecureContentInfoBarDelegate::DISPLAY);
    204 }
    205 
    206 void InfoBarService::OnDidBlockRunningInsecureContent() {
    207   InsecureContentInfoBarDelegate::Create(this,
    208                                          InsecureContentInfoBarDelegate::RUN);
    209 }
    210