blob: 1a55e71364ddb55316fe927be6fdf3b98610c953 [file] [log] [blame]
// 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 <algorithm>
#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/browser/web_contents.h"
#include "third_party/blink/public/common/web_preferences/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"
#include "weblayer/public/browser_restore_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> Browser::Create(
Profile* profile,
const PersistenceInfo* persistence_info) {
// BrowserImpl's constructor is private.
auto browser =
base::WrapUnique(new BrowserImpl(static_cast<ProfileImpl*>(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<content::WebContents> web_contents,
const std::string& guid) {
if (!web_contents) {
content::WebContents::CreateParams create_params(
profile_->GetBrowserContext());
web_contents = content::WebContents::Create(create_params);
}
std::unique_ptr<TabImpl> tab =
std::make_unique<TabImpl>(profile_, std::move(web_contents), guid);
#if defined(OS_ANDROID)
Java_BrowserImpl_createJavaTabForNativeTab(
AttachCurrentThread(), java_impl_, reinterpret_cast<jlong>(tab.get()));
#endif
return AddTab(std::move(tab));
}
TabImpl* BrowserImpl::CreateTab(
std::unique_ptr<content::WebContents> 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<TabImpl*>(native_tab));
}
ScopedJavaLocalRef<jobjectArray> BrowserImpl::GetTabs(JNIEnv* env) {
ScopedJavaLocalRef<jclass> 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<TabImpl*>(tabs_[i].get());
env->SetObjectArrayElement(tabs, i, tab->GetJavaTab().obj());
}
return ScopedJavaLocalRef<jobjectArray>(env, tabs);
}
void BrowserImpl::SetActiveTab(JNIEnv* env,
long native_tab) {
SetActiveTab(reinterpret_cast<TabImpl*>(native_tab));
}
ScopedJavaLocalRef<jobject> BrowserImpl::GetActiveTab(JNIEnv* env) {
if (!active_tab_)
return nullptr;
return ScopedJavaLocalRef<jobject>(active_tab_->GetJavaTab());
}
void BrowserImpl::PrepareForShutdown(JNIEnv* env) {
PrepareForShutdown();
}
ScopedJavaLocalRef<jstring> BrowserImpl::GetPersistenceId(JNIEnv* env) {
return ScopedJavaLocalRef<jstring>(
base::android::ConvertUTF8ToJavaString(env, GetPersistenceId()));
}
void BrowserImpl::SaveBrowserPersisterIfNecessary(JNIEnv* env) {
browser_persister_->SaveIfNecessary();
}
ScopedJavaLocalRef<jbyteArray> BrowserImpl::GetBrowserPersisterCryptoKey(
JNIEnv* env) {
std::vector<uint8_t> key;
if (browser_persister_)
key = browser_persister_->GetCryptoKey();
return base::android::ToJavaByteArray(env, key);
}
ScopedJavaLocalRef<jbyteArray> BrowserImpl::GetMinimalPersistenceState(
JNIEnv* env) {
return base::android::ToJavaByteArray(env, GetMinimalPersistenceState());
}
void BrowserImpl::RestoreStateIfNecessary(
JNIEnv* env,
const JavaParamRef<jstring>& j_persistence_id,
const JavaParamRef<jbyteArray>& j_persistence_crypto_key,
const JavaParamRef<jbyteArray>& 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<TabImpl*>(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<uint8_t> BrowserImpl::GetMinimalPersistenceState(
int max_size_in_bytes) {
return PersistMinimalState(this, max_size_in_bytes);
}
void BrowserImpl::SetWebPreferences(blink::web_pref::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::mojom::PreferredColorScheme::kDark
: blink::mojom::PreferredColorScheme::kLight;
prefs->font_scale_factor =
Java_BrowserImpl_getFontScale(AttachCurrentThread(), java_impl_);
#endif
}
#if defined(OS_ANDROID)
void BrowserImpl::RemoveTabBeforeDestroyingFromJava(Tab* tab) {
// The java side owns the Tab, and is going to delete it shortly. See
// JNI_TabImpl_DeleteTab.
RemoveTab(tab).release();
}
#endif
void BrowserImpl::AddTab(Tab* tab) {
DCHECK(tab);
TabImpl* tab_impl = static_cast<TabImpl*>(tab);
std::unique_ptr<Tab> 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) {
#if defined(OS_ANDROID)
// Route destruction through the java side.
Java_BrowserImpl_destroyTabImpl(AttachCurrentThread(), java_impl_,
static_cast<TabImpl*>(tab)->GetJavaTab());
#else
RemoveTab(tab);
#endif
}
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<TabImpl*>(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<Tab*> BrowserImpl::GetTabs() {
std::vector<Tab*> 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::OnRestoreCompleted() {
for (BrowserRestoreObserver& obs : browser_restore_observers_)
obs.OnRestoreCompleted();
#if defined(OS_ANDROID)
Java_BrowserImpl_onRestoreCompleted(AttachCurrentThread(), java_impl_);
#endif
}
void BrowserImpl::PrepareForShutdown() {
browser_persister_.reset();
}
std::string BrowserImpl::GetPersistenceId() {
return persistence_id_;
}
std::vector<uint8_t> BrowserImpl::GetMinimalPersistenceState() {
// 0 means use the default max.
return GetMinimalPersistenceState(0);
}
bool BrowserImpl::IsRestoringPreviousState() {
return browser_persister_ && browser_persister_->is_restore_in_progress();
}
void BrowserImpl::AddObserver(BrowserObserver* observer) {
browser_observers_.AddObserver(observer);
}
void BrowserImpl::RemoveObserver(BrowserObserver* observer) {
browser_observers_.RemoveObserver(observer);
}
void BrowserImpl::AddBrowserRestoreObserver(BrowserRestoreObserver* observer) {
browser_restore_observers_.AddObserver(observer);
}
void BrowserImpl::RemoveBrowserRestoreObserver(
BrowserRestoreObserver* observer) {
browser_restore_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<BrowserPersister>(
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> tab) {
TabImpl* tab_impl = static_cast<TabImpl*>(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<Tab> BrowserImpl::RemoveTab(Tab* tab) {
TabImpl* tab_impl = static_cast<TabImpl*>(tab);
DCHECK_EQ(this, tab_impl->browser());
static_cast<TabImpl*>(tab)->set_browser(nullptr);
auto iter =
std::find_if(tabs_.begin(), tabs_.end(), base::MatchesUniquePtr(tab));
DCHECK(iter != tabs_.end());
std::unique_ptr<Tab> 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<jobject>& 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<jobject>& 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<intptr_t>(CreateBrowserForAndroid(
reinterpret_cast<ProfileImpl*>(profile), java_impl));
}
static void JNI_BrowserImpl_DeleteBrowser(JNIEnv* env, jlong browser) {
delete reinterpret_cast<BrowserImpl*>(browser);
}
#endif
} // namespace weblayer