blob: 37581a24677ff047e2368a7a91045ccf36ebc2fc [file] [log] [blame]
// Copyright 2013 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/recently_closed_tabs_bridge.h"
#include "base/android/jni_array.h"
#include "base/android/jni_string.h"
#include "base/containers/span.h"
#include "chrome/android/chrome_jni_headers/RecentlyClosedBridge_jni.h"
#include "chrome/browser/android/tab_android.h"
#include "chrome/browser/flags/android/chrome_feature_list.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_android.h"
#include "chrome/browser/sessions/session_restore.h"
#include "chrome/browser/sessions/tab_restore_service_factory.h"
#include "chrome/browser/ui/android/tab_model/android_live_tab_context.h"
#include "chrome/browser/ui/android/tab_model/tab_model.h"
#include "chrome/browser/ui/android/tab_model/tab_model_list.h"
#include "components/sessions/core/live_tab.h"
#include "components/sessions/core/tab_restore_service.h"
#include "content/public/browser/web_contents.h"
#include "url/android/gurl_android.h"
using base::android::AttachCurrentThread;
using base::android::ConvertUTF16ToJavaString;
using base::android::ConvertUTF8ToJavaString;
using base::android::JavaParamRef;
using base::android::JavaRef;
using base::android::ScopedJavaGlobalRef;
using base::android::ScopedJavaLocalRef;
using base::android::ToJavaArrayOfStrings;
using base::android::ToJavaIntArray;
using base::android::ToJavaLongArray;
namespace recent_tabs {
namespace {
bool TabEntryWithIdExists(const sessions::TabRestoreService::Entries& entries,
SessionID session_id) {
const auto end = TabIterator::end(entries);
for (auto it = TabIterator::begin(entries); it != end; ++it) {
if (it->id == session_id) {
return true;
}
}
return false;
}
// Helpers for GetRecentlyClosedEntries:
void PrepareTabs(
JNIEnv* env,
TabIterator& it,
const sessions::TabRestoreService::Entries::const_iterator& current_entry,
std::vector<int>& ids,
std::vector<int64_t>& timestamps,
std::vector<std::u16string>& titles,
std::vector<ScopedJavaLocalRef<jobject>>& urls,
std::vector<std::string>& group_ids) {
while (it.CurrentEntry() == current_entry) {
const sessions::TabRestoreService::Tab& tab = *it;
const sessions::SerializedNavigationEntry& current_navigation =
tab.navigations.at(tab.current_navigation_index);
ids.push_back(tab.id.id());
timestamps.push_back(tab.timestamp.ToJavaTime());
titles.push_back(current_navigation.title());
urls.push_back(url::GURLAndroid::FromNativeGURL(
env, current_navigation.virtual_url()));
group_ids.push_back(tab.group ? tab.group->ToString() : "");
++it;
}
}
// Add a tab entry to the main entries list.
void JNI_RecentlyClosedBridge_AddTabToEntries(
JNIEnv* env,
const sessions::TabRestoreService::Tab& tab,
const JavaRef<jobject>& jentries) {
const sessions::SerializedNavigationEntry& current_navigation =
tab.navigations.at(tab.current_navigation_index);
Java_RecentlyClosedBridge_addTabToEntries(
env, jentries, tab.id.id(), tab.timestamp.ToJavaTime(),
ConvertUTF16ToJavaString(env, current_navigation.title()),
url::GURLAndroid::FromNativeGURL(env, current_navigation.virtual_url()),
tab.group ? ConvertUTF8ToJavaString(env, tab.group->ToString())
: nullptr);
}
void JNI_RecentlyClosedBridge_AddGroupToEntries(
JNIEnv* env,
TabIterator& it,
const sessions::TabRestoreService::Entries::const_iterator& current_entry,
const sessions::TabRestoreService::Group& group,
const JavaRef<jobject>& jentries) {
std::vector<int> ids;
std::vector<int64_t> timestamps;
std::vector<std::u16string> titles;
std::vector<ScopedJavaLocalRef<jobject>> urls;
std::vector<std::string> group_ids;
const size_t tab_count = group.tabs.size();
ids.reserve(tab_count);
timestamps.reserve(tab_count);
titles.reserve(tab_count);
urls.reserve(tab_count);
group_ids.reserve(tab_count);
PrepareTabs(env, it, current_entry, ids, timestamps, titles, urls, group_ids);
Java_RecentlyClosedBridge_addGroupToEntries(
env, jentries, group.id.id(), group.timestamp.ToJavaTime(),
ConvertUTF16ToJavaString(env, group.visual_data.title()),
ToJavaIntArray(env, ids), ToJavaLongArray(env, timestamps),
ToJavaArrayOfStrings(env, titles),
url::GURLAndroid::ToJavaArrayOfGURLs(env, urls),
ToJavaArrayOfStrings(env, group_ids));
}
void JNI_RecentlyClosedBridge_AddBulkEventToEntries(
JNIEnv* env,
TabIterator& it,
const sessions::TabRestoreService::Entries::const_iterator& current_entry,
const sessions::TabRestoreService::Window& window,
const JavaRef<jobject>& jentries) {
std::vector<int> ids;
std::vector<int64_t> timestamps;
std::vector<std::u16string> titles;
std::vector<ScopedJavaLocalRef<jobject>> urls;
std::vector<std::string> per_tab_group_ids;
const size_t tab_count = window.tabs.size();
ids.reserve(tab_count);
timestamps.reserve(tab_count);
titles.reserve(tab_count);
urls.reserve(tab_count);
per_tab_group_ids.reserve(tab_count);
PrepareTabs(env, it, current_entry, ids, timestamps, titles, urls,
per_tab_group_ids);
std::vector<std::string> group_ids;
std::vector<std::u16string> group_titles;
const size_t group_count = window.tab_groups.size();
group_ids.reserve(group_count);
group_titles.reserve(group_count);
for (const auto& tab_group : window.tab_groups) {
group_ids.push_back(tab_group.first.ToString());
group_titles.push_back(tab_group.second.title());
}
Java_RecentlyClosedBridge_addBulkEventToEntries(
env, jentries, window.id.id(), window.timestamp.ToJavaTime(),
ToJavaArrayOfStrings(env, group_ids),
ToJavaArrayOfStrings(env, group_titles), ToJavaIntArray(env, ids),
ToJavaLongArray(env, timestamps), ToJavaArrayOfStrings(env, titles),
url::GURLAndroid::ToJavaArrayOfGURLs(env, urls),
ToJavaArrayOfStrings(env, per_tab_group_ids));
}
// Add `entries` to `jentries`.
void JNI_RecentlyClosedBridge_AddEntriesToList(
JNIEnv* env,
const sessions::TabRestoreService::Entries& entries,
const JavaRef<jobject>& jentries,
int max_entry_count) {
int added_count = 0;
for (auto it = TabIterator::begin(entries);
it != TabIterator::end(entries) && added_count < max_entry_count;
++added_count) {
if (it.IsCurrentEntryTab()) {
JNI_RecentlyClosedBridge_AddTabToEntries(env, *it, jentries);
++it;
continue;
}
auto entry = it.CurrentEntry();
if ((*entry)->type == sessions::TabRestoreService::GROUP) {
const auto& group =
static_cast<const sessions::TabRestoreService::Group&>(**entry);
JNI_RecentlyClosedBridge_AddGroupToEntries(env, it, entry, group,
jentries);
continue;
}
if ((*entry)->type == sessions::TabRestoreService::WINDOW) {
const auto& window =
static_cast<const sessions::TabRestoreService::Window&>(**entry);
JNI_RecentlyClosedBridge_AddBulkEventToEntries(env, it, entry, window,
jentries);
continue;
}
NOTREACHED();
}
}
} // namespace
TabIterator::TabIterator(
const sessions::TabRestoreService::Entries& entries,
sessions::TabRestoreService::Entries::const_iterator it)
: entries_(entries), current_entry_(it) {
SetupInnerTabList();
}
TabIterator::~TabIterator() = default;
// static.
TabIterator TabIterator::begin(
const sessions::TabRestoreService::Entries& entries) {
return TabIterator(entries, entries.cbegin());
}
// static.
TabIterator TabIterator::end(
const sessions::TabRestoreService::Entries& entries) {
return TabIterator(entries, entries.cend());
}
bool TabIterator::IsCurrentEntryTab() const {
return (*current_entry_)->type == sessions::TabRestoreService::TAB;
}
sessions::TabRestoreService::Entries::const_iterator TabIterator::CurrentEntry()
const {
return current_entry_;
}
TabIterator& TabIterator::operator++() {
// Early out at end.
if (current_entry_ == entries_.cend()) {
return *this;
}
// Iterate backward over current set of tabs if possible.
if (current_tab_ && tabs_ && current_tab_ != tabs_->crend()) {
(*current_tab_)++;
if (*current_tab_ != tabs_->crend()) {
return *this;
}
}
// At the end of an entry then go to the next entry.
tabs_ = nullptr;
current_tab_ = absl::nullopt;
current_entry_++;
if (current_entry_ == entries_.cend()) {
return *this;
}
SetupInnerTabList();
return *this;
}
TabIterator TabIterator::operator++(int) {
TabIterator retval = *this;
++(*this);
return retval;
}
bool TabIterator::operator==(TabIterator other) const {
return current_entry_ == other.current_entry_ &&
current_tab_ == other.current_tab_;
}
bool TabIterator::operator!=(TabIterator other) const {
return !(*this == other);
}
const sessions::TabRestoreService::Tab& TabIterator::operator*() const {
return current_tab_ ? ***current_tab_
: static_cast<const sessions::TabRestoreService::Tab&>(
**current_entry_);
}
const sessions::TabRestoreService::Tab* TabIterator::operator->() const {
return current_tab_ ? (*current_tab_)->get()
: static_cast<const sessions::TabRestoreService::Tab*>(
current_entry_->get());
}
void TabIterator::SetupInnerTabList() {
if (current_entry_ == entries_.cend()) {
return;
}
if ((*current_entry_)->type == sessions::TabRestoreService::GROUP) {
tabs_ = &static_cast<const sessions::TabRestoreService::Group*>(
current_entry_->get())
->tabs;
}
if ((*current_entry_)->type == sessions::TabRestoreService::WINDOW) {
tabs_ = &static_cast<const sessions::TabRestoreService::Window*>(
current_entry_->get())
->tabs;
}
if (tabs_) {
current_tab_ = tabs_->crbegin();
if (current_tab_ == tabs_->crend()) {
++(*this);
}
}
}
RecentlyClosedTabsBridge::RecentlyClosedTabsBridge(
ScopedJavaGlobalRef<jobject> jbridge,
Profile* profile)
: bridge_(std::move(jbridge)),
profile_(profile),
tab_restore_service_(nullptr) {}
RecentlyClosedTabsBridge::~RecentlyClosedTabsBridge() {
if (tab_restore_service_)
tab_restore_service_->RemoveObserver(this);
}
void RecentlyClosedTabsBridge::Destroy(JNIEnv* env) {
delete this;
}
jboolean RecentlyClosedTabsBridge::GetRecentlyClosedEntries(
JNIEnv* env,
const JavaParamRef<jobject>& jentries_list,
jint max_entry_count) {
EnsureTabRestoreService();
if (!tab_restore_service_)
return false;
JNI_RecentlyClosedBridge_AddEntriesToList(
env, tab_restore_service_->entries(), jentries_list, max_entry_count);
return true;
}
jboolean RecentlyClosedTabsBridge::OpenRecentlyClosedTab(
JNIEnv* env,
const JavaParamRef<jobject>& jtab_model,
jint tab_session_id,
jint j_disposition) {
if (!tab_restore_service_)
return false;
SessionID entry_id = SessionID::FromSerializedValue(tab_session_id);
// Ensure the corresponding tab entry from TabRestoreService is a tab.
if (!TabEntryWithIdExists(tab_restore_service_->entries(), entry_id)) {
return false;
}
auto* model = TabModelList::FindNativeTabModelForJavaObject(
ScopedJavaLocalRef<jobject>(env, jtab_model.obj()));
if (model == nullptr) {
return false;
}
AndroidLiveTabContextRestoreWrapper restore_context(model);
std::vector<sessions::LiveTab*> restored_tabs =
tab_restore_service_->RestoreEntryById(
&restore_context, entry_id,
static_cast<WindowOpenDisposition>(j_disposition));
return !restored_tabs.empty();
}
jboolean RecentlyClosedTabsBridge::OpenRecentlyClosedEntry(
JNIEnv* env,
const JavaParamRef<jobject>& jtab_model,
jint entry_session_id) {
// This should only be called when in bulk restore mode otherwise per-tab
// restore should always be used.
if (!tab_restore_service_)
return false;
auto* model = TabModelList::FindNativeTabModelForJavaObject(
ScopedJavaLocalRef<jobject>(env, jtab_model.obj()));
if (model == nullptr) {
return false;
}
AndroidLiveTabContextRestoreWrapper restore_context(model);
std::vector<sessions::LiveTab*> restored_tabs =
tab_restore_service_->RestoreEntryById(
&restore_context, SessionID::FromSerializedValue(entry_session_id),
WindowOpenDisposition::NEW_BACKGROUND_TAB);
RestoreAndroidTabGroups(env, jtab_model, restore_context.GetTabGroups());
return !restored_tabs.empty();
}
jboolean RecentlyClosedTabsBridge::OpenMostRecentlyClosedEntry(
JNIEnv* env,
const JavaParamRef<jobject>& jtab_model) {
EnsureTabRestoreService();
if (!tab_restore_service_ || tab_restore_service_->entries().empty()) {
return false;
}
auto* model = TabModelList::FindNativeTabModelForJavaObject(
ScopedJavaLocalRef<jobject>(env, jtab_model.obj()));
if (model == nullptr) {
return false;
}
AndroidLiveTabContextRestoreWrapper restore_context(model);
std::vector<sessions::LiveTab*> restored_tabs;
// Do not use OpenMostRecentEntry as it uses WindowOpenDisposition::UNKNOWN.
// WindowOpenDisposition::UNKNOWN looks for a desktop window to use (N/A on
// Android) this ends up replacing `restore_context` with the base
// AndroidLiveTabContext. `restore_context` is required to rebuild groups
// information. To avoid this just use the first entry in entries when
// restoring.
restored_tabs = tab_restore_service_->RestoreEntryById(
&restore_context, tab_restore_service_->entries().front()->id,
WindowOpenDisposition::NEW_BACKGROUND_TAB);
RestoreAndroidTabGroups(env, jtab_model, restore_context.GetTabGroups());
return !restored_tabs.empty();
}
void RecentlyClosedTabsBridge::ClearRecentlyClosedEntries(JNIEnv* env) {
EnsureTabRestoreService();
if (tab_restore_service_)
tab_restore_service_->ClearEntries();
}
void RecentlyClosedTabsBridge::TabRestoreServiceChanged(
sessions::TabRestoreService* service) {
Java_RecentlyClosedBridge_onUpdated(AttachCurrentThread(), bridge_);
}
void RecentlyClosedTabsBridge::TabRestoreServiceDestroyed(
sessions::TabRestoreService* service) {
tab_restore_service_ = nullptr;
}
void RecentlyClosedTabsBridge::EnsureTabRestoreService() {
if (tab_restore_service_)
return;
tab_restore_service_ = TabRestoreServiceFactory::GetForProfile(profile_);
// TabRestoreServiceFactory::GetForProfile() can return nullptr (e.g. in
// incognito mode).
if (tab_restore_service_) {
// This does nothing if the tabs have already been loaded or they
// shouldn't be loaded.
tab_restore_service_->LoadTabsFromLastSession();
tab_restore_service_->AddObserver(this);
}
}
void RecentlyClosedTabsBridge::RestoreAndroidTabGroups(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& jtab_model,
const std::map<tab_groups::TabGroupId,
AndroidLiveTabContextRestoreWrapper::TabGroup>& groups) {
for (const auto& group : groups) {
base::span<int const> tab_ids(group.second.tab_ids);
// Ignore single tabs. This can occur if a grouped tab is restored on its
// own.
if (tab_ids.size() < 2U) {
continue;
}
const int group_id = tab_ids[0];
Java_RecentlyClosedBridge_restoreTabGroup(
env, bridge_, jtab_model, group_id,
ConvertUTF16ToJavaString(env, group.second.visual_data.title()),
base::android::ToJavaIntArray(env, tab_ids.subspan(1)));
}
}
static jlong JNI_RecentlyClosedBridge_Init(
JNIEnv* env,
const JavaParamRef<jobject>& jbridge,
const JavaParamRef<jobject>& jprofile) {
RecentlyClosedTabsBridge* bridge = new RecentlyClosedTabsBridge(
ScopedJavaGlobalRef<jobject>(env, jbridge.obj()),
ProfileAndroid::FromProfileAndroid(jprofile));
return reinterpret_cast<intptr_t>(bridge);
}
} // namespace recent_tabs