blob: c94cd0ccc93ff798d75db86edd41dcee7161e353 [file] [log] [blame]
/*
* Copyright (C) 2013 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*/
#include "third_party/blink/renderer/core/css/font_face_set_document.h"
#include "base/metrics/histogram_functions.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/renderer/bindings/core/v8/dictionary.h"
#include "third_party/blink/renderer/core/css/css_font_face.h"
#include "third_party/blink/renderer/core/css/css_font_selector.h"
#include "third_party/blink/renderer/core/css/css_property_value_set.h"
#include "third_party/blink/renderer/core/css/css_segmented_font_face.h"
#include "third_party/blink/renderer/core/css/font_face_cache.h"
#include "third_party/blink/renderer/core/css/font_face_set_load_event.h"
#include "third_party/blink/renderer/core/css/parser/css_parser.h"
#include "third_party/blink/renderer/core/css/properties/css_parsing_utils.h"
#include "third_party/blink/renderer/core/css/resolver/font_style_resolver.h"
#include "third_party/blink/renderer/core/css/resolver/style_resolver.h"
#include "third_party/blink/renderer/core/css/style_engine.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/frame/local_frame_view.h"
#include "third_party/blink/renderer/core/loader/document_loader.h"
#include "third_party/blink/renderer/core/style/computed_style.h"
#include "third_party/blink/renderer/platform/bindings/script_state.h"
#include "third_party/blink/renderer/platform/heap/heap.h"
namespace blink {
// static
const char FontFaceSetDocument::kSupplementName[] = "FontFaceSetDocument";
FontFaceSetDocument::FontFaceSetDocument(Document& document)
: FontFaceSet(*document.GetExecutionContext()),
Supplement<Document>(document),
lcp_limit_timer_(document.GetTaskRunner(TaskType::kInternalLoading),
this,
&FontFaceSetDocument::LCPLimitReached) {}
FontFaceSetDocument::~FontFaceSetDocument() = default;
bool FontFaceSetDocument::InActiveContext() const {
ExecutionContext* context = GetExecutionContext();
return context && To<LocalDOMWindow>(context)->document()->IsActive();
}
AtomicString FontFaceSetDocument::status() const {
DEFINE_STATIC_LOCAL(AtomicString, loading, ("loading"));
DEFINE_STATIC_LOCAL(AtomicString, loaded, ("loaded"));
return is_loading_ ? loading : loaded;
}
void FontFaceSetDocument::DidLayout() {
if (!GetExecutionContext())
return;
if (GetDocument()->GetFrame()->IsMainFrame() && loading_fonts_.IsEmpty())
font_load_histogram_.Record();
if (!ShouldSignalReady())
return;
HandlePendingEventsAndPromisesSoon();
}
void FontFaceSetDocument::StartLCPLimitTimerIfNeeded() {
// Make sure the timer is started at most once for each document, and only
// when the feature is enabled
if (!base::FeatureList::IsEnabled(
features::kAlignFontDisplayAutoTimeoutWithLCPGoal) ||
has_reached_lcp_limit_ || lcp_limit_timer_.IsActive() ||
!GetDocument()->Loader()) {
return;
}
lcp_limit_timer_.StartOneShot(
GetDocument()->Loader()->RemainingTimeToLCPLimit(), FROM_HERE);
}
void FontFaceSetDocument::BeginFontLoading(FontFace* font_face) {
AddToLoadingFonts(font_face);
StartLCPLimitTimerIfNeeded();
}
void FontFaceSetDocument::NotifyLoaded(FontFace* font_face) {
font_load_histogram_.UpdateStatus(font_face);
loaded_fonts_.push_back(font_face);
RemoveFromLoadingFonts(font_face);
}
void FontFaceSetDocument::NotifyError(FontFace* font_face) {
font_load_histogram_.UpdateStatus(font_face);
failed_fonts_.push_back(font_face);
RemoveFromLoadingFonts(font_face);
}
size_t FontFaceSetDocument::ApproximateBlankCharacterCount() const {
size_t count = 0;
for (auto& font_face : loading_fonts_)
count += font_face->ApproximateBlankCharacterCount();
return count;
}
ScriptPromise FontFaceSetDocument::ready(ScriptState* script_state) {
if (ready_->GetState() != ReadyProperty::kPending && InActiveContext()) {
// |ready_| is already resolved, but there may be pending stylesheet
// changes and/or layout operations that may cause another font loads.
// So synchronously update style and layout here.
// This may trigger font loads, and replace |ready_| with a new Promise.
GetDocument()->UpdateStyleAndLayout(DocumentUpdateReason::kJavaScript);
}
return ready_->Promise(script_state->World());
}
const HeapLinkedHashSet<Member<FontFace>>&
FontFaceSetDocument::CSSConnectedFontFaceList() const {
Document* document = this->GetDocument();
document->GetStyleEngine().UpdateActiveStyle();
return GetFontSelector()->GetFontFaceCache()->CssConnectedFontFaces();
}
void FontFaceSetDocument::FireDoneEventIfPossible() {
if (should_fire_loading_event_)
return;
if (!ShouldSignalReady())
return;
Document* d = GetDocument();
if (!d)
return;
// If the layout was invalidated in between when we thought layout
// was updated and when we're ready to fire the event, just wait
// until after the next layout before firing events.
if (!d->View() || d->View()->NeedsLayout())
return;
FireDoneEvent();
}
bool FontFaceSetDocument::ResolveFontStyle(const String& font_string,
Font& font) {
if (font_string.IsEmpty())
return false;
// Interpret fontString in the same way as the 'font' attribute of
// CanvasRenderingContext2D.
auto* parsed_style = CSSParser::ParseFont(font_string, GetExecutionContext());
if (!parsed_style)
return false;
if (!GetDocument()->documentElement()) {
auto* font_selector = GetDocument()->GetStyleEngine().GetFontSelector();
FontDescription description =
FontStyleResolver::ComputeFont(*parsed_style, font_selector);
font = Font(description, font_selector);
return true;
}
scoped_refptr<ComputedStyle> style = ComputedStyle::Create();
FontFamily font_family;
font_family.SetFamily(FontFaceSet::kDefaultFontFamily);
FontDescription default_font_description;
default_font_description.SetFamily(font_family);
default_font_description.SetSpecifiedSize(FontFaceSet::kDefaultFontSize);
default_font_description.SetComputedSize(FontFaceSet::kDefaultFontSize);
style->SetFontDescription(default_font_description);
GetDocument()->GetStyleEngine().ComputeFont(*GetDocument()->documentElement(),
style.get(), *parsed_style);
font = style->GetFont();
// StyleResolver::ComputeFont() should have set the document's FontSelector
// to |style|.
DCHECK_EQ(font.GetFontSelector(), GetFontSelector());
return true;
}
Document* FontFaceSetDocument::GetDocument() const {
if (auto* window = To<LocalDOMWindow>(GetExecutionContext()))
return window->document();
return nullptr;
}
FontFaceSetDocument* FontFaceSetDocument::From(Document& document) {
FontFaceSetDocument* fonts =
Supplement<Document>::From<FontFaceSetDocument>(document);
if (!fonts) {
fonts = MakeGarbageCollected<FontFaceSetDocument>(document);
Supplement<Document>::ProvideTo(document, fonts);
}
return fonts;
}
void FontFaceSetDocument::DidLayout(Document& document) {
if (!document.LoadEventFinished()) {
// https://www.w3.org/TR/2014/WD-css-font-loading-3-20140522/#font-face-set-ready
// doesn't say when document.fonts.ready should actually fire, but the
// existing tests depend on it firing after onload.
return;
}
if (FontFaceSetDocument* fonts =
Supplement<Document>::From<FontFaceSetDocument>(document))
fonts->DidLayout();
}
size_t FontFaceSetDocument::ApproximateBlankCharacterCount(Document& document) {
if (FontFaceSetDocument* fonts =
Supplement<Document>::From<FontFaceSetDocument>(document))
return fonts->ApproximateBlankCharacterCount();
return 0;
}
void FontFaceSetDocument::AlignTimeoutWithLCPGoal(FontFace* font_face) {
bool is_loading = font_face->LoadStatus() == FontFace::kLoading;
bool affected = font_face->CssFontFace()->UpdatePeriod();
// We only count loading font faces, so that unused fonts are excluded. This
// is especially useful when the page uses a font library, where most of the
// fonts are unused.
if (is_loading && font_face->display() == "auto") {
font_display_auto_align_histogram_.SetHasFontDisplayAuto();
if (affected)
font_display_auto_align_histogram_.CountAffected();
}
}
void FontFaceSetDocument::LCPLimitReached(TimerBase*) {
DCHECK(base::FeatureList::IsEnabled(
features::kAlignFontDisplayAutoTimeoutWithLCPGoal));
if (!GetDocument() || !GetDocument()->IsActive())
return;
has_reached_lcp_limit_ = true;
for (FontFace* font_face : CSSConnectedFontFaceList())
AlignTimeoutWithLCPGoal(font_face);
for (FontFace* font_face : non_css_connected_faces_)
AlignTimeoutWithLCPGoal(font_face);
font_display_auto_align_histogram_.Record();
}
void FontFaceSetDocument::Trace(Visitor* visitor) const {
visitor->Trace(lcp_limit_timer_);
Supplement<Document>::Trace(visitor);
FontFaceSet::Trace(visitor);
}
void FontFaceSetDocument::FontLoadHistogram::UpdateStatus(FontFace* font_face) {
if (status_ == kReported)
return;
if (font_face->HadBlankText())
status_ = kHadBlankText;
else if (status_ == kNoWebFonts)
status_ = kDidNotHaveBlankText;
}
void FontFaceSetDocument::FontLoadHistogram::Record() {
if (status_ == kHadBlankText || status_ == kDidNotHaveBlankText) {
base::UmaHistogramBoolean("WebFont.HadBlankText", status_ == kHadBlankText);
status_ = kReported;
}
}
void FontFaceSetDocument::FontDisplayAutoAlignHistogram::Record() {
if (!base::FeatureList::IsEnabled(
features::kAlignFontDisplayAutoTimeoutWithLCPGoal)) {
return;
}
if (!has_font_display_auto_ || reported_)
return;
base::UmaHistogramCounts100(
"WebFont.Clients.AlignFontDisplayAuto.FontFacesAffected",
affected_count_);
reported_ = true;
}
} // namespace blink