371 lines
13 KiB
C++
371 lines
13 KiB
C++
/*
|
|
* Copyright (C) 2007, 2008, 2011, 2013 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(makeWeakPtr(context))
|
|
, m_fontCache(makeRef(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);
|
|
}
|
|
}
|
|
|
|
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 (fontFace->computeFailureState())
|
|
return;
|
|
|
|
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::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::fontStyleUpdateNeeded(CSSFontFace&)
|
|
{
|
|
if (is<Document>(m_context.get()))
|
|
downcast<Document>(*m_context).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;
|
|
}
|
|
|
|
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;
|
|
};
|
|
|
|
if (resolveGenericFamilyFirst)
|
|
resolveAndAssignGenericFamily();
|
|
Document* document = is<Document>(m_context.get()) ? &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);
|
|
}
|
|
|
|
if (!resolveGenericFamilyFirst)
|
|
resolveAndAssignGenericFamily();
|
|
auto font = m_fontCache->fontForFamily(*fontDescriptionForLookup, familyForLookup);
|
|
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.get()))
|
|
ResourceLoadObserver::shared().logFontLoad(downcast<Document>(*m_context), pictographFontFamily, !!font);
|
|
|
|
return font;
|
|
}
|
|
|
|
}
|