blob: 3d2e77746b5fb78038c931c12b33b2cb63d43a94 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "core/css/RemoteFontFaceSource.h"
#include "core/css/CSSCustomFontData.h"
#include "core/css/CSSFontFace.h"
#include "core/css/CSSFontSelector.h"
#include "core/dom/Document.h"
#include "core/fetch/ResourceFetcher.h"
#include "core/inspector/ConsoleMessage.h"
#include "core/loader/FrameLoaderClient.h"
#include "core/page/NetworkStateNotifier.h"
#include "platform/Histogram.h"
#include "platform/RuntimeEnabledFeatures.h"
#include "platform/fonts/FontCache.h"
#include "platform/fonts/FontDescription.h"
#include "platform/fonts/SimpleFontData.h"
#include "platform/network/ResourceLoadPriority.h"
#include "public/platform/WebEffectiveConnectionType.h"
#include "wtf/CurrentTime.h"
namespace blink {
namespace {
bool isEffectiveConnectionTypeSlowFor(Document* document) {
WebEffectiveConnectionType type =
document->frame()->loader().client()->getEffectiveConnectionType();
WebEffectiveConnectionType thresholdType =
WebEffectiveConnectionType::TypeUnknown;
if (RuntimeEnabledFeatures::webFontsInterventionV2With2GEnabled()) {
thresholdType = WebEffectiveConnectionType::Type2G;
} else if (RuntimeEnabledFeatures::webFontsInterventionV2With3GEnabled()) {
thresholdType = WebEffectiveConnectionType::Type3G;
} else if (RuntimeEnabledFeatures::
webFontsInterventionV2WithSlow2GEnabled()) {
thresholdType = WebEffectiveConnectionType::TypeSlow2G;
}
DCHECK_NE(WebEffectiveConnectionType::TypeUnknown, thresholdType);
return WebEffectiveConnectionType::TypeOffline <= type &&
type <= thresholdType;
}
bool isConnectionTypeSlow() {
return networkStateNotifier().connectionType() == WebConnectionTypeCellular2G;
}
} // namespace
RemoteFontFaceSource::RemoteFontFaceSource(FontResource* font,
CSSFontSelector* fontSelector,
FontDisplay display)
: m_font(font),
m_fontSelector(fontSelector),
m_display(display),
m_period(display == FontDisplaySwap ? SwapPeriod : BlockPeriod),
m_histograms(font->url().protocolIsData()
? FontLoadHistograms::FromDataURL
: font->isLoaded() ? FontLoadHistograms::FromMemoryCache
: FontLoadHistograms::FromUnknown,
m_display),
m_isInterventionTriggered(false) {
m_font->addClient(this);
if (shouldTriggerWebFontsIntervention()) {
m_isInterventionTriggered = true;
m_period = SwapPeriod;
m_fontSelector->document()->addConsoleMessage(ConsoleMessage::create(
OtherMessageSource, InfoMessageLevel,
"Slow network is detected. Fallback font will be used while loading: " +
m_font->url().elidedString()));
}
}
RemoteFontFaceSource::~RemoteFontFaceSource() {}
void RemoteFontFaceSource::dispose() {
m_font->removeClient(this);
m_font = nullptr;
pruneTable();
}
void RemoteFontFaceSource::pruneTable() {
if (m_fontDataTable.isEmpty())
return;
for (const auto& item : m_fontDataTable) {
SimpleFontData* fontData = item.value.get();
if (fontData && fontData->customFontData())
fontData->customFontData()->clearFontFaceSource();
}
m_fontDataTable.clear();
}
bool RemoteFontFaceSource::isLoading() const {
return m_font->isLoading();
}
bool RemoteFontFaceSource::isLoaded() const {
return m_font->isLoaded();
}
bool RemoteFontFaceSource::isValid() const {
return !m_font->errorOccurred();
}
void RemoteFontFaceSource::notifyFinished(Resource*) {
m_histograms.maySetDataSource(m_font->response().wasCached()
? FontLoadHistograms::FromDiskCache
: FontLoadHistograms::FromNetwork);
m_histograms.recordRemoteFont(m_font.get(), m_isInterventionTriggered);
m_histograms.fontLoaded(m_font->isCORSFailed(),
m_font->getStatus() == Resource::LoadError,
m_isInterventionTriggered);
m_font->ensureCustomFontData();
// FIXME: Provide more useful message such as OTS rejection reason.
// See crbug.com/97467
if (m_font->getStatus() == Resource::DecodeError &&
m_fontSelector->document()) {
m_fontSelector->document()->addConsoleMessage(ConsoleMessage::create(
OtherMessageSource, WarningMessageLevel,
"Failed to decode downloaded font: " + m_font->url().elidedString()));
if (m_font->otsParsingMessage().length() > 1)
m_fontSelector->document()->addConsoleMessage(ConsoleMessage::create(
OtherMessageSource, WarningMessageLevel,
"OTS parsing error: " + m_font->otsParsingMessage()));
}
pruneTable();
if (m_face) {
m_fontSelector->fontFaceInvalidated();
m_face->fontLoaded(this);
}
}
void RemoteFontFaceSource::fontLoadShortLimitExceeded(FontResource*) {
if (m_font->isLoaded())
return;
if (m_display == FontDisplayFallback)
switchToSwapPeriod();
else if (m_display == FontDisplayOptional)
switchToFailurePeriod();
}
void RemoteFontFaceSource::fontLoadLongLimitExceeded(FontResource*) {
if (m_font->isLoaded())
return;
if (m_display == FontDisplayBlock ||
(!m_isInterventionTriggered && m_display == FontDisplayAuto))
switchToSwapPeriod();
else if (m_display == FontDisplayFallback)
switchToFailurePeriod();
m_histograms.longLimitExceeded(m_isInterventionTriggered);
}
void RemoteFontFaceSource::switchToSwapPeriod() {
ASSERT(m_period == BlockPeriod);
m_period = SwapPeriod;
pruneTable();
if (m_face) {
m_fontSelector->fontFaceInvalidated();
m_face->didBecomeVisibleFallback(this);
}
m_histograms.recordFallbackTime(m_font.get());
}
void RemoteFontFaceSource::switchToFailurePeriod() {
if (m_period == BlockPeriod)
switchToSwapPeriod();
ASSERT(m_period == SwapPeriod);
m_period = FailurePeriod;
}
bool RemoteFontFaceSource::shouldTriggerWebFontsIntervention() {
if (RuntimeEnabledFeatures::webFontsInterventionTriggerEnabled())
return true;
if (m_histograms.dataSource() == FontLoadHistograms::FromMemoryCache ||
m_histograms.dataSource() == FontLoadHistograms::FromDataURL)
return false;
bool isV2Enabled =
RuntimeEnabledFeatures::webFontsInterventionV2With2GEnabled() ||
RuntimeEnabledFeatures::webFontsInterventionV2With3GEnabled() ||
RuntimeEnabledFeatures::webFontsInterventionV2WithSlow2GEnabled();
bool networkIsSlow =
isV2Enabled ? isEffectiveConnectionTypeSlowFor(m_fontSelector->document())
: isConnectionTypeSlow();
return networkIsSlow && m_display == FontDisplayAuto;
}
bool RemoteFontFaceSource::isLowPriorityLoadingAllowedForRemoteFont() const {
return m_isInterventionTriggered;
}
PassRefPtr<SimpleFontData> RemoteFontFaceSource::createFontData(
const FontDescription& fontDescription) {
if (!isLoaded())
return createLoadingFallbackFontData(fontDescription);
if (!m_font->ensureCustomFontData() || m_period == FailurePeriod)
return nullptr;
m_histograms.recordFallbackTime(m_font.get());
return SimpleFontData::create(
m_font->platformDataFromCustomData(fontDescription.effectiveFontSize(),
fontDescription.isSyntheticBold(),
fontDescription.isSyntheticItalic(),
fontDescription.orientation(),
fontDescription.variationSettings()),
CustomFontData::create());
}
PassRefPtr<SimpleFontData> RemoteFontFaceSource::createLoadingFallbackFontData(
const FontDescription& fontDescription) {
// This temporary font is not retained and should not be returned.
FontCachePurgePreventer fontCachePurgePreventer;
SimpleFontData* temporaryFont =
FontCache::fontCache()->getNonRetainedLastResortFallbackFont(
fontDescription);
if (!temporaryFont) {
ASSERT_NOT_REACHED();
return nullptr;
}
RefPtr<CSSCustomFontData> cssFontData = CSSCustomFontData::create(
this, m_period == BlockPeriod ? CSSCustomFontData::InvisibleFallback
: CSSCustomFontData::VisibleFallback);
return SimpleFontData::create(temporaryFont->platformData(), cssFontData);
}
void RemoteFontFaceSource::beginLoadIfNeeded() {
if (m_fontSelector->document() && m_font->stillNeedsLoad()) {
if (!m_font->url().protocolIsData() && !m_font->isLoaded() &&
m_display == FontDisplayAuto &&
m_font->isLowPriorityLoadingAllowedForRemoteFont()) {
// Set the loading priority to VeryLow since this font is not required
// for painting the text.
m_font->didChangePriority(ResourceLoadPriorityVeryLow, 0);
}
if (m_fontSelector->document()->fetcher()->startLoad(m_font)) {
// Start timers only when load is actually started asynchronously.
if (!m_font->isLoaded())
m_font->startLoadLimitTimers();
m_histograms.loadStarted();
}
}
if (m_face)
m_face->didBeginLoad();
}
DEFINE_TRACE(RemoteFontFaceSource) {
visitor->trace(m_font);
visitor->trace(m_fontSelector);
CSSFontFaceSource::trace(visitor);
FontResourceClient::trace(visitor);
}
void RemoteFontFaceSource::FontLoadHistograms::loadStarted() {
if (!m_loadStartTime)
m_loadStartTime = currentTimeMS();
}
void RemoteFontFaceSource::FontLoadHistograms::fallbackFontPainted(
DisplayPeriod period) {
if (period == BlockPeriod && !m_blankPaintTime)
m_blankPaintTime = currentTimeMS();
}
void RemoteFontFaceSource::FontLoadHistograms::fontLoaded(
bool isCorsFailed,
bool loadError,
bool isInterventionTriggered) {
if (!m_isLongLimitExceeded && m_fontDisplay == FontDisplayAuto &&
!isCorsFailed && !loadError) {
recordInterventionResult(isInterventionTriggered);
}
}
void RemoteFontFaceSource::FontLoadHistograms::longLimitExceeded(
bool isInterventionTriggered) {
m_isLongLimitExceeded = true;
maySetDataSource(FromNetwork);
if (m_fontDisplay == FontDisplayAuto)
recordInterventionResult(isInterventionTriggered);
}
void RemoteFontFaceSource::FontLoadHistograms::recordFallbackTime(
const FontResource* font) {
if (m_blankPaintTime <= 0)
return;
int duration = static_cast<int>(currentTimeMS() - m_blankPaintTime);
DEFINE_STATIC_LOCAL(CustomCountHistogram, blankTextShownTimeHistogram,
("WebFont.BlankTextShownTime", 0, 10000, 50));
blankTextShownTimeHistogram.count(duration);
m_blankPaintTime = -1;
}
void RemoteFontFaceSource::FontLoadHistograms::recordRemoteFont(
const FontResource* font,
bool isInterventionTriggered) {
DEFINE_STATIC_LOCAL(EnumerationHistogram, cacheHitHistogram,
("WebFont.CacheHit", CacheHitEnumMax));
cacheHitHistogram.count(dataSourceMetricsValue());
if (m_dataSource == FromDiskCache || m_dataSource == FromNetwork) {
DCHECK_NE(m_loadStartTime, 0);
int duration = static_cast<int>(currentTimeMS() - m_loadStartTime);
recordLoadTimeHistogram(font, duration, isInterventionTriggered);
enum { CORSFail, CORSSuccess, CORSEnumMax };
int corsValue = font->isCORSFailed() ? CORSFail : CORSSuccess;
DEFINE_STATIC_LOCAL(EnumerationHistogram, corsHistogram,
("WebFont.CORSSuccess", CORSEnumMax));
corsHistogram.count(corsValue);
}
}
void RemoteFontFaceSource::FontLoadHistograms::maySetDataSource(
DataSource dataSource) {
if (m_dataSource != FromUnknown)
return;
// Classify as memory cache hit if |m_loadStartTime| is not set, i.e.
// this RemoteFontFaceSource instance didn't trigger FontResource
// loading.
if (m_loadStartTime == 0)
m_dataSource = FromMemoryCache;
else
m_dataSource = dataSource;
}
void RemoteFontFaceSource::FontLoadHistograms::recordLoadTimeHistogram(
const FontResource* font,
int duration,
bool isInterventionTriggered) {
CHECK_NE(FromUnknown, m_dataSource);
if (font->errorOccurred()) {
DEFINE_STATIC_LOCAL(CustomCountHistogram, loadErrorHistogram,
("WebFont.DownloadTime.LoadError", 0, 10000, 50));
DEFINE_STATIC_LOCAL(
CustomCountHistogram, missedCacheLoadErrorHistogram,
("WebFont.MissedCache.DownloadTime.LoadError", 0, 10000, 50));
loadErrorHistogram.count(duration);
if (m_dataSource == FromNetwork)
missedCacheLoadErrorHistogram.count(duration);
return;
}
unsigned size = font->encodedSize();
if (size < 10 * 1024) {
DEFINE_STATIC_LOCAL(CustomCountHistogram, under10kHistogram,
("WebFont.DownloadTime.0.Under10KB", 0, 10000, 50));
DEFINE_STATIC_LOCAL(
CustomCountHistogram, missedCacheUnder10kHistogram,
("WebFont.MissedCache.DownloadTime.0.Under10KB", 0, 10000, 50));
under10kHistogram.count(duration);
if (m_dataSource == FromNetwork)
missedCacheUnder10kHistogram.count(duration);
return;
}
if (size < 50 * 1024) {
DEFINE_STATIC_LOCAL(CustomCountHistogram, under50kHistogram,
("WebFont.DownloadTime.1.10KBTo50KB", 0, 10000, 50));
DEFINE_STATIC_LOCAL(
CustomCountHistogram, missedCacheUnder50kHistogram,
("WebFont.MissedCache.DownloadTime.1.10KBTo50KB", 0, 10000, 50));
// Breakdowns metrics to understand WebFonts intervention.
// Now we only cover this 10KBto50KB range because 70% of requests are
// covered in this range, and having metrics for all size cases cost.
DEFINE_STATIC_LOCAL(CustomCountHistogram,
missedCacheAndInterventionTriggeredUnder50kHistogram,
("WebFont.MissedCacheAndInterventionTriggered."
"DownloadTime.1.10KBTo50KB",
0, 10000, 50));
DEFINE_STATIC_LOCAL(CustomCountHistogram,
missedCacheAndInterventionNotTriggeredUnder50kHistogram,
("WebFont.MissedCacheAndInterventionNotTriggered."
"DownloadTime.1.10KBTo50KB",
0, 10000, 50));
under50kHistogram.count(duration);
if (m_dataSource == FromNetwork) {
missedCacheUnder50kHistogram.count(duration);
if (isInterventionTriggered)
missedCacheAndInterventionTriggeredUnder50kHistogram.count(duration);
else
missedCacheAndInterventionNotTriggeredUnder50kHistogram.count(duration);
}
return;
}
if (size < 100 * 1024) {
DEFINE_STATIC_LOCAL(CustomCountHistogram, under100kHistogram,
("WebFont.DownloadTime.2.50KBTo100KB", 0, 10000, 50));
DEFINE_STATIC_LOCAL(
CustomCountHistogram, missedCacheUnder100kHistogram,
("WebFont.MissedCache.DownloadTime.2.50KBTo100KB", 0, 10000, 50));
under100kHistogram.count(duration);
if (m_dataSource == FromNetwork)
missedCacheUnder100kHistogram.count(duration);
return;
}
if (size < 1024 * 1024) {
DEFINE_STATIC_LOCAL(CustomCountHistogram, under1mbHistogram,
("WebFont.DownloadTime.3.100KBTo1MB", 0, 10000, 50));
DEFINE_STATIC_LOCAL(
CustomCountHistogram, missedCacheUnder1mbHistogram,
("WebFont.MissedCache.DownloadTime.3.100KBTo1MB", 0, 10000, 50));
under1mbHistogram.count(duration);
if (m_dataSource == FromNetwork)
missedCacheUnder1mbHistogram.count(duration);
return;
}
DEFINE_STATIC_LOCAL(CustomCountHistogram, over1mbHistogram,
("WebFont.DownloadTime.4.Over1MB", 0, 10000, 50));
DEFINE_STATIC_LOCAL(
CustomCountHistogram, missedCacheOver1mbHistogram,
("WebFont.MissedCache.DownloadTime.4.Over1MB", 0, 10000, 50));
over1mbHistogram.count(duration);
if (m_dataSource == FromNetwork)
missedCacheOver1mbHistogram.count(duration);
}
void RemoteFontFaceSource::FontLoadHistograms::recordInterventionResult(
bool isTriggered) {
CHECK_NE(FromUnknown, m_dataSource);
// interventionResult takes 0-3 values.
int interventionResult = 0;
if (m_isLongLimitExceeded)
interventionResult |= 1 << 0;
if (isTriggered)
interventionResult |= 1 << 1;
const int boundary = 1 << 2;
DEFINE_STATIC_LOCAL(EnumerationHistogram, interventionHistogram,
("WebFont.InterventionResult", boundary));
DEFINE_STATIC_LOCAL(EnumerationHistogram, missedCacheInterventionHistogram,
("WebFont.InterventionResult.MissedCache", boundary));
interventionHistogram.count(interventionResult);
if (m_dataSource == FromNetwork)
missedCacheInterventionHistogram.count(interventionResult);
}
RemoteFontFaceSource::FontLoadHistograms::CacheHitMetrics
RemoteFontFaceSource::FontLoadHistograms::dataSourceMetricsValue() {
switch (m_dataSource) {
case FromDataURL:
return DataUrl;
case FromMemoryCache:
return MemoryHit;
case FromDiskCache:
return DiskHit;
case FromNetwork:
return Miss;
case FromUnknown:
// Fall through.
default:
NOTREACHED();
}
return Miss;
}
} // namespace blink