1/*
2 * Copyright (C) 2013 Google Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are met:
6 *
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.
12 *
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
23 * DAMAGE.
24 */
25
26#include "config.h"
27#include "core/css/FontFaceSet.h"
28
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/CSSParser.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 "public/platform/Platform.h"
45
46namespace blink {
47
48static const int defaultFontSize = 10;
49static const char defaultFontFamily[] = "sans-serif";
50
51class LoadFontPromiseResolver FINAL : public FontFace::LoadFontCallback {
52public:
53    static PassRefPtrWillBeRawPtr<LoadFontPromiseResolver> create(FontFaceArray faces, ScriptState* scriptState)
54    {
55        return adoptRefWillBeNoop(new LoadFontPromiseResolver(faces, scriptState));
56    }
57
58    void loadFonts(ExecutionContext*);
59    ScriptPromise promise() { return m_resolver->promise(); }
60
61    virtual void notifyLoaded(FontFace*) OVERRIDE;
62    virtual void notifyError(FontFace*) OVERRIDE;
63
64    virtual void trace(Visitor*) OVERRIDE;
65
66private:
67    LoadFontPromiseResolver(FontFaceArray faces, ScriptState* scriptState)
68        : m_numLoading(faces.size())
69        , m_errorOccured(false)
70        , m_resolver(ScriptPromiseResolver::create(scriptState))
71    {
72        m_fontFaces.swap(faces);
73    }
74
75    WillBeHeapVector<RefPtrWillBeMember<FontFace> > m_fontFaces;
76    int m_numLoading;
77    bool m_errorOccured;
78    RefPtr<ScriptPromiseResolver> m_resolver;
79};
80
81void LoadFontPromiseResolver::loadFonts(ExecutionContext* context)
82{
83    if (!m_numLoading) {
84        m_resolver->resolve(m_fontFaces);
85        return;
86    }
87
88    for (size_t i = 0; i < m_fontFaces.size(); i++)
89        m_fontFaces[i]->loadWithCallback(this, context);
90}
91
92void LoadFontPromiseResolver::notifyLoaded(FontFace* fontFace)
93{
94    m_numLoading--;
95    if (m_numLoading || m_errorOccured)
96        return;
97
98    m_resolver->resolve(m_fontFaces);
99}
100
101void LoadFontPromiseResolver::notifyError(FontFace* fontFace)
102{
103    m_numLoading--;
104    if (!m_errorOccured) {
105        m_errorOccured = true;
106        m_resolver->reject(fontFace->error());
107    }
108}
109
110void LoadFontPromiseResolver::trace(Visitor* visitor)
111{
112    visitor->trace(m_fontFaces);
113    LoadFontCallback::trace(visitor);
114}
115
116class FontsReadyPromiseResolver {
117public:
118    static PassOwnPtr<FontsReadyPromiseResolver> create(ScriptState* scriptState)
119    {
120        return adoptPtr(new FontsReadyPromiseResolver(scriptState));
121    }
122
123    void resolve(PassRefPtrWillBeRawPtr<FontFaceSet> fontFaceSet)
124    {
125        m_resolver->resolve(fontFaceSet);
126    }
127
128    ScriptPromise promise() { return m_resolver->promise(); }
129
130private:
131    explicit FontsReadyPromiseResolver(ScriptState* scriptState)
132        : m_resolver(ScriptPromiseResolver::create(scriptState))
133    {
134    }
135
136    RefPtr<ScriptPromiseResolver> m_resolver;
137};
138
139FontFaceSet::FontFaceSet(Document& document)
140    : ActiveDOMObject(&document)
141    , m_shouldFireLoadingEvent(false)
142    , m_asyncRunner(this, &FontFaceSet::handlePendingEventsAndPromises)
143{
144    suspendIfNeeded();
145}
146
147FontFaceSet::~FontFaceSet()
148{
149}
150
151Document* FontFaceSet::document() const
152{
153    return toDocument(executionContext());
154}
155
156bool FontFaceSet::inActiveDocumentContext() const
157{
158    ExecutionContext* context = executionContext();
159    return context && toDocument(context)->isActive();
160}
161
162void FontFaceSet::addFontFacesToFontFaceCache(FontFaceCache* fontFaceCache, CSSFontSelector* fontSelector)
163{
164    for (WillBeHeapListHashSet<RefPtrWillBeMember<FontFace> >::iterator it = m_nonCSSConnectedFaces.begin(); it != m_nonCSSConnectedFaces.end(); ++it)
165        fontFaceCache->addFontFace(fontSelector, *it, false);
166}
167
168const AtomicString& FontFaceSet::interfaceName() const
169{
170    return EventTargetNames::FontFaceSet;
171}
172
173ExecutionContext* FontFaceSet::executionContext() const
174{
175    return ActiveDOMObject::executionContext();
176}
177
178AtomicString FontFaceSet::status() const
179{
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;
183}
184
185void FontFaceSet::handlePendingEventsAndPromisesSoon()
186{
187    // m_asyncRunner will be automatically stopped on destruction.
188    m_asyncRunner.runAsync();
189}
190
191void FontFaceSet::didLayout()
192{
193    if (document()->frame()->isMainFrame() && m_loadingFonts.isEmpty())
194        m_histogram.record();
195    if (!m_loadingFonts.isEmpty() || (!hasLoadedFonts() && m_readyResolvers.isEmpty()))
196        return;
197    handlePendingEventsAndPromisesSoon();
198}
199
200void FontFaceSet::handlePendingEventsAndPromises()
201{
202    fireLoadingEvent();
203    fireDoneEventIfPossible();
204}
205
206void FontFaceSet::fireLoadingEvent()
207{
208    if (m_shouldFireLoadingEvent) {
209        m_shouldFireLoadingEvent = false;
210        dispatchEvent(FontFaceSetLoadEvent::createForFontFaces(EventTypeNames::loading));
211    }
212}
213
214void FontFaceSet::suspend()
215{
216    m_asyncRunner.suspend();
217}
218
219void FontFaceSet::resume()
220{
221    m_asyncRunner.resume();
222}
223
224void FontFaceSet::stop()
225{
226    m_asyncRunner.stop();
227}
228
229void FontFaceSet::beginFontLoading(FontFace* fontFace)
230{
231    m_histogram.incrementCount();
232    addToLoadingFonts(fontFace);
233}
234
235void FontFaceSet::fontLoaded(FontFace* fontFace)
236{
237    m_histogram.updateStatus(fontFace);
238    m_loadedFonts.append(fontFace);
239    removeFromLoadingFonts(fontFace);
240}
241
242void FontFaceSet::loadError(FontFace* fontFace)
243{
244    m_histogram.updateStatus(fontFace);
245    m_failedFonts.append(fontFace);
246    removeFromLoadingFonts(fontFace);
247}
248
249void FontFaceSet::addToLoadingFonts(PassRefPtrWillBeRawPtr<FontFace> fontFace)
250{
251    if (m_loadingFonts.isEmpty() && !hasLoadedFonts()) {
252        m_shouldFireLoadingEvent = true;
253        handlePendingEventsAndPromisesSoon();
254    }
255    m_loadingFonts.add(fontFace);
256}
257
258void FontFaceSet::removeFromLoadingFonts(PassRefPtrWillBeRawPtr<FontFace> fontFace)
259{
260    m_loadingFonts.remove(fontFace);
261    if (m_loadingFonts.isEmpty())
262        handlePendingEventsAndPromisesSoon();
263}
264
265ScriptPromise FontFaceSet::ready(ScriptState* scriptState)
266{
267    if (!inActiveDocumentContext())
268        return ScriptPromise();
269    OwnPtr<FontsReadyPromiseResolver> resolver = FontsReadyPromiseResolver::create(scriptState);
270    ScriptPromise promise = resolver->promise();
271    m_readyResolvers.append(resolver.release());
272    handlePendingEventsAndPromisesSoon();
273    return promise;
274}
275
276void FontFaceSet::add(FontFace* fontFace, ExceptionState& exceptionState)
277{
278    if (!inActiveDocumentContext())
279        return;
280    if (!fontFace) {
281        exceptionState.throwTypeError("The argument is not a FontFace.");
282        return;
283    }
284    if (m_nonCSSConnectedFaces.contains(fontFace))
285        return;
286    if (isCSSConnectedFontFace(fontFace)) {
287        exceptionState.throwDOMException(InvalidModificationError, "Cannot add a CSS-connected FontFace.");
288        return;
289    }
290    CSSFontSelector* fontSelector = document()->styleEngine()->fontSelector();
291    m_nonCSSConnectedFaces.add(fontFace);
292    fontSelector->fontFaceCache()->addFontFace(fontSelector, fontFace, false);
293    if (fontFace->loadStatus() == FontFace::Loading)
294        addToLoadingFonts(fontFace);
295    fontSelector->fontFaceInvalidated();
296}
297
298void FontFaceSet::clear()
299{
300    if (!inActiveDocumentContext() || m_nonCSSConnectedFaces.isEmpty())
301        return;
302    CSSFontSelector* fontSelector = document()->styleEngine()->fontSelector();
303    FontFaceCache* fontFaceCache = fontSelector->fontFaceCache();
304    for (WillBeHeapListHashSet<RefPtrWillBeMember<FontFace> >::iterator it = m_nonCSSConnectedFaces.begin(); it != m_nonCSSConnectedFaces.end(); ++it) {
305        fontFaceCache->removeFontFace(it->get(), false);
306        if ((*it)->loadStatus() == FontFace::Loading)
307            removeFromLoadingFonts(*it);
308    }
309    m_nonCSSConnectedFaces.clear();
310    fontSelector->fontFaceInvalidated();
311}
312
313bool FontFaceSet::remove(FontFace* fontFace, ExceptionState& exceptionState)
314{
315    if (!inActiveDocumentContext())
316        return false;
317    if (!fontFace) {
318        exceptionState.throwTypeError("The argument is not a FontFace.");
319        return false;
320    }
321    WillBeHeapListHashSet<RefPtrWillBeMember<FontFace> >::iterator it = m_nonCSSConnectedFaces.find(fontFace);
322    if (it != m_nonCSSConnectedFaces.end()) {
323        m_nonCSSConnectedFaces.remove(it);
324        CSSFontSelector* fontSelector = document()->styleEngine()->fontSelector();
325        fontSelector->fontFaceCache()->removeFontFace(fontFace, false);
326        if (fontFace->loadStatus() == FontFace::Loading)
327            removeFromLoadingFonts(fontFace);
328        fontSelector->fontFaceInvalidated();
329        return true;
330    }
331    if (isCSSConnectedFontFace(fontFace))
332        exceptionState.throwDOMException(InvalidModificationError, "Cannot delete a CSS-connected FontFace.");
333    return false;
334}
335
336bool FontFaceSet::has(FontFace* fontFace, ExceptionState& exceptionState) const
337{
338    if (!inActiveDocumentContext())
339        return false;
340    if (!fontFace) {
341        exceptionState.throwTypeError("The argument is not a FontFace.");
342        return false;
343    }
344    return m_nonCSSConnectedFaces.contains(fontFace) || isCSSConnectedFontFace(fontFace);
345}
346
347const WillBeHeapListHashSet<RefPtrWillBeMember<FontFace> >& FontFaceSet::cssConnectedFontFaceList() const
348{
349    Document* d = document();
350    d->ensureStyleResolver(); // Flush pending style changes.
351    return d->styleEngine()->fontSelector()->fontFaceCache()->cssConnectedFontFaces();
352}
353
354bool FontFaceSet::isCSSConnectedFontFace(FontFace* fontFace) const
355{
356    return cssConnectedFontFaceList().contains(fontFace);
357}
358
359void FontFaceSet::forEach(FontFaceSetForEachCallback* callback, const ScriptValue& thisArg) const
360{
361    forEachInternal(callback, &thisArg);
362}
363
364void FontFaceSet::forEach(FontFaceSetForEachCallback* callback) const
365{
366    forEachInternal(callback, 0);
367}
368
369void FontFaceSet::forEachInternal(FontFaceSetForEachCallback* callback, const ScriptValue* thisArg) const
370{
371    if (!inActiveDocumentContext())
372        return;
373    const WillBeHeapListHashSet<RefPtrWillBeMember<FontFace> >& cssConnectedFaces = cssConnectedFontFaceList();
374    WillBeHeapVector<RefPtrWillBeMember<FontFace> > fontFaces;
375    fontFaces.reserveInitialCapacity(cssConnectedFaces.size() + m_nonCSSConnectedFaces.size());
376    for (WillBeHeapListHashSet<RefPtrWillBeMember<FontFace> >::const_iterator it = cssConnectedFaces.begin(); it != cssConnectedFaces.end(); ++it)
377        fontFaces.append(*it);
378    for (WillBeHeapListHashSet<RefPtrWillBeMember<FontFace> >::const_iterator it = m_nonCSSConnectedFaces.begin(); it != m_nonCSSConnectedFaces.end(); ++it)
379        fontFaces.append(*it);
380
381    for (size_t i = 0; i < fontFaces.size(); ++i) {
382        FontFace* face = fontFaces[i].get();
383        if (thisArg)
384            callback->handleItem(*thisArg, face, face, const_cast<FontFaceSet*>(this));
385        else
386            callback->handleItem(face, face, const_cast<FontFaceSet*>(this));
387    }
388}
389
390unsigned long FontFaceSet::size() const
391{
392    if (!inActiveDocumentContext())
393        return m_nonCSSConnectedFaces.size();
394    return cssConnectedFontFaceList().size() + m_nonCSSConnectedFaces.size();
395}
396
397void FontFaceSet::fireDoneEventIfPossible()
398{
399    if (m_shouldFireLoadingEvent)
400        return;
401    if (!m_loadingFonts.isEmpty() || (!hasLoadedFonts() && m_readyResolvers.isEmpty()))
402        return;
403
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())
409        return;
410
411    if (hasLoadedFonts()) {
412        RefPtrWillBeRawPtr<FontFaceSetLoadEvent> doneEvent = nullptr;
413        RefPtrWillBeRawPtr<FontFaceSetLoadEvent> errorEvent = nullptr;
414        doneEvent = FontFaceSetLoadEvent::createForFontFaces(EventTypeNames::loadingdone, m_loadedFonts);
415        m_loadedFonts.clear();
416        if (!m_failedFonts.isEmpty()) {
417            errorEvent = FontFaceSetLoadEvent::createForFontFaces(EventTypeNames::loadingerror, m_failedFonts);
418            m_failedFonts.clear();
419        }
420        dispatchEvent(doneEvent);
421        if (errorEvent)
422            dispatchEvent(errorEvent);
423    }
424
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);
430    }
431}
432
433ScriptPromise FontFaceSet::load(ScriptState* scriptState, const String& fontString, const String& text)
434{
435    if (!inActiveDocumentContext())
436        return ScriptPromise();
437
438    Font font;
439    if (!resolveFontStyle(fontString, font)) {
440        RefPtr<ScriptPromiseResolver> resolver = ScriptPromiseResolver::create(scriptState);
441        ScriptPromise promise = resolver->promise();
442        resolver->reject(DOMException::create(SyntaxError, "Could not resolve '" + fontString + "' as a font."));
443        return promise;
444    }
445
446    FontFaceCache* fontFaceCache = document()->styleEngine()->fontSelector()->fontFaceCache();
447    FontFaceArray faces;
448    for (const FontFamily* f = &font.fontDescription().family(); f; f = f->next()) {
449        CSSSegmentedFontFace* segmentedFontFace = fontFaceCache->get(font.fontDescription(), f->family());
450        if (segmentedFontFace)
451            segmentedFontFace->match(text, faces);
452    }
453
454    RefPtrWillBeRawPtr<LoadFontPromiseResolver> resolver = LoadFontPromiseResolver::create(faces, scriptState);
455    ScriptPromise promise = resolver->promise();
456    resolver->loadFonts(executionContext()); // After this, resolver->promise() may return null.
457    return promise;
458}
459
460bool FontFaceSet::check(const String& fontString, const String& text, ExceptionState& exceptionState)
461{
462    if (!inActiveDocumentContext())
463        return false;
464
465    Font font;
466    if (!resolveFontStyle(fontString, font)) {
467        exceptionState.throwDOMException(SyntaxError, "Could not resolve '" + fontString + "' as a font.");
468        return false;
469    }
470
471    CSSFontSelector* fontSelector = document()->styleEngine()->fontSelector();
472    FontFaceCache* fontFaceCache = fontSelector->fontFaceCache();
473
474    bool hasLoadedFaces = false;
475    for (const FontFamily* f = &font.fontDescription().family(); f; f = f->next()) {
476        CSSSegmentedFontFace* face = fontFaceCache->get(font.fontDescription(), f->family());
477        if (face) {
478            if (!face->checkFont(text))
479                return false;
480            hasLoadedFaces = true;
481        }
482    }
483    if (hasLoadedFaces)
484        return true;
485    for (const FontFamily* f = &font.fontDescription().family(); f; f = f->next()) {
486        if (fontSelector->isPlatformFontAvailable(font.fontDescription(), f->family()))
487            return true;
488    }
489    return false;
490}
491
492bool FontFaceSet::resolveFontStyle(const String& fontString, Font& font)
493{
494    if (fontString.isEmpty())
495        return false;
496
497    // Interpret fontString in the same way as the 'font' attribute of CanvasRenderingContext2D.
498    RefPtrWillBeRawPtr<MutableStylePropertySet> parsedStyle = MutableStylePropertySet::create();
499    CSSParser::parseValue(parsedStyle.get(), CSSPropertyFont, fontString, true, HTMLStandardMode, 0);
500    if (parsedStyle->isEmpty())
501        return false;
502
503    String fontValue = parsedStyle->getPropertyValue(CSSPropertyFont);
504    if (fontValue == "inherit" || fontValue == "initial")
505        return false;
506
507    RefPtr<RenderStyle> style = RenderStyle::create();
508
509    FontFamily fontFamily;
510    fontFamily.setFamily(defaultFontFamily);
511
512    FontDescription defaultFontDescription;
513    defaultFontDescription.setFamily(fontFamily);
514    defaultFontDescription.setSpecifiedSize(defaultFontSize);
515    defaultFontDescription.setComputedSize(defaultFontSize);
516
517    style->setFontDescription(defaultFontDescription);
518
519    style->font().update(style->font().fontSelector());
520
521    // Now map the font property longhands into the style.
522    CSSPropertyValue properties[] = {
523        CSSPropertyValue(CSSPropertyFontFamily, *parsedStyle),
524        CSSPropertyValue(CSSPropertyFontStretch, *parsedStyle),
525        CSSPropertyValue(CSSPropertyFontStyle, *parsedStyle),
526        CSSPropertyValue(CSSPropertyFontVariant, *parsedStyle),
527        CSSPropertyValue(CSSPropertyFontWeight, *parsedStyle),
528        CSSPropertyValue(CSSPropertyFontSize, *parsedStyle),
529        CSSPropertyValue(CSSPropertyLineHeight, *parsedStyle),
530    };
531    StyleResolver& styleResolver = document()->ensureStyleResolver();
532    styleResolver.applyPropertiesToStyle(properties, WTF_ARRAY_LENGTH(properties), style.get());
533
534    font = style->font();
535    font.update(document()->styleEngine()->fontSelector());
536    return true;
537}
538
539void FontFaceSet::FontLoadHistogram::updateStatus(FontFace* fontFace)
540{
541    if (m_status == Reported)
542        return;
543    if (fontFace->hadBlankText())
544        m_status = HadBlankText;
545    else if (m_status == NoWebFonts)
546        m_status = DidNotHaveBlankText;
547}
548
549void FontFaceSet::FontLoadHistogram::record()
550{
551    if (!m_recorded) {
552        m_recorded = true;
553        blink::Platform::current()->histogramCustomCounts("WebFont.WebFontsInPage", m_count, 1, 100, 50);
554    }
555    if (m_status == HadBlankText || m_status == DidNotHaveBlankText) {
556        blink::Platform::current()->histogramEnumeration("WebFont.HadBlankText", m_status == HadBlankText ? 1 : 0, 2);
557        m_status = Reported;
558    }
559}
560
561static const char* supplementName()
562{
563    return "FontFaceSet";
564}
565
566PassRefPtrWillBeRawPtr<FontFaceSet> FontFaceSet::from(Document& document)
567{
568    RefPtrWillBeRawPtr<FontFaceSet> fonts = static_cast<FontFaceSet*>(SupplementType::from(document, supplementName()));
569    if (!fonts) {
570        fonts = FontFaceSet::create(document);
571        SupplementType::provideTo(document, supplementName(), fonts);
572    }
573
574    return fonts.release();
575}
576
577void FontFaceSet::didLayout(Document& document)
578{
579    if (FontFaceSet* fonts = static_cast<FontFaceSet*>(SupplementType::from(document, supplementName())))
580        fonts->didLayout();
581}
582
583#if ENABLE(OILPAN)
584void FontFaceSet::trace(Visitor* visitor)
585{
586    visitor->trace(m_loadingFonts);
587    visitor->trace(m_loadedFonts);
588    visitor->trace(m_failedFonts);
589    visitor->trace(m_nonCSSConnectedFaces);
590    DocumentSupplement::trace(visitor);
591    EventTargetWithInlineData::trace(visitor);
592}
593#endif
594
595} // namespace blink
596