blob: ae11a8b9ce8e0f7af2e1c729a1a35d59846ee986 [file] [log] [blame]
/*
* Copyright (C) 2007-2021 Apple Inc. All rights reserved.
* (C) 2007, 2008 Nikolas Zimmermann <zimmermann@kde.org>
*
* 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 "CSSFontSelector.h"
#include "CachedFont.h"
#include "CSSFontFace.h"
#include "CSSFontFaceSource.h"
#include "CSSFontFamily.h"
#include "CSSPrimitiveValue.h"
#include "CSSPropertyNames.h"
#include "CSSSegmentedFontFace.h"
#include "CSSValueKeywords.h"
#include "CSSValueList.h"
#include "CachedResourceLoader.h"
#include "Document.h"
#include "Font.h"
#include "FontCache.h"
#include "FontFace.h"
#include "FontFaceSet.h"
#include "FontSelectorClient.h"
#include "Frame.h"
#include "FrameLoader.h"
#include "Logging.h"
#include "ResourceLoadObserver.h"
#include "RuntimeEnabledFeatures.h"
#include "Settings.h"
#include "StyleProperties.h"
#include "StyleResolver.h"
#include "StyleRule.h"
#include <wtf/Ref.h>
#include <wtf/SetForScope.h>
#include <wtf/text/AtomString.h>
namespace WebCore {
using namespace WebKitFontFamilyNames;
static unsigned fontSelectorId;
CSSFontSelector::CSSFontSelector(ScriptExecutionContext& context)
: ActiveDOMObject(&context)
, m_context(context)
, m_fontCache(context.fontCache())
, m_cssFontFaceSet(CSSFontFaceSet::create(this))
, m_fontModifiedObserver([this] { fontModified(); })
, m_uniqueId(++fontSelectorId)
, m_version(0)
{
if (is<Document>(context)) {
m_fontFamilyNames.reserveInitialCapacity(familyNames->size());
for (auto& familyName : familyNames.get())
m_fontFamilyNames.uncheckedConstructAndAppend(familyName);
} else {
m_fontFamilyNames.reserveInitialCapacity(familyNamesData->size());
for (auto& familyName : familyNamesData.get())
m_fontFamilyNames.uncheckedAppend(familyName);
}
m_fontCache->addClient(*this);
m_cssFontFaceSet->addFontModifiedObserver(m_fontModifiedObserver);
LOG(Fonts, "CSSFontSelector %p ctor", this);
suspendIfNeeded();
}
CSSFontSelector::~CSSFontSelector()
{
LOG(Fonts, "CSSFontSelector %p dtor", this);
clearFonts();
m_fontCache->removeClient(*this);
}
FontFaceSet* CSSFontSelector::fontFaceSetIfExists()
{
return m_fontFaceSet.get();
}
FontFaceSet& CSSFontSelector::fontFaceSet()
{
if (!m_fontFaceSet) {
ASSERT(m_context);
m_fontFaceSet = FontFaceSet::create(*m_context, m_cssFontFaceSet.get());
}
return *m_fontFaceSet;
}
bool CSSFontSelector::isEmpty() const
{
return !m_cssFontFaceSet->faceCount();
}
void CSSFontSelector::emptyCaches()
{
m_cssFontFaceSet->emptyCaches();
}
void CSSFontSelector::buildStarted()
{
m_buildIsUnderway = true;
m_cssFontFaceSet->purge();
++m_version;
ASSERT(m_cssConnectionsPossiblyToRemove.isEmpty());
ASSERT(m_cssConnectionsEncounteredDuringBuild.isEmpty());
ASSERT(m_stagingArea.isEmpty());
for (size_t i = 0; i < m_cssFontFaceSet->faceCount(); ++i) {
CSSFontFace& face = m_cssFontFaceSet.get()[i];
if (face.cssConnection())
m_cssConnectionsPossiblyToRemove.add(&face);
}
m_paletteMap.clear();
}
void CSSFontSelector::buildCompleted()
{
if (!m_buildIsUnderway)
return;
m_buildIsUnderway = false;
// Some font faces weren't re-added during the build process.
for (auto& face : m_cssConnectionsPossiblyToRemove) {
auto* connection = face->cssConnection();
ASSERT(connection);
if (!m_cssConnectionsEncounteredDuringBuild.contains(connection))
m_cssFontFaceSet->remove(*face);
}
for (auto& item : m_stagingArea)
addFontFaceRule(item.styleRuleFontFace, item.isInitiatingElementInUserAgentShadowTree);
m_cssConnectionsEncounteredDuringBuild.clear();
m_stagingArea.clear();
m_cssConnectionsPossiblyToRemove.clear();
}
void CSSFontSelector::addFontFaceRule(StyleRuleFontFace& fontFaceRule, bool isInitiatingElementInUserAgentShadowTree)
{
if (m_buildIsUnderway) {
m_cssConnectionsEncounteredDuringBuild.add(&fontFaceRule);
m_stagingArea.append({fontFaceRule, isInitiatingElementInUserAgentShadowTree});
return;
}
const StyleProperties& style = fontFaceRule.properties();
RefPtr<CSSValue> fontFamily = style.getPropertyCSSValue(CSSPropertyFontFamily);
RefPtr<CSSValue> fontStyle = style.getPropertyCSSValue(CSSPropertyFontStyle);
RefPtr<CSSValue> fontWeight = style.getPropertyCSSValue(CSSPropertyFontWeight);
RefPtr<CSSValue> fontStretch = style.getPropertyCSSValue(CSSPropertyFontStretch);
RefPtr<CSSValue> src = style.getPropertyCSSValue(CSSPropertySrc);
RefPtr<CSSValue> unicodeRange = style.getPropertyCSSValue(CSSPropertyUnicodeRange);
RefPtr<CSSValue> featureSettings = style.getPropertyCSSValue(CSSPropertyFontFeatureSettings);
RefPtr<CSSValue> loadingBehavior = style.getPropertyCSSValue(CSSPropertyFontDisplay);
if (!is<CSSValueList>(fontFamily) || !is<CSSValueList>(src) || (unicodeRange && !is<CSSValueList>(*unicodeRange)))
return;
CSSValueList& familyList = downcast<CSSValueList>(*fontFamily);
if (!familyList.length())
return;
CSSValueList* rangeList = downcast<CSSValueList>(unicodeRange.get());
CSSValueList& srcList = downcast<CSSValueList>(*src);
if (!srcList.length())
return;
SetForScope<bool> creatingFont(m_creatingFont, true);
auto fontFace = CSSFontFace::create(*this, &fontFaceRule);
if (!fontFace->setFamilies(*fontFamily))
return;
if (fontStyle)
fontFace->setStyle(*fontStyle);
if (fontWeight)
fontFace->setWeight(*fontWeight);
if (fontStretch)
fontFace->setStretch(*fontStretch);
if (rangeList && !fontFace->setUnicodeRange(*rangeList))
return;
if (featureSettings)
fontFace->setFeatureSettings(*featureSettings);
if (loadingBehavior)
fontFace->setLoadingBehavior(*loadingBehavior);
CSSFontFace::appendSources(fontFace, srcList, m_context.get(), isInitiatingElementInUserAgentShadowTree);
if (RefPtr<CSSFontFace> existingFace = m_cssFontFaceSet->lookUpByCSSConnection(fontFaceRule)) {
// This adoption is fairly subtle. Script can trigger a purge of m_cssFontFaceSet at any time,
// which will cause us to just rely on the memory cache to retain the bytes of the file the next
// time we build up the CSSFontFaceSet. However, when the CSS Font Loading API is involved,
// the FontFace and FontFaceSet objects need to retain state. We create the new CSSFontFace object
// while the old one is still in scope so that the memory cache will be forced to retain the bytes
// of the resource. This means that the CachedFont will temporarily have two clients (until the
// old CSSFontFace goes out of scope, which should happen at the end of this "if" block). Because
// the CSSFontFaceSource objects will inspect their CachedFonts, the new CSSFontFace is smart enough
// to enter the correct state() during the next pump(). This approach of making a new CSSFontFace is
// simpler than computing and applying a diff of the StyleProperties.
m_cssFontFaceSet->remove(*existingFace);
if (auto* existingWrapper = existingFace->existingWrapper())
existingWrapper->adopt(fontFace.get());
}
m_cssFontFaceSet->add(fontFace.get());
++m_version;
}
void CSSFontSelector::addFontPaletteValuesRule(StyleRuleFontPaletteValues& fontPaletteValuesRule)
{
AtomString fontFamily = fontPaletteValuesRule.fontFamily().isNull() ? emptyAtom() : fontPaletteValuesRule.fontFamily();
AtomString name = fontPaletteValuesRule.name().isNull() ? emptyAtom() : fontPaletteValuesRule.name();
m_paletteMap.set(std::make_pair(fontFamily, name), fontPaletteValuesRule.fontPaletteValues());
++m_version;
}
void CSSFontSelector::registerForInvalidationCallbacks(FontSelectorClient& client)
{
m_clients.add(&client);
}
void CSSFontSelector::unregisterForInvalidationCallbacks(FontSelectorClient& client)
{
m_clients.remove(&client);
}
void CSSFontSelector::dispatchInvalidationCallbacks()
{
++m_version;
for (auto& client : copyToVector(m_clients))
client->fontsNeedUpdate(*this);
}
void CSSFontSelector::opportunisticallyStartFontDataURLLoading(const FontCascadeDescription& description, const AtomString& familyName)
{
const auto& segmentedFontFace = m_cssFontFaceSet->fontFace(description.fontSelectionRequest(), familyName);
if (!segmentedFontFace)
return;
for (auto& face : segmentedFontFace->constituentFaces())
face->opportunisticallyStartFontDataURLLoading();
}
void CSSFontSelector::fontLoaded(CSSFontFace&)
{
dispatchInvalidationCallbacks();
}
void CSSFontSelector::fontModified()
{
if (!m_creatingFont && !m_buildIsUnderway)
dispatchInvalidationCallbacks();
}
void CSSFontSelector::updateStyleIfNeeded()
{
if (is<Document>(m_context))
downcast<Document>(*m_context).updateStyleIfNeeded();
}
void CSSFontSelector::updateStyleIfNeeded(CSSFontFace&)
{
updateStyleIfNeeded();
}
void CSSFontSelector::fontCacheInvalidated()
{
dispatchInvalidationCallbacks();
}
std::optional<AtomString> CSSFontSelector::resolveGenericFamily(const FontDescription& fontDescription, const AtomString& familyName)
{
auto platformResult = FontDescription::platformResolveGenericFamily(fontDescription.script(), fontDescription.computedLocale(), familyName);
if (!platformResult.isNull())
return platformResult;
if (!m_context)
return std::nullopt;
const auto& settings = m_context->settingsValues();
UScriptCode script = fontDescription.script();
auto familyNameIndex = m_fontFamilyNames.find(familyName);
if (familyNameIndex != notFound) {
if (auto familyString = settings.fontGenericFamilies.fontFamily(static_cast<FamilyNamesIndex>(familyNameIndex), script))
return AtomString(*familyString);
}
return std::nullopt;
}
const FontPaletteValues& CSSFontSelector::lookupFontPaletteValues(const AtomString& familyName, const FontDescription& fontDescription)
{
static NeverDestroyed<FontPaletteValues> emptyFontPaletteValues;
if (fontDescription.fontPalette().type != FontPalette::Type::Custom)
return emptyFontPaletteValues.get();
const AtomString paletteName = fontDescription.fontPalette().identifier;
auto iterator = m_paletteMap.find(std::make_pair(familyName, paletteName));
if (iterator == m_paletteMap.end())
return emptyFontPaletteValues.get();
return iterator->value;
}
FontRanges CSSFontSelector::fontRangesForFamily(const FontDescription& fontDescription, const AtomString& familyName)
{
// If this ASSERT() fires, it usually means you forgot a document.updateStyleIfNeeded() somewhere.
ASSERT(!m_buildIsUnderway || m_computingRootStyleFontCount);
// FIXME: The spec (and Firefox) says user specified generic families (sans-serif etc.) should be resolved before the @font-face lookup too.
bool resolveGenericFamilyFirst = familyName == m_fontFamilyNames.at(FamilyNamesIndex::StandardFamily);
AtomString familyForLookup = familyName;
std::optional<FontDescription> overrideFontDescription;
const FontDescription* fontDescriptionForLookup = &fontDescription;
auto resolveAndAssignGenericFamily = [&]() {
if (auto genericFamilyOptional = resolveGenericFamily(fontDescription, familyName))
familyForLookup = *genericFamilyOptional;
};
const auto& fontPaletteValues = lookupFontPaletteValues(familyName, fontDescription);
if (resolveGenericFamilyFirst)
resolveAndAssignGenericFamily();
Document* document = is<Document>(m_context) ? &downcast<Document>(*m_context) : nullptr;
auto* face = m_cssFontFaceSet->fontFace(fontDescriptionForLookup->fontSelectionRequest(), familyForLookup);
if (face) {
if (document && RuntimeEnabledFeatures::sharedFeatures().webAPIStatisticsEnabled())
ResourceLoadObserver::shared().logFontLoad(*document, familyForLookup.string(), true);
return face->fontRanges(*fontDescriptionForLookup, fontPaletteValues);
}
if (!resolveGenericFamilyFirst)
resolveAndAssignGenericFamily();
auto font = m_fontCache->fontForFamily(*fontDescriptionForLookup, familyForLookup, { { }, { }, fontPaletteValues });
if (document && RuntimeEnabledFeatures::sharedFeatures().webAPIStatisticsEnabled())
ResourceLoadObserver::shared().logFontLoad(*document, familyForLookup.string(), !!font);
return FontRanges { WTFMove(font) };
}
void CSSFontSelector::clearFonts()
{
m_isStopped = true;
m_cssFontFaceSet->clear();
m_clients.clear();
}
size_t CSSFontSelector::fallbackFontCount()
{
if (m_isStopped)
return 0;
return m_context->settingsValues().fontFallbackPrefersPictographs ? 1 : 0;
}
RefPtr<Font> CSSFontSelector::fallbackFontAt(const FontDescription& fontDescription, size_t index)
{
ASSERT_UNUSED(index, !index);
if (m_isStopped)
return nullptr;
if (!m_context->settingsValues().fontFallbackPrefersPictographs)
return nullptr;
auto& pictographFontFamily = m_context->settingsValues().fontGenericFamilies.pictographFontFamily();
auto font = m_fontCache->fontForFamily(fontDescription, pictographFontFamily);
if (RuntimeEnabledFeatures::sharedFeatures().webAPIStatisticsEnabled() && is<Document>(m_context))
ResourceLoadObserver::shared().logFontLoad(downcast<Document>(*m_context), pictographFontFamily, !!font);
return font;
}
}