1/*
2 * Copyright (C) 2007, 2008, 2011, 2013 Apple Inc. All rights reserved.
3 * (C) 2007, 2008 Nikolas Zimmermann <zimmermann@kde.org>
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
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.
13 *
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.
25 */
26
27#include "config.h"
28#include "CSSFontSelector.h"
29
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"
40#include "Document.h"
41#include "Font.h"
42#include "FontCache.h"
43#include "FontFace.h"
44#include "FontFaceSet.h"
45#include "FontSelectorClient.h"
46#include "Frame.h"
47#include "FrameLoader.h"
48#include "Logging.h"
49#include "ResourceLoadObserver.h"
50#include "RuntimeEnabledFeatures.h"
51#include "Settings.h"
52#include "StyleProperties.h"
53#include "StyleResolver.h"
54#include "StyleRule.h"
55#include "WebKitFontFamilyNames.h"
56#include <wtf/Ref.h>
57#include <wtf/SetForScope.h>
58#include <wtf/text/AtomicString.h>
59
60namespace WebCore {
61
62static unsigned fontSelectorId;
63
64CSSFontSelector::CSSFontSelector(Document& document)
65 : m_document(makeWeakPtr(document))
66 , m_cssFontFaceSet(CSSFontFaceSet::create(this))
67 , m_beginLoadingTimer(*this, &CSSFontSelector::beginLoadTimerFired)
68 , m_uniqueId(++fontSelectorId)
69 , m_version(0)
70{
71 ASSERT(m_document);
72 FontCache::singleton().addClient(*this);
73 m_cssFontFaceSet->addClient(*this);
74 LOG(Fonts, "CSSFontSelector %p ctor", this);
75}
76
77CSSFontSelector::~CSSFontSelector()
78{
79 LOG(Fonts, "CSSFontSelector %p dtor", this);
80
81 clearDocument();
82 m_cssFontFaceSet->removeClient(*this);
83 FontCache::singleton().removeClient(*this);
84}
85
86FontFaceSet& CSSFontSelector::fontFaceSet()
87{
88 if (!m_fontFaceSet) {
89 ASSERT(m_document);
90 m_fontFaceSet = FontFaceSet::create(*m_document, m_cssFontFaceSet.get());
91 }
92
93 return *m_fontFaceSet;
94}
95
96bool CSSFontSelector::isEmpty() const
97{
98 return !m_cssFontFaceSet->faceCount();
99}
100
101void CSSFontSelector::emptyCaches()
102{
103 m_cssFontFaceSet->emptyCaches();
104}
105
106void CSSFontSelector::buildStarted()
107{
108 m_buildIsUnderway = true;
109 m_cssFontFaceSet->purge();
110 ++m_version;
111
112 ASSERT(m_cssConnectionsPossiblyToRemove.isEmpty());
113 ASSERT(m_cssConnectionsEncounteredDuringBuild.isEmpty());
114 ASSERT(m_stagingArea.isEmpty());
115 for (size_t i = 0; i < m_cssFontFaceSet->faceCount(); ++i) {
116 CSSFontFace& face = m_cssFontFaceSet.get()[i];
117 if (face.cssConnection())
118 m_cssConnectionsPossiblyToRemove.add(&face);
119 }
120}
121
122void CSSFontSelector::buildCompleted()
123{
124 if (!m_buildIsUnderway)
125 return;
126
127 m_buildIsUnderway = false;
128
129 // Some font faces weren't re-added during the build process.
130 for (auto& face : m_cssConnectionsPossiblyToRemove) {
131 auto* connection = face->cssConnection();
132 ASSERT(connection);
133 if (!m_cssConnectionsEncounteredDuringBuild.contains(connection))
134 m_cssFontFaceSet->remove(*face);
135 }
136
137 for (auto& item : m_stagingArea)
138 addFontFaceRule(item.styleRuleFontFace, item.isInitiatingElementInUserAgentShadowTree);
139 m_cssConnectionsEncounteredDuringBuild.clear();
140 m_stagingArea.clear();
141 m_cssConnectionsPossiblyToRemove.clear();
142}
143
144void CSSFontSelector::addFontFaceRule(StyleRuleFontFace& fontFaceRule, bool isInitiatingElementInUserAgentShadowTree)
145{
146 if (m_buildIsUnderway) {
147 m_cssConnectionsEncounteredDuringBuild.add(&fontFaceRule);
148 m_stagingArea.append({fontFaceRule, isInitiatingElementInUserAgentShadowTree});
149 return;
150 }
151
152 const StyleProperties& style = fontFaceRule.properties();
153 RefPtr<CSSValue> fontFamily = style.getPropertyCSSValue(CSSPropertyFontFamily);
154 RefPtr<CSSValue> fontStyle = style.getPropertyCSSValue(CSSPropertyFontStyle);
155 RefPtr<CSSValue> fontWeight = style.getPropertyCSSValue(CSSPropertyFontWeight);
156 RefPtr<CSSValue> fontStretch = style.getPropertyCSSValue(CSSPropertyFontStretch);
157 RefPtr<CSSValue> src = style.getPropertyCSSValue(CSSPropertySrc);
158 RefPtr<CSSValue> unicodeRange = style.getPropertyCSSValue(CSSPropertyUnicodeRange);
159 RefPtr<CSSValue> featureSettings = style.getPropertyCSSValue(CSSPropertyFontFeatureSettings);
160 RefPtr<CSSValue> variantLigatures = style.getPropertyCSSValue(CSSPropertyFontVariantLigatures);
161 RefPtr<CSSValue> variantPosition = style.getPropertyCSSValue(CSSPropertyFontVariantPosition);
162 RefPtr<CSSValue> variantCaps = style.getPropertyCSSValue(CSSPropertyFontVariantCaps);
163 RefPtr<CSSValue> variantNumeric = style.getPropertyCSSValue(CSSPropertyFontVariantNumeric);
164 RefPtr<CSSValue> variantAlternates = style.getPropertyCSSValue(CSSPropertyFontVariantAlternates);
165 RefPtr<CSSValue> variantEastAsian = style.getPropertyCSSValue(CSSPropertyFontVariantEastAsian);
166 RefPtr<CSSValue> loadingBehavior = style.getPropertyCSSValue(CSSPropertyFontDisplay);
167 if (!is<CSSValueList>(fontFamily) || !is<CSSValueList>(src) || (unicodeRange && !is<CSSValueList>(*unicodeRange)))
168 return;
169
170 CSSValueList& familyList = downcast<CSSValueList>(*fontFamily);
171 if (!familyList.length())
172 return;
173
174 CSSValueList* rangeList = downcast<CSSValueList>(unicodeRange.get());
175
176 CSSValueList& srcList = downcast<CSSValueList>(*src);
177 if (!srcList.length())
178 return;
179
180 SetForScope<bool> creatingFont(m_creatingFont, true);
181 Ref<CSSFontFace> fontFace = CSSFontFace::create(this, &fontFaceRule);
182
183 if (!fontFace->setFamilies(*fontFamily))
184 return;
185 if (fontStyle)
186 fontFace->setStyle(*fontStyle);
187 if (fontWeight)
188 fontFace->setWeight(*fontWeight);
189 if (fontStretch)
190 fontFace->setStretch(*fontStretch);
191 if (rangeList && !fontFace->setUnicodeRange(*rangeList))
192 return;
193 if (variantLigatures && !fontFace->setVariantLigatures(*variantLigatures))
194 return;
195 if (variantPosition && !fontFace->setVariantPosition(*variantPosition))
196 return;
197 if (variantCaps && !fontFace->setVariantCaps(*variantCaps))
198 return;
199 if (variantNumeric && !fontFace->setVariantNumeric(*variantNumeric))
200 return;
201 if (variantAlternates && !fontFace->setVariantAlternates(*variantAlternates))
202 return;
203 if (variantEastAsian && !fontFace->setVariantEastAsian(*variantEastAsian))
204 return;
205 if (featureSettings)
206 fontFace->setFeatureSettings(*featureSettings);
207 if (loadingBehavior)
208 fontFace->setLoadingBehavior(*loadingBehavior);
209
210 CSSFontFace::appendSources(fontFace, srcList, m_document.get(), isInitiatingElementInUserAgentShadowTree);
211 if (fontFace->computeFailureState())
212 return;
213
214 if (RefPtr<CSSFontFace> existingFace = m_cssFontFaceSet->lookUpByCSSConnection(fontFaceRule)) {
215 // This adoption is fairly subtle. Script can trigger a purge of m_cssFontFaceSet at any time,
216 // which will cause us to just rely on the memory cache to retain the bytes of the file the next
217 // time we build up the CSSFontFaceSet. However, when the CSS Font Loading API is involved,
218 // the FontFace and FontFaceSet objects need to retain state. We create the new CSSFontFace object
219 // while the old one is still in scope so that the memory cache will be forced to retain the bytes
220 // of the resource. This means that the CachedFont will temporarily have two clients (until the
221 // old CSSFontFace goes out of scope, which should happen at the end of this "if" block). Because
222 // the CSSFontFaceSource objects will inspect their CachedFonts, the new CSSFontFace is smart enough
223 // to enter the correct state() during the next pump(). This approach of making a new CSSFontFace is
224 // simpler than computing and applying a diff of the StyleProperties.
225 m_cssFontFaceSet->remove(*existingFace);
226 if (auto* existingWrapper = existingFace->existingWrapper())
227 existingWrapper->adopt(fontFace.get());
228 }
229
230 m_cssFontFaceSet->add(fontFace.get());
231 ++m_version;
232}
233
234void CSSFontSelector::registerForInvalidationCallbacks(FontSelectorClient& client)
235{
236 m_clients.add(&client);
237}
238
239void CSSFontSelector::unregisterForInvalidationCallbacks(FontSelectorClient& client)
240{
241 m_clients.remove(&client);
242}
243
244void CSSFontSelector::dispatchInvalidationCallbacks()
245{
246 ++m_version;
247
248 for (auto& client : copyToVector(m_clients))
249 client->fontsNeedUpdate(*this);
250}
251
252void CSSFontSelector::opportunisticallyStartFontDataURLLoading(const FontCascadeDescription& description, const AtomicString& familyName)
253{
254 const auto& segmentedFontFace = m_cssFontFaceSet->fontFace(description.fontSelectionRequest(), familyName);
255 if (!segmentedFontFace)
256 return;
257 for (auto& face : segmentedFontFace->constituentFaces())
258 face->opportunisticallyStartFontDataURLLoading(*this);
259}
260
261void CSSFontSelector::fontLoaded()
262{
263 dispatchInvalidationCallbacks();
264}
265
266void CSSFontSelector::fontModified()
267{
268 if (!m_creatingFont && !m_buildIsUnderway)
269 dispatchInvalidationCallbacks();
270}
271
272void CSSFontSelector::fontCacheInvalidated()
273{
274 dispatchInvalidationCallbacks();
275}
276
277static AtomicString resolveGenericFamily(Document* document, const FontDescription& fontDescription, const AtomicString& familyName)
278{
279 auto platformResult = FontDescription::platformResolveGenericFamily(fontDescription.script(), fontDescription.locale(), familyName);
280 if (!platformResult.isNull())
281 return platformResult;
282
283 if (!document)
284 return familyName;
285
286 const Settings& settings = document->settings();
287
288 UScriptCode script = fontDescription.script();
289 if (familyName == serifFamily)
290 return settings.serifFontFamily(script);
291 if (familyName == sansSerifFamily)
292 return settings.sansSerifFontFamily(script);
293 if (familyName == cursiveFamily)
294 return settings.cursiveFontFamily(script);
295 if (familyName == fantasyFamily)
296 return settings.fantasyFontFamily(script);
297 if (familyName == monospaceFamily)
298 return settings.fixedFontFamily(script);
299 if (familyName == pictographFamily)
300 return settings.pictographFontFamily(script);
301 if (familyName == standardFamily)
302 return settings.standardFontFamily(script);
303
304 return familyName;
305}
306
307FontRanges CSSFontSelector::fontRangesForFamily(const FontDescription& fontDescription, const AtomicString& familyName)
308{
309 // If this ASSERT() fires, it usually means you forgot a document.updateStyleIfNeeded() somewhere.
310 ASSERT(!m_buildIsUnderway || m_computingRootStyleFontCount);
311
312 // FIXME: The spec (and Firefox) says user specified generic families (sans-serif etc.) should be resolved before the @font-face lookup too.
313 bool resolveGenericFamilyFirst = familyName == standardFamily;
314
315 AtomicString familyForLookup = resolveGenericFamilyFirst ? resolveGenericFamily(m_document.get(), fontDescription, familyName) : familyName;
316 auto* face = m_cssFontFaceSet->fontFace(fontDescription.fontSelectionRequest(), familyForLookup);
317 if (face) {
318 if (RuntimeEnabledFeatures::sharedFeatures().webAPIStatisticsEnabled()) {
319 if (m_document)
320 ResourceLoadObserver::shared().logFontLoad(*m_document, familyForLookup.string(), true);
321 }
322 return face->fontRanges(fontDescription);
323 }
324 if (!resolveGenericFamilyFirst)
325 familyForLookup = resolveGenericFamily(m_document.get(), fontDescription, familyName);
326 auto font = FontCache::singleton().fontForFamily(fontDescription, familyForLookup);
327 if (RuntimeEnabledFeatures::sharedFeatures().webAPIStatisticsEnabled()) {
328 if (m_document)
329 ResourceLoadObserver::shared().logFontLoad(*m_document, familyForLookup.string(), !!font);
330 }
331 return FontRanges { WTFMove(font) };
332}
333
334void CSSFontSelector::clearDocument()
335{
336 if (!m_document) {
337 ASSERT(!m_beginLoadingTimer.isActive());
338 ASSERT(m_fontsToBeginLoading.isEmpty());
339 return;
340 }
341
342 m_beginLoadingTimer.stop();
343
344 CachedResourceLoader& cachedResourceLoader = m_document->cachedResourceLoader();
345 for (auto& fontHandle : m_fontsToBeginLoading) {
346 // Balances incrementRequestCount() in beginLoadingFontSoon().
347 cachedResourceLoader.decrementRequestCount(*fontHandle);
348 }
349 m_fontsToBeginLoading.clear();
350
351 m_document = nullptr;
352
353 m_cssFontFaceSet->clear();
354 m_clients.clear();
355}
356
357void CSSFontSelector::beginLoadingFontSoon(CachedFont& font)
358{
359 if (!m_document)
360 return;
361
362 m_fontsToBeginLoading.append(&font);
363 // Increment the request count now, in order to prevent didFinishLoad from being dispatched
364 // after this font has been requested but before it began loading. Balanced by
365 // decrementRequestCount() in beginLoadTimerFired() and in clearDocument().
366 m_document->cachedResourceLoader().incrementRequestCount(font);
367
368 m_beginLoadingTimer.startOneShot(0_s);
369}
370
371void CSSFontSelector::beginLoadTimerFired()
372{
373 Vector<CachedResourceHandle<CachedFont>> fontsToBeginLoading;
374 fontsToBeginLoading.swap(m_fontsToBeginLoading);
375
376 // CSSFontSelector could get deleted via beginLoadIfNeeded() or loadDone() unless protected.
377 Ref<CSSFontSelector> protectedThis(*this);
378
379 CachedResourceLoader& cachedResourceLoader = m_document->cachedResourceLoader();
380 for (auto& fontHandle : fontsToBeginLoading) {
381 fontHandle->beginLoadIfNeeded(cachedResourceLoader);
382 // Balances incrementRequestCount() in beginLoadingFontSoon().
383 cachedResourceLoader.decrementRequestCount(*fontHandle);
384 }
385 // Ensure that if the request count reaches zero, the frame loader will know about it.
386 // New font loads may be triggered by layout after the document load is complete but before we have dispatched
387 // didFinishLoading for the frame. Make sure the delegate is always dispatched by checking explicitly.
388 if (m_document && m_document->frame())
389 m_document->frame()->loader().checkLoadComplete();
390 cachedResourceLoader.loadDone(LoadCompletionType::Finish);
391}
392
393
394size_t CSSFontSelector::fallbackFontCount()
395{
396 if (!m_document)
397 return 0;
398
399 return m_document->settings().fontFallbackPrefersPictographs() ? 1 : 0;
400}
401
402RefPtr<Font> CSSFontSelector::fallbackFontAt(const FontDescription& fontDescription, size_t index)
403{
404 ASSERT_UNUSED(index, !index);
405
406 if (!m_document)
407 return nullptr;
408
409 if (!m_document->settings().fontFallbackPrefersPictographs())
410 return nullptr;
411 auto& pictographFontFamily = m_document->settings().pictographFontFamily();
412 auto font = FontCache::singleton().fontForFamily(fontDescription, pictographFontFamily);
413 if (RuntimeEnabledFeatures::sharedFeatures().webAPIStatisticsEnabled())
414 ResourceLoadObserver::shared().logFontLoad(*m_document, pictographFontFamily.string(), !!font);
415
416 return font;
417}
418
419}
420