2 * Copyright (C) 2013 Google Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND
14 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16 * ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE
17 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
18 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
19 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
20 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
21 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
22 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
27 #include "core/css/FontFaceSet.h"
29 #include "RuntimeEnabledFeatures.h"
30 #include "bindings/v8/Dictionary.h"
31 #include "bindings/v8/ScriptPromiseResolverWithContext.h"
32 #include "bindings/v8/ScriptState.h"
33 #include "core/css/CSSFontFaceLoadEvent.h"
34 #include "core/css/CSSFontSelector.h"
35 #include "core/css/parser/BisonCSSParser.h"
36 #include "core/css/CSSSegmentedFontFace.h"
37 #include "core/css/FontFaceCache.h"
38 #include "core/css/StylePropertySet.h"
39 #include "core/css/resolver/StyleResolver.h"
40 #include "core/dom/Document.h"
41 #include "core/dom/StyleEngine.h"
42 #include "core/frame/FrameView.h"
43 #include "core/frame/LocalFrame.h"
44 #include "public/platform/Platform.h"
48 static const int defaultFontSize = 10;
49 static const char defaultFontFamily[] = "sans-serif";
51 class LoadFontPromiseResolver FINAL : public FontFace::LoadFontCallback {
53 static PassRefPtrWillBeRawPtr<LoadFontPromiseResolver> create(FontFaceArray faces, ExecutionContext* context)
55 return adoptRefWillBeNoop(new LoadFontPromiseResolver(faces, context));
58 void loadFonts(ExecutionContext*);
59 ScriptPromise promise() { return m_resolver->promise(); }
61 virtual void notifyLoaded(FontFace*) OVERRIDE;
62 virtual void notifyError(FontFace*) OVERRIDE;
64 virtual void trace(Visitor*) OVERRIDE;
67 LoadFontPromiseResolver(FontFaceArray faces, ExecutionContext* context)
68 : m_numLoading(faces.size())
69 , m_errorOccured(false)
70 , m_resolver(ScriptPromiseResolverWithContext::create(ScriptState::current(toIsolate(context))))
72 m_fontFaces.swap(faces);
75 WillBeHeapVector<RefPtrWillBeMember<FontFace> > m_fontFaces;
78 RefPtr<ScriptPromiseResolverWithContext> m_resolver;
81 void LoadFontPromiseResolver::loadFonts(ExecutionContext* context)
84 m_resolver->resolve(m_fontFaces);
88 for (size_t i = 0; i < m_fontFaces.size(); i++)
89 m_fontFaces[i]->loadWithCallback(this, context);
92 void LoadFontPromiseResolver::notifyLoaded(FontFace* fontFace)
95 if (m_numLoading || m_errorOccured)
98 m_resolver->resolve(m_fontFaces);
101 void LoadFontPromiseResolver::notifyError(FontFace* fontFace)
104 if (!m_errorOccured) {
105 m_errorOccured = true;
106 m_resolver->reject(fontFace->error());
110 void LoadFontPromiseResolver::trace(Visitor* visitor)
112 visitor->trace(m_fontFaces);
113 LoadFontCallback::trace(visitor);
116 class FontsReadyPromiseResolver {
118 static PassOwnPtr<FontsReadyPromiseResolver> create(ScriptState* scriptState)
120 return adoptPtr(new FontsReadyPromiseResolver(scriptState));
123 void resolve(PassRefPtrWillBeRawPtr<FontFaceSet> fontFaceSet)
125 m_resolver->resolve(fontFaceSet);
128 ScriptPromise promise() { return m_resolver->promise(); }
131 explicit FontsReadyPromiseResolver(ScriptState* scriptState)
132 : m_resolver(ScriptPromiseResolverWithContext::create(scriptState))
136 RefPtr<ScriptPromiseResolverWithContext> m_resolver;
139 FontFaceSet::FontFaceSet(Document& document)
140 : ActiveDOMObject(&document)
141 , m_shouldFireLoadingEvent(false)
142 , m_asyncRunner(this, &FontFaceSet::handlePendingEventsAndPromises)
147 FontFaceSet::~FontFaceSet()
151 Document* FontFaceSet::document() const
153 return toDocument(executionContext());
156 bool FontFaceSet::inActiveDocumentContext() const
158 ExecutionContext* context = executionContext();
159 return context && toDocument(context)->isActive();
162 void FontFaceSet::addFontFacesToFontFaceCache(FontFaceCache* fontFaceCache, CSSFontSelector* fontSelector)
164 for (ListHashSet<RefPtrWillBeMember<FontFace> >::iterator it = m_nonCSSConnectedFaces.begin(); it != m_nonCSSConnectedFaces.end(); ++it)
165 fontFaceCache->addFontFace(fontSelector, *it, false);
168 const AtomicString& FontFaceSet::interfaceName() const
170 return EventTargetNames::FontFaceSet;
173 ExecutionContext* FontFaceSet::executionContext() const
175 return ActiveDOMObject::executionContext();
178 AtomicString FontFaceSet::status() const
180 DEFINE_STATIC_LOCAL(AtomicString, loading, ("loading", AtomicString::ConstructFromLiteral));
181 DEFINE_STATIC_LOCAL(AtomicString, loaded, ("loaded", AtomicString::ConstructFromLiteral));
182 return (!m_loadingFonts.isEmpty() || hasLoadedFonts()) ? loading : loaded;
185 void FontFaceSet::handlePendingEventsAndPromisesSoon()
187 // setPendingActivity() is unnecessary because m_asyncRunner will be
188 // automatically stopped on destruction.
189 m_asyncRunner.runAsync();
192 void FontFaceSet::didLayout()
194 if (document()->frame()->isMainFrame() && m_loadingFonts.isEmpty())
195 m_histogram.record();
196 if (!RuntimeEnabledFeatures::fontLoadEventsEnabled())
198 if (!m_loadingFonts.isEmpty() || (!hasLoadedFonts() && m_readyResolvers.isEmpty()))
200 handlePendingEventsAndPromisesSoon();
203 void FontFaceSet::handlePendingEventsAndPromises()
206 fireDoneEventIfPossible();
209 void FontFaceSet::fireLoadingEvent()
211 if (m_shouldFireLoadingEvent) {
212 m_shouldFireLoadingEvent = false;
213 dispatchEvent(CSSFontFaceLoadEvent::createForFontFaces(EventTypeNames::loading));
217 void FontFaceSet::suspend()
219 m_asyncRunner.suspend();
222 void FontFaceSet::resume()
224 m_asyncRunner.resume();
227 void FontFaceSet::stop()
229 m_asyncRunner.stop();
232 void FontFaceSet::beginFontLoading(FontFace* fontFace)
234 m_histogram.incrementCount();
235 addToLoadingFonts(fontFace);
238 void FontFaceSet::fontLoaded(FontFace* fontFace)
240 m_histogram.updateStatus(fontFace);
241 if (RuntimeEnabledFeatures::fontLoadEventsEnabled())
242 m_loadedFonts.append(fontFace);
243 removeFromLoadingFonts(fontFace);
246 void FontFaceSet::loadError(FontFace* fontFace)
248 m_histogram.updateStatus(fontFace);
249 if (RuntimeEnabledFeatures::fontLoadEventsEnabled())
250 m_failedFonts.append(fontFace);
251 removeFromLoadingFonts(fontFace);
254 void FontFaceSet::addToLoadingFonts(PassRefPtrWillBeRawPtr<FontFace> fontFace)
256 if (RuntimeEnabledFeatures::fontLoadEventsEnabled() && m_loadingFonts.isEmpty() && !hasLoadedFonts()) {
257 m_shouldFireLoadingEvent = true;
258 handlePendingEventsAndPromisesSoon();
260 m_loadingFonts.add(fontFace);
263 void FontFaceSet::removeFromLoadingFonts(PassRefPtrWillBeRawPtr<FontFace> fontFace)
265 m_loadingFonts.remove(fontFace);
266 if (RuntimeEnabledFeatures::fontLoadEventsEnabled() && m_loadingFonts.isEmpty())
267 handlePendingEventsAndPromisesSoon();
270 ScriptPromise FontFaceSet::ready(ScriptState* scriptState)
272 if (!inActiveDocumentContext())
273 return ScriptPromise();
274 OwnPtr<FontsReadyPromiseResolver> resolver = FontsReadyPromiseResolver::create(scriptState);
275 ScriptPromise promise = resolver->promise();
276 m_readyResolvers.append(resolver.release());
277 handlePendingEventsAndPromisesSoon();
281 void FontFaceSet::add(FontFace* fontFace, ExceptionState& exceptionState)
283 if (!inActiveDocumentContext())
286 exceptionState.throwTypeError("The argument is not a FontFace.");
289 if (m_nonCSSConnectedFaces.contains(fontFace))
291 if (isCSSConnectedFontFace(fontFace)) {
292 exceptionState.throwDOMException(InvalidModificationError, "Cannot add a CSS-connected FontFace.");
295 CSSFontSelector* fontSelector = document()->styleEngine()->fontSelector();
296 m_nonCSSConnectedFaces.add(fontFace);
297 fontSelector->fontFaceCache()->addFontFace(fontSelector, fontFace, false);
298 if (fontFace->loadStatus() == FontFace::Loading)
299 addToLoadingFonts(fontFace);
302 void FontFaceSet::clear()
304 if (!inActiveDocumentContext())
306 FontFaceCache* fontFaceCache = document()->styleEngine()->fontSelector()->fontFaceCache();
307 for (ListHashSet<RefPtrWillBeMember<FontFace> >::iterator it = m_nonCSSConnectedFaces.begin(); it != m_nonCSSConnectedFaces.end(); ++it) {
308 fontFaceCache->removeFontFace(it->get(), false);
309 if ((*it)->loadStatus() == FontFace::Loading)
310 removeFromLoadingFonts(*it);
312 m_nonCSSConnectedFaces.clear();
315 bool FontFaceSet::remove(FontFace* fontFace, ExceptionState& exceptionState)
317 if (!inActiveDocumentContext())
320 exceptionState.throwTypeError("The argument is not a FontFace.");
323 ListHashSet<RefPtrWillBeMember<FontFace> >::iterator it = m_nonCSSConnectedFaces.find(fontFace);
324 if (it != m_nonCSSConnectedFaces.end()) {
325 m_nonCSSConnectedFaces.remove(it);
326 document()->styleEngine()->fontSelector()->fontFaceCache()->removeFontFace(fontFace, false);
327 if (fontFace->loadStatus() == FontFace::Loading)
328 removeFromLoadingFonts(fontFace);
331 if (isCSSConnectedFontFace(fontFace))
332 exceptionState.throwDOMException(InvalidModificationError, "Cannot delete a CSS-connected FontFace.");
336 bool FontFaceSet::has(FontFace* fontFace, ExceptionState& exceptionState) const
338 if (!inActiveDocumentContext())
341 exceptionState.throwTypeError("The argument is not a FontFace.");
344 return m_nonCSSConnectedFaces.contains(fontFace) || isCSSConnectedFontFace(fontFace);
347 const ListHashSet<RefPtrWillBeMember<FontFace> >& FontFaceSet::cssConnectedFontFaceList() const
349 Document* d = document();
350 d->ensureStyleResolver(); // Flush pending style changes.
351 return d->styleEngine()->fontSelector()->fontFaceCache()->cssConnectedFontFaces();
354 bool FontFaceSet::isCSSConnectedFontFace(FontFace* fontFace) const
356 return cssConnectedFontFaceList().contains(fontFace);
359 void FontFaceSet::forEach(PassOwnPtr<FontFaceSetForEachCallback> callback, ScriptValue& thisArg) const
361 forEachInternal(callback, &thisArg);
364 void FontFaceSet::forEach(PassOwnPtr<FontFaceSetForEachCallback> callback) const
366 forEachInternal(callback, 0);
369 void FontFaceSet::forEachInternal(PassOwnPtr<FontFaceSetForEachCallback> callback, ScriptValue* thisArg) const
371 if (!inActiveDocumentContext())
373 const ListHashSet<RefPtrWillBeMember<FontFace> >& cssConnectedFaces = cssConnectedFontFaceList();
374 WillBeHeapVector<RefPtrWillBeMember<FontFace> > fontFaces;
375 fontFaces.reserveInitialCapacity(cssConnectedFaces.size() + m_nonCSSConnectedFaces.size());
376 for (ListHashSet<RefPtrWillBeMember<FontFace> >::const_iterator it = cssConnectedFaces.begin(); it != cssConnectedFaces.end(); ++it)
377 fontFaces.append(*it);
378 for (ListHashSet<RefPtrWillBeMember<FontFace> >::const_iterator it = m_nonCSSConnectedFaces.begin(); it != m_nonCSSConnectedFaces.end(); ++it)
379 fontFaces.append(*it);
381 for (size_t i = 0; i < fontFaces.size(); ++i) {
382 FontFace* face = fontFaces[i].get();
384 callback->handleItem(*thisArg, face, face, const_cast<FontFaceSet*>(this));
386 callback->handleItem(face, face, const_cast<FontFaceSet*>(this));
390 unsigned long FontFaceSet::size() const
392 if (!inActiveDocumentContext())
393 return m_nonCSSConnectedFaces.size();
394 return cssConnectedFontFaceList().size() + m_nonCSSConnectedFaces.size();
397 void FontFaceSet::fireDoneEventIfPossible()
399 if (m_shouldFireLoadingEvent)
401 if (!m_loadingFonts.isEmpty() || (!hasLoadedFonts() && m_readyResolvers.isEmpty()))
404 // If the layout was invalidated in between when we thought layout
405 // was updated and when we're ready to fire the event, just wait
406 // until after the next layout before firing events.
407 Document* d = document();
408 if (!d->view() || d->view()->needsLayout())
411 if (hasLoadedFonts()) {
412 RefPtrWillBeRawPtr<CSSFontFaceLoadEvent> doneEvent = nullptr;
413 RefPtrWillBeRawPtr<CSSFontFaceLoadEvent> errorEvent = nullptr;
414 doneEvent = CSSFontFaceLoadEvent::createForFontFaces(EventTypeNames::loadingdone, m_loadedFonts);
415 m_loadedFonts.clear();
416 if (!m_failedFonts.isEmpty()) {
417 errorEvent = CSSFontFaceLoadEvent::createForFontFaces(EventTypeNames::loadingerror, m_failedFonts);
418 m_failedFonts.clear();
420 dispatchEvent(doneEvent);
422 dispatchEvent(errorEvent);
425 if (!m_readyResolvers.isEmpty()) {
426 Vector<OwnPtr<FontsReadyPromiseResolver> > resolvers;
427 m_readyResolvers.swap(resolvers);
428 for (size_t index = 0; index < resolvers.size(); ++index)
429 resolvers[index]->resolve(this);
433 static const String& nullToSpace(const String& s)
435 DEFINE_STATIC_LOCAL(String, space, (" "));
436 return s.isNull() ? space : s;
439 ScriptPromise FontFaceSet::load(ScriptState* scriptState, const String& fontString, const String& text)
441 if (!inActiveDocumentContext())
442 return ScriptPromise();
445 if (!resolveFontStyle(fontString, font)) {
446 RefPtr<ScriptPromiseResolver> resolver = ScriptPromiseResolver::create(scriptState);
447 ScriptPromise promise = resolver->promise();
448 resolver->reject(DOMError::create(SyntaxError, "Could not resolve '" + fontString + "' as a font."));
452 FontFaceCache* fontFaceCache = document()->styleEngine()->fontSelector()->fontFaceCache();
454 for (const FontFamily* f = &font.fontDescription().family(); f; f = f->next()) {
455 CSSSegmentedFontFace* segmentedFontFace = fontFaceCache->get(font.fontDescription(), f->family());
456 if (segmentedFontFace)
457 segmentedFontFace->match(nullToSpace(text), faces);
460 RefPtrWillBeRawPtr<LoadFontPromiseResolver> resolver = LoadFontPromiseResolver::create(faces, executionContext());
461 ScriptPromise promise = resolver->promise();
462 resolver->loadFonts(executionContext()); // After this, resolver->promise() may return null.
466 bool FontFaceSet::check(const String& fontString, const String& text, ExceptionState& exceptionState)
468 if (!inActiveDocumentContext())
472 if (!resolveFontStyle(fontString, font)) {
473 exceptionState.throwDOMException(SyntaxError, "Could not resolve '" + fontString + "' as a font.");
477 FontFaceCache* fontFaceCache = document()->styleEngine()->fontSelector()->fontFaceCache();
478 for (const FontFamily* f = &font.fontDescription().family(); f; f = f->next()) {
479 CSSSegmentedFontFace* face = fontFaceCache->get(font.fontDescription(), f->family());
480 if (face && !face->checkFont(nullToSpace(text)))
486 bool FontFaceSet::resolveFontStyle(const String& fontString, Font& font)
488 if (fontString.isEmpty())
491 // Interpret fontString in the same way as the 'font' attribute of CanvasRenderingContext2D.
492 RefPtrWillBeRawPtr<MutableStylePropertySet> parsedStyle = MutableStylePropertySet::create();
493 BisonCSSParser::parseValue(parsedStyle.get(), CSSPropertyFont, fontString, true, HTMLStandardMode, 0);
494 if (parsedStyle->isEmpty())
497 String fontValue = parsedStyle->getPropertyValue(CSSPropertyFont);
498 if (fontValue == "inherit" || fontValue == "initial")
501 RefPtr<RenderStyle> style = RenderStyle::create();
503 FontFamily fontFamily;
504 fontFamily.setFamily(defaultFontFamily);
506 FontDescription defaultFontDescription;
507 defaultFontDescription.setFamily(fontFamily);
508 defaultFontDescription.setSpecifiedSize(defaultFontSize);
509 defaultFontDescription.setComputedSize(defaultFontSize);
511 style->setFontDescription(defaultFontDescription);
513 style->font().update(style->font().fontSelector());
515 // Now map the font property longhands into the style.
516 CSSPropertyValue properties[] = {
517 CSSPropertyValue(CSSPropertyFontFamily, *parsedStyle),
518 CSSPropertyValue(CSSPropertyFontStyle, *parsedStyle),
519 CSSPropertyValue(CSSPropertyFontVariant, *parsedStyle),
520 CSSPropertyValue(CSSPropertyFontWeight, *parsedStyle),
521 CSSPropertyValue(CSSPropertyFontSize, *parsedStyle),
522 CSSPropertyValue(CSSPropertyLineHeight, *parsedStyle),
524 StyleResolver& styleResolver = document()->ensureStyleResolver();
525 styleResolver.applyPropertiesToStyle(properties, WTF_ARRAY_LENGTH(properties), style.get());
527 font = style->font();
528 font.update(document()->styleEngine()->fontSelector());
532 void FontFaceSet::FontLoadHistogram::updateStatus(FontFace* fontFace)
534 if (m_status == Reported)
536 if (fontFace->hadBlankText())
537 m_status = HadBlankText;
538 else if (m_status == NoWebFonts)
539 m_status = DidNotHaveBlankText;
542 void FontFaceSet::FontLoadHistogram::record()
546 blink::Platform::current()->histogramCustomCounts("WebFont.WebFontsInPage", m_count, 1, 100, 50);
548 if (m_status == HadBlankText || m_status == DidNotHaveBlankText) {
549 blink::Platform::current()->histogramEnumeration("WebFont.HadBlankText", m_status == HadBlankText ? 1 : 0, 2);
554 static const char* supplementName()
556 return "FontFaceSet";
559 PassRefPtrWillBeRawPtr<FontFaceSet> FontFaceSet::from(Document& document)
561 RefPtrWillBeRawPtr<FontFaceSet> fonts = static_cast<FontFaceSet*>(SupplementType::from(document, supplementName()));
563 fonts = FontFaceSet::create(document);
564 SupplementType::provideTo(document, supplementName(), fonts);
567 return fonts.release();
570 void FontFaceSet::didLayout(Document& document)
572 if (FontFaceSet* fonts = static_cast<FontFaceSet*>(SupplementType::from(document, supplementName())))
577 void FontFaceSet::trace(Visitor* visitor)
579 visitor->trace(m_loadingFonts);
580 visitor->trace(m_loadedFonts);
581 visitor->trace(m_failedFonts);
582 visitor->trace(m_nonCSSConnectedFaces);
583 DocumentSupplement::trace(visitor);
587 } // namespace WebCore