blob: 0f3e03fbfb99b199107e52b36c901662910c8be3 [file] [log] [blame]
/*
* Copyright (C) 2007-2019 Apple 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. ``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
* 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 "config.h"
#include "CSSFontFace.h"
#include "CSSFontFaceSource.h"
#include "CSSFontFaceSrcValue.h"
#include "CSSFontFeatureValue.h"
#include "CSSFontSelector.h"
#include "CSSFontStyleRangeValue.h"
#include "CSSPrimitiveValueMappings.h"
#include "CSSUnicodeRangeValue.h"
#include "CSSValue.h"
#include "CSSValueList.h"
#include "Document.h"
#include "Font.h"
#include "FontCache.h"
#include "FontDescription.h"
#include "FontFace.h"
#include "FontVariantBuilder.h"
#include "Settings.h"
#include "StyleBuilderConverter.h"
#include "StyleProperties.h"
#include "StyleRule.h"
namespace WebCore {
template<typename T> void iterateClients(HashSet<CSSFontFace::Client*>& clients, T callback)
{
Vector<Ref<CSSFontFace::Client>> clientsCopy;
clientsCopy.reserveInitialCapacity(clients.size());
for (auto* client : clients)
clientsCopy.uncheckedAppend(*client);
for (auto* client : clients)
callback(*client);
}
void CSSFontFace::appendSources(CSSFontFace& fontFace, CSSValueList& srcList, Document* document, bool isInitiatingElementInUserAgentShadowTree)
{
for (auto& src : srcList) {
// An item in the list either specifies a string (local font name) or a URL (remote font to download).
CSSFontFaceSrcValue& item = downcast<CSSFontFaceSrcValue>(src.get());
std::unique_ptr<CSSFontFaceSource> source;
SVGFontFaceElement* fontFaceElement = nullptr;
bool foundSVGFont = false;
#if ENABLE(SVG_FONTS)
foundSVGFont = item.isSVGFontFaceSrc() || item.svgFontFaceElement();
fontFaceElement = item.svgFontFaceElement();
#endif
if (!item.isLocal()) {
const Settings* settings = document ? &document->settings() : nullptr;
bool allowDownloading = foundSVGFont || (settings && settings->downloadableBinaryFontsEnabled());
if (allowDownloading && item.isSupportedFormat() && document) {
if (CachedFont* cachedFont = item.cachedFont(document, foundSVGFont, isInitiatingElementInUserAgentShadowTree))
source = std::make_unique<CSSFontFaceSource>(fontFace, item.resource(), cachedFont);
}
} else
source = std::make_unique<CSSFontFaceSource>(fontFace, item.resource(), nullptr, fontFaceElement);
if (source)
fontFace.adoptSource(WTFMove(source));
}
fontFace.sourcesPopulated();
}
CSSFontFace::CSSFontFace(CSSFontSelector* fontSelector, StyleRuleFontFace* cssConnection, FontFace* wrapper, bool isLocalFallback)
: m_fontSelector(fontSelector)
, m_cssConnection(cssConnection)
, m_wrapper(makeWeakPtr(wrapper))
, m_isLocalFallback(isLocalFallback)
, m_mayBePurged(!wrapper)
, m_timeoutTimer(*this, &CSSFontFace::timeoutFired)
{
}
CSSFontFace::~CSSFontFace() = default;
bool CSSFontFace::setFamilies(CSSValue& family)
{
if (!is<CSSValueList>(family))
return false;
CSSValueList& familyList = downcast<CSSValueList>(family);
if (!familyList.length())
return false;
RefPtr<CSSValueList> oldFamilies = m_families;
m_families = &familyList;
if (m_cssConnection)
m_cssConnection->mutableProperties().setProperty(CSSPropertyFontFamily, &family);
iterateClients(m_clients, [&](Client& client) {
client.fontPropertyChanged(*this, oldFamilies.get());
});
return true;
}
static FontSelectionRange calculateWeightRange(CSSValue& value)
{
if (value.isValueList()) {
auto& valueList = downcast<CSSValueList>(value);
ASSERT(valueList.length() == 2);
if (valueList.length() != 2)
return { normalWeightValue(), normalWeightValue() };
ASSERT(valueList.item(0)->isPrimitiveValue());
ASSERT(valueList.item(1)->isPrimitiveValue());
auto& value0 = downcast<CSSPrimitiveValue>(*valueList.item(0));
auto& value1 = downcast<CSSPrimitiveValue>(*valueList.item(1));
auto result0 = StyleBuilderConverter::convertFontWeightFromValue(value0);
auto result1 = StyleBuilderConverter::convertFontWeightFromValue(value1);
return { result0, result1 };
}
ASSERT(is<CSSPrimitiveValue>(value));
auto& primitiveValue = downcast<CSSPrimitiveValue>(value);
FontSelectionValue result = StyleBuilderConverter::convertFontWeightFromValue(primitiveValue);
return { result, result };
}
void CSSFontFace::setWeight(CSSValue& weight)
{
auto range = calculateWeightRange(weight);
if (m_fontSelectionCapabilities.weight == range)
return;
setWeight(range);
if (m_cssConnection)
m_cssConnection->mutableProperties().setProperty(CSSPropertyFontWeight, &weight);
iterateClients(m_clients, [&](Client& client) {
client.fontPropertyChanged(*this);
});
}
static FontSelectionRange calculateStretchRange(CSSValue& value)
{
if (value.isValueList()) {
auto& valueList = downcast<CSSValueList>(value);
ASSERT(valueList.length() == 2);
if (valueList.length() != 2)
return { normalStretchValue(), normalStretchValue() };
ASSERT(valueList.item(0)->isPrimitiveValue());
ASSERT(valueList.item(1)->isPrimitiveValue());
auto& value0 = downcast<CSSPrimitiveValue>(*valueList.item(0));
auto& value1 = downcast<CSSPrimitiveValue>(*valueList.item(1));
auto result0 = StyleBuilderConverter::convertFontStretchFromValue(value0);
auto result1 = StyleBuilderConverter::convertFontStretchFromValue(value1);
return { result0, result1 };
}
ASSERT(is<CSSPrimitiveValue>(value));
const auto& primitiveValue = downcast<CSSPrimitiveValue>(value);
FontSelectionValue result = StyleBuilderConverter::convertFontStretchFromValue(primitiveValue);
return { result, result };
}
void CSSFontFace::setStretch(CSSValue& style)
{
auto range = calculateStretchRange(style);
if (m_fontSelectionCapabilities.width == range)
return;
setStretch(range);
if (m_cssConnection)
m_cssConnection->mutableProperties().setProperty(CSSPropertyFontStretch, &style);
iterateClients(m_clients, [&](Client& client) {
client.fontPropertyChanged(*this);
});
}
static FontSelectionRange calculateItalicRange(CSSValue& value)
{
if (value.isFontStyleValue()) {
auto result = StyleBuilderConverter::convertFontStyleFromValue(value);
return { result.valueOr(normalItalicValue()), result.valueOr(normalItalicValue()) };
}
ASSERT(value.isFontStyleRangeValue());
auto& rangeValue = downcast<CSSFontStyleRangeValue>(value);
ASSERT(rangeValue.fontStyleValue->isValueID());
auto valueID = rangeValue.fontStyleValue->valueID();
if (!rangeValue.obliqueValues) {
if (valueID == CSSValueNormal)
return { normalItalicValue(), normalItalicValue() };
ASSERT(valueID == CSSValueItalic || valueID == CSSValueOblique);
return { italicValue(), italicValue() };
}
ASSERT(valueID == CSSValueOblique);
auto length = rangeValue.obliqueValues->length();
if (length == 1) {
auto& primitiveValue = downcast<CSSPrimitiveValue>(*rangeValue.obliqueValues->item(0));
FontSelectionValue result(primitiveValue.value<float>(CSSPrimitiveValue::CSS_DEG));
return { result, result };
}
ASSERT(length == 2);
auto& primitiveValue1 = downcast<CSSPrimitiveValue>(*rangeValue.obliqueValues->item(0));
auto& primitiveValue2 = downcast<CSSPrimitiveValue>(*rangeValue.obliqueValues->item(1));
FontSelectionValue result1(primitiveValue1.value<float>(CSSPrimitiveValue::CSS_DEG));
FontSelectionValue result2(primitiveValue2.value<float>(CSSPrimitiveValue::CSS_DEG));
return { result1, result2 };
}
void CSSFontFace::setStyle(CSSValue& style)
{
auto range = calculateItalicRange(style);
if (m_fontSelectionCapabilities.slope == range)
return;
setStyle(range);
if (m_cssConnection)
m_cssConnection->mutableProperties().setProperty(CSSPropertyFontStyle, &style);
iterateClients(m_clients, [&](Client& client) {
client.fontPropertyChanged(*this);
});
}
bool CSSFontFace::setUnicodeRange(CSSValue& unicodeRange)
{
if (!is<CSSValueList>(unicodeRange))
return false;
Vector<UnicodeRange> ranges;
auto& list = downcast<CSSValueList>(unicodeRange);
for (auto& rangeValue : list) {
auto& range = downcast<CSSUnicodeRangeValue>(rangeValue.get());
ranges.append({ range.from(), range.to() });
}
if (ranges == m_ranges)
return true;
m_ranges = WTFMove(ranges);
if (m_cssConnection)
m_cssConnection->mutableProperties().setProperty(CSSPropertyUnicodeRange, &unicodeRange);
iterateClients(m_clients, [&](Client& client) {
client.fontPropertyChanged(*this);
});
return true;
}
bool CSSFontFace::setVariantLigatures(CSSValue& variantLigatures)
{
auto ligatures = extractFontVariantLigatures(variantLigatures);
if (m_variantSettings.commonLigatures == ligatures.commonLigatures
&& m_variantSettings.discretionaryLigatures == ligatures.discretionaryLigatures
&& m_variantSettings.historicalLigatures == ligatures.historicalLigatures
&& m_variantSettings.contextualAlternates == ligatures.contextualAlternates)
return true;
m_variantSettings.commonLigatures = ligatures.commonLigatures;
m_variantSettings.discretionaryLigatures = ligatures.discretionaryLigatures;
m_variantSettings.historicalLigatures = ligatures.historicalLigatures;
m_variantSettings.contextualAlternates = ligatures.contextualAlternates;
if (m_cssConnection)
m_cssConnection->mutableProperties().setProperty(CSSPropertyFontVariantLigatures, &variantLigatures);
iterateClients(m_clients, [&](Client& client) {
client.fontPropertyChanged(*this);
});
return true;
}
bool CSSFontFace::setVariantPosition(CSSValue& variantPosition)
{
if (!is<CSSPrimitiveValue>(variantPosition))
return false;
FontVariantPosition position = downcast<CSSPrimitiveValue>(variantPosition);
if (m_variantSettings.position == position)
return true;
m_variantSettings.position = position;
if (m_cssConnection)
m_cssConnection->mutableProperties().setProperty(CSSPropertyFontVariantPosition, &variantPosition);
iterateClients(m_clients, [&](Client& client) {
client.fontPropertyChanged(*this);
});
return true;
}
bool CSSFontFace::setVariantCaps(CSSValue& variantCaps)
{
if (!is<CSSPrimitiveValue>(variantCaps))
return false;
FontVariantCaps caps = downcast<CSSPrimitiveValue>(variantCaps);
if (m_variantSettings.caps == caps)
return true;
m_variantSettings.caps = caps;
if (m_cssConnection)
m_cssConnection->mutableProperties().setProperty(CSSPropertyFontVariantCaps, &variantCaps);
iterateClients(m_clients, [&](Client& client) {
client.fontPropertyChanged(*this);
});
return true;
}
bool CSSFontFace::setVariantNumeric(CSSValue& variantNumeric)
{
auto numeric = extractFontVariantNumeric(variantNumeric);
if (m_variantSettings.numericFigure == numeric.figure
&& m_variantSettings.numericSpacing == numeric.spacing
&& m_variantSettings.numericFraction == numeric.fraction
&& m_variantSettings.numericOrdinal == numeric.ordinal
&& m_variantSettings.numericSlashedZero == numeric.slashedZero)
return true;
m_variantSettings.numericFigure = numeric.figure;
m_variantSettings.numericSpacing = numeric.spacing;
m_variantSettings.numericFraction = numeric.fraction;
m_variantSettings.numericOrdinal = numeric.ordinal;
m_variantSettings.numericSlashedZero = numeric.slashedZero;
if (m_cssConnection)
m_cssConnection->mutableProperties().setProperty(CSSPropertyFontVariantNumeric, &variantNumeric);
iterateClients(m_clients, [&](Client& client) {
client.fontPropertyChanged(*this);
});
return true;
}
bool CSSFontFace::setVariantAlternates(CSSValue& variantAlternates)
{
if (!is<CSSPrimitiveValue>(variantAlternates))
return false;
FontVariantAlternates alternates = downcast<CSSPrimitiveValue>(variantAlternates);
if (m_variantSettings.alternates == alternates)
return true;
m_variantSettings.alternates = alternates;
if (m_cssConnection)
m_cssConnection->mutableProperties().setProperty(CSSPropertyFontVariantAlternates, &variantAlternates);
iterateClients(m_clients, [&](Client& client) {
client.fontPropertyChanged(*this);
});
return true;
}
bool CSSFontFace::setVariantEastAsian(CSSValue& variantEastAsian)
{
auto eastAsian = extractFontVariantEastAsian(variantEastAsian);
if (m_variantSettings.eastAsianVariant == eastAsian.variant
&& m_variantSettings.eastAsianWidth == eastAsian.width
&& m_variantSettings.eastAsianRuby == eastAsian.ruby)
return true;
m_variantSettings.eastAsianVariant = eastAsian.variant;
m_variantSettings.eastAsianWidth = eastAsian.width;
m_variantSettings.eastAsianRuby = eastAsian.ruby;
if (m_cssConnection)
m_cssConnection->mutableProperties().setProperty(CSSPropertyFontVariantEastAsian, &variantEastAsian);
iterateClients(m_clients, [&](Client& client) {
client.fontPropertyChanged(*this);
});
return true;
}
void CSSFontFace::setFeatureSettings(CSSValue& featureSettings)
{
// Can only call this with a primitive value of normal, or a value list containing font feature values.
ASSERT(is<CSSPrimitiveValue>(featureSettings) || is<CSSValueList>(featureSettings));
FontFeatureSettings settings;
if (is<CSSValueList>(featureSettings)) {
auto& list = downcast<CSSValueList>(featureSettings);
for (auto& rangeValue : list) {
auto& feature = downcast<CSSFontFeatureValue>(rangeValue.get());
settings.insert({ feature.tag(), feature.value() });
}
}
if (m_featureSettings == settings)
return;
m_featureSettings = WTFMove(settings);
if (m_cssConnection)
m_cssConnection->mutableProperties().setProperty(CSSPropertyFontFeatureSettings, &featureSettings);
iterateClients(m_clients, [&](Client& client) {
client.fontPropertyChanged(*this);
});
}
void CSSFontFace::setLoadingBehavior(CSSValue& loadingBehaviorValue)
{
auto loadingBehavior = static_cast<FontLoadingBehavior>(downcast<CSSPrimitiveValue>(loadingBehaviorValue));
if (m_loadingBehavior == loadingBehavior)
return;
m_loadingBehavior = loadingBehavior;
if (m_cssConnection)
m_cssConnection->mutableProperties().setProperty(CSSPropertyFontDisplay, &loadingBehaviorValue);
iterateClients(m_clients, [&](Client& client) {
client.fontPropertyChanged(*this);
});
}
bool CSSFontFace::rangesMatchCodePoint(UChar32 character) const
{
if (m_ranges.isEmpty())
return true;
for (auto& range : m_ranges) {
if (range.from <= character && character <= range.to)
return true;
}
return false;
}
void CSSFontFace::fontLoadEventOccurred()
{
// If the font is already in the cache, CSSFontFaceSource may report it's loaded before it is added here as a source.
// Let's not pump the state machine until we've got all our sources. font() and load() are smart enough to act correctly
// when a source is failed or succeeded before we have asked it to load.
if (m_sourcesPopulated)
pump(ExternalResourceDownloadPolicy::Forbid);
ASSERT(m_fontSelector);
m_fontSelector->fontLoaded();
iterateClients(m_clients, [&](Client& client) {
client.fontLoaded(*this);
});
}
void CSSFontFace::timeoutFired()
{
Ref<CSSFontFace> protectedThis(*this);
switch (status()) {
case Status::Loading:
setStatus(Status::TimedOut);
break;
case Status::TimedOut:
// Cancelling the network request here could lead to a situation where a site's font never gets
// shown as the user navigates around to different pages on the site. This would occur if the
// download always takes longer than the timeout (even though the user may spend substantial time
// on each page). Therefore, we shouldn't cancel the network request here, but should use the
// loading infrastructure's timeout policies instead.
setStatus(Status::Failure);
break;
default:
ASSERT_NOT_REACHED();
break;
}
fontLoadEventOccurred();
}
bool CSSFontFace::computeFailureState() const
{
if (status() == Status::Failure)
return true;
for (auto& source : m_sources) {
if (source->status() != CSSFontFaceSource::Status::Failure)
return false;
}
return true;
}
void CSSFontFace::addClient(Client& client)
{
m_clients.add(&client);
}
void CSSFontFace::removeClient(Client& client)
{
ASSERT(m_clients.contains(&client));
m_clients.remove(&client);
}
void CSSFontFace::initializeWrapper()
{
switch (m_status) {
case Status::Pending:
break;
case Status::Loading:
m_wrapper->fontStateChanged(*this, Status::Pending, Status::Loading);
break;
case Status::TimedOut:
m_wrapper->fontStateChanged(*this, Status::Pending, Status::Loading);
m_wrapper->fontStateChanged(*this, Status::Loading, Status::TimedOut);
break;
case Status::Success:
m_wrapper->fontStateChanged(*this, Status::Pending, Status::Loading);
m_wrapper->fontStateChanged(*this, Status::Pending, Status::Success);
break;
case Status::Failure:
m_wrapper->fontStateChanged(*this, Status::Pending, Status::Loading);
m_wrapper->fontStateChanged(*this, Status::Pending, Status::Failure);
break;
}
m_mayBePurged = false;
}
Ref<FontFace> CSSFontFace::wrapper()
{
if (m_wrapper)
return *m_wrapper.get();
auto wrapper = FontFace::create(*this);
m_wrapper = makeWeakPtr(wrapper.get());
initializeWrapper();
return wrapper;
}
void CSSFontFace::setWrapper(FontFace& newWrapper)
{
m_wrapper = makeWeakPtr(newWrapper);
initializeWrapper();
}
void CSSFontFace::adoptSource(std::unique_ptr<CSSFontFaceSource>&& source)
{
m_sources.append(WTFMove(source));
// We should never add sources in the middle of loading.
ASSERT(!m_sourcesPopulated);
}
AllowUserInstalledFonts CSSFontFace::allowUserInstalledFonts() const
{
if (m_fontSelector && m_fontSelector->document())
return m_fontSelector->document()->settings().shouldAllowUserInstalledFonts() ? AllowUserInstalledFonts::Yes : AllowUserInstalledFonts::No;
return AllowUserInstalledFonts::Yes;
}
static Settings::FontLoadTimingOverride fontLoadTimingOverride(CSSFontSelector* fontSelector)
{
auto overrideValue = Settings::FontLoadTimingOverride::None;
if (fontSelector && fontSelector->document())
overrideValue = fontSelector->document()->settings().fontLoadTimingOverride();
return overrideValue;
}
auto CSSFontFace::fontLoadTiming() const -> FontLoadTiming
{
switch (fontLoadTimingOverride(m_fontSelector.get())) {
case Settings::FontLoadTimingOverride::None:
switch (m_loadingBehavior) {
case FontLoadingBehavior::Auto:
case FontLoadingBehavior::Block:
return { 3_s, Seconds::infinity() };
case FontLoadingBehavior::Swap:
return { 0_s, Seconds::infinity() };
case FontLoadingBehavior::Fallback:
return { 0.1_s, 3_s };
case FontLoadingBehavior::Optional:
return { 0.1_s, 0_s };
}
RELEASE_ASSERT_NOT_REACHED();
case Settings::FontLoadTimingOverride::Block:
return { Seconds::infinity(), 0_s };
case Settings::FontLoadTimingOverride::Swap:
return { 0_s, Seconds::infinity() };
case Settings::FontLoadTimingOverride::Failure:
return { 0_s, 0_s };
}
RELEASE_ASSERT_NOT_REACHED();
}
void CSSFontFace::setStatus(Status newStatus)
{
switch (newStatus) {
case Status::Pending:
ASSERT_NOT_REACHED();
break;
case Status::Loading:
ASSERT(m_status == Status::Pending);
break;
case Status::TimedOut:
ASSERT(m_status == Status::Loading);
break;
case Status::Success:
ASSERT(m_status == Status::Loading || m_status == Status::TimedOut);
break;
case Status::Failure:
ASSERT(m_status == Status::Loading || m_status == Status::TimedOut);
break;
}
iterateClients(m_clients, [&](Client& client) {
client.fontStateChanged(*this, m_status, newStatus);
});
m_status = newStatus;
Seconds blockPeriodTimeout;
Seconds swapPeriodTimeout;
auto timeouts = fontLoadTiming();
blockPeriodTimeout = timeouts.blockPeriod;
swapPeriodTimeout = timeouts.swapPeriod;
// Transfer across 0-delay timers synchronously. Layouts/script may
// take arbitrarily long time, and we shouldn't be in a 0-duration
// state for an arbitrarily long time. Also it's necessary for
// testing so we don't have a race with the font load.
switch (newStatus) {
case Status::Pending:
ASSERT_NOT_REACHED();
break;
case Status::Loading:
if (blockPeriodTimeout == 0_s)
setStatus(Status::TimedOut);
else if (std::isfinite(blockPeriodTimeout.value()))
m_timeoutTimer.startOneShot(blockPeriodTimeout);
break;
case Status::TimedOut:
if (swapPeriodTimeout == 0_s)
setStatus(Status::Failure);
else if (std::isfinite(swapPeriodTimeout.value()))
m_timeoutTimer.startOneShot(swapPeriodTimeout);
break;
case Status::Success:
case Status::Failure:
m_timeoutTimer.stop();
break;
}
}
void CSSFontFace::fontLoaded(CSSFontFaceSource&)
{
Ref<CSSFontFace> protectedThis(*this);
fontLoadEventOccurred();
}
bool CSSFontFace::shouldIgnoreFontLoadCompletions() const
{
if (m_fontSelector && m_fontSelector->document())
return m_fontSelector->document()->settings().shouldIgnoreFontLoadCompletions();
return false;
}
void CSSFontFace::opportunisticallyStartFontDataURLLoading(CSSFontSelector& fontSelector)
{
// We don't want to go crazy here and blow the cache. Usually these data URLs are the first item in the src: list, so let's just check that one.
if (!m_sources.isEmpty())
m_sources[0]->opportunisticallyStartFontDataURLLoading(fontSelector);
}
size_t CSSFontFace::pump(ExternalResourceDownloadPolicy policy)
{
if (status() == Status::Failure)
return 0;
size_t i;
for (i = 0; i < m_sources.size(); ++i) {
auto& source = m_sources[i];
if (source->status() == CSSFontFaceSource::Status::Pending) {
ASSERT(m_status == Status::Pending || m_status == Status::Loading || m_status == Status::TimedOut);
// This is a little tricky. After calling CSSFontFace::font(Forbid), a font must never fail later in
// this turn of the runloop because the return value of CSSFontFace::font() shouldn't get nulled out
// from under an existing FontRanges object. Remote fonts are all downloaded asynchronously, so this
// isn't a problem for them because CSSFontFace::font() will always return the interstitial font.
// However, local fonts may synchronously fail when you call load() on them. Therefore, we have to call
// load() here in order to guarantee that, if the font synchronously fails, it happens now during the
// first call to CSSFontFace::font() and the FontRanges object sees a consistent view of the
// CSSFontFace. This means we eagerly create some internal font objects when they may not be needed,
// but it seems that this behavior is a requirement of the design of FontRanges. FIXME: Perhaps rethink
// this design.
if (policy == ExternalResourceDownloadPolicy::Allow || !source->requiresExternalResource()) {
if (m_status == Status::Pending)
setStatus(Status::Loading);
source->load(m_fontSelector.get());
}
}
switch (source->status()) {
case CSSFontFaceSource::Status::Pending:
ASSERT(policy == ExternalResourceDownloadPolicy::Forbid);
return i;
case CSSFontFaceSource::Status::Loading:
ASSERT(m_status == Status::Pending || m_status == Status::Loading || m_status == Status::TimedOut || m_status == Status::Failure);
if (m_status == Status::Pending)
setStatus(Status::Loading);
return i;
case CSSFontFaceSource::Status::Success:
ASSERT(m_status == Status::Pending || m_status == Status::Loading || m_status == Status::TimedOut || m_status == Status::Success || m_status == Status::Failure);
if (m_status == Status::Pending)
setStatus(Status::Loading);
if (m_status == Status::Loading || m_status == Status::TimedOut)
setStatus(Status::Success);
return i;
case CSSFontFaceSource::Status::Failure:
if (m_status == Status::Pending)
setStatus(Status::Loading);
break;
}
}
if (m_sources.isEmpty() && m_status == Status::Pending)
setStatus(Status::Loading);
if (m_status == Status::Loading || m_status == Status::TimedOut)
setStatus(Status::Failure);
return m_sources.size();
}
void CSSFontFace::load()
{
pump(ExternalResourceDownloadPolicy::Allow);
}
static Font::Visibility visibility(CSSFontFace::Status status, CSSFontFace::FontLoadTiming timing)
{
switch (status) {
case CSSFontFace::Status::Pending:
return timing.blockPeriod == 0_s ? Font::Visibility::Visible : Font::Visibility::Invisible;
case CSSFontFace::Status::Loading:
return Font::Visibility::Invisible;
case CSSFontFace::Status::TimedOut:
case CSSFontFace::Status::Failure:
case CSSFontFace::Status::Success:
default:
return Font::Visibility::Visible;
}
}
RefPtr<Font> CSSFontFace::font(const FontDescription& fontDescription, bool syntheticBold, bool syntheticItalic, ExternalResourceDownloadPolicy policy)
{
if (computeFailureState())
return nullptr;
Ref<CSSFontFace> protectedThis(*this);
// Our status is derived from the first non-failed source. However, this source may
// return null from font(), which means we need to continue looping through the remainder
// of the sources to try to find a font to use. These subsequent tries should not affect
// our own state, though.
size_t startIndex = pump(policy);
if (computeFailureState())
return nullptr;
for (size_t i = startIndex; i < m_sources.size(); ++i) {
auto& source = m_sources[i];
if (source->status() == CSSFontFaceSource::Status::Pending && (policy == ExternalResourceDownloadPolicy::Allow || !source->requiresExternalResource()))
source->load(m_fontSelector.get());
switch (source->status()) {
case CSSFontFaceSource::Status::Pending:
case CSSFontFaceSource::Status::Loading: {
Font::Visibility visibility = WebCore::visibility(status(), fontLoadTiming());
return Font::create(FontCache::singleton().lastResortFallbackFont(fontDescription)->platformData(), Font::Origin::Local, Font::Interstitial::Yes, visibility);
}
case CSSFontFaceSource::Status::Success:
if (RefPtr<Font> result = source->font(fontDescription, syntheticBold, syntheticItalic, m_featureSettings, m_variantSettings, m_fontSelectionCapabilities))
return result;
break;
case CSSFontFaceSource::Status::Failure:
break;
}
}
return nullptr;
}
bool CSSFontFace::purgeable() const
{
return cssConnection() && m_mayBePurged;
}
void CSSFontFace::updateStyleIfNeeded()
{
if (m_fontSelector && m_fontSelector->document())
m_fontSelector->document()->updateStyleIfNeeded();
}
#if ENABLE(SVG_FONTS)
bool CSSFontFace::hasSVGFontFaceSource() const
{
size_t size = m_sources.size();
for (size_t i = 0; i < size; i++) {
if (m_sources[i]->isSVGFontFaceSource())
return true;
}
return false;
}
#endif
}