text: // Copyright (c) 2011 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/infobars/infobar_tab_helper.h"
  
  #include "chrome/browser/infobars/infobar.h"
  #include "chrome/browser/infobars/infobar_delegate.h"
  #include "chrome/browser/tab_contents/insecure_content_infobar_delegate.h"
  #include "chrome/common/chrome_notification_types.h"
  #include "chrome/common/render_messages.h"
  #include "content/public/browser/navigation_controller.h"
  #include "content/public/browser/notification_service.h"
  #include "content/public/browser/web_contents.h"
  
  using content::NavigationController;
  using content::WebContents;
  
  InfoBarTabHelper::InfoBarTabHelper(WebContents* web_contents)
      : content::WebContentsObserver(web_contents),
        infobars_enabled_(true) {
    DCHECK(web_contents);
  }
  
  InfoBarTabHelper::~InfoBarTabHelper() {
    // Destroy all remaining InfoBars.  It's important to not animate here so that
    // we guarantee that we'll delete all delegates before we do anything else.
    //
    // TODO(pkasting): If there is no InfoBarContainer, this leaks all the
    // InfoBarDelegates.  This will be fixed once we call CloseSoon() directly on
    // Infobars.
    RemoveAllInfoBars(false);
  }
  
  void InfoBarTabHelper::AddInfoBar(InfoBarDelegate* delegate) {
    if (!infobars_enabled_) {
      delegate->InfoBarClosed();
      return;
    }
  
    for (size_t i = 0; i < infobars_.size(); ++i) {
      if (GetInfoBarDelegateAt(i)->EqualsDelegate(delegate)) {
        delegate->InfoBarClosed();
        return;
      }
    }
  
    // TODO(pkasting): Consider removing InfoBarTabHelper arg from delegate
    // constructors and instead using a setter from here.
    infobars_.push_back(delegate);
    content::NotificationService::current()->Notify(
        chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_ADDED,
        content::Source<InfoBarTabHelper>(this),
        content::Details<InfoBarAddedDetails>(delegate));
  
    // Add ourselves as an observer for navigations the first time a delegate is
    // added. We use this notification to expire InfoBars that need to expire on
    // page transitions.
    if (infobars_.size() == 1) {
      registrar_.Add(
          this, content::NOTIFICATION_NAV_ENTRY_COMMITTED,
          content::Source<NavigationController>(
              &web_contents()->GetController()));
    }
  }
  
  void InfoBarTabHelper::RemoveInfoBar(InfoBarDelegate* delegate) {
    RemoveInfoBarInternal(delegate, true);
  }
  
  void InfoBarTabHelper::ReplaceInfoBar(InfoBarDelegate* old_delegate,
                                        InfoBarDelegate* new_delegate) {
    if (!infobars_enabled_) {
      AddInfoBar(new_delegate);  // Deletes the delegate.
      return;
    }
  
    size_t i;
    for (i = 0; i < infobars_.size(); ++i) {
      if (GetInfoBarDelegateAt(i) == old_delegate)
        break;
    }
    DCHECK_LT(i, infobars_.size());
  
    infobars_.insert(infobars_.begin() + i, new_delegate);
  
    old_delegate->clear_owner();
    InfoBarReplacedDetails replaced_details(old_delegate, new_delegate);
    content::NotificationService::current()->Notify(
        chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REPLACED,
        content::Source<InfoBarTabHelper>(this),
        content::Details<InfoBarReplacedDetails>(&replaced_details));
  
    infobars_.erase(infobars_.begin() + i + 1);
  }
  
  InfoBarDelegate* InfoBarTabHelper::GetInfoBarDelegateAt(size_t index) {
    return infobars_[index];
  }
  
  void InfoBarTabHelper::RemoveInfoBarInternal(InfoBarDelegate* delegate,
                                               bool animate) {
    if (!infobars_enabled_) {
      DCHECK(infobars_.empty());
      return;
    }
  
    size_t i;
    for (i = 0; i < infobars_.size(); ++i) {
      if (GetInfoBarDelegateAt(i) == delegate)
        break;
    }
    DCHECK_LT(i, infobars_.size());
    InfoBarDelegate* infobar = infobars_[i];
  
    infobar->clear_owner();
    InfoBarRemovedDetails removed_details(infobar, animate);
    content::NotificationService::current()->Notify(
        chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED,
        content::Source<InfoBarTabHelper>(this),
        content::Details<InfoBarRemovedDetails>(&removed_details));
  
    infobars_.erase(infobars_.begin() + i);
    // Remove ourselves as an observer if we are tracking no more InfoBars.
    if (infobars_.empty()) {
      registrar_.Remove(
          this, content::NOTIFICATION_NAV_ENTRY_COMMITTED,
          content::Source<NavigationController>(
              &web_contents()->GetController()));
    }
  }
  
  void InfoBarTabHelper::RemoveAllInfoBars(bool animate) {
    while (!infobars_.empty())
      RemoveInfoBarInternal(GetInfoBarDelegateAt(infobar_count() - 1), animate);
  }
  
  void InfoBarTabHelper::OnDidBlockDisplayingInsecureContent() {
    // At most one infobar and do not supersede the stronger running content bar.
    for (size_t i = 0; i < infobars_.size(); ++i) {
      if (GetInfoBarDelegateAt(i)->AsInsecureContentInfoBarDelegate())
        return;
    }
    AddInfoBar(new InsecureContentInfoBarDelegate(this,
        InsecureContentInfoBarDelegate::DISPLAY));
  }
  
  void InfoBarTabHelper::OnDidBlockRunningInsecureContent() {
    // At most one infobar superseding any weaker displaying content bar.
    for (size_t i = 0; i < infobars_.size(); ++i) {
      InsecureContentInfoBarDelegate* delegate =
          GetInfoBarDelegateAt(i)->AsInsecureContentInfoBarDelegate();
      if (delegate) {
        if (delegate->type() != InsecureContentInfoBarDelegate::RUN) {
          ReplaceInfoBar(delegate, new InsecureContentInfoBarDelegate(
              this, InsecureContentInfoBarDelegate::RUN));
        }
        return;
      }
    }
    AddInfoBar(new InsecureContentInfoBarDelegate(this,
        InsecureContentInfoBarDelegate::RUN));
  }
  
  void InfoBarTabHelper::RenderViewGone(base::TerminationStatus status) {
    RemoveAllInfoBars(true);
  }
  
  bool InfoBarTabHelper::OnMessageReceived(const IPC::Message& message) {
    bool handled = true;
    IPC_BEGIN_MESSAGE_MAP(InfoBarTabHelper, message)
      IPC_MESSAGE_HANDLER(ChromeViewHostMsg_DidBlockDisplayingInsecureContent,
                          OnDidBlockDisplayingInsecureContent)
      IPC_MESSAGE_HANDLER(ChromeViewHostMsg_DidBlockRunningInsecureContent,
                          OnDidBlockRunningInsecureContent)
      IPC_MESSAGE_UNHANDLED(handled = false)
    IPC_END_MESSAGE_MAP()
    return handled;
  }
  
  void InfoBarTabHelper::Observe(int type,
                                 const content::NotificationSource& source,
                                 const content::NotificationDetails& details) {
    switch (type) {
      case content::NOTIFICATION_NAV_ENTRY_COMMITTED: {
        DCHECK(&web_contents()->GetController() ==
               content::Source<NavigationController>(source).ptr());
  
        content::LoadCommittedDetails& committed_details =
            *(content::Details<content::LoadCommittedDetails>(details).ptr());
  
        // NOTE: It is not safe to change the following code to count upwards or
        // use iterators, as the RemoveInfoBar() call synchronously modifies our
        // delegate list.
        for (size_t i = infobars_.size(); i > 0; --i) {
          InfoBarDelegate* delegate = GetInfoBarDelegateAt(i - 1);
          if (delegate->ShouldExpire(committed_details))
            RemoveInfoBar(delegate);
        }
  
        break;
      }
      default:
        NOTREACHED();
    }
  }
  