// Copyright 2013 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/browser_state/test_chrome_browser_state.h" #include #include "base/base_paths.h" #include "base/files/file_util.h" #include "base/logging.h" #include "base/macros.h" #include "base/message_loop/message_loop.h" #include "base/path_service.h" #include "base/run_loop.h" #include "base/thread_task_runner_handle.h" #include "components/bookmarks/browser/bookmark_model.h" #include "components/bookmarks/common/bookmark_constants.h" #include "components/history/core/browser/history_constants.h" #include "components/history/core/browser/history_database_params.h" #include "components/history/core/browser/history_service.h" #include "components/history/core/browser/top_sites.h" #include "components/history/core/browser/visit_delegate.h" #include "components/history/ios/browser/history_database_helper.h" #include "components/keyed_service/core/service_access_type.h" #include "components/keyed_service/ios/browser_state_dependency_manager.h" #include "components/syncable_prefs/pref_service_syncable.h" #include "components/syncable_prefs/testing_pref_service_syncable.h" #include "components/user_prefs/user_prefs.h" #include "components/webdata_services/web_data_service_wrapper.h" #include "ios/chrome/browser/application_context.h" #include "ios/chrome/browser/autocomplete/in_memory_url_index_factory.h" #include "ios/chrome/browser/bookmarks/bookmark_client_impl.h" #include "ios/chrome/browser/bookmarks/bookmark_model_factory.h" #include "ios/chrome/browser/browser_state/browser_state_keyed_service_factories.h" #include "ios/chrome/browser/history/history_client_impl.h" #include "ios/chrome/browser/history/history_service_factory.h" #include "ios/chrome/browser/history/top_sites_factory.h" #include "ios/chrome/browser/history/web_history_service_factory.h" #include "ios/chrome/browser/pref_names.h" #include "ios/chrome/browser/prefs/browser_prefs.h" #include "ios/chrome/browser/prefs/ios_chrome_pref_service_factory.h" #include "ios/chrome/browser/sync/glue/sync_start_util.h" #include "ios/chrome/browser/web_data_service_factory.h" #include "ios/public/provider/chrome/browser/chrome_browser_provider.h" #include "ios/web/public/web_thread.h" #include "net/url_request/url_request_test_util.h" namespace { scoped_ptr BuildHistoryService(web::BrowserState* context) { ios::ChromeBrowserState* browser_state = ios::ChromeBrowserState::FromBrowserState(context); return make_scoped_ptr(new history::HistoryService( make_scoped_ptr(new HistoryClientImpl( ios::BookmarkModelFactory::GetForBrowserState(browser_state))), nullptr)); } scoped_ptr BuildBookmarkModel(web::BrowserState* context) { ios::ChromeBrowserState* browser_state = ios::ChromeBrowserState::FromBrowserState(context); scoped_ptr bookmark_model( new bookmarks::BookmarkModel( make_scoped_ptr(new BookmarkClientImpl(browser_state)))); bookmark_model->Load( browser_state->GetPrefs(), browser_state->GetPrefs()->GetString(prefs::kAcceptLanguages), browser_state->GetStatePath(), browser_state->GetIOTaskRunner(), web::WebThread::GetTaskRunnerForThread(web::WebThread::UI)); return std::move(bookmark_model); } void NotReachedErrorCallback(WebDataServiceWrapper::ErrorType error_type, sql::InitStatus status) { NOTREACHED(); } scoped_ptr BuildWebDataService(web::BrowserState* context) { const base::FilePath& browser_state_path = context->GetStatePath(); return make_scoped_ptr(new WebDataServiceWrapper( browser_state_path, GetApplicationContext()->GetApplicationLocale(), web::WebThread::GetTaskRunnerForThread(web::WebThread::UI), web::WebThread::GetTaskRunnerForThread(web::WebThread::DB), ios::sync_start_util::GetFlareForSyncableService(browser_state_path), &NotReachedErrorCallback)); } base::FilePath CreateTempBrowserStateDir(base::ScopedTempDir* temp_dir) { DCHECK(temp_dir); if (!temp_dir->CreateUniqueTempDir()) { // Fallback logic in case we fail to create unique temporary directory. LOG(ERROR) << "Failed to create unique temporary directory."; base::FilePath system_tmp_dir; bool success = PathService::Get(base::DIR_TEMP, &system_tmp_dir); // We're severly screwed if we can't get the system temporary // directory. Die now to avoid writing to the filesystem root // or other bad places. CHECK(success); base::FilePath fallback_dir( system_tmp_dir.Append(FILE_PATH_LITERAL("TestChromeBrowserStatePath"))); base::DeleteFile(fallback_dir, true); base::CreateDirectory(fallback_dir); if (!temp_dir->Set(fallback_dir)) { // That shouldn't happen, but if it does, try to recover. LOG(ERROR) << "Failed to use a fallback temporary directory."; // We're screwed if this fails, see CHECK above. CHECK(temp_dir->Set(system_tmp_dir)); } } return temp_dir->path(); } } // namespace TestChromeBrowserState::TestChromeBrowserState( TestChromeBrowserState* original_browser_state) : testing_prefs_(nullptr), otr_browser_state_(nullptr), original_browser_state_(original_browser_state) { // Not calling Init() here as the bi-directional link between original and // off-the-record TestChromeBrowserState must be established before this // method can be called. DCHECK(original_browser_state_); } TestChromeBrowserState::TestChromeBrowserState( const base::FilePath& path, scoped_ptr prefs, const TestingFactories& testing_factories, const RefcountedTestingFactories& refcounted_testing_factories) : state_path_(path), prefs_(std::move(prefs)), testing_prefs_(nullptr), otr_browser_state_(nullptr), original_browser_state_(nullptr) { Init(); for (const auto& pair : testing_factories) { pair.first->SetTestingFactory(this, pair.second); } for (const auto& pair : refcounted_testing_factories) { pair.first->SetTestingFactory(this, pair.second); } } TestChromeBrowserState::~TestChromeBrowserState() { // If this TestChromeBrowserState owns an incognito TestChromeBrowserState, // tear it down first. otr_browser_state_.reset(); BrowserStateDependencyManager::GetInstance()->DestroyBrowserStateServices( this); } void TestChromeBrowserState::Init() { // If threads have been initialized, we should be on the UI thread. DCHECK(!web::WebThread::IsThreadInitialized(web::WebThread::UI) || web::WebThread::CurrentlyOn(web::WebThread::UI)); if (state_path_.empty()) state_path_ = CreateTempBrowserStateDir(&temp_dir_); if (IsOffTheRecord()) state_path_ = state_path_.Append(FILE_PATH_LITERAL("OTR")); if (!base::PathExists(state_path_)) base::CreateDirectory(state_path_); // Normally this would happen during browser startup, but for tests we need to // trigger creation of BrowserState-related services. EnsureBrowserStateKeyedServiceFactoriesBuilt(); if (ios::GetChromeBrowserProvider()) ios::GetChromeBrowserProvider()->AssertBrowserContextKeyedFactoriesBuilt(); if (prefs_) { // If user passed a custom PrefServiceSyncable, then leave |testing_prefs_| // unset as it is not possible to determine its type. } else if (IsOffTheRecord()) { // This leaves |testing_prefs_| unset as CreateIncognitoBrowserStatePrefs() // does not return a TestingPrefServiceSyncable. DCHECK(original_browser_state_); prefs_ = CreateIncognitoBrowserStatePrefs(original_browser_state_->prefs_.get()); } else { testing_prefs_ = new syncable_prefs::TestingPrefServiceSyncable(); RegisterBrowserStatePrefs(testing_prefs_->registry()); prefs_.reset(testing_prefs_); } user_prefs::UserPrefs::Set(this, prefs_.get()); // Prefs for incognito TestChromeBrowserState are set in // CreateIncognitoBrowserStatePrefs(). if (!IsOffTheRecord()) { user_prefs::PrefRegistrySyncable* pref_registry = static_cast( prefs_->DeprecatedGetPrefRegistry()); BrowserStateDependencyManager::GetInstance() ->RegisterBrowserStatePrefsForServices(this, pref_registry); } BrowserStateDependencyManager::GetInstance() ->CreateBrowserStateServicesForTest(this); } bool TestChromeBrowserState::IsOffTheRecord() const { return original_browser_state_ != nullptr; } base::FilePath TestChromeBrowserState::GetStatePath() const { if (!IsOffTheRecord()) return state_path_; base::FilePath otr_stash_state_path = state_path_.Append(FILE_PATH_LITERAL("OTR")); if (!base::PathExists(otr_stash_state_path)) base::CreateDirectory(otr_stash_state_path); return otr_stash_state_path; } scoped_refptr TestChromeBrowserState::GetIOTaskRunner() { return base::MessageLoop::current()->task_runner(); } ios::ChromeBrowserState* TestChromeBrowserState::GetOriginalChromeBrowserState() { if (IsOffTheRecord()) return original_browser_state_; return this; } bool TestChromeBrowserState::HasOffTheRecordChromeBrowserState() const { return otr_browser_state_ != nullptr; } ios::ChromeBrowserState* TestChromeBrowserState::GetOffTheRecordChromeBrowserState() { if (IsOffTheRecord()) return this; if (!otr_browser_state_) { otr_browser_state_.reset(new TestChromeBrowserState(this)); otr_browser_state_->Init(); } return otr_browser_state_.get(); } PrefProxyConfigTracker* TestChromeBrowserState::GetProxyConfigTracker() { return nullptr; } net::SSLConfigService* TestChromeBrowserState::GetSSLConfigService() { return nullptr; } PrefService* TestChromeBrowserState::GetPrefs() { return prefs_.get(); } PrefService* TestChromeBrowserState::GetOffTheRecordPrefs() { return nullptr; } ChromeBrowserStateIOData* TestChromeBrowserState::GetIOData() { return nullptr; } void TestChromeBrowserState::ClearNetworkingHistorySince( base::Time time, const base::Closure& completion) { if (!completion.is_null()) completion.Run(); } net::URLRequestContextGetter* TestChromeBrowserState::CreateRequestContext( ProtocolHandlerMap* protocol_handlers, URLRequestInterceptorScopedVector request_interceptors) { return new net::TestURLRequestContextGetter( web::WebThread::GetTaskRunnerForThread(web::WebThread::IO)); } net::URLRequestContextGetter* TestChromeBrowserState::CreateIsolatedRequestContext( const base::FilePath& partition_path) { return nullptr; } TestChromeBrowserState* TestChromeBrowserState::AsTestChromeBrowserState() { return this; } void TestChromeBrowserState::CreateWebDataService() { ignore_result( ios::WebDataServiceFactory::GetInstance()->SetTestingFactoryAndUse( this, &BuildWebDataService)); // Wait a bit after creating the WebDataService to allow the initialisation // to complete (otherwise the TestChromeBrowserState may be destroyed before // initialisation of the database is complete which leads to SQL init errors). base::RunLoop run_loop; run_loop.RunUntilIdle(); } void TestChromeBrowserState::CreateBookmarkModel(bool delete_file) { if (delete_file) { base::DeleteFile(GetOriginalChromeBrowserState()->GetStatePath().Append( bookmarks::kBookmarksFileName), false /* recursive */); } ignore_result( ios::BookmarkModelFactory::GetInstance()->SetTestingFactoryAndUse( this, &BuildBookmarkModel)); } bool TestChromeBrowserState::CreateHistoryService(bool delete_file) { // Ensure that no HistoryService exists before creating a new one. DestroyHistoryService(); if (delete_file) { base::FilePath path = GetOriginalChromeBrowserState()->GetStatePath().Append( history::kHistoryFilename); if (!base::DeleteFile(path, false) && base::PathExists(path)) return false; } // Create and initialize the HistoryService, but destroy it if the init fails. history::HistoryService* history_service = static_cast( ios::HistoryServiceFactory::GetInstance()->SetTestingFactoryAndUse( this, &BuildHistoryService)); if (!history_service->Init( GetPrefs()->GetString(prefs::kAcceptLanguages), history::HistoryDatabaseParamsForPath( GetOriginalChromeBrowserState()->GetStatePath()))) { ios::HistoryServiceFactory::GetInstance()->SetTestingFactory(this, nullptr); return false; } // Some tests expect that CreateHistoryService() will also make the // InMemoryURLIndex available. ios::InMemoryURLIndexFactory::GetInstance()->SetTestingFactory( this, ios::InMemoryURLIndexFactory::GetDefaultFactory()); // Disable WebHistoryService by default, since it makes network requests. ios::WebHistoryServiceFactory::GetInstance()->SetTestingFactory(this, nullptr); return true; } void TestChromeBrowserState::DestroyHistoryService() { history::HistoryService* history_service = ios::HistoryServiceFactory::GetInstance()->GetForBrowserStateIfExists( this, ServiceAccessType::EXPLICIT_ACCESS); if (!history_service) return; history_service->ClearCachedDataForContextID(0); history_service->SetOnBackendDestroyTask( base::MessageLoop::QuitWhenIdleClosure()); history_service->Shutdown(); history_service = nullptr; // Resetting the testing factory force the destruction of the current // HistoryService instance associated with the TestChromeBrowserState. ios::HistoryServiceFactory::GetInstance()->SetTestingFactory(this, nullptr); // Wait for the backend class to terminate before deleting the files and // moving to the next test. Note: if this never terminates, somebody is // probably leaking a reference to the history backend, so it never calls // our destroy task. base::MessageLoop::current()->Run(); // Make sure we don't have any event pending that could disrupt the next // test. base::ThreadTaskRunnerHandle::Get()->PostTask( FROM_HERE, base::MessageLoop::QuitWhenIdleClosure()); base::MessageLoop::current()->Run(); } syncable_prefs::TestingPrefServiceSyncable* TestChromeBrowserState::GetTestingPrefService() { DCHECK(prefs_); DCHECK(testing_prefs_); return testing_prefs_; } TestChromeBrowserState::Builder::Builder() : build_called_(false) {} TestChromeBrowserState::Builder::~Builder() {} void TestChromeBrowserState::Builder::AddTestingFactory( BrowserStateKeyedServiceFactory* service_factory, BrowserStateKeyedServiceFactory::TestingFactoryFunction cb) { DCHECK(!build_called_); testing_factories_.push_back(std::make_pair(service_factory, cb)); } void TestChromeBrowserState::Builder::AddTestingFactory( RefcountedBrowserStateKeyedServiceFactory* service_factory, RefcountedBrowserStateKeyedServiceFactory::TestingFactoryFunction cb) { DCHECK(!build_called_); refcounted_testing_factories_.push_back(std::make_pair(service_factory, cb)); } void TestChromeBrowserState::Builder::SetPath(const base::FilePath& path) { DCHECK(!build_called_); state_path_ = path; } void TestChromeBrowserState::Builder::SetPrefService( scoped_ptr prefs) { DCHECK(!build_called_); pref_service_ = std::move(prefs); } scoped_ptr TestChromeBrowserState::Builder::Build() { DCHECK(!build_called_); return make_scoped_ptr(new TestChromeBrowserState( state_path_, std::move(pref_service_), testing_factories_, refcounted_testing_factories_)); }