blob: c9f0a1236966cccff98578741dae8d7b7c485756 [file] [log] [blame]
// Copyright 2015 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 "ios/chrome/browser/browsing_data/ios_chrome_browsing_data_remover.h"
#include <map>
#include <set>
#include <string>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/callback.h"
#include "base/logging.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/user_metrics.h"
#include "base/threading/thread_task_runner_handle.h"
#include "components/autofill/core/browser/personal_data_manager.h"
#include "components/autofill/core/browser/webdata/autofill_webdata_service.h"
#include "components/history/core/browser/history_service.h"
#include "components/keyed_service/core/service_access_type.h"
#include "components/language/core/browser/url_language_histogram.h"
#include "components/omnibox/browser/omnibox_pref_names.h"
#include "components/password_manager/core/browser/password_store.h"
#include "components/prefs/pref_service.h"
#include "components/search_engines/template_url_service.h"
#include "components/sessions/core/tab_restore_service.h"
#include "ios/chrome/browser/application_context.h"
#include "ios/chrome/browser/autofill/personal_data_manager_factory.h"
#include "ios/chrome/browser/browser_state/chrome_browser_state.h"
#include "ios/chrome/browser/history/history_service_factory.h"
#include "ios/chrome/browser/history/web_history_service_factory.h"
#include "ios/chrome/browser/ios_chrome_io_thread.h"
#include "ios/chrome/browser/language/url_language_histogram_factory.h"
#include "ios/chrome/browser/passwords/ios_chrome_password_store_factory.h"
#include "ios/chrome/browser/search_engines/template_url_service_factory.h"
#include "ios/chrome/browser/sessions/ios_chrome_tab_restore_service_factory.h"
#include "ios/chrome/browser/web_data_service_factory.h"
#include "ios/net/http_cache_helper.h"
#include "ios/public/provider/chrome/browser/chrome_browser_provider.h"
#include "ios/web/public/web_thread.h"
#include "net/base/net_errors.h"
#include "net/cookies/cookie_store.h"
#include "net/http/transport_security_state.h"
#include "net/ssl/channel_id_service.h"
#include "net/ssl/channel_id_store.h"
#include "net/url_request/url_request_context.h"
#include "net/url_request/url_request_context_getter.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
using base::UserMetricsAction;
using web::WebThread;
namespace {
using CallbackList = base::CallbackList<void(
const IOSChromeBrowsingDataRemover::NotificationDetails&)>;
// Contains all registered callbacks for browsing data removed notifications.
CallbackList* g_on_browsing_data_removed_callbacks = nullptr;
// Accessor for |*g_on_browsing_data_removed_callbacks|. Creates a new object
// the first time so that it always returns a valid object.
CallbackList* GetOnBrowsingDataRemovedCallbacks() {
if (!g_on_browsing_data_removed_callbacks)
g_on_browsing_data_removed_callbacks = new CallbackList();
return g_on_browsing_data_removed_callbacks;
}
bool AllDomainsPredicate(const std::string& domain) {
return true;
}
} // namespace
bool IOSChromeBrowsingDataRemover::is_removing_ = false;
IOSChromeBrowsingDataRemover::NotificationDetails::NotificationDetails()
: removal_begin(base::Time()), removal_mask(-1) {}
IOSChromeBrowsingDataRemover::NotificationDetails::NotificationDetails(
const IOSChromeBrowsingDataRemover::NotificationDetails& details)
: removal_begin(details.removal_begin),
removal_mask(details.removal_mask) {}
IOSChromeBrowsingDataRemover::NotificationDetails::NotificationDetails(
base::Time removal_begin,
int removal_mask)
: removal_begin(removal_begin), removal_mask(removal_mask) {}
IOSChromeBrowsingDataRemover::NotificationDetails::~NotificationDetails() {}
// Static.
IOSChromeBrowsingDataRemover* IOSChromeBrowsingDataRemover::CreateForPeriod(
ios::ChromeBrowserState* browser_state,
browsing_data::TimePeriod period) {
switch (period) {
case browsing_data::TimePeriod::LAST_HOUR:
base::RecordAction(UserMetricsAction("ClearBrowsingData_LastHour"));
break;
case browsing_data::TimePeriod::LAST_DAY:
base::RecordAction(UserMetricsAction("ClearBrowsingData_LastDay"));
break;
case browsing_data::TimePeriod::LAST_WEEK:
base::RecordAction(UserMetricsAction("ClearBrowsingData_LastWeek"));
break;
case browsing_data::TimePeriod::FOUR_WEEKS:
base::RecordAction(UserMetricsAction("ClearBrowsingData_LastMonth"));
break;
case browsing_data::TimePeriod::ALL_TIME:
base::RecordAction(UserMetricsAction("ClearBrowsingData_Everything"));
break;
}
return new IOSChromeBrowsingDataRemover(
browser_state, browsing_data::CalculateBeginDeleteTime(period),
base::Time::Max());
}
IOSChromeBrowsingDataRemover::IOSChromeBrowsingDataRemover(
ios::ChromeBrowserState* browser_state,
base::Time delete_begin,
base::Time delete_end)
: browser_state_(browser_state),
delete_begin_(delete_begin),
delete_end_(delete_end),
main_context_getter_(browser_state->GetRequestContext()) {
DCHECK(browser_state);
// crbug.com/140910: Many places were calling this with base::Time() as
// delete_end, even though they should've used base::Time::Max(). Work around
// it here. New code should use base::Time::Max().
DCHECK(delete_end_ != base::Time());
if (delete_end_ == base::Time())
delete_end_ = base::Time::Max();
}
IOSChromeBrowsingDataRemover::~IOSChromeBrowsingDataRemover() {
DCHECK(AllDone());
}
// Static.
void IOSChromeBrowsingDataRemover::set_removing(bool is_removing) {
DCHECK(is_removing_ != is_removing);
is_removing_ = is_removing;
}
void IOSChromeBrowsingDataRemover::Remove(int remove_mask) {
RemoveImpl(remove_mask);
}
void IOSChromeBrowsingDataRemover::RemoveImpl(int remove_mask) {
DCHECK_CURRENTLY_ON(WebThread::UI);
set_removing(true);
remove_mask_ = remove_mask;
// On other platforms, it is possible to specify different types of origins
// to clear data for (e.g., unprotected web vs. extensions). On iOS, this
// mask is always implicitly the unprotected web, which is the only type that
// is relevant. This metric is left here for historical consistency.
base::RecordAction(
UserMetricsAction("ClearBrowsingData_MaskContainsUnprotectedWeb"));
if (remove_mask & REMOVE_HISTORY) {
history::HistoryService* history_service =
ios::HistoryServiceFactory::GetForBrowserState(
browser_state_, ServiceAccessType::EXPLICIT_ACCESS);
if (history_service) {
std::set<GURL> restrict_urls;
base::RecordAction(UserMetricsAction("ClearBrowsingData_History"));
waiting_for_clear_history_ = true;
history_service->ExpireLocalAndRemoteHistoryBetween(
ios::WebHistoryServiceFactory::GetForBrowserState(browser_state_),
restrict_urls, delete_begin_, delete_end_,
base::Bind(&IOSChromeBrowsingDataRemover::OnHistoryDeletionDone,
base::Unretained(this)),
&history_task_tracker_);
}
// Need to clear the host cache and accumulated speculative data, as it also
// reveals some history: we have no mechanism to track when these items were
// created, so we'll clear them all. Better safe than sorry.
IOSChromeIOThread* ios_chrome_io_thread =
GetApplicationContext()->GetIOSChromeIOThread();
if (ios_chrome_io_thread) {
waiting_for_clear_hostname_resolution_cache_ = true;
web::WebThread::PostTaskAndReply(
web::WebThread::IO, FROM_HERE,
base::Bind(&IOSChromeIOThread::ClearHostCache,
base::Unretained(ios_chrome_io_thread)),
base::Bind(
&IOSChromeBrowsingDataRemover::OnClearedHostnameResolutionCache,
base::Unretained(this)));
}
// As part of history deletion we also delete the auto-generated keywords.
// Because the TemplateURLService is shared between incognito and
// non-incognito profiles, don't do this in incognito.
if (!browser_state_->IsOffTheRecord()) {
TemplateURLService* keywords_model =
ios::TemplateURLServiceFactory::GetForBrowserState(browser_state_);
if (keywords_model && !keywords_model->loaded()) {
template_url_sub_ = keywords_model->RegisterOnLoadedCallback(
base::Bind(&IOSChromeBrowsingDataRemover::OnKeywordsLoaded,
base::Unretained(this)));
keywords_model->Load();
waiting_for_clear_keyword_data_ = true;
} else if (keywords_model) {
keywords_model->RemoveAutoGeneratedBetween(delete_begin_, delete_end_);
}
}
// If the caller is removing history for all hosts, then clear ancillary
// historical information.
// We also delete the list of recently closed tabs. Since these expire,
// they can't be more than a day old, so we can simply clear them all.
sessions::TabRestoreService* tab_service =
IOSChromeTabRestoreServiceFactory::GetForBrowserState(browser_state_);
if (tab_service) {
tab_service->ClearEntries();
tab_service->DeleteLastSession();
}
// The saved Autofill profiles and credit cards can include the origin from
// which these profiles and credit cards were learned. These are a form of
// history, so clear them as well.
scoped_refptr<autofill::AutofillWebDataService> web_data_service =
ios::WebDataServiceFactory::GetAutofillWebDataForBrowserState(
browser_state_, ServiceAccessType::EXPLICIT_ACCESS);
if (web_data_service.get()) {
waiting_for_clear_autofill_origin_urls_ = true;
web_data_service->RemoveOriginURLsModifiedBetween(delete_begin_,
delete_end_);
// Ask for a call back when the above call is finished.
web_data_service->GetDBTaskRunner()->PostTaskAndReply(
FROM_HERE, base::Bind(&base::DoNothing),
base::Bind(&IOSChromeBrowsingDataRemover::OnClearedAutofillOriginURLs,
base::Unretained(this)));
autofill::PersonalDataManager* data_manager =
autofill::PersonalDataManagerFactory::GetForBrowserState(
browser_state_);
if (data_manager)
data_manager->Refresh();
}
// Remove language histogram history.
language::UrlLanguageHistogram* language_histogram =
UrlLanguageHistogramFactory::GetForBrowserState(browser_state_);
if (language_histogram) {
language_histogram->ClearHistory(delete_begin_, delete_end_);
}
}
if (remove_mask & REMOVE_COOKIES) {
base::RecordAction(UserMetricsAction("ClearBrowsingData_Cookies"));
++waiting_for_clear_cookies_count_;
WebThread::PostTask(
WebThread::IO, FROM_HERE,
base::Bind(&IOSChromeBrowsingDataRemover::ClearCookiesOnIOThread,
base::Unretained(this), main_context_getter_));
// TODO(mkwst): If we're not removing passwords, then clear the 'zero-click'
// flag for all credentials in the password store.
}
if (remove_mask & REMOVE_CHANNEL_IDS) {
base::RecordAction(UserMetricsAction("ClearBrowsingData_ChannelIDs"));
if (main_context_getter_) {
waiting_for_clear_channel_ids_ = true;
WebThread::PostTask(
WebThread::IO, FROM_HERE,
base::Bind(&IOSChromeBrowsingDataRemover::ClearChannelIDsOnIOThread,
base::Unretained(this), main_context_getter_));
}
}
if (remove_mask & REMOVE_PASSWORDS) {
base::RecordAction(UserMetricsAction("ClearBrowsingData_Passwords"));
password_manager::PasswordStore* password_store =
IOSChromePasswordStoreFactory::GetForBrowserState(
browser_state_, ServiceAccessType::EXPLICIT_ACCESS)
.get();
if (password_store) {
waiting_for_clear_passwords_ = true;
password_store->RemoveLoginsCreatedBetween(
delete_begin_, delete_end_,
base::Bind(&IOSChromeBrowsingDataRemover::OnClearedPasswords,
base::Unretained(this)));
}
}
if (remove_mask & REMOVE_FORM_DATA) {
base::RecordAction(UserMetricsAction("ClearBrowsingData_Autofill"));
scoped_refptr<autofill::AutofillWebDataService> web_data_service =
ios::WebDataServiceFactory::GetAutofillWebDataForBrowserState(
browser_state_, ServiceAccessType::EXPLICIT_ACCESS);
if (web_data_service.get()) {
waiting_for_clear_form_ = true;
web_data_service->RemoveFormElementsAddedBetween(delete_begin_,
delete_end_);
web_data_service->RemoveAutofillDataModifiedBetween(delete_begin_,
delete_end_);
// Ask for a call back when the above calls are finished.
web_data_service->GetDBTaskRunner()->PostTaskAndReply(
FROM_HERE, base::Bind(&base::DoNothing),
base::Bind(&IOSChromeBrowsingDataRemover::OnClearedFormData,
base::Unretained(this)));
autofill::PersonalDataManager* data_manager =
autofill::PersonalDataManagerFactory::GetForBrowserState(
browser_state_);
if (data_manager)
data_manager->Refresh();
}
}
if (remove_mask & REMOVE_CACHE) {
base::RecordAction(UserMetricsAction("ClearBrowsingData_Cache"));
waiting_for_clear_cache_ = true;
ClearHttpCache(browser_state_->GetRequestContext(),
WebThread::GetTaskRunnerForThread(WebThread::IO),
delete_begin_, delete_end_,
base::Bind(&IOSChromeBrowsingDataRemover::OnClearedCache,
base::Unretained(this)));
}
// Remove omnibox zero-suggest cache results.
if ((remove_mask & (REMOVE_CACHE | REMOVE_COOKIES))) {
browser_state_->GetPrefs()->SetString(omnibox::kZeroSuggestCachedResults,
std::string());
}
// Always wipe accumulated network related data (TransportSecurityState and
// HttpServerPropertiesManager data).
waiting_for_clear_networking_history_ = true;
browser_state_->ClearNetworkingHistorySince(
delete_begin_,
base::Bind(&IOSChromeBrowsingDataRemover::OnClearedNetworkingHistory,
base::Unretained(this)));
// Record the combined deletion of cookies and cache.
CookieOrCacheDeletionChoice choice = NEITHER_COOKIES_NOR_CACHE;
if (remove_mask & REMOVE_COOKIES) {
choice = remove_mask & REMOVE_CACHE ? BOTH_COOKIES_AND_CACHE : ONLY_COOKIES;
} else if (remove_mask & REMOVE_CACHE) {
choice = ONLY_CACHE;
}
UMA_HISTOGRAM_ENUMERATION(
"History.ClearBrowsingData.UserDeletedCookieOrCache", choice,
MAX_CHOICE_VALUE);
}
void IOSChromeBrowsingDataRemover::AddObserver(Observer* observer) {
observer_list_.AddObserver(observer);
}
void IOSChromeBrowsingDataRemover::RemoveObserver(Observer* observer) {
observer_list_.RemoveObserver(observer);
}
void IOSChromeBrowsingDataRemover::OnHistoryDeletionDone() {
waiting_for_clear_history_ = false;
NotifyAndDeleteIfDone();
}
bool IOSChromeBrowsingDataRemover::AllDone() {
return !waiting_for_clear_autofill_origin_urls_ &&
!waiting_for_clear_cache_ && !waiting_for_clear_channel_ids_ &&
!waiting_for_clear_cookies_count_ && !waiting_for_clear_form_ &&
!waiting_for_clear_history_ &&
!waiting_for_clear_hostname_resolution_cache_ &&
!waiting_for_clear_keyword_data_ &&
!waiting_for_clear_networking_history_ &&
!waiting_for_clear_passwords_;
}
void IOSChromeBrowsingDataRemover::OnKeywordsLoaded() {
// Deletes the entries from the model, and if we're not waiting on anything
// else notifies observers and deletes this IOSChromeBrowsingDataRemover.
TemplateURLService* model =
ios::TemplateURLServiceFactory::GetForBrowserState(browser_state_);
model->RemoveAutoGeneratedBetween(delete_begin_, delete_end_);
waiting_for_clear_keyword_data_ = false;
template_url_sub_.reset();
NotifyAndDeleteIfDone();
}
void IOSChromeBrowsingDataRemover::NotifyAndDelete() {
set_removing(false);
// Notify observers.
IOSChromeBrowsingDataRemover::NotificationDetails details(delete_begin_,
remove_mask_);
GetOnBrowsingDataRemovedCallbacks()->Notify(details);
for (auto& observer : observer_list_)
observer.OnIOSChromeBrowsingDataRemoverDone();
// History requests aren't happy if you delete yourself from the callback.
// As such, we do a delete later.
base::ThreadTaskRunnerHandle::Get()->DeleteSoon(FROM_HERE, this);
}
void IOSChromeBrowsingDataRemover::NotifyAndDeleteIfDone() {
// TODO(brettw) http://crbug.com/305259: This should also observe session
// clearing (what about other things such as passwords, etc.?) and wait for
// them to complete before continuing.
if (!AllDone())
return;
NotifyAndDelete();
}
void IOSChromeBrowsingDataRemover::OnClearedHostnameResolutionCache() {
DCHECK_CURRENTLY_ON(WebThread::UI);
waiting_for_clear_hostname_resolution_cache_ = false;
NotifyAndDeleteIfDone();
}
void IOSChromeBrowsingDataRemover::OnClearedNetworkingHistory() {
DCHECK_CURRENTLY_ON(WebThread::UI);
waiting_for_clear_networking_history_ = false;
NotifyAndDeleteIfDone();
}
void IOSChromeBrowsingDataRemover::OnClearedCache(int error) {
waiting_for_clear_cache_ = false;
NotifyAndDeleteIfDone();
}
void IOSChromeBrowsingDataRemover::OnClearedPasswords() {
DCHECK_CURRENTLY_ON(WebThread::UI);
waiting_for_clear_passwords_ = false;
NotifyAndDeleteIfDone();
}
void IOSChromeBrowsingDataRemover::OnClearedCookies(uint32_t num_deleted) {
if (!WebThread::CurrentlyOn(WebThread::UI)) {
WebThread::PostTask(
WebThread::UI, FROM_HERE,
base::Bind(&IOSChromeBrowsingDataRemover::OnClearedCookies,
base::Unretained(this), num_deleted));
return;
}
DCHECK_GT(waiting_for_clear_cookies_count_, 0);
--waiting_for_clear_cookies_count_;
NotifyAndDeleteIfDone();
}
void IOSChromeBrowsingDataRemover::ClearCookiesOnIOThread(
const scoped_refptr<net::URLRequestContextGetter>& rq_context) {
DCHECK_CURRENTLY_ON(WebThread::IO);
net::CookieStore* cookie_store =
rq_context->GetURLRequestContext()->cookie_store();
cookie_store->DeleteAllCreatedBetweenAsync(
delete_begin_, delete_end_,
base::Bind(&IOSChromeBrowsingDataRemover::OnClearedCookies,
base::Unretained(this)));
}
void IOSChromeBrowsingDataRemover::ClearChannelIDsOnIOThread(
const scoped_refptr<net::URLRequestContextGetter>& rq_context) {
DCHECK_CURRENTLY_ON(WebThread::IO);
net::ChannelIDService* channel_id_service =
rq_context->GetURLRequestContext()->channel_id_service();
channel_id_service->GetChannelIDStore()->DeleteForDomainsCreatedBetween(
base::Bind(&AllDomainsPredicate), delete_begin_, delete_end_,
base::Bind(&IOSChromeBrowsingDataRemover::OnClearedChannelIDsOnIOThread,
base::Unretained(this), rq_context));
}
void IOSChromeBrowsingDataRemover::OnClearedChannelIDsOnIOThread(
const scoped_refptr<net::URLRequestContextGetter>& rq_context) {
// Need to close open SSL connections which may be using the channel ids we
// are deleting.
// TODO(mattm): http://crbug.com/166069 Make the server bound cert
// service/store have observers that can notify relevant things directly.
rq_context->GetURLRequestContext()
->ssl_config_service()
->NotifySSLConfigChange();
WebThread::PostTask(
WebThread::UI, FROM_HERE,
base::Bind(&IOSChromeBrowsingDataRemover::OnClearedChannelIDs,
base::Unretained(this)));
}
void IOSChromeBrowsingDataRemover::OnClearedChannelIDs() {
DCHECK_CURRENTLY_ON(WebThread::UI);
waiting_for_clear_channel_ids_ = false;
NotifyAndDeleteIfDone();
}
void IOSChromeBrowsingDataRemover::OnClearedFormData() {
DCHECK_CURRENTLY_ON(WebThread::UI);
waiting_for_clear_form_ = false;
NotifyAndDeleteIfDone();
}
void IOSChromeBrowsingDataRemover::OnClearedAutofillOriginURLs() {
DCHECK_CURRENTLY_ON(WebThread::UI);
waiting_for_clear_autofill_origin_urls_ = false;
NotifyAndDeleteIfDone();
}
// static
IOSChromeBrowsingDataRemover::CallbackSubscription
IOSChromeBrowsingDataRemover::RegisterOnBrowsingDataRemovedCallback(
const IOSChromeBrowsingDataRemover::Callback& callback) {
return GetOnBrowsingDataRemovedCallbacks()->Add(callback);
}