blob: 25c953d8a51120203e94664f86f5b4d4eea202b0 [file] [log] [blame]
// Copyright 2015 The Cobalt Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <memory>
#include <vector>
#include "cobalt/dom/font_cache.h"
#include "cobalt/dom/global_stats.h"
namespace cobalt {
namespace dom {
namespace {
const int64 kInactiveProcessTimeIntervalMs = 1000;
const int32_t kInactiveFontListPurgeDelayMs = 300000;
const int32_t kInactiveFontPurgeDelayMs = 900000;
const int32_t kTotalFontCountPurgeThreshold = 64;
} // namespace
FontCache::RequestedRemoteTypefaceInfo::RequestedRemoteTypefaceInfo(
const scoped_refptr<loader::font::CachedRemoteTypeface>&
cached_remote_typeface,
const base::Closure& typeface_load_event_callback)
: cached_remote_typeface_reference_(
new loader::font::CachedRemoteTypefaceReferenceWithCallbacks(
cached_remote_typeface, typeface_load_event_callback,
typeface_load_event_callback)),
request_timer_(new base::OneShotTimer()) {
request_timer_->Start(FROM_HERE,
base::TimeDelta::FromMilliseconds(kRequestTimerDelay),
typeface_load_event_callback);
}
FontCache::FontCache(render_tree::ResourceProvider** resource_provider,
loader::font::RemoteTypefaceCache* remote_typeface_cache,
const base::Closure& external_typeface_load_event_callback,
const std::string& language_script,
scoped_refptr<Location> document_location)
: resource_provider_(resource_provider),
remote_typeface_cache_(remote_typeface_cache),
external_typeface_load_event_callback_(
external_typeface_load_event_callback),
language_script_(language_script),
font_face_map_(new FontFaceMap()),
last_inactive_process_time_(base::TimeTicks::Now()),
document_location_(document_location) {}
void FontCache::SetFontFaceMap(std::unique_ptr<FontFaceMap> font_face_map) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
// If nothing has changed, then there's nothing to update. Just return.
if (*font_face_map == *font_face_map_) {
return;
}
// Clear out the cached font lists. It may no longer contain valid font
// mappings as a result of the font face map changing.
font_list_map_.clear();
font_face_map_ = std::move(font_face_map);
// Generate a set of the urls contained within the new font face map.
std::set<GURL> new_url_set;
for (FontFaceMap::iterator map_iterator = font_face_map_->begin();
map_iterator != font_face_map_->end(); ++map_iterator) {
map_iterator->second.CollectUrlSources(&new_url_set);
}
// Iterate through active remote typeface references, verifying that the font
// face map still contains each remote typeface's url. Any remote typeface
// with a url that is no longer contained within |font_face_map_| is purged to
// allow the remote cache to release the memory.
RequestedRemoteTypefaceMap::iterator requested_remote_typeface_iterator =
requested_remote_typeface_cache_.begin();
while (requested_remote_typeface_iterator !=
requested_remote_typeface_cache_.end()) {
RequestedRemoteTypefaceMap::iterator current_iterator =
requested_remote_typeface_iterator++;
// If the referenced url is not in the new url set, then purge it.
if (new_url_set.find(current_iterator->first) == new_url_set.end()) {
requested_remote_typeface_cache_.erase(current_iterator);
}
}
}
void FontCache::PurgeCachedResources() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
requested_remote_typeface_cache_.clear();
// Remove all font lists that are unreferenced outside of the cache and reset
// those that are retained to their initial state.
for (FontListMap::iterator iter = font_list_map_.begin();
iter != font_list_map_.end();) {
FontListInfo& font_list_info = iter->second;
if (font_list_info.font_list->HasOneRef()) {
font_list_map_.erase(iter++);
continue;
}
DLOG(WARNING) << "Unable to purge font list!";
font_list_info.font_list->Reset();
++iter;
}
local_typeface_map_.clear();
font_map_.clear();
inactive_font_set_.clear();
// Walk the character fallback maps, clearing their typefaces.
for (CharacterFallbackTypefaceMaps::iterator iter =
character_fallback_typeface_maps_.begin();
iter != character_fallback_typeface_maps_.end(); ++iter) {
iter->second.clear();
}
}
void FontCache::ProcessInactiveFontListsAndFonts() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
base::TimeTicks current_time = base::TimeTicks::Now();
if ((current_time - last_inactive_process_time_).InMilliseconds() >
kInactiveProcessTimeIntervalMs) {
last_inactive_process_time_ = current_time;
ProcessInactiveFontLists(current_time);
ProcessInactiveFonts(current_time);
}
}
const scoped_refptr<dom::FontList>& FontCache::GetFontList(
const FontListKey& font_list_key) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
FontListInfo& font_list_info = font_list_map_[font_list_key];
if (font_list_info.font_list.get() == NULL) {
font_list_info.font_list = new FontList(this, font_list_key);
}
return font_list_info.font_list;
}
const scoped_refptr<render_tree::Font>& FontCache::GetFontFromTypefaceAndSize(
const scoped_refptr<render_tree::Typeface>& typeface, float size) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
FontKey font_key(typeface->GetId(), size);
// Check to see if the font is already in the cache. If it is not, then
// create it from the typeface and size and add it to the cache.
FontInfo& cached_font_info = font_map_[font_key];
if (cached_font_info.font.get() == NULL) {
cached_font_info.font = typeface->CreateFontWithSize(size);
}
return cached_font_info.font;
}
std::vector<FontFace*> FontCache::GetFacesForFamilyAndStyle(
const std::string& family, render_tree::FontStyle style) {
std::vector<FontFace*> faces;
FontFaceMap::iterator font_face_map_iterator = font_face_map_->find(family);
if (font_face_map_iterator != font_face_map_->end()) {
// Add all font-face entries that match the family.
std::vector<scoped_refptr<FontFaceStyleSet::Entry>> entries =
font_face_map_iterator->second.GetEntriesThatMatchStyle(style);
for (auto entry : entries) {
FontFace* face = new FontFace();
face->entry = entry;
faces.push_back(face);
}
} else {
// This is a local font. One face can represent it.
FontFace* face = new FontFace();
faces.push_back(face);
}
return faces;
}
scoped_refptr<render_tree::Font> FontCache::TryGetFont(
const std::string& family, render_tree::FontStyle style, float size,
FontFace::State* state,
const FontFaceStyleSet::Entry* maybe_style_set_entry) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
int64 request_time_start = base::TimeTicks::Now().ToInternalValue();
if (maybe_style_set_entry) {
// Walk the entry's sources:
// - If a remote source is encountered, always return the results of its
// attempted retrieval, regardless of its success.
// - If a local source is encountered, only return the local font if it is
// successfully retrieved. In the case where the font is not locally
// available, the next font in the source list should be attempted
// instead.
// https://www.w3.org/TR/css3-fonts/#src-desc
for (FontFaceSources::const_iterator source_iterator =
maybe_style_set_entry->sources.begin();
source_iterator != maybe_style_set_entry->sources.end();
++source_iterator) {
if (source_iterator->IsUrlSource()) {
auto font = TryGetRemoteFont(source_iterator->GetUrl(), size, state);
GlobalStats::GetInstance()->OnFontRequestComplete(request_time_start);
return font;
} else {
scoped_refptr<render_tree::Font> font =
TryGetLocalFontByFaceName(source_iterator->GetName(), size, state);
if (font.get() != NULL) {
GlobalStats::GetInstance()->OnFontRequestComplete(request_time_start);
return font;
}
}
}
*state = FontFace::kUnavailableState;
return NULL;
} else {
auto font = TryGetLocalFont(family, style, size, state);
GlobalStats::GetInstance()->OnFontRequestComplete(request_time_start);
return font;
}
}
FontCache::CharacterFallbackTypefaceMap&
FontCache::GetCharacterFallbackTypefaceMap(
const render_tree::FontStyle& style) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
return character_fallback_typeface_maps_[CharacterFallbackKey(style)];
}
const scoped_refptr<render_tree::Typeface>&
FontCache::GetCharacterFallbackTypeface(int32 utf32_character,
const render_tree::FontStyle& style) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(resource_provider());
return GetCachedLocalTypeface(
resource_provider()->GetCharacterFallbackTypeface(utf32_character, style,
language_script_));
}
scoped_refptr<render_tree::GlyphBuffer> FontCache::CreateGlyphBuffer(
const base::char16* text_buffer, int32 text_length, bool is_rtl,
FontList* font_list) {
DCHECK(resource_provider());
return resource_provider()->CreateGlyphBuffer(
text_buffer, static_cast<size_t>(text_length), language_script_, is_rtl,
font_list);
}
float FontCache::GetTextWidth(const base::char16* text_buffer,
int32 text_length, bool is_rtl,
FontList* font_list,
render_tree::FontVector* maybe_used_fonts) {
DCHECK(resource_provider());
return resource_provider()->GetTextWidth(
text_buffer, static_cast<size_t>(text_length), language_script_, is_rtl,
font_list, maybe_used_fonts);
}
void FontCache::ProcessInactiveFontLists(const base::TimeTicks& current_time) {
for (FontListMap::iterator font_list_iterator = font_list_map_.begin();
font_list_iterator != font_list_map_.end();) {
FontListInfo& font_list_info = font_list_iterator->second;
// Any font list that has a single ref is unreferenced outside of the font
// cache and is no longer active.
if (font_list_info.font_list->HasOneRef()) {
// If there is no inactive time set, then the font list has just become
// inactive, so set the time now.
if (font_list_info.inactive_time.is_null()) {
font_list_info.inactive_time = current_time;
// Otherwise, check to see if the purge delay has been reached.
} else if ((current_time - font_list_info.inactive_time)
.InMilliseconds() >= kInactiveFontListPurgeDelayMs) {
font_list_map_.erase(font_list_iterator++);
continue;
}
// Otherwise, if there's an inactive time set on this font list, clear it.
} else if (!font_list_info.inactive_time.is_null()) {
font_list_info.inactive_time = base::TimeTicks();
}
++font_list_iterator;
}
}
void FontCache::ProcessInactiveFonts(const base::TimeTicks& current_time) {
for (FontMap::iterator font_iterator = font_map_.begin();
font_iterator != font_map_.end();) {
FontInfo& font_info = font_iterator->second;
// Any font that has a single ref is unreferenced outside of the font cache
// and is no longer active.
if (font_info.font->HasOneRef()) {
// If there is no inactive time set, then the font has just become
// inactive, so set the time now and insert it into the inactive font set.
if (font_info.inactive_time.is_null()) {
font_info.inactive_time = current_time;
inactive_font_set_.insert(
InactiveFontKey(font_info.inactive_time, font_iterator->first));
}
// Otherwise, if there's an inactive time set on this font, clear it and
// remove the font from the inactive font set.
} else if (!font_info.inactive_time.is_null()) {
inactive_font_set_.erase(
InactiveFontKey(font_info.inactive_time, font_iterator->first));
font_info.inactive_time = base::TimeTicks();
}
++font_iterator;
}
// Continue looping until the total font count drops to the purge threshold or
// there are no more inactive fonts to purge.
while (font_map_.size() > kTotalFontCountPurgeThreshold &&
inactive_font_set_.size() > 0) {
// Grab the first inactive font in the set. They are ordered by the time
// they became inactive, so the first inactive font in the set is the
// oldest.
InactiveFontSet::iterator inactive_font_iterator =
inactive_font_set_.begin();
// Check to see if the oldest inactive font is old enough to be purged. If
// it is not, simply break out. None of the other inactive fonts will be
// purgeable either.
if ((current_time - inactive_font_iterator->inactive_time)
.InMilliseconds() < kInactiveFontPurgeDelayMs) {
break;
}
// The inactive font is old enough to be purged. Remove it from both the
// font map and the inactive font set.
font_map_.erase(inactive_font_iterator->font_key);
inactive_font_set_.erase(inactive_font_iterator);
}
}
const scoped_refptr<render_tree::Typeface>& FontCache::GetCachedLocalTypeface(
const scoped_refptr<render_tree::Typeface>& typeface) {
DCHECK(typeface);
// Check to see if a typeface with a matching id is already in the cache. If
// it is not, then add the passed in typeface to the cache.
scoped_refptr<render_tree::Typeface>& cached_typeface =
local_typeface_map_[typeface->GetId()];
if (cached_typeface.get() == NULL) {
cached_typeface = typeface;
}
return cached_typeface;
}
scoped_refptr<render_tree::Font> FontCache::TryGetRemoteFont(
const GURL& url, float size, FontFace::State* state) {
// Retrieve the font from the remote typeface cache, potentially triggering a
// load.
scoped_refptr<loader::font::CachedRemoteTypeface> cached_remote_typeface =
remote_typeface_cache_->GetOrCreateCachedResource(
url, document_location_ ? document_location_->GetOriginAsObject()
: loader::Origin());
RequestedRemoteTypefaceMap::iterator requested_remote_typeface_iterator =
requested_remote_typeface_cache_.find(url);
// If the requested url is not currently cached, then create a cached
// reference and request timer, providing callbacks for when the load is
// completed or the timer expires.
if (requested_remote_typeface_iterator ==
requested_remote_typeface_cache_.end()) {
DLOG(INFO) << "Requested remote font from " << url;
// Create the remote typeface load event's callback. This callback occurs on
// successful loads, failed loads, and when the request's timer expires.
base::Closure typeface_load_event_callback = base::Bind(
&FontCache::OnRemoteTypefaceLoadEvent, base::Unretained(this), url);
// Insert the newly requested remote typeface's info into the cache, and set
// the iterator from the return value of the map insertion.
requested_remote_typeface_iterator =
requested_remote_typeface_cache_
.insert(RequestedRemoteTypefaceMap::value_type(
url, new RequestedRemoteTypefaceInfo(
cached_remote_typeface, typeface_load_event_callback)))
.first;
}
scoped_refptr<render_tree::Typeface> typeface =
cached_remote_typeface->TryGetResource();
if (typeface.get() != NULL) {
*state = FontFace::kLoadedState;
return GetFontFromTypefaceAndSize(typeface, size);
} else {
if (cached_remote_typeface->IsLoadingComplete()) {
*state = FontFace::kUnavailableState;
} else if (requested_remote_typeface_iterator->second
->HasActiveRequestTimer()) {
*state = FontFace::kLoadingWithTimerActiveState;
} else {
*state = FontFace::kLoadingWithTimerExpiredState;
}
return NULL;
}
}
scoped_refptr<render_tree::Font> FontCache::TryGetLocalFont(
const std::string& family, render_tree::FontStyle style, float size,
FontFace::State* state) {
DCHECK(resource_provider());
DCHECK(resource_provider() != NULL);
// Only request the local font from the resource provider if the family is
// empty or the resource provider actually has the family. The reason for this
// is that the resource provider's |GetLocalTypeface()| is guaranteed to
// return a non-NULL value, and in the case where a family is not supported,
// the subsequent fonts in the font list need to be attempted. An empty family
// signifies using the default font.
if (!family.empty() &&
!resource_provider()->HasLocalFontFamily(family.c_str())) {
*state = FontFace::kUnavailableState;
return NULL;
} else {
*state = FontFace::kLoadedState;
return GetFontFromTypefaceAndSize(
GetCachedLocalTypeface(
resource_provider()->GetLocalTypeface(family.c_str(), style)),
size);
}
}
scoped_refptr<render_tree::Font> FontCache::TryGetLocalFontByFaceName(
const std::string& font_face, float size, FontFace::State* state) {
do {
if (font_face.empty()) {
break;
}
const scoped_refptr<render_tree::Typeface>& typeface(
resource_provider()->GetLocalTypefaceByFaceNameIfAvailable(
font_face.c_str()));
if (!typeface) {
break;
}
const scoped_refptr<render_tree::Typeface>& typeface_cached(
GetCachedLocalTypeface(typeface));
*state = FontFace::kLoadedState;
return GetFontFromTypefaceAndSize(typeface_cached, size);
} while (false);
*state = FontFace::kUnavailableState;
return NULL;
}
void FontCache::OnRemoteTypefaceLoadEvent(const GURL& url) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
RequestedRemoteTypefaceMap::iterator requested_remote_typeface_iterator =
requested_remote_typeface_cache_.find(url);
if (requested_remote_typeface_iterator !=
requested_remote_typeface_cache_.end()) {
// NOTE: We can potentially track the exact font list fonts that are
// impacted by each load event and only reset them. However, as a result of
// the minimal amount of processing required to update the loading status of
// a font, the small number of fonts involved, and the fact that this is an
// infrequent event, adding this additional layer of tracking complexity
// doesn't appear to offer any meaningful benefits.
for (FontListMap::iterator font_list_iterator = font_list_map_.begin();
font_list_iterator != font_list_map_.end(); ++font_list_iterator) {
FontListInfo& font_list_info = font_list_iterator->second;
font_list_info.font_list->ResetLoadingFonts();
}
// Clear the request timer. It only runs until the first load event occurs.
requested_remote_typeface_iterator->second->ClearRequestTimer();
external_typeface_load_event_callback_.Run();
}
}
} // namespace dom
} // namespace cobalt