| // 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 |