blob: d8ffca5f9f2146f7d5740968932baa2eb71f30e6 [file] [log] [blame]
// Copyright 2021 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/autocomplete/tab_matcher_android.h"
#include "base/feature_list.h"
#include "base/memory/raw_ptr.h"
#include "base/metrics/histogram_macros.h"
#include "chrome/browser/android/tab_android_user_data.h"
#include "chrome/browser/flags/android/chrome_session_state.h"
#include "chrome/browser/ui/android/tab_model/tab_model.h"
#include "chrome/browser/ui/android/tab_model/tab_model_jni_bridge.h"
#include "chrome/browser/ui/android/tab_model/tab_model_list.h"
#include "components/omnibox/browser/autocomplete_input.h"
#include "components/omnibox/browser/autocomplete_match.h"
#include "components/omnibox/browser/tab_matcher.h"
#include "components/omnibox/common/omnibox_features.h"
#include "components/search_engines/template_url_service.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_user_data.h"
// Must come after all headers that specialize FromJniType() / ToJniType().
#include "chrome/browser/ui/android/omnibox/jni_headers/ChromeAutocompleteProviderClient_jni.h"
namespace {
class AutocompleteClientTabAndroidUserData
: public TabAndroidUserData<AutocompleteClientTabAndroidUserData>,
public TabAndroid::Observer {
public:
~AutocompleteClientTabAndroidUserData() override {
tab_->RemoveObserver(this);
}
const GURL& GetStrippedURL() const { return stripped_url_; }
bool IsInitialized() const { return initialized_; }
void UpdateStrippedURL(const GURL& url,
const TemplateURLService* template_url_service,
const bool keep_search_intent_params) {
initialized_ = true;
if (url.is_valid()) {
// Use a blank input as the stripped URL will be reused with other inputs.
// Also keep the search intent params. Otherwise, this can result in over
// triggering of the Switch to Tab action on plain-text suggestions for
// open entity SRPs, or vice versa, on entity suggestions for open
// plain-text SRPs.
stripped_url_ = AutocompleteMatch::GURLToStrippedGURL(
url, AutocompleteInput(), template_url_service, std::u16string(),
keep_search_intent_params);
}
}
// TabAndroid::Observer implementation
void OnInitWebContents(TabAndroid* tab) override {
tab->RemoveUserData(UserDataKey());
}
private:
explicit AutocompleteClientTabAndroidUserData(TabAndroid* tab) : tab_(tab) {
DCHECK(tab);
tab->AddObserver(this);
}
friend class TabAndroidUserData<AutocompleteClientTabAndroidUserData>;
raw_ptr<TabAndroid> tab_;
bool initialized_ = false;
GURL stripped_url_;
TAB_ANDROID_USER_DATA_KEY_DECL();
};
TAB_ANDROID_USER_DATA_KEY_IMPL(AutocompleteClientTabAndroidUserData)
} // namespace
bool TabMatcherAndroid::IsTabOpenWithURL(const GURL& url,
const AutocompleteInput* input) const {
DCHECK(input);
const AutocompleteInput empty_input;
if (!input)
input = &empty_input;
// Use a blank input as the stripped URL will be reused with other inputs.
// Also keep the search intent params. Otherwise, this can result in over
// triggering of the Switch to Tab action on plain-text suggestions for
// open entity SRPs, or vice versa, on entity suggestions for open plain-text
// SRPs.
const bool keep_search_intent_params = base::FeatureList::IsEnabled(
omnibox::kDisambiguateTabMatchingForEntitySuggestions);
const GURL stripped_url = AutocompleteMatch::GURLToStrippedGURL(
url, *input, template_url_service_, std::u16string(),
keep_search_intent_params);
const auto all_tabs =
GetAllHiddenAndNonCCTTabInfos(input, keep_search_intent_params);
return all_tabs.find(stripped_url) != all_tabs.end();
}
void TabMatcherAndroid::FindMatchingTabs(GURLToTabInfoMap* map,
const AutocompleteInput* input) const {
DCHECK(map);
DCHECK(input);
const AutocompleteInput empty_input;
if (!input)
input = &empty_input;
const bool keep_search_intent_params = base::FeatureList::IsEnabled(
omnibox::kDisambiguateTabMatchingForEntitySuggestions);
auto all_tabs =
GetAllHiddenAndNonCCTTabInfos(input, keep_search_intent_params);
for (auto& gurl_to_tab_info : *map) {
const GURL stripped_url = AutocompleteMatch::GURLToStrippedGURL(
gurl_to_tab_info.first, *input, template_url_service_, std::u16string(),
keep_search_intent_params);
auto found_tab = all_tabs.find(stripped_url);
if (found_tab != all_tabs.end()) {
gurl_to_tab_info.second = found_tab->second;
}
}
}
std::vector<TabMatcher::TabWrapper> TabMatcherAndroid::GetOpenTabs(
const AutocompleteInput* input,
bool unused_exclude_active_tab) const {
std::vector<TabMatcher::TabWrapper> open_tabs;
for (auto& open_tab : GetOpenAndroidTabs(input)) {
open_tabs.emplace_back(open_tab->GetTitle(), open_tab->GetURL(),
open_tab->GetLastShownTimestamp());
}
return open_tabs;
}
std::vector<raw_ptr<TabAndroid, VectorExperimental>>
TabMatcherAndroid::GetOpenAndroidTabs(const AutocompleteInput* input) const {
using chrome::android::ActivityType;
// Collect tab models that host tabs eligible for SwitchToTab.
// Ignore:
// - tab models for not matching profile (eg. incognito vs non-incognito)
// - custom and trusted tabs.
std::vector<TabModel*> tab_models;
for (TabModel* model : TabModelList::models()) {
if (profile_ != model->GetProfile())
continue;
auto type = model->activity_type();
if (type == ActivityType::kCustomTab ||
type == ActivityType::kTrustedWebActivity) {
continue;
}
tab_models.push_back(model);
}
CHECK(input);
if (input->current_page_classification() ==
metrics::OmniboxEventProto_PageClassification_ANDROID_HUB &&
profile_->IsRegularProfile()) {
TabModel* archived_tab_model = TabModelList::GetArchivedTabModel();
if (archived_tab_model) {
tab_models.push_back(archived_tab_model);
}
}
// Short circuit in the event we have no tab models hosting eligible tabs.
if (tab_models.size() == 0)
return std::vector<raw_ptr<TabAndroid, VectorExperimental>>();
// Create and populate an array of Java TabModels.
// The most expensive series of calls that reach to Java for every single tab
// at least once start here and span until the end of this method.
JNIEnv* env = base::android::AttachCurrentThread();
jclass tab_model_clazz = TabModelJniBridge::GetClazz(env);
auto j_tab_model_array =
base::android::ScopedJavaLocalRef<jobjectArray>::Adopt(
env,
env->NewObjectArray(tab_models.size(), tab_model_clazz, nullptr));
// Get all the hidden and non CCT tabs. Filter the tabs in CCT tabmodel first.
for (size_t i = 0; i < tab_models.size(); ++i) {
env->SetObjectArrayElement(j_tab_model_array.obj(), i,
tab_models[i]->GetJavaObject().obj());
}
// Retrieve all Tabs associated with previously built TabModels array.
base::android::ScopedJavaLocalRef<jobjectArray> j_tabs =
Java_ChromeAutocompleteProviderClient_getAllEligibleTabs(
env, j_tab_model_array, input->current_page_classification());
if (j_tabs.is_null())
return std::vector<raw_ptr<TabAndroid, VectorExperimental>>();
return TabAndroid::GetAllNativeTabs(env, j_tabs);
}
TabMatcher::GURLToTabInfoMap TabMatcherAndroid::GetAllHiddenAndNonCCTTabInfos(
const AutocompleteInput* input,
const bool keep_search_intent_params) const {
using chrome::android::ActivityType;
GURLToTabInfoMap tab_infos;
JNIEnv* env = base::android::AttachCurrentThread();
for (TabAndroid* tab : GetOpenAndroidTabs(input)) {
// Browser did not load the tab yet after Chrome started. To avoid
// reloading WebContents, we just compare URLs.
AutocompleteClientTabAndroidUserData::CreateForTabAndroid(tab);
AutocompleteClientTabAndroidUserData* user_data =
AutocompleteClientTabAndroidUserData::FromTabAndroid(tab);
DCHECK(user_data);
if (!user_data->IsInitialized()) {
user_data->UpdateStrippedURL(tab->GetURL(), template_url_service_,
keep_search_intent_params);
}
const GURL& tab_stripped_url = user_data->GetStrippedURL();
TabInfo info;
info.has_matching_tab = true;
info.android_tab = JavaObjectWeakGlobalRef(env, tab->GetJavaObject());
tab_infos[tab_stripped_url] = info;
}
return tab_infos;
}