// Copyright 2020 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 "weblayer/browser/browser_impl.h" #include #include "base/callback_forward.h" #include "base/containers/unique_ptr_adapters.h" #include "base/memory/ptr_util.h" #include "base/path_service.h" #include "base/stl_util.h" #include "components/base32/base32.h" #include "content/public/common/web_preferences.h" #include "weblayer/browser/browser_context_impl.h" #include "weblayer/browser/browser_list.h" #include "weblayer/browser/feature_list_creator.h" #include "weblayer/browser/persistence/browser_persister.h" #include "weblayer/browser/persistence/browser_persister_file_utils.h" #include "weblayer/browser/persistence/minimal_browser_persister.h" #include "weblayer/browser/profile_impl.h" #include "weblayer/browser/tab_impl.h" #include "weblayer/common/weblayer_paths.h" #include "weblayer/public/browser_observer.h" #if defined(OS_ANDROID) #include "base/android/callback_android.h" #include "base/android/jni_array.h" #include "base/android/jni_string.h" #include "base/json/json_writer.h" #include "components/metrics/metrics_service.h" #include "components/ukm/ukm_service.h" #include "weblayer/browser/android/metrics/weblayer_metrics_service_client.h" #include "weblayer/browser/browser_process.h" #include "weblayer/browser/java/jni/BrowserImpl_jni.h" #endif #if defined(OS_ANDROID) using base::android::AttachCurrentThread; using base::android::JavaParamRef; using base::android::ScopedJavaLocalRef; #endif namespace weblayer { namespace { #if defined(OS_ANDROID) void UpdateMetricsService() { static bool s_foreground = false; // TODO(sky): convert this to observer. bool foreground = BrowserList::GetInstance()->HasAtLeastOneResumedBrowser(); if (foreground == s_foreground) return; s_foreground = foreground; auto* metrics_service = WebLayerMetricsServiceClient::GetInstance()->GetMetricsService(); if (metrics_service) { if (foreground) metrics_service->OnAppEnterForeground(); else metrics_service->OnAppEnterBackground(); } auto* ukm_service = WebLayerMetricsServiceClient::GetInstance()->GetUkmService(); if (ukm_service) { if (foreground) ukm_service->OnAppEnterForeground(); else ukm_service->OnAppEnterBackground(); } } #endif // defined(OS_ANDROID) } // namespace // static constexpr char BrowserImpl::kPersistenceFilePrefix[]; std::unique_ptr Browser::Create( Profile* profile, const PersistenceInfo* persistence_info) { // BrowserImpl's constructor is private. auto browser = base::WrapUnique(new BrowserImpl(static_cast(profile))); if (persistence_info) browser->RestoreStateIfNecessary(*persistence_info); return browser; } BrowserImpl::~BrowserImpl() { #if defined(OS_ANDROID) // Android side should always remove tabs first (because the Java Tab class // owns the C++ Tab). See BrowserImpl.destroy() (in the Java BrowserImpl // class). DCHECK(tabs_.empty()); #else while (!tabs_.empty()) DestroyTab(tabs_.back().get()); #endif BrowserList::GetInstance()->RemoveBrowser(this); #if defined(OS_ANDROID) if (BrowserList::GetInstance()->browsers().empty()) BrowserProcess::GetInstance()->StopSafeBrowsingService(); #endif } TabImpl* BrowserImpl::CreateTabForSessionRestore( std::unique_ptr web_contents, const std::string& guid) { std::unique_ptr tab = std::make_unique(profile_, std::move(web_contents), guid); #if defined(OS_ANDROID) Java_BrowserImpl_createJavaTabForNativeTab( AttachCurrentThread(), java_impl_, reinterpret_cast(tab.get())); #endif return AddTab(std::move(tab)); } TabImpl* BrowserImpl::CreateTab( std::unique_ptr web_contents) { return CreateTabForSessionRestore(std::move(web_contents), std::string()); } #if defined(OS_ANDROID) bool BrowserImpl::CompositorHasSurface() { return Java_BrowserImpl_compositorHasSurface(AttachCurrentThread(), java_impl_); } void BrowserImpl::AddTab(JNIEnv* env, long native_tab) { AddTab(reinterpret_cast(native_tab)); } void BrowserImpl::RemoveTab(JNIEnv* env, long native_tab) { // The Java side owns the Tab. RemoveTab(reinterpret_cast(native_tab)).release(); } ScopedJavaLocalRef BrowserImpl::GetTabs(JNIEnv* env) { ScopedJavaLocalRef clazz = base::android::GetClass(env, "org/chromium/weblayer_private/TabImpl"); jobjectArray tabs = env->NewObjectArray(tabs_.size(), clazz.obj(), nullptr /* initialElement */); base::android::CheckException(env); for (size_t i = 0; i < tabs_.size(); ++i) { TabImpl* tab = static_cast(tabs_[i].get()); env->SetObjectArrayElement(tabs, i, tab->GetJavaTab().obj()); } return ScopedJavaLocalRef(env, tabs); } void BrowserImpl::SetActiveTab(JNIEnv* env, long native_tab) { SetActiveTab(reinterpret_cast(native_tab)); } ScopedJavaLocalRef BrowserImpl::GetActiveTab(JNIEnv* env) { if (!active_tab_) return nullptr; return ScopedJavaLocalRef(active_tab_->GetJavaTab()); } void BrowserImpl::PrepareForShutdown(JNIEnv* env) { PrepareForShutdown(); } ScopedJavaLocalRef BrowserImpl::GetPersistenceId(JNIEnv* env) { return ScopedJavaLocalRef( base::android::ConvertUTF8ToJavaString(env, GetPersistenceId())); } void BrowserImpl::SaveBrowserPersisterIfNecessary(JNIEnv* env) { browser_persister_->SaveIfNecessary(); } ScopedJavaLocalRef BrowserImpl::GetBrowserPersisterCryptoKey( JNIEnv* env) { std::vector key; if (browser_persister_) key = browser_persister_->GetCryptoKey(); return base::android::ToJavaByteArray(env, key); } ScopedJavaLocalRef BrowserImpl::GetMinimalPersistenceState( JNIEnv* env) { return base::android::ToJavaByteArray(env, GetMinimalPersistenceState()); } void BrowserImpl::RestoreStateIfNecessary( JNIEnv* env, const JavaParamRef& j_persistence_id, const JavaParamRef& j_persistence_crypto_key, const JavaParamRef& j_minimal_persistence_state) { Browser::PersistenceInfo persistence_info; Browser::PersistenceInfo* persistence_info_ptr = nullptr; if (j_persistence_id.obj()) { const std::string persistence_id = base::android::ConvertJavaStringToUTF8(j_persistence_id); if (!persistence_id.empty()) { persistence_info.id = persistence_id; if (j_persistence_crypto_key.obj()) { base::android::JavaByteArrayToByteVector( env, j_persistence_crypto_key, &(persistence_info.last_crypto_key)); } persistence_info_ptr = &persistence_info; } } else if (j_minimal_persistence_state.obj()) { base::android::JavaByteArrayToByteVector(env, j_minimal_persistence_state, &(persistence_info.minimal_state)); persistence_info_ptr = &persistence_info; } if (persistence_info_ptr) RestoreStateIfNecessary(*persistence_info_ptr); } void BrowserImpl::WebPreferencesChanged(JNIEnv* env) { for (const auto& tab : tabs_) { TabImpl* tab_impl = static_cast(tab.get()); tab_impl->WebPreferencesChanged(); } } void BrowserImpl::OnFragmentStart(JNIEnv* env) { // FeatureListCreator is created before any Browsers. DCHECK(FeatureListCreator::GetInstance()); FeatureListCreator::GetInstance()->OnBrowserFragmentStarted(); } void BrowserImpl::OnFragmentResume(JNIEnv* env) { UpdateFragmentResumedState(true); } void BrowserImpl::OnFragmentPause(JNIEnv* env) { UpdateFragmentResumedState(false); } #endif std::vector BrowserImpl::GetMinimalPersistenceState( int max_size_in_bytes) { return PersistMinimalState(this, max_size_in_bytes); } void BrowserImpl::SetWebPreferences(content::WebPreferences* prefs) { #if defined(OS_ANDROID) prefs->password_echo_enabled = Java_BrowserImpl_getPasswordEchoEnabled( AttachCurrentThread(), java_impl_); prefs->preferred_color_scheme = Java_BrowserImpl_getDarkThemeEnabled(AttachCurrentThread(), java_impl_) ? blink::PreferredColorScheme::kDark : blink::PreferredColorScheme::kLight; prefs->font_scale_factor = Java_BrowserImpl_getFontScale(AttachCurrentThread(), java_impl_); #endif } void BrowserImpl::AddTab(Tab* tab) { DCHECK(tab); TabImpl* tab_impl = static_cast(tab); std::unique_ptr owned_tab; if (tab_impl->browser()) owned_tab = tab_impl->browser()->RemoveTab(tab_impl); else owned_tab.reset(tab_impl); AddTab(std::move(owned_tab)); } void BrowserImpl::DestroyTab(Tab* tab) { RemoveTab(tab); } void BrowserImpl::SetActiveTab(Tab* tab) { if (GetActiveTab() == tab) return; if (active_tab_) active_tab_->OnLosingActive(); // TODO: currently the java side sets visibility, this code likely should // too and it should be removed from the java side. active_tab_ = static_cast(tab); #if defined(OS_ANDROID) Java_BrowserImpl_onActiveTabChanged( AttachCurrentThread(), java_impl_, active_tab_ ? active_tab_->GetJavaTab() : nullptr); #endif VisibleSecurityStateOfActiveTabChanged(); for (BrowserObserver& obs : browser_observers_) obs.OnActiveTabChanged(active_tab_); if (active_tab_) active_tab_->web_contents()->GetController().LoadIfNecessary(); } Tab* BrowserImpl::GetActiveTab() { return active_tab_; } std::vector BrowserImpl::GetTabs() { std::vector tabs(tabs_.size()); for (size_t i = 0; i < tabs_.size(); ++i) tabs[i] = tabs_[i].get(); return tabs; } Tab* BrowserImpl::CreateTab() { return CreateTab(nullptr); } void BrowserImpl::PrepareForShutdown() { browser_persister_.reset(); } std::string BrowserImpl::GetPersistenceId() { return persistence_id_; } std::vector BrowserImpl::GetMinimalPersistenceState() { // 0 means use the default max. return GetMinimalPersistenceState(0); } void BrowserImpl::AddObserver(BrowserObserver* observer) { browser_observers_.AddObserver(observer); } void BrowserImpl::RemoveObserver(BrowserObserver* observer) { browser_observers_.RemoveObserver(observer); } void BrowserImpl::VisibleSecurityStateOfActiveTabChanged() { if (visible_security_state_changed_callback_for_tests_) std::move(visible_security_state_changed_callback_for_tests_).Run(); #if defined(OS_ANDROID) JNIEnv* env = base::android::AttachCurrentThread(); Java_BrowserImpl_onVisibleSecurityStateOfActiveTabChanged(env, java_impl_); #endif } BrowserImpl::BrowserImpl(ProfileImpl* profile) : profile_(profile) { BrowserList::GetInstance()->AddBrowser(this); } void BrowserImpl::RestoreStateIfNecessary( const PersistenceInfo& persistence_info) { persistence_id_ = persistence_info.id; if (!persistence_id_.empty()) { browser_persister_ = std::make_unique( GetBrowserPersisterDataPath(), this, persistence_info.last_crypto_key); } else if (!persistence_info.minimal_state.empty()) { RestoreMinimalState(this, persistence_info.minimal_state); } } TabImpl* BrowserImpl::AddTab(std::unique_ptr tab) { TabImpl* tab_impl = static_cast(tab.get()); DCHECK(!tab_impl->browser()); tabs_.push_back(std::move(tab)); tab_impl->set_browser(this); #if defined(OS_ANDROID) Java_BrowserImpl_onTabAdded(AttachCurrentThread(), java_impl_, tab_impl->GetJavaTab()); #endif for (BrowserObserver& obs : browser_observers_) obs.OnTabAdded(tab_impl); return tab_impl; } std::unique_ptr BrowserImpl::RemoveTab(Tab* tab) { TabImpl* tab_impl = static_cast(tab); DCHECK_EQ(this, tab_impl->browser()); static_cast(tab)->set_browser(nullptr); auto iter = std::find_if(tabs_.begin(), tabs_.end(), base::MatchesUniquePtr(tab)); DCHECK(iter != tabs_.end()); std::unique_ptr owned_tab = std::move(*iter); tabs_.erase(iter); const bool active_tab_changed = active_tab_ == tab; if (active_tab_changed) SetActiveTab(nullptr); #if defined(OS_ANDROID) Java_BrowserImpl_onTabRemoved(AttachCurrentThread(), java_impl_, tab ? tab_impl->GetJavaTab() : nullptr); #endif for (BrowserObserver& obs : browser_observers_) obs.OnTabRemoved(tab, active_tab_changed); return owned_tab; } base::FilePath BrowserImpl::GetBrowserPersisterDataPath() { return BuildPathForBrowserPersister( profile_->GetBrowserPersisterDataBaseDir(), GetPersistenceId()); } #if defined(OS_ANDROID) void BrowserImpl::UpdateFragmentResumedState(bool state) { const bool old_has_at_least_one_active_browser = BrowserList::GetInstance()->HasAtLeastOneResumedBrowser(); fragment_resumed_ = state; UpdateMetricsService(); if (old_has_at_least_one_active_browser != BrowserList::GetInstance()->HasAtLeastOneResumedBrowser()) { BrowserList::GetInstance()->NotifyHasAtLeastOneResumedBrowserChanged(); } } // This function is friended. JNI_BrowserImpl_CreateBrowser can not be // friended, as it requires browser_impl.h to include BrowserImpl_jni.h, which // is problematic (meaning not really supported and generates compile errors). BrowserImpl* CreateBrowserForAndroid(ProfileImpl* profile, const JavaParamRef& java_impl) { BrowserImpl* browser = new BrowserImpl(profile); browser->java_impl_ = java_impl; return browser; } static jlong JNI_BrowserImpl_CreateBrowser( JNIEnv* env, jlong profile, const JavaParamRef& java_impl) { // The android side does not trigger restore from the constructor as at the // time this is called not enough of WebLayer has been wired up. Specifically, // when this is called BrowserImpl.java hasn't obtained the return value so // that it can't call any functions and further the client side hasn't been // fully created, leading to all sort of assertions if Tabs are created // and/or navigations start (which restore may trigger). return reinterpret_cast(CreateBrowserForAndroid( reinterpret_cast(profile), java_impl)); } static void JNI_BrowserImpl_DeleteBrowser(JNIEnv* env, jlong browser) { delete reinterpret_cast(browser); } #endif } // namespace weblayer