// Copyright (c) 2009 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/blocked_popup_container.h" #include "chrome/browser/profile.h" #include "chrome/browser/tab_contents/tab_contents.h" #include "chrome/common/pref_names.h" #include "chrome/common/pref_service.h" #include "chrome/common/notification_service.h" // static const size_t BlockedPopupContainer::kImpossibleNumberOfPopups = 30; // static BlockedPopupContainer* BlockedPopupContainer::Create( TabContents* owner, Profile* profile) { BlockedPopupContainer* container = new BlockedPopupContainer(owner, profile); BlockedPopupContainerView* view = BlockedPopupContainerView::Create(container); container->set_view(view); return container; } // static BlockedPopupContainer* BlockedPopupContainer::Create( TabContents* owner, Profile* profile, BlockedPopupContainerView* view) { BlockedPopupContainer* container = new BlockedPopupContainer(owner, profile); container->set_view(view); return container; } // static void BlockedPopupContainer::RegisterUserPrefs(PrefService* prefs) { prefs->RegisterListPref(prefs::kPopupWhitelistedHosts); } void BlockedPopupContainer::AddTabContents(TabContents* tab_contents, const gfx::Rect& bounds, const std::string& host) { // Show whitelisted popups immediately. bool whitelisted = !!whitelist_.count(host); if (whitelisted) owner_->AddNewContents(tab_contents, NEW_POPUP, bounds, true, GURL()); if (has_been_dismissed_) { // Don't want to show any other UI. if (!whitelisted) delete tab_contents; // Discard blocked popups entirely. return; } if (whitelisted) { // Listen for this popup's destruction, so if the user closes it manually, // we'll know to stop caring about it. registrar_.Add(this, NotificationType::TAB_CONTENTS_DESTROYED, Source(tab_contents)); unblocked_popups_[tab_contents] = host; } else { if (blocked_popups_.size() == (kImpossibleNumberOfPopups - 1)) { delete tab_contents; LOG(INFO) << "Warning: Renderer is sending more popups to us than should " "be possible. Renderer compromised?"; return; } blocked_popups_.push_back(BlockedPopup(tab_contents, bounds, host)); tab_contents->set_delegate(this); } PopupHosts::const_iterator i(popup_hosts_.find(host)); if (i == popup_hosts_.end()) popup_hosts_[host] = whitelisted; else DCHECK_EQ(whitelisted, i->second); UpdateView(); view_->ShowView(); owner_->PopupNotificationVisibilityChanged(true); } void BlockedPopupContainer::LaunchPopupAtIndex(size_t index) { if (index >= blocked_popups_.size()) return; // Open the popup. BlockedPopups::iterator i(blocked_popups_.begin() + index); TabContents* tab_contents = i->tab_contents; tab_contents->set_delegate(NULL); owner_->AddNewContents(tab_contents, NEW_POPUP, i->bounds, true, GURL()); const std::string& host = i->host; if (!host.empty()) { // Listen for this popup's destruction, so if the user closes it manually, // we'll know to stop caring about it. registrar_.Add(this, NotificationType::TAB_CONTENTS_DESTROYED, Source(tab_contents)); // Add the popup to the unblocked list. (Do this before the below call!) unblocked_popups_[tab_contents] = i->host; } // Remove the popup from the blocked list. EraseDataForPopupAndUpdateUI(i); } void BlockedPopupContainer::AddBlockedNotice(const GURL& url, const string16& reason) { // Nothing to show after we have been dismissed. if (has_been_dismissed_) return; // Don't notify for hosts which are already shown. Too much noise otherwise. // TODO(idanan): Figure out what to do for multiple reasons of blocking the // same host. if (notice_hosts_.find(url.host()) != notice_hosts_.end()) return; notice_hosts_.insert(url.host()); blocked_notices_.push_back(BlockedNotice(url, reason)); UpdateView(); view_->ShowView(); owner_->PopupNotificationVisibilityChanged(true); } void BlockedPopupContainer::GetHostAndReasonForNotice(size_t index, std::string* host, string16* reason) const { DCHECK(host); DCHECK(reason); *host = blocked_notices_[index].url_.host(); *reason = blocked_notices_[index].reason_; } size_t BlockedPopupContainer::GetBlockedPopupCount() const { return blocked_popups_.size(); } size_t BlockedPopupContainer::GetBlockedNoticeCount() const { return blocked_notices_.size(); } bool BlockedPopupContainer::IsHostWhitelisted(size_t index) const { return ConvertHostIndexToIterator(index)->second; } void BlockedPopupContainer::ToggleWhitelistingForHost(size_t index) { PopupHosts::const_iterator i(ConvertHostIndexToIterator(index)); const std::string& host = i->first; bool should_whitelist = !i->second; popup_hosts_[host] = should_whitelist; if (should_whitelist) { whitelist_.insert(host); // Open the popups in order. for (size_t j = 0; j < blocked_popups_.size();) { if (blocked_popups_[j].host == host) LaunchPopupAtIndex(j); // This shifts the rest of the entries down. else ++j; } } else { // Remove from whitelist. whitelist_.erase(host); for (UnblockedPopups::iterator i(unblocked_popups_.begin()); i != unblocked_popups_.end(); ) { TabContents* tab_contents = i->first; TabContentsDelegate* delegate = tab_contents->delegate(); if ((i->second == host) && delegate->IsPopup(tab_contents)) { // Convert the popup back into a blocked popup. delegate->DetachContents(tab_contents); tab_contents->set_delegate(this); // Add the popup to the blocked list. (Do this before the below call!) gfx::Rect bounds; tab_contents->GetContainerBounds(&bounds); blocked_popups_.push_back(BlockedPopup(tab_contents, bounds, host)); // Remove the popup from the unblocked list. UnblockedPopups::iterator to_erase = i; ++i; EraseDataForPopupAndUpdateUI(to_erase); } else { ++i; } } } // Persist whitelisting state if we're not off the record. if (prefs_) { ListValue* whitelist_pref = prefs_->GetMutableList(prefs::kPopupWhitelistedHosts); if (should_whitelist) { whitelist_pref->Append(new StringValue(host)); } else { // Stupidly, gcc complains that you're accessing the (private) StringValue // copy constructor if you inline the temp creation into the Remove() // call. StringValue host_value(host); whitelist_pref->Remove(host_value); } } } void BlockedPopupContainer::CloseAll() { ClearData(); HideSelf(); } void BlockedPopupContainer::Destroy() { view_->Destroy(); ClearData(); GetConstrainingContents(NULL)->WillCloseBlockedPopupContainer(this); delete this; } void BlockedPopupContainer::RepositionBlockedPopupContainer() { view_->SetPosition(); } TabContents* BlockedPopupContainer::GetTabContentsAt(size_t index) const { return blocked_popups_[index].tab_contents; } std::vector BlockedPopupContainer::GetHosts() const { std::vector hosts; for (PopupHosts::const_iterator i(popup_hosts_.begin()); i != popup_hosts_.end(); ++i) hosts.push_back(i->first); return hosts; } // Overridden from TabContentsDelegate: void BlockedPopupContainer::OpenURLFromTab(TabContents* source, const GURL& url, const GURL& referrer, WindowOpenDisposition disposition, PageTransition::Type transition) { owner_->OpenURL(url, referrer, disposition, transition); } void BlockedPopupContainer::AddNewContents(TabContents* source, TabContents* new_contents, WindowOpenDisposition disposition, const gfx::Rect& initial_position, bool user_gesture) { owner_->AddNewContents(new_contents, disposition, initial_position, user_gesture, GURL()); } void BlockedPopupContainer::CloseContents(TabContents* source) { for (BlockedPopups::iterator it = blocked_popups_.begin(); it != blocked_popups_.end(); ++it) { TabContents* tab_contents = it->tab_contents; if (tab_contents == source) { tab_contents->set_delegate(NULL); EraseDataForPopupAndUpdateUI(it); delete tab_contents; break; } } } void BlockedPopupContainer::MoveContents(TabContents* source, const gfx::Rect& new_bounds) { for (BlockedPopups::iterator it = blocked_popups_.begin(); it != blocked_popups_.end(); ++it) { if (it->tab_contents == source) { it->bounds = new_bounds; break; } } } bool BlockedPopupContainer::IsPopup(TabContents* source) { return true; } TabContents* BlockedPopupContainer::GetConstrainingContents( TabContents* source) { return owner_; } void BlockedPopupContainer::HideSelf() { view_->HideView(); owner_->PopupNotificationVisibilityChanged(false); } void BlockedPopupContainer::ClearData() { for (BlockedPopups::iterator i(blocked_popups_.begin()); i != blocked_popups_.end(); ++i) { TabContents* tab_contents = i->tab_contents; tab_contents->set_delegate(NULL); delete tab_contents; } blocked_popups_.clear(); registrar_.RemoveAll(); unblocked_popups_.clear(); popup_hosts_.clear(); } BlockedPopupContainer::PopupHosts::const_iterator BlockedPopupContainer::ConvertHostIndexToIterator(size_t index) const { if (index >= popup_hosts_.size()) return popup_hosts_.end(); // If only there was a std::map::const_iterator::operator +=() ... PopupHosts::const_iterator i(popup_hosts_.begin()); for (size_t j = 0; j < index; ++j) ++i; return i; } void BlockedPopupContainer::EraseDataForPopupAndUpdateUI( BlockedPopups::iterator i) { // Erase the host if this is the last popup for that host. const std::string& host = i->host; if (!host.empty()) { bool should_erase_host = true; for (BlockedPopups::const_iterator j(blocked_popups_.begin()); j != blocked_popups_.end(); ++j) { if ((j != i) && (j->host == host)) { should_erase_host = false; break; } } if (should_erase_host) { for (UnblockedPopups::const_iterator j(unblocked_popups_.begin()); j != unblocked_popups_.end(); ++j) { if (j->second == host) { should_erase_host = false; break; } } if (should_erase_host) popup_hosts_.erase(host); } } // Erase the popup and update the UI. blocked_popups_.erase(i); UpdateView(); } void BlockedPopupContainer::EraseDataForPopupAndUpdateUI( UnblockedPopups::iterator i) { // Stop listening for this popup's destruction. registrar_.Remove(this, NotificationType::TAB_CONTENTS_DESTROYED, Source(i->first)); // Erase the host if this is the last popup for that host. const std::string& host = i->second; if (!host.empty()) { bool should_erase_host = true; for (UnblockedPopups::const_iterator j(unblocked_popups_.begin()); j != unblocked_popups_.end(); ++j) { if ((j != i) && (j->second == host)) { should_erase_host = false; break; } } if (should_erase_host) { for (BlockedPopups::const_iterator j(blocked_popups_.begin()); j != blocked_popups_.end(); ++j) { if (j->host == host) { should_erase_host = false; break; } } if (should_erase_host) popup_hosts_.erase(host); } } // Erase the popup and update the UI. unblocked_popups_.erase(i); UpdateView(); } // private: BlockedPopupContainer::BlockedPopupContainer(TabContents* owner, Profile* profile) : owner_(owner), prefs_(profile->GetPrefs()), has_been_dismissed_(false), view_(NULL), profile_(profile) { // Copy whitelist pref into local member that's easier to use. const ListValue* whitelist_pref = prefs_->GetList(prefs::kPopupWhitelistedHosts); // Careful: The returned value could be NULL if the pref has never been set. if (whitelist_pref != NULL) { for (ListValue::const_iterator i(whitelist_pref->begin()); i != whitelist_pref->end(); ++i) { std::string host; (*i)->GetAsString(&host); whitelist_.insert(host); } } if (profile->IsOffTheRecord()) prefs_ = NULL; // Don't persist whitelist changes. } void BlockedPopupContainer::UpdateView() { if (blocked_popups_.empty() && unblocked_popups_.empty() && blocked_notices_.empty()) HideSelf(); else view_->UpdateLabel(); } void BlockedPopupContainer::Observe(NotificationType type, const NotificationSource& source, const NotificationDetails& details) { DCHECK(type == NotificationType::TAB_CONTENTS_DESTROYED); TabContents* tab_contents = Source(source).ptr(); UnblockedPopups::iterator i(unblocked_popups_.find(tab_contents)); DCHECK(i != unblocked_popups_.end()); EraseDataForPopupAndUpdateUI(i); }