910e62b5创建于 1月15日历史提交
// 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.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 GURL stripped_url = AutocompleteMatch::GURLToStrippedGURL(
      url, *input, template_url_service_, /*keyword=*/std::u16string(),
      /*keep_search_intent_params=*/true);
  const auto all_tabs = GetAllHiddenAndNonCCTTabInfos(input);
  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;

  auto all_tabs = GetAllHiddenAndNonCCTTabInfos(input);

  for (auto& gurl_to_tab_info : *map) {
    const GURL stripped_url = AutocompleteMatch::GURLToStrippedGURL(
        gurl_to_tab_info.first, *input, template_url_service_,
        /*keyword=*/std::u16string(),
        /*keep_search_intent_params=*/true);
    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 {
  using chrome::android::ActivityType;
  GURLToTabInfoMap tab_infos;

  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=*/true);
    }

    const GURL& tab_stripped_url = user_data->GetStrippedURL();
    TabInfo info;
    info.has_matching_tab = true;
    info.android_tab_id = tab->GetAndroidId();
    tab_infos[tab_stripped_url] = info;
  }

  return tab_infos;
}

DEFINE_JNI(ChromeAutocompleteProviderClient)