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 "bindings/core/v8/Dictionary.h"
30 #include "bindings/core/v8/ScriptPromiseResolver.h"
31 #include "bindings/core/v8/ScriptState.h"
32 #include "core/css/CSSFontSelector.h"
33 #include "core/css/CSSSegmentedFontFace.h"
34 #include "core/css/FontFaceCache.h"
35 #include "core/css/FontFaceSetLoadEvent.h"
36 #include "core/css/StylePropertySet.h"
37 #include "core/css/parser/BisonCSSParser.h"
38 #include "core/css/resolver/StyleResolver.h"
39 #include "core/dom/Document.h"
40 #include "core/dom/StyleEngine.h"
41 #include "core/frame/FrameView.h"
42 #include "core/frame/LocalFrame.h"
43 #include "core/rendering/style/StyleInheritedData.h"
44 #include "platform/RuntimeEnabledFeatures.h"
45 #include "public/platform/Platform.h"
49 static const int defaultFontSize = 10;
50 static const char defaultFontFamily[] = "sans-serif";
52 class LoadFontPromiseResolver FINAL : public FontFace::LoadFontCallback {
54 static PassRefPtrWillBeRawPtr<LoadFontPromiseResolver> create(FontFaceArray faces, ScriptState* scriptState)
56 return adoptRefWillBeNoop(new LoadFontPromiseResolver(faces, scriptState));
59 void loadFonts(ExecutionContext*);
60 ScriptPromise promise() { return m_resolver->promise(); }
62 virtual void notifyLoaded(FontFace*) OVERRIDE;
63 virtual void notifyError(FontFace*) OVERRIDE;
65 virtual void trace(Visitor*) OVERRIDE;
68 LoadFontPromiseResolver(FontFaceArray faces, ScriptState* scriptState)
69 : m_numLoading(faces.size())
70 , m_errorOccured(false)
71 , m_resolver(ScriptPromiseResolver::create(scriptState))
73 m_fontFaces.swap(faces);
76 WillBeHeapVector<RefPtrWillBeMember<FontFace> > m_fontFaces;
79 RefPtr<ScriptPromiseResolver> m_resolver;
82 void LoadFontPromiseResolver::loadFonts(ExecutionContext* context)
85 m_resolver->resolve(m_fontFaces);
89 for (size_t i = 0; i < m_fontFaces.size(); i++)
90 m_fontFaces[i]->loadWithCallback(this, context);
93 void LoadFontPromiseResolver::notifyLoaded(FontFace* fontFace)
96 if (m_numLoading || m_errorOccured)
99 m_resolver->resolve(m_fontFaces);
102 void LoadFontPromiseResolver::notifyError(FontFace* fontFace)
105 if (!m_errorOccured) {
106 m_errorOccured = true;
107 m_resolver->reject(fontFace->error());
111 void LoadFontPromiseResolver::trace(Visitor* visitor)
113 visitor->trace(m_fontFaces);
114 LoadFontCallback::trace(visitor);
117 class FontsReadyPromiseResolver {
119 static PassOwnPtr<FontsReadyPromiseResolver> create(ScriptState* scriptState)
121 return adoptPtr(new FontsReadyPromiseResolver(scriptState));
124 void resolve(PassRefPtrWillBeRawPtr<FontFaceSet> fontFaceSet)
126 m_resolver->resolve(fontFaceSet);
129 ScriptPromise promise() { return m_resolver->promise(); }
132 explicit FontsReadyPromiseResolver(ScriptState* scriptState)
133 : m_resolver(ScriptPromiseResolver::create(scriptState))
137 RefPtr<ScriptPromiseResolver> m_resolver;
140 FontFaceSet::FontFaceSet(Document& document)
141 : ActiveDOMObject(&document)
142 , m_shouldFireLoadingEvent(false)
143 , m_asyncRunner(this, &FontFaceSet::handlePendingEventsAndPromises)
145 ScriptWrappable::init(this);
149 FontFaceSet::~FontFaceSet()
153 Document* FontFaceSet::document() const
155 return toDocument(executionContext());
158 bool FontFaceSet::inActiveDocumentContext() const
160 ExecutionContext* context = executionContext();
161 return context && toDocument(context)->isActive();
164 void FontFaceSet::addFontFacesToFontFaceCache(FontFaceCache* fontFaceCache, CSSFontSelector* fontSelector)
166 for (WillBeHeapListHashSet<RefPtrWillBeMember<FontFace> >::iterator it = m_nonCSSConnectedFaces.begin(); it != m_nonCSSConnectedFaces.end(); ++it)
167 fontFaceCache->addFontFace(fontSelector, *it, false);
170 const AtomicString& FontFaceSet::interfaceName() const
172 return EventTargetNames::FontFaceSet;
175 ExecutionContext* FontFaceSet::executionContext() const
177 return ActiveDOMObject::executionContext();
180 AtomicString FontFaceSet::status() const
182 DEFINE_STATIC_LOCAL(AtomicString, loading, ("loading", AtomicString::ConstructFromLiteral));
183 DEFINE_STATIC_LOCAL(AtomicString, loaded, ("loaded", AtomicString::ConstructFromLiteral));
184 return (!m_loadingFonts.isEmpty() || hasLoadedFonts()) ? loading : loaded;
187 void FontFaceSet::handlePendingEventsAndPromisesSoon()
189 // m_asyncRunner will be automatically stopped on destruction.
190 m_asyncRunner.runAsync();
193 void FontFaceSet::didLayout()
195 if (document()->frame()->isMainFrame() && m_loadingFonts.isEmpty())
196 m_histogram.record();
197 if (!RuntimeEnabledFeatures::fontLoadEventsEnabled())
199 if (!m_loadingFonts.isEmpty() || (!hasLoadedFonts() && m_readyResolvers.isEmpty()))
201 handlePendingEventsAndPromisesSoon();
204 void FontFaceSet::handlePendingEventsAndPromises()
207 fireDoneEventIfPossible();
210 void FontFaceSet::fireLoadingEvent()
212 if (m_shouldFireLoadingEvent) {
213 m_shouldFireLoadingEvent = false;
214 dispatchEvent(FontFaceSetLoadEvent::createForFontFaces(EventTypeNames::loading));
218 void FontFaceSet::suspend()
220 m_asyncRunner.suspend();
223 void FontFaceSet::resume()
225 m_asyncRunner.resume();
228 void FontFaceSet::stop()
230 m_asyncRunner.stop();
233 void FontFaceSet::beginFontLoading(FontFace* fontFace)
235 m_histogram.incrementCount();
236 addToLoadingFonts(fontFace);
239 void FontFaceSet::fontLoaded(FontFace* fontFace)
241 m_histogram.updateStatus(fontFace);
242 if (RuntimeEnabledFeatures::fontLoadEventsEnabled())
243 m_loadedFonts.append(fontFace);
244 removeFromLoadingFonts(fontFace);
247 void FontFaceSet::loadError(FontFace* fontFace)
249 m_histogram.updateStatus(fontFace);
250 if (RuntimeEnabledFeatures::fontLoadEventsEnabled())
251 m_failedFonts.append(fontFace);
252 removeFromLoadingFonts(fontFace);
255 void FontFaceSet::addToLoadingFonts(PassRefPtrWillBeRawPtr<FontFace> fontFace)
257 if (RuntimeEnabledFeatures::fontLoadEventsEnabled() && m_loadingFonts.isEmpty() && !hasLoadedFonts()) {
258 m_shouldFireLoadingEvent = true;
259 handlePendingEventsAndPromisesSoon();
261 m_loadingFonts.add(fontFace);
264 void FontFaceSet::removeFromLoadingFonts(PassRefPtrWillBeRawPtr<FontFace> fontFace)
266 m_loadingFonts.remove(fontFace);
267 if (RuntimeEnabledFeatures::fontLoadEventsEnabled() && m_loadingFonts.isEmpty())
268 handlePendingEventsAndPromisesSoon();
271 ScriptPromise FontFaceSet::ready(ScriptState* scriptState)
273 if (!inActiveDocumentContext())
274 return ScriptPromise();
275 OwnPtr<FontsReadyPromiseResolver> resolver = FontsReadyPromiseResolver::create(scriptState);
276 ScriptPromise promise = resolver->promise();
277 m_readyResolvers.append(resolver.release());
278 handlePendingEventsAndPromisesSoon();
282 void FontFaceSet::add(FontFace* fontFace, ExceptionState& exceptionState)
284 if (!inActiveDocumentContext())
287 exceptionState.throwTypeError("The argument is not a FontFace.");
290 if (m_nonCSSConnectedFaces.contains(fontFace))
292 if (isCSSConnectedFontFace(fontFace)) {
293 exceptionState.throwDOMException(InvalidModificationError, "Cannot add a CSS-connected FontFace.");
296 CSSFontSelector* fontSelector = document()->styleEngine()->fontSelector();
297 m_nonCSSConnectedFaces.add(fontFace);
298 fontSelector->fontFaceCache()->addFontFace(fontSelector, fontFace, false);
299 if (fontFace->loadStatus() == FontFace::Loading)
300 addToLoadingFonts(fontFace);
301 fontSelector->fontFaceInvalidated();
304 void FontFaceSet::clear()
306 if (!inActiveDocumentContext() || m_nonCSSConnectedFaces.isEmpty())
308 CSSFontSelector* fontSelector = document()->styleEngine()->fontSelector();
309 FontFaceCache* fontFaceCache = fontSelector->fontFaceCache();
310 for (WillBeHeapListHashSet<RefPtrWillBeMember<FontFace> >::iterator it = m_nonCSSConnectedFaces.begin(); it != m_nonCSSConnectedFaces.end(); ++it) {
311 fontFaceCache->removeFontFace(it->get(), false);
312 if ((*it)->loadStatus() == FontFace::Loading)
313 removeFromLoadingFonts(*it);
315 m_nonCSSConnectedFaces.clear();
316 fontSelector->fontFaceInvalidated();
319 bool FontFaceSet::remove(FontFace* fontFace, ExceptionState& exceptionState)
321 if (!inActiveDocumentContext())
324 exceptionState.throwTypeError("The argument is not a FontFace.");
327 WillBeHeapListHashSet<RefPtrWillBeMember<FontFace> >::iterator it = m_nonCSSConnectedFaces.find(fontFace);
328 if (it != m_nonCSSConnectedFaces.end()) {
329 m_nonCSSConnectedFaces.remove(it);
330 CSSFontSelector* fontSelector = document()->styleEngine()->fontSelector();
331 fontSelector->fontFaceCache()->removeFontFace(fontFace, false);
332 if (fontFace->loadStatus() == FontFace::Loading)
333 removeFromLoadingFonts(fontFace);
334 fontSelector->fontFaceInvalidated();
337 if (isCSSConnectedFontFace(fontFace))
338 exceptionState.throwDOMException(InvalidModificationError, "Cannot delete a CSS-connected FontFace.");
342 bool FontFaceSet::has(FontFace* fontFace, ExceptionState& exceptionState) const
344 if (!inActiveDocumentContext())
347 exceptionState.throwTypeError("The argument is not a FontFace.");
350 return m_nonCSSConnectedFaces.contains(fontFace) || isCSSConnectedFontFace(fontFace);
353 const WillBeHeapListHashSet<RefPtrWillBeMember<FontFace> >& FontFaceSet::cssConnectedFontFaceList() const
355 Document* d = document();
356 d->ensureStyleResolver(); // Flush pending style changes.
357 return d->styleEngine()->fontSelector()->fontFaceCache()->cssConnectedFontFaces();
360 bool FontFaceSet::isCSSConnectedFontFace(FontFace* fontFace) const
362 return cssConnectedFontFaceList().contains(fontFace);
365 void FontFaceSet::forEach(PassOwnPtr<FontFaceSetForEachCallback> callback, ScriptValue& thisArg) const
367 forEachInternal(callback, &thisArg);
370 void FontFaceSet::forEach(PassOwnPtr<FontFaceSetForEachCallback> callback) const
372 forEachInternal(callback, 0);
375 void FontFaceSet::forEachInternal(PassOwnPtr<FontFaceSetForEachCallback> callback, ScriptValue* thisArg) const
377 if (!inActiveDocumentContext())
379 const WillBeHeapListHashSet<RefPtrWillBeMember<FontFace> >& cssConnectedFaces = cssConnectedFontFaceList();
380 WillBeHeapVector<RefPtrWillBeMember<FontFace> > fontFaces;
381 fontFaces.reserveInitialCapacity(cssConnectedFaces.size() + m_nonCSSConnectedFaces.size());
382 for (WillBeHeapListHashSet<RefPtrWillBeMember<FontFace> >::const_iterator it = cssConnectedFaces.begin(); it != cssConnectedFaces.end(); ++it)
383 fontFaces.append(*it);
384 for (WillBeHeapListHashSet<RefPtrWillBeMember<FontFace> >::const_iterator it = m_nonCSSConnectedFaces.begin(); it != m_nonCSSConnectedFaces.end(); ++it)
385 fontFaces.append(*it);
387 for (size_t i = 0; i < fontFaces.size(); ++i) {
388 FontFace* face = fontFaces[i].get();
390 callback->handleItem(*thisArg, face, face, const_cast<FontFaceSet*>(this));
392 callback->handleItem(face, face, const_cast<FontFaceSet*>(this));
396 unsigned long FontFaceSet::size() const
398 if (!inActiveDocumentContext())
399 return m_nonCSSConnectedFaces.size();
400 return cssConnectedFontFaceList().size() + m_nonCSSConnectedFaces.size();
403 void FontFaceSet::fireDoneEventIfPossible()
405 if (m_shouldFireLoadingEvent)
407 if (!m_loadingFonts.isEmpty() || (!hasLoadedFonts() && m_readyResolvers.isEmpty()))
410 // If the layout was invalidated in between when we thought layout
411 // was updated and when we're ready to fire the event, just wait
412 // until after the next layout before firing events.
413 Document* d = document();
414 if (!d->view() || d->view()->needsLayout())
417 if (hasLoadedFonts()) {
418 RefPtrWillBeRawPtr<FontFaceSetLoadEvent> doneEvent = nullptr;
419 RefPtrWillBeRawPtr<FontFaceSetLoadEvent> errorEvent = nullptr;
420 doneEvent = FontFaceSetLoadEvent::createForFontFaces(EventTypeNames::loadingdone, m_loadedFonts);
421 m_loadedFonts.clear();
422 if (!m_failedFonts.isEmpty()) {
423 errorEvent = FontFaceSetLoadEvent::createForFontFaces(EventTypeNames::loadingerror, m_failedFonts);
424 m_failedFonts.clear();
426 dispatchEvent(doneEvent);
428 dispatchEvent(errorEvent);
431 if (!m_readyResolvers.isEmpty()) {
432 Vector<OwnPtr<FontsReadyPromiseResolver> > resolvers;
433 m_readyResolvers.swap(resolvers);
434 for (size_t index = 0; index < resolvers.size(); ++index)
435 resolvers[index]->resolve(this);
439 static const String& nullToSpace(const String& s)
441 DEFINE_STATIC_LOCAL(String, space, (" "));
442 return s.isNull() ? space : s;
445 ScriptPromise FontFaceSet::load(ScriptState* scriptState, const String& fontString, const String& text)
447 if (!inActiveDocumentContext())
448 return ScriptPromise();
451 if (!resolveFontStyle(fontString, font)) {
452 RefPtr<ScriptPromiseResolver> resolver = ScriptPromiseResolver::create(scriptState);
453 ScriptPromise promise = resolver->promise();
454 resolver->reject(DOMException::create(SyntaxError, "Could not resolve '" + fontString + "' as a font."));
458 FontFaceCache* fontFaceCache = document()->styleEngine()->fontSelector()->fontFaceCache();
460 for (const FontFamily* f = &font.fontDescription().family(); f; f = f->next()) {
461 CSSSegmentedFontFace* segmentedFontFace = fontFaceCache->get(font.fontDescription(), f->family());
462 if (segmentedFontFace)
463 segmentedFontFace->match(nullToSpace(text), faces);
466 RefPtrWillBeRawPtr<LoadFontPromiseResolver> resolver = LoadFontPromiseResolver::create(faces, scriptState);
467 ScriptPromise promise = resolver->promise();
468 resolver->loadFonts(executionContext()); // After this, resolver->promise() may return null.
472 bool FontFaceSet::check(const String& fontString, const String& text, ExceptionState& exceptionState)
474 if (!inActiveDocumentContext())
478 if (!resolveFontStyle(fontString, font)) {
479 exceptionState.throwDOMException(SyntaxError, "Could not resolve '" + fontString + "' as a font.");
483 CSSFontSelector* fontSelector = document()->styleEngine()->fontSelector();
484 FontFaceCache* fontFaceCache = fontSelector->fontFaceCache();
486 bool hasLoadedFaces = false;
487 for (const FontFamily* f = &font.fontDescription().family(); f; f = f->next()) {
488 CSSSegmentedFontFace* face = fontFaceCache->get(font.fontDescription(), f->family());
490 if (!face->checkFont(nullToSpace(text)))
492 hasLoadedFaces = true;
497 for (const FontFamily* f = &font.fontDescription().family(); f; f = f->next()) {
498 if (fontSelector->isPlatformFontAvailable(font.fontDescription(), f->family()))
504 bool FontFaceSet::resolveFontStyle(const String& fontString, Font& font)
506 if (fontString.isEmpty())
509 // Interpret fontString in the same way as the 'font' attribute of CanvasRenderingContext2D.
510 RefPtrWillBeRawPtr<MutableStylePropertySet> parsedStyle = MutableStylePropertySet::create();
511 BisonCSSParser::parseValue(parsedStyle.get(), CSSPropertyFont, fontString, true, HTMLStandardMode, 0);
512 if (parsedStyle->isEmpty())
515 String fontValue = parsedStyle->getPropertyValue(CSSPropertyFont);
516 if (fontValue == "inherit" || fontValue == "initial")
519 RefPtr<RenderStyle> style = RenderStyle::create();
521 FontFamily fontFamily;
522 fontFamily.setFamily(defaultFontFamily);
524 FontDescription defaultFontDescription;
525 defaultFontDescription.setFamily(fontFamily);
526 defaultFontDescription.setSpecifiedSize(defaultFontSize);
527 defaultFontDescription.setComputedSize(defaultFontSize);
529 style->setFontDescription(defaultFontDescription);
531 style->font().update(style->font().fontSelector());
533 // Now map the font property longhands into the style.
534 CSSPropertyValue properties[] = {
535 CSSPropertyValue(CSSPropertyFontFamily, *parsedStyle),
536 CSSPropertyValue(CSSPropertyFontStretch, *parsedStyle),
537 CSSPropertyValue(CSSPropertyFontStyle, *parsedStyle),
538 CSSPropertyValue(CSSPropertyFontVariant, *parsedStyle),
539 CSSPropertyValue(CSSPropertyFontWeight, *parsedStyle),
540 CSSPropertyValue(CSSPropertyFontSize, *parsedStyle),
541 CSSPropertyValue(CSSPropertyLineHeight, *parsedStyle),
543 StyleResolver& styleResolver = document()->ensureStyleResolver();
544 styleResolver.applyPropertiesToStyle(properties, WTF_ARRAY_LENGTH(properties), style.get());
546 font = style->font();
547 font.update(document()->styleEngine()->fontSelector());
551 void FontFaceSet::FontLoadHistogram::updateStatus(FontFace* fontFace)
553 if (m_status == Reported)
555 if (fontFace->hadBlankText())
556 m_status = HadBlankText;
557 else if (m_status == NoWebFonts)
558 m_status = DidNotHaveBlankText;
561 void FontFaceSet::FontLoadHistogram::record()
565 blink::Platform::current()->histogramCustomCounts("WebFont.WebFontsInPage", m_count, 1, 100, 50);
567 if (m_status == HadBlankText || m_status == DidNotHaveBlankText) {
568 blink::Platform::current()->histogramEnumeration("WebFont.HadBlankText", m_status == HadBlankText ? 1 : 0, 2);
573 static const char* supplementName()
575 return "FontFaceSet";
578 PassRefPtrWillBeRawPtr<FontFaceSet> FontFaceSet::from(Document& document)
580 RefPtrWillBeRawPtr<FontFaceSet> fonts = static_cast<FontFaceSet*>(SupplementType::from(document, supplementName()));
582 fonts = FontFaceSet::create(document);
583 SupplementType::provideTo(document, supplementName(), fonts);
586 return fonts.release();
589 void FontFaceSet::didLayout(Document& document)
591 if (FontFaceSet* fonts = static_cast<FontFaceSet*>(SupplementType::from(document, supplementName())))
596 void FontFaceSet::trace(Visitor* visitor)
598 visitor->trace(m_loadingFonts);
599 visitor->trace(m_loadedFonts);
600 visitor->trace(m_failedFonts);
601 visitor->trace(m_nonCSSConnectedFaces);
602 DocumentSupplement::trace(visitor);
603 EventTargetWithInlineData::trace(visitor);