// 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/font_prewarmer_tab_helper.h"
#include <optional>
#include <set>
#include <string>
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/values.h"
#include "build/build_config.h"
#include "chrome/browser/history/history_service_factory.h"
#include "chrome/browser/history_clusters/history_clusters_tab_helper.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/search_engines/template_url_service_factory.h"
#include "chrome/common/font_prewarmer.mojom.h"
#include "components/history/content/browser/history_context_helper.h"
#include "components/history/core/browser/history_constants.h"
#include "components/history/core/browser/history_service.h"
#include "components/no_state_prefetch/browser/no_state_prefetch_manager.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "components/prefs/pref_service.h"
#include "components/search_engines/template_url_service.h"
#include "content/public/browser/child_process_host.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_process_host_observer.h"
#include "content/public/browser/web_contents.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
namespace {
const char kSearchResultsPageFontsPref[] =
"cached_fonts.search_results_page.fonts";
// Key used to associate FontPrewarmerProfileState with BrowserContext.
const void* const kUserDataKey = &kUserDataKey;
// Returns the font names previously stored to the specified key.
std::vector<std::string> GetFontNamesFromPrefsForKey(Profile* profile,
const char* pref_name) {
const base::Value::List& font_name_list =
profile->GetPrefs()->GetList(pref_name);
if (font_name_list.empty())
return {};
std::vector<std::string> font_names;
for (const auto& font_name_value : font_name_list) {
if (const std::string* font_name = font_name_value.GetIfString())
font_names.push_back(*font_name);
}
return font_names;
}
// Saves font names to prefs.
void SaveFontNamesToPref(Profile* profile,
const char* pref_name,
const std::vector<std::string>& font_family_names) {
base::Value::List font_family_names_values;
for (auto& name : font_family_names)
font_family_names_values.Append(name);
profile->GetPrefs()->SetList(pref_name, std::move(font_family_names_values));
}
// FontPrewarmerCoordinator is responsible for coordinating with the renderer
// to request the fonts used by a page as well as prewarm the last set of fonts
// used. There is one FontPrewarmerCoordinator per Profile.
class FontPrewarmerCoordinator : public base::SupportsUserData::Data,
public content::RenderProcessHostObserver {
public:
using RemoteFontPrewarmer = mojo::Remote<chrome::mojom::FontPrewarmer>;
explicit FontPrewarmerCoordinator(Profile* profile) : profile_(profile) {}
FontPrewarmerCoordinator(const FontPrewarmerCoordinator&) = delete;
FontPrewarmerCoordinator& operator=(const FontPrewarmerCoordinator&) = delete;
~FontPrewarmerCoordinator() override {
for (content::RenderProcessHost* rph : prewarmed_hosts_)
rph->RemoveObserver(this);
}
static FontPrewarmerCoordinator& ForProfile(Profile* profile) {
FontPrewarmerCoordinator* instance = static_cast<FontPrewarmerCoordinator*>(
profile->GetUserData(kUserDataKey));
if (!instance) {
profile->SetUserData(kUserDataKey,
std::make_unique<FontPrewarmerCoordinator>(profile));
instance = static_cast<FontPrewarmerCoordinator*>(
profile->GetUserData(kUserDataKey));
}
return *instance;
}
// Requests the renderer to prewarm the last set of fonts used for displaying
// a search page. Prewarming is done at most once per RenderProcessHost.
void SendFontsToPrewarm(content::RenderProcessHost* rph) {
// Only need to prewarm a particular host once.
if (prewarmed_hosts_.count(rph))
return;
// The following code may early out. Insert the entry to ensure an early out
// doesn't attempt to send the fonts again.
prewarmed_hosts_.insert(rph);
rph->AddObserver(this);
std::vector<std::string> font_names =
GetFontNamesFromPrefsForKey(profile_, kSearchResultsPageFontsPref);
if (font_names.empty()) {
return;
}
RemoteFontPrewarmer remote_font_prewarmer;
rph->BindReceiver(remote_font_prewarmer.BindNewPipeAndPassReceiver());
remote_font_prewarmer->PrewarmFonts(std::move(font_names));
}
// Requests the set of fonts needed to display a search page from `rfh`.
void RequestFonts(content::RenderFrameHost* rfh) {
mojo::AssociatedRemote<chrome::mojom::RenderFrameFontFamilyAccessor>
font_family_accessor;
rfh->GetRemoteAssociatedInterfaces()->GetInterface(&font_family_accessor);
auto* font_family_accessor_raw = font_family_accessor.get();
// Pass ownership of the remote to the callback as otherwise the callback
// will never be run (because the mojo connection was destroyed).
font_family_accessor_raw->GetFontFamilyNames(base::BindOnce(
&FontPrewarmerCoordinator::OnGotFontsForFrame,
weak_factory_.GetWeakPtr(), std::move(font_family_accessor)));
}
private:
void OnGotFontsForFrame(
mojo::AssociatedRemote<chrome::mojom::RenderFrameFontFamilyAccessor>
font_family_accessor,
const std::vector<std::string>& font_names) {
// TODO(sky): add some metrics here so that we know how often the
// fonts change.
SaveFontNamesToPref(profile_, kSearchResultsPageFontsPref, font_names);
}
// content::RenderProcessHostObserver:
void RenderProcessHostDestroyed(content::RenderProcessHost* host) override {
host->RemoveObserver(this);
prewarmed_hosts_.erase(host);
}
raw_ptr<Profile> profile_;
// Set of hosts that were requested to be prewarmed.
std::set<raw_ptr<content::RenderProcessHost, SetExperimental>>
prewarmed_hosts_;
base::WeakPtrFactory<FontPrewarmerCoordinator> weak_factory_{this};
};
} // namespace
// static
void FontPrewarmerTabHelper::RegisterProfilePrefs(
user_prefs::PrefRegistrySyncable* registry) {
registry->RegisterListPref(kSearchResultsPageFontsPref);
}
FontPrewarmerTabHelper::~FontPrewarmerTabHelper() = default;
FontPrewarmerTabHelper::FontPrewarmerTabHelper(
content::WebContents* web_contents)
: content::WebContentsObserver(web_contents),
content::WebContentsUserData<FontPrewarmerTabHelper>(*web_contents) {}
// static
std::string FontPrewarmerTabHelper::GetSearchResultsPageFontsPref() {
return kSearchResultsPageFontsPref;
}
// static
std::vector<std::string> FontPrewarmerTabHelper::GetFontNames(
Profile* profile) {
return GetFontNamesFromPrefsForKey(profile, kSearchResultsPageFontsPref);
}
Profile* FontPrewarmerTabHelper::GetProfile() {
return Profile::FromBrowserContext(web_contents()->GetBrowserContext());
}
bool FontPrewarmerTabHelper::IsSearchResultsPageNavigation(
content::NavigationHandle* navigation_handle) {
if (!navigation_handle->IsInPrimaryMainFrame())
return false;
TemplateURLService* template_url_service =
TemplateURLServiceFactory::GetForProfile(GetProfile());
return template_url_service &&
template_url_service->IsSearchResultsPageFromDefaultSearchProvider(
navigation_handle->GetURL());
}
void FontPrewarmerTabHelper::DidStartNavigation(
content::NavigationHandle* navigation_handle) {
if (!IsSearchResultsPageNavigation(navigation_handle))
return;
const int expected_render_process_host_id =
navigation_handle->GetExpectedRenderProcessHostId();
if (expected_render_process_host_id ==
content::ChildProcessHost::kInvalidUniqueID) {
expected_render_process_host_id_.reset();
} else {
expected_render_process_host_id_ = expected_render_process_host_id;
content::RenderProcessHost* rph =
content::RenderProcessHost::FromID(expected_render_process_host_id);
DCHECK(rph);
FontPrewarmerCoordinator::ForProfile(GetProfile()).SendFontsToPrewarm(rph);
}
}
void FontPrewarmerTabHelper::ReadyToCommitNavigation(
content::NavigationHandle* navigation_handle) {
if (!IsSearchResultsPageNavigation(navigation_handle))
return;
content::RenderFrameHost* rfh = navigation_handle->GetRenderFrameHost();
DCHECK(rfh);
FontPrewarmerCoordinator& coordinator =
FontPrewarmerCoordinator::ForProfile(GetProfile());
if (expected_render_process_host_id_ != rfh->GetProcess()->GetID())
coordinator.SendFontsToPrewarm(rfh->GetProcess());
coordinator.RequestFonts(rfh);
}
WEB_CONTENTS_USER_DATA_KEY_IMPL(FontPrewarmerTabHelper);