2 * Copyright (C) 2007, 2008, 2011, 2013 Apple Inc. All rights reserved.
3 * (C) 2007, 2008 Nikolas Zimmermann <zimmermann@kde.org>
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
14 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
18 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 #include "CSSFontSelector.h"
30 #include "CachedFont.h"
31 #include "CSSFontFace.h"
32 #include "CSSFontFaceSource.h"
33 #include "CSSFontFamily.h"
34 #include "CSSPrimitiveValue.h"
35 #include "CSSPropertyNames.h"
36 #include "CSSSegmentedFontFace.h"
37 #include "CSSValueKeywords.h"
38 #include "CSSValueList.h"
39 #include "CachedResourceLoader.h"
42 #include "FontCache.h"
44 #include "FontFaceSet.h"
45 #include "FontSelectorClient.h"
47 #include "FrameLoader.h"
49 #include "StyleProperties.h"
50 #include "StyleResolver.h"
51 #include "StyleRule.h"
52 #include "WebKitFontFamilyNames.h"
54 #include <wtf/SetForScope.h>
55 #include <wtf/text/AtomicString.h>
59 static unsigned fontSelectorId;
61 CSSFontSelector::CSSFontSelector(Document& document)
62 : m_document(&document)
63 , m_cssFontFaceSet(CSSFontFaceSet::create())
64 , m_beginLoadingTimer(*this, &CSSFontSelector::beginLoadTimerFired)
65 , m_uniqueId(++fontSelectorId)
69 FontCache::singleton().addClient(*this);
70 m_cssFontFaceSet->addClient(*this);
73 CSSFontSelector::~CSSFontSelector()
76 m_cssFontFaceSet->removeClient(*this);
77 FontCache::singleton().removeClient(*this);
80 FontFaceSet& CSSFontSelector::fontFaceSet()
84 m_fontFaceSet = FontFaceSet::create(*m_document, m_cssFontFaceSet.get());
87 return *m_fontFaceSet;
90 bool CSSFontSelector::isEmpty() const
92 return !m_cssFontFaceSet->faceCount();
95 void CSSFontSelector::emptyCaches()
97 m_cssFontFaceSet->emptyCaches();
100 void CSSFontSelector::buildStarted()
102 m_buildIsUnderway = true;
103 m_cssFontFaceSet->purge();
106 ASSERT(m_cssConnectionsPossiblyToRemove.isEmpty());
107 ASSERT(m_cssConnectionsEncounteredDuringBuild.isEmpty());
108 ASSERT(m_stagingArea.isEmpty());
109 for (size_t i = 0; i < m_cssFontFaceSet->faceCount(); ++i) {
110 CSSFontFace& face = m_cssFontFaceSet.get()[i];
111 if (face.cssConnection())
112 m_cssConnectionsPossiblyToRemove.add(&face);
116 void CSSFontSelector::buildCompleted()
118 if (!m_buildIsUnderway)
121 m_buildIsUnderway = false;
123 // Some font faces weren't re-added during the build process.
124 for (auto& face : m_cssConnectionsPossiblyToRemove) {
125 auto* connection = face->cssConnection();
127 if (!m_cssConnectionsEncounteredDuringBuild.contains(connection))
128 m_cssFontFaceSet->remove(*face);
131 for (auto& item : m_stagingArea)
132 addFontFaceRule(item.styleRuleFontFace, item.isInitiatingElementInUserAgentShadowTree);
133 m_cssConnectionsEncounteredDuringBuild.clear();
134 m_stagingArea.clear();
135 m_cssConnectionsPossiblyToRemove.clear();
138 void CSSFontSelector::addFontFaceRule(StyleRuleFontFace& fontFaceRule, bool isInitiatingElementInUserAgentShadowTree)
140 if (m_buildIsUnderway) {
141 m_cssConnectionsEncounteredDuringBuild.add(&fontFaceRule);
142 m_stagingArea.append({fontFaceRule, isInitiatingElementInUserAgentShadowTree});
146 const StyleProperties& style = fontFaceRule.properties();
147 RefPtr<CSSValue> fontFamily = style.getPropertyCSSValue(CSSPropertyFontFamily);
148 RefPtr<CSSValue> fontStyle = style.getPropertyCSSValue(CSSPropertyFontStyle);
149 RefPtr<CSSValue> fontWeight = style.getPropertyCSSValue(CSSPropertyFontWeight);
150 RefPtr<CSSValue> fontStretch = style.getPropertyCSSValue(CSSPropertyFontStretch);
151 RefPtr<CSSValue> src = style.getPropertyCSSValue(CSSPropertySrc);
152 RefPtr<CSSValue> unicodeRange = style.getPropertyCSSValue(CSSPropertyUnicodeRange);
153 RefPtr<CSSValue> featureSettings = style.getPropertyCSSValue(CSSPropertyFontFeatureSettings);
154 RefPtr<CSSValue> variantLigatures = style.getPropertyCSSValue(CSSPropertyFontVariantLigatures);
155 RefPtr<CSSValue> variantPosition = style.getPropertyCSSValue(CSSPropertyFontVariantPosition);
156 RefPtr<CSSValue> variantCaps = style.getPropertyCSSValue(CSSPropertyFontVariantCaps);
157 RefPtr<CSSValue> variantNumeric = style.getPropertyCSSValue(CSSPropertyFontVariantNumeric);
158 RefPtr<CSSValue> variantAlternates = style.getPropertyCSSValue(CSSPropertyFontVariantAlternates);
159 RefPtr<CSSValue> variantEastAsian = style.getPropertyCSSValue(CSSPropertyFontVariantEastAsian);
160 if (!is<CSSValueList>(fontFamily.get()) || !is<CSSValueList>(src.get()) || (unicodeRange && !is<CSSValueList>(*unicodeRange)))
163 CSSValueList& familyList = downcast<CSSValueList>(*fontFamily);
164 if (!familyList.length())
167 CSSValueList* rangeList = downcast<CSSValueList>(unicodeRange.get());
169 CSSValueList& srcList = downcast<CSSValueList>(*src);
170 if (!srcList.length())
173 SetForScope<bool> creatingFont(m_creatingFont, true);
174 Ref<CSSFontFace> fontFace = CSSFontFace::create(this, &fontFaceRule);
176 if (!fontFace->setFamilies(*fontFamily))
179 fontFace->setStyle(*fontStyle);
181 fontFace->setWeight(*fontWeight);
183 fontFace->setStretch(*fontStretch);
184 if (rangeList && !fontFace->setUnicodeRange(*rangeList))
186 if (variantLigatures && !fontFace->setVariantLigatures(*variantLigatures))
188 if (variantPosition && !fontFace->setVariantPosition(*variantPosition))
190 if (variantCaps && !fontFace->setVariantCaps(*variantCaps))
192 if (variantNumeric && !fontFace->setVariantNumeric(*variantNumeric))
194 if (variantAlternates && !fontFace->setVariantAlternates(*variantAlternates))
196 if (variantEastAsian && !fontFace->setVariantEastAsian(*variantEastAsian))
199 fontFace->setFeatureSettings(*featureSettings);
201 CSSFontFace::appendSources(fontFace, srcList, m_document, isInitiatingElementInUserAgentShadowTree);
202 if (fontFace->allSourcesFailed())
205 if (RefPtr<CSSFontFace> existingFace = m_cssFontFaceSet->lookUpByCSSConnection(fontFaceRule)) {
206 // This adoption is fairly subtle. Script can trigger a purge of m_cssFontFaceSet at any time,
207 // which will cause us to just rely on the memory cache to retain the bytes of the file the next
208 // time we build up the CSSFontFaceSet. However, when the CSS Font Loading API is involved,
209 // the FontFace and FontFaceSet objects need to retain state. We create the new CSSFontFace object
210 // while the old one is still in scope so that the memory cache will be forced to retain the bytes
211 // of the resource. This means that the CachedFont will temporarily have two clients (until the
212 // old CSSFontFace goes out of scope, which should happen at the end of this "if" block). Because
213 // the CSSFontFaceSource objects will inspect their CachedFonts, the new CSSFontFace is smart enough
214 // to enter the correct state() during the next pump(). This approach of making a new CSSFontFace is
215 // simpler than computing and applying a diff of the StyleProperties.
216 m_cssFontFaceSet->remove(*existingFace);
217 if (auto* existingWrapper = existingFace->existingWrapper())
218 existingWrapper->adopt(fontFace.get());
221 m_cssFontFaceSet->add(fontFace.get());
225 void CSSFontSelector::registerForInvalidationCallbacks(FontSelectorClient& client)
227 m_clients.add(&client);
230 void CSSFontSelector::unregisterForInvalidationCallbacks(FontSelectorClient& client)
232 m_clients.remove(&client);
235 void CSSFontSelector::dispatchInvalidationCallbacks()
239 Vector<FontSelectorClient*> clients;
240 copyToVector(m_clients, clients);
241 for (size_t i = 0; i < clients.size(); ++i)
242 clients[i]->fontsNeedUpdate(*this);
245 void CSSFontSelector::fontLoaded()
247 dispatchInvalidationCallbacks();
250 void CSSFontSelector::fontModified()
252 if (!m_creatingFont && !m_buildIsUnderway)
253 dispatchInvalidationCallbacks();
256 void CSSFontSelector::fontCacheInvalidated()
258 dispatchInvalidationCallbacks();
261 static const AtomicString& resolveGenericFamily(Document* document, const FontDescription& fontDescription, const AtomicString& familyName)
266 const Settings& settings = document->settings();
268 UScriptCode script = fontDescription.script();
269 if (familyName == serifFamily)
270 return settings.serifFontFamily(script);
271 if (familyName == sansSerifFamily)
272 return settings.sansSerifFontFamily(script);
273 if (familyName == cursiveFamily)
274 return settings.cursiveFontFamily(script);
275 if (familyName == fantasyFamily)
276 return settings.fantasyFontFamily(script);
277 if (familyName == monospaceFamily)
278 return settings.fixedFontFamily(script);
279 if (familyName == pictographFamily)
280 return settings.pictographFontFamily(script);
281 if (familyName == standardFamily)
282 return settings.standardFontFamily(script);
287 FontRanges CSSFontSelector::fontRangesForFamily(const FontDescription& fontDescription, const AtomicString& familyName)
289 // If this ASSERT() fires, it usually means you forgot a document.updateStyleIfNeeded() somewhere.
290 ASSERT(!m_buildIsUnderway || m_computingRootStyleFontCount);
292 // FIXME: The spec (and Firefox) says user specified generic families (sans-serif etc.) should be resolved before the @font-face lookup too.
293 bool resolveGenericFamilyFirst = familyName == standardFamily;
295 AtomicString familyForLookup = resolveGenericFamilyFirst ? resolveGenericFamily(m_document, fontDescription, familyName) : familyName;
296 auto* face = m_cssFontFaceSet->fontFace(fontDescription.fontSelectionRequest(), familyForLookup);
298 if (!resolveGenericFamilyFirst)
299 familyForLookup = resolveGenericFamily(m_document, fontDescription, familyName);
300 return FontRanges(FontCache::singleton().fontForFamily(fontDescription, familyForLookup));
303 return face->fontRanges(fontDescription);
306 void CSSFontSelector::clearDocument()
309 ASSERT(!m_beginLoadingTimer.isActive());
310 ASSERT(m_fontsToBeginLoading.isEmpty());
314 m_beginLoadingTimer.stop();
316 CachedResourceLoader& cachedResourceLoader = m_document->cachedResourceLoader();
317 for (auto& fontHandle : m_fontsToBeginLoading) {
318 // Balances incrementRequestCount() in beginLoadingFontSoon().
319 cachedResourceLoader.decrementRequestCount(*fontHandle);
321 m_fontsToBeginLoading.clear();
323 m_document = nullptr;
325 m_cssFontFaceSet->clear();
329 void CSSFontSelector::beginLoadingFontSoon(CachedFont& font)
334 if (!m_document->settings().webFontsAlwaysFallBack()) {
335 m_fontsToBeginLoading.append(&font);
336 // Increment the request count now, in order to prevent didFinishLoad from being dispatched
337 // after this font has been requested but before it began loading. Balanced by
338 // decrementRequestCount() in beginLoadTimerFired() and in clearDocument().
339 m_document->cachedResourceLoader().incrementRequestCount(font);
341 m_beginLoadingTimer.startOneShot(0_s);
344 void CSSFontSelector::beginLoadTimerFired()
346 Vector<CachedResourceHandle<CachedFont>> fontsToBeginLoading;
347 fontsToBeginLoading.swap(m_fontsToBeginLoading);
349 // CSSFontSelector could get deleted via beginLoadIfNeeded() or loadDone() unless protected.
350 Ref<CSSFontSelector> protectedThis(*this);
352 CachedResourceLoader& cachedResourceLoader = m_document->cachedResourceLoader();
353 for (auto& fontHandle : fontsToBeginLoading) {
354 fontHandle->beginLoadIfNeeded(cachedResourceLoader);
355 // Balances incrementRequestCount() in beginLoadingFontSoon().
356 cachedResourceLoader.decrementRequestCount(*fontHandle);
358 // Ensure that if the request count reaches zero, the frame loader will know about it.
359 cachedResourceLoader.loadDone();
360 // New font loads may be triggered by layout after the document load is complete but before we have dispatched
361 // didFinishLoading for the frame. Make sure the delegate is always dispatched by checking explicitly.
362 if (m_document && m_document->frame())
363 m_document->frame()->loader().checkLoadComplete();
367 size_t CSSFontSelector::fallbackFontCount()
372 return m_document->settings().fontFallbackPrefersPictographs() ? 1 : 0;
375 RefPtr<Font> CSSFontSelector::fallbackFontAt(const FontDescription& fontDescription, size_t index)
377 ASSERT_UNUSED(index, !index);
382 if (!m_document->settings().fontFallbackPrefersPictographs())
385 return FontCache::singleton().fontForFamily(fontDescription, m_document->settings().pictographFontFamily());