#include "weblayer/browser/browser_impl.h"
#include "base/containers/unique_ptr_adapters.h"
#include "base/memory/ptr_util.h"
#include "base/path_service.h"
#include "base/ranges/algorithm.h"
#include "build/build_config.h"
#include "components/base32/base32.h"
#include "components/prefs/pref_service.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/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 BUILDFLAG(IS_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/browser_ui/accessibility/android/font_size_prefs_android.h"
#include "weblayer/browser/browser_process.h"
#include "weblayer/browser/java/jni/BrowserImpl_jni.h"
using base::android::AttachCurrentThread;
using base::android::JavaParamRef;
using base::android::ScopedJavaLocalRef;
#endif
namespace weblayer {
#if BUILDFLAG(IS_ANDROID)
enum class DarkModeStrategy {
kWebThemeDarkeningOnly = 0,
kUserAgentDarkeningOnly = 1,
kPreferWebThemeOverUserAgentDarkening = 2,
};
#endif
constexpr char BrowserImpl::kPersistenceFilePrefix[];
std::unique_ptr<Browser> Browser::Create(
Profile* profile,
const PersistenceInfo* persistence_info) {
auto browser =
base::WrapUnique(new BrowserImpl(static_cast<ProfileImpl*>(profile)));
if (persistence_info)
browser->RestoreStateIfNecessary(*persistence_info);
return browser;
}
BrowserImpl::~BrowserImpl() {
#if BUILDFLAG(IS_ANDROID)
DCHECK(tabs_.empty());
#else
while (!tabs_.empty())
DestroyTab(tabs_.back().get());
#endif
BrowserList::GetInstance()->RemoveBrowser(this);
#if BUILDFLAG(IS_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 BUILDFLAG(IS_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 BUILDFLAG(IS_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 );
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();
}
void BrowserImpl::RestoreStateIfNecessary(
JNIEnv* env,
const JavaParamRef<jstring>& j_persistence_id) {
if (!j_persistence_id.obj())
return;
Browser::PersistenceInfo persistence_info;
persistence_info.id =
base::android::ConvertJavaStringToUTF8(j_persistence_id);
if (persistence_info.id.empty())
return;
RestoreStateIfNecessary(persistence_info);
}
void BrowserImpl::WebPreferencesChanged(JNIEnv* env) {
OnWebPreferenceChanged(std::string());
}
void BrowserImpl::OnFragmentStart(JNIEnv* env) {
DCHECK(FeatureListCreator::GetInstance());
FeatureListCreator::GetInstance()->OnBrowserFragmentStarted();
}
void BrowserImpl::OnFragmentResume(JNIEnv* env) {
UpdateFragmentResumedState(true);
}
void BrowserImpl::OnFragmentPause(JNIEnv* env) {
UpdateFragmentResumedState(false);
}
#endif
void BrowserImpl::SetWebPreferences(blink::web_pref::WebPreferences* prefs) {
#if BUILDFLAG(IS_ANDROID)
PrefService* pref_service = profile()->GetBrowserContext()->pref_service();
prefs->password_echo_enabled = Java_BrowserImpl_getPasswordEchoEnabled(
AttachCurrentThread(), java_impl_);
prefs->font_scale_factor = static_cast<float>(
pref_service->GetDouble(browser_ui::prefs::kWebKitFontScaleFactor));
prefs->force_enable_zoom =
pref_service->GetBoolean(browser_ui::prefs::kWebKitForceEnableZoom);
bool is_dark =
Java_BrowserImpl_getDarkThemeEnabled(AttachCurrentThread(), java_impl_);
if (is_dark) {
DarkModeStrategy dark_strategy =
static_cast<DarkModeStrategy>(Java_BrowserImpl_getDarkModeStrategy(
AttachCurrentThread(), java_impl_));
switch (dark_strategy) {
case DarkModeStrategy::kPreferWebThemeOverUserAgentDarkening:
prefs->preferred_color_scheme =
blink::mojom::PreferredColorScheme::kDark;
prefs->force_dark_mode_enabled = true;
break;
case DarkModeStrategy::kWebThemeDarkeningOnly:
prefs->preferred_color_scheme =
blink::mojom::PreferredColorScheme::kDark;
prefs->force_dark_mode_enabled = false;
break;
case DarkModeStrategy::kUserAgentDarkeningOnly:
prefs->preferred_color_scheme =
blink::mojom::PreferredColorScheme::kLight;
prefs->force_dark_mode_enabled = true;
break;
}
} else {
prefs->preferred_color_scheme = blink::mojom::PreferredColorScheme::kLight;
prefs->force_dark_mode_enabled = false;
}
#endif
}
#if BUILDFLAG(IS_ANDROID)
void BrowserImpl::RemoveTabBeforeDestroyingFromJava(Tab* tab) {
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 BUILDFLAG(IS_ANDROID)
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();
active_tab_ = static_cast<TabImpl*>(tab);
#if BUILDFLAG(IS_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_->OnGainedActive();
}
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 BUILDFLAG(IS_ANDROID)
Java_BrowserImpl_onRestoreCompleted(AttachCurrentThread(), java_impl_);
#endif
}
void BrowserImpl::PrepareForShutdown() {
browser_persister_.reset();
}
std::string BrowserImpl::GetPersistenceId() {
return persistence_id_;
}
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 BUILDFLAG(IS_ANDROID)
JNIEnv* env = base::android::AttachCurrentThread();
Java_BrowserImpl_onVisibleSecurityStateOfActiveTabChanged(env, java_impl_);
#endif
}
BrowserImpl::BrowserImpl(ProfileImpl* profile) : profile_(profile) {
BrowserList::GetInstance()->AddBrowser(this);
#if BUILDFLAG(IS_ANDROID)
profile_pref_change_registrar_.Init(
profile_->GetBrowserContext()->pref_service());
auto pref_change_callback = base::BindRepeating(
&BrowserImpl::OnWebPreferenceChanged, base::Unretained(this));
profile_pref_change_registrar_.Add(browser_ui::prefs::kWebKitFontScaleFactor,
pref_change_callback);
profile_pref_change_registrar_.Add(browser_ui::prefs::kWebKitForceEnableZoom,
pref_change_callback);
#endif
}
void BrowserImpl::RestoreStateIfNecessary(
const PersistenceInfo& persistence_info) {
persistence_id_ = persistence_info.id;
if (!persistence_id_.empty()) {
browser_persister_ =
std::make_unique<BrowserPersister>(GetBrowserPersisterDataPath(), this);
}
}
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 BUILDFLAG(IS_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 = base::ranges::find_if(tabs_, 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 BUILDFLAG(IS_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 BuildBasePathForBrowserPersister(
profile_->GetBrowserPersisterDataBaseDir(), GetPersistenceId());
}
void BrowserImpl::OnWebPreferenceChanged(const std::string& pref_name) {
for (const auto& tab : tabs_) {
TabImpl* tab_impl = static_cast<TabImpl*>(tab.get());
tab_impl->WebPreferencesChanged();
}
}
#if BUILDFLAG(IS_ANDROID)
void BrowserImpl::UpdateFragmentResumedState(bool state) {
const bool old_has_at_least_one_active_browser =
BrowserList::GetInstance()->HasAtLeastOneResumedBrowser();
fragment_resumed_ = state;
if (old_has_at_least_one_active_browser !=
BrowserList::GetInstance()->HasAtLeastOneResumedBrowser()) {
BrowserList::GetInstance()->NotifyHasAtLeastOneResumedBrowserChanged();
}
}
BrowserImpl* CreateBrowserForAndroid(ProfileImpl* profile,
const std::string& package_name,
const JavaParamRef<jobject>& java_impl) {
BrowserImpl* browser = new BrowserImpl(profile);
browser->java_impl_ = java_impl;
browser->package_name_ = package_name;
return browser;
}
static jlong JNI_BrowserImpl_CreateBrowser(
JNIEnv* env,
jlong profile,
const base::android::JavaParamRef<jstring>& package_name,
const JavaParamRef<jobject>& java_impl) {
return reinterpret_cast<intptr_t>(CreateBrowserForAndroid(
reinterpret_cast<ProfileImpl*>(profile),
base::android::ConvertJavaStringToUTF8(env, package_name), java_impl));
}
static void JNI_BrowserImpl_DeleteBrowser(JNIEnv* env, jlong browser) {
delete reinterpret_cast<BrowserImpl*>(browser);
}
#endif
}