blob: 0f324b2dca9b1236a9467bb594f2b1a93ba6e83d [file] [log] [blame]
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/android/historical_tab_saver.h"
#include <map>
#include <memory>
#include <optional>
#include <utility>
#include <vector>
#include "base/android/jni_android.h"
#include "base/android/jni_array.h"
#include "base/android/jni_string.h"
#include "base/android/token_android.h"
#include "base/memory/raw_ptr.h"
#include "base/not_fatal_until.h"
#include "base/uuid.h"
#include "chrome/browser/android/tab_android.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/sessions/tab_restore_service_factory.h"
#include "chrome/browser/tab/web_contents_state.h"
#include "chrome/browser/ui/android/tab_model/android_live_tab_context_wrapper.h"
#include "chrome/browser/ui/android/tab_model/tab_model.h"
#include "chrome/browser/ui/android/tab_model/tab_model_list.h"
#include "chrome/common/url_constants.h"
#include "components/sessions/content/content_live_tab.h"
#include "components/sessions/core/tab_restore_service.h"
#include "content/public/browser/web_contents.h"
// Must come after all headers that specialize FromJniType() / ToJniType().
#include "chrome/android/chrome_jni_headers/HistoricalTabSaverImpl_jni.h"
using base::android::JavaParamRef;
using base::android::JavaRef;
using base::android::ScopedJavaLocalRef;
namespace historical_tab_saver {
namespace {
std::vector<WebContentsStateByteBuffer> AllTabsWebContentsStateByteBuffer(
JNIEnv* env,
const JavaParamRef<jobjectArray>& jbyte_buffers,
std::vector<int> saved_state_versions) {
int jbyte_buffers_count = env->GetArrayLength(jbyte_buffers.obj());
std::vector<WebContentsStateByteBuffer> web_contents_states;
web_contents_states.reserve(jbyte_buffers_count);
for (int i = 0; i < jbyte_buffers_count; ++i) {
web_contents_states.emplace_back(
ScopedJavaLocalRef<jobject>::Adopt(
env, env->GetObjectArrayElement(jbyte_buffers.obj(), i)),
saved_state_versions[i]);
}
return web_contents_states;
}
std::optional<tab_groups::TabGroupId> JavaTokenToTabGroupId(
JNIEnv* env,
const JavaRef<jobject>& jtab_group_id) {
if (jtab_group_id.is_null()) {
return std::nullopt;
}
return tab_groups::TabGroupId::FromRawToken(
base::android::TokenAndroid::FromJavaToken(env, jtab_group_id));
}
std::vector<std::optional<tab_groups::TabGroupId>> JavaTokensToTabGroupIds(
JNIEnv* env,
const JavaParamRef<jobjectArray>& jtab_group_ids) {
std::vector<std::optional<tab_groups::TabGroupId>> tab_group_ids;
auto array_reader = jtab_group_ids.ReadElements();
tab_group_ids.reserve(array_reader.size());
for (auto element : array_reader) {
tab_group_ids.push_back(JavaTokenToTabGroupId(env, element));
}
return tab_group_ids;
}
std::optional<base::Uuid> StringToUuid(
const std::u16string& serialized_saved_tab_group_id) {
if (serialized_saved_tab_group_id.empty()) {
return std::nullopt;
}
return base::Uuid::ParseLowercase(serialized_saved_tab_group_id);
}
std::vector<std::optional<base::Uuid>> StringsToUuids(
const std::vector<std::u16string>& serialized_saved_tab_group_ids) {
std::vector<std::optional<base::Uuid>> saved_tab_group_ids;
saved_tab_group_ids.reserve(serialized_saved_tab_group_ids.size());
for (const auto& serialized_saved_tab_group_id :
serialized_saved_tab_group_ids) {
saved_tab_group_ids.push_back(StringToUuid(serialized_saved_tab_group_id));
}
return saved_tab_group_ids;
}
void CreateHistoricalTab(
TabAndroid* tab_android,
WebContentsStateByteBuffer web_contents_state_byte_buffer) {
if (!tab_android) {
return;
}
auto scoped_web_contents = ScopedWebContents::CreateForTab(
tab_android, &web_contents_state_byte_buffer);
if (!scoped_web_contents->web_contents()) {
return;
}
sessions::TabRestoreService* service =
TabRestoreServiceFactory::GetForProfile(Profile::FromBrowserContext(
scoped_web_contents->web_contents()->GetBrowserContext()));
if (!service) {
return;
}
// TODO(crbug/41496693): We should update AndroidLiveTabContext to return
// group data for single tabs when not closing an entire group to align with
// desktop. Right now any individual tab closure is treated as not being in a
// group.
// Index is unimportant on Android.
service->CreateHistoricalTab(sessions::ContentLiveTab::GetForWebContents(
scoped_web_contents->web_contents()),
/*index=*/-1);
}
void CreateHistoricalGroup(
TabModel* model,
const tab_groups::TabGroupId& tab_group_id,
const std::optional<base::Uuid> saved_tab_group_id,
const std::u16string& group_title,
int group_color,
std::vector<raw_ptr<TabAndroid, VectorExperimental>> tabs,
std::vector<WebContentsStateByteBuffer> web_contents_state) {
DCHECK(model);
sessions::TabRestoreService* service =
TabRestoreServiceFactory::GetForProfile(model->GetProfile());
if (!service) {
return;
}
std::map<int, tab_groups::TabGroupId> tab_id_to_group_id;
for (const TabAndroid* tab : tabs) {
DCHECK(tab);
tab_id_to_group_id.insert(
std::make_pair(tab->GetAndroidId(), tab_group_id));
}
// TODO(crbug/41496693): If we update AndroidLiveTabContext to return group
// data for tabs it should be possible to eliminate the need for this wrapper
// when closing an entire tab group.
AndroidLiveTabContextCloseWrapper context(
model, std::move(tabs), std::move(tab_id_to_group_id),
std::map<tab_groups::TabGroupId, tab_groups::TabGroupVisualData>(
{{tab_group_id,
tab_groups::TabGroupVisualData(
group_title, static_cast<tab_groups::TabGroupColorId>(group_color))}}),
std::map<tab_groups::TabGroupId, std::optional<base::Uuid>>(
{{tab_group_id, saved_tab_group_id}}),
std::move(web_contents_state));
service->CreateHistoricalGroup(&context, tab_group_id);
service->GroupClosed(tab_group_id);
}
void CreateHistoricalBulkClosure(
TabModel* model,
std::vector<std::optional<tab_groups::TabGroupId>> tab_group_ids,
std::vector<std::optional<base::Uuid>> saved_tab_group_ids,
std::vector<std::u16string> group_titles,
std::vector<int> group_colors,
std::vector<std::optional<tab_groups::TabGroupId>>
per_tab_optional_tab_group_ids,
std::vector<raw_ptr<TabAndroid, VectorExperimental>> tabs,
std::vector<WebContentsStateByteBuffer> web_contents_state) {
DCHECK(model);
DCHECK_EQ(tab_group_ids.size(), group_titles.size());
DCHECK_EQ(tab_group_ids.size(), group_colors.size());
DCHECK_EQ(tab_group_ids.size(), tab_group_ids.size());
DCHECK_EQ(tab_group_ids.size(), saved_tab_group_ids.size());
DCHECK_EQ(per_tab_optional_tab_group_ids.size(), tabs.size());
sessions::TabRestoreService* service =
TabRestoreServiceFactory::GetForProfile(model->GetProfile());
if (!service) {
return;
}
// Map each tab_group::TabGroupId to corresponding data for consumption
// downstream.
std::map<tab_groups::TabGroupId, tab_groups::TabGroupVisualData>
tab_group_visual_data;
std::map<tab_groups::TabGroupId, std::optional<base::Uuid>>
saved_tab_group_ids_map;
for (size_t i = 0; i < tab_group_ids.size(); ++i) {
DCHECK(tab_group_ids[i]);
auto group_id = *tab_group_ids[i];
auto saved_tab_group_id = saved_tab_group_ids[i];
if (saved_tab_group_id) {
saved_tab_group_ids_map.insert({group_id, saved_tab_group_id});
}
const std::u16string title = group_titles[i];
int color = group_colors[i];
tab_group_visual_data[group_id] = tab_groups::TabGroupVisualData(
title, /*color=*/(tab_groups::TabGroupColorId)color);
}
// Map Android Tabs by ID to their new or existing native
// tab_group::TabGroupId.
std::map<int, tab_groups::TabGroupId> tab_id_to_group_id;
for (size_t i = 0; i < tabs.size(); ++i) {
TabAndroid* tab = tabs[i];
if (auto optional_tab_group_id = per_tab_optional_tab_group_ids[i]) {
tab_id_to_group_id.insert(
std::make_pair(tab->GetAndroidId(), *optional_tab_group_id));
}
}
// This wrapper is necessary for bulk closures that don't close all tabs via
// the bulk tab editor.
AndroidLiveTabContextCloseWrapper context(
model, std::move(tabs), std::move(tab_id_to_group_id),
std::move(tab_group_visual_data), std::move(saved_tab_group_ids_map),
std::move(web_contents_state));
service->BrowserClosing(&context);
service->BrowserClosed(&context);
}
} // namespace
ScopedWebContents::ScopedWebContents(content::WebContents* unowned_web_contents)
: unowned_web_contents_(unowned_web_contents),
owned_web_contents_(nullptr) {}
ScopedWebContents::ScopedWebContents(
std::unique_ptr<content::WebContents> owned_web_contents)
: unowned_web_contents_(nullptr),
owned_web_contents_(std::move(owned_web_contents)) {
if (owned_web_contents_) {
owned_web_contents_->SetOwnerLocationForDebug(FROM_HERE);
}
}
ScopedWebContents::~ScopedWebContents() = default;
content::WebContents* ScopedWebContents::web_contents() const {
if (!unowned_web_contents_) {
return owned_web_contents_.get();
} else {
return unowned_web_contents_;
}
}
// static
std::unique_ptr<ScopedWebContents> ScopedWebContents::CreateForTab(
TabAndroid* tab,
const WebContentsStateByteBuffer* web_contents_state) {
if (tab->web_contents()) {
return std::make_unique<ScopedWebContents>(tab->web_contents());
}
if (web_contents_state->state_version != -1) {
auto native_contents = WebContentsState::RestoreContentsFromByteBuffer(
tab->profile(), web_contents_state, /*initially_hidden=*/true,
/*no_renderer=*/true);
if (native_contents) {
return std::make_unique<ScopedWebContents>(std::move(native_contents));
}
}
// Fallback to an empty web contents in the event state restoration
// fails. This will just not be added to the TabRestoreService.
// This is only called on non-incognito pathways.
CHECK(!tab->IsIncognito());
content::WebContents::CreateParams params(tab->profile());
params.initially_hidden = true;
params.desired_renderer_state =
content::WebContents::CreateParams::kNoRendererProcess;
return std::make_unique<ScopedWebContents>(
content::WebContents::Create(params));
}
// Static JNI methods.
static void JNI_HistoricalTabSaverImpl_CreateHistoricalTab(
JNIEnv* env,
const JavaParamRef<jobject>& jtab_android,
const JavaParamRef<jobject>& state,
jint saved_state_version) {
WebContentsStateByteBuffer web_contents_state = WebContentsStateByteBuffer(
ScopedJavaLocalRef<jobject>(state), (int)saved_state_version);
CreateHistoricalTab(TabAndroid::GetNativeTab(env, jtab_android),
std::move(web_contents_state));
}
static void JNI_HistoricalTabSaverImpl_CreateHistoricalGroup(
JNIEnv* env,
const JavaParamRef<jobject>& jtab_model,
const JavaParamRef<jobject>& jtab_group_id,
std::u16string& serialized_saved_tab_group_id,
std::u16string& title,
jint jcolor,
const JavaParamRef<jobjectArray>& jtabs_android,
const JavaParamRef<jobjectArray>& jbyte_buffers,
std::vector<int32_t>& saved_state_versions) {
tab_groups::TabGroupId tab_group_id =
*JavaTokenToTabGroupId(env, jtab_group_id);
std::optional<base::Uuid> saved_tab_group_id =
StringToUuid(serialized_saved_tab_group_id);
auto tabs_android = TabAndroid::GetAllNativeTabs(
env, base::android::ScopedJavaLocalRef<jobjectArray>(jtabs_android));
int tabs_android_count = env->GetArrayLength(jtabs_android.obj());
DCHECK_EQ(tabs_android_count, env->GetArrayLength(jbyte_buffers.obj()));
DCHECK_EQ(tabs_android_count, static_cast<int>(saved_state_versions.size()));
std::vector<WebContentsStateByteBuffer> web_contents_states =
AllTabsWebContentsStateByteBuffer(env, jbyte_buffers,
std::move(saved_state_versions));
CreateHistoricalGroup(
TabModelList::FindNativeTabModelForJavaObject(jtab_model), tab_group_id,
saved_tab_group_id, title, (int)jcolor, std::move(tabs_android),
std::move(web_contents_states));
}
static void JNI_HistoricalTabSaverImpl_CreateHistoricalBulkClosure(
JNIEnv* env,
const JavaParamRef<jobject>& jtab_model,
const JavaParamRef<jobjectArray>& jtab_group_ids,
std::vector<std::u16string>& serialized_saved_tab_group_ids,
std::vector<std::u16string>& group_titles,
std::vector<int32_t>& group_colors,
const JavaParamRef<jobjectArray>& jper_tab_optional_tab_group_ids,
const JavaParamRef<jobjectArray>& jtabs_android,
const JavaParamRef<jobjectArray>& jbyte_buffers,
std::vector<int32_t>& saved_state_versions) {
std::vector<std::optional<tab_groups::TabGroupId>> tab_group_ids =
JavaTokensToTabGroupIds(env, jtab_group_ids);
std::vector<std::optional<base::Uuid>> saved_tab_group_ids =
StringsToUuids(serialized_saved_tab_group_ids);
std::vector<std::optional<tab_groups::TabGroupId>>
per_tab_optional_tab_group_ids =
JavaTokensToTabGroupIds(env, jper_tab_optional_tab_group_ids);
int tabs_android_count = env->GetArrayLength(jtabs_android.obj());
DCHECK_EQ(tabs_android_count, env->GetArrayLength(jbyte_buffers.obj()));
DCHECK_EQ(tabs_android_count, static_cast<int>(saved_state_versions.size()));
std::vector<WebContentsStateByteBuffer> web_contents_states =
AllTabsWebContentsStateByteBuffer(env, jbyte_buffers,
std::move(saved_state_versions));
CreateHistoricalBulkClosure(
TabModelList::FindNativeTabModelForJavaObject(jtab_model),
std::move(tab_group_ids), std::move(saved_tab_group_ids),
std::move(group_titles), std::move(group_colors),
std::move(per_tab_optional_tab_group_ids),
TabAndroid::GetAllNativeTabs(
env, base::android::ScopedJavaLocalRef<jobjectArray>(jtabs_android)),
std::move(web_contents_states));
}
} // namespace historical_tab_saver