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