Home | History | Annotate | Download | only in css
      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 "RuntimeEnabledFeatures.h"
     30 #include "bindings/v8/Dictionary.h"
     31 #include "bindings/v8/ScriptPromiseResolver.h"
     32 #include "bindings/v8/ScriptScope.h"
     33 #include "bindings/v8/ScriptState.h"
     34 #include "core/css/CSSFontFaceLoadEvent.h"
     35 #include "core/css/CSSFontFaceSource.h"
     36 #include "core/css/CSSFontSelector.h"
     37 #include "core/css/CSSParser.h"
     38 #include "core/css/CSSSegmentedFontFace.h"
     39 #include "core/css/StylePropertySet.h"
     40 #include "core/css/resolver/StyleResolver.h"
     41 #include "core/dom/Document.h"
     42 #include "core/frame/Frame.h"
     43 #include "core/frame/FrameView.h"
     44 #include "public/platform/Platform.h"
     45 
     46 namespace WebCore {
     47 
     48 static const int defaultFontSize = 10;
     49 static const char defaultFontFamily[] = "sans-serif";
     50 
     51 class LoadFontPromiseResolver : public CSSSegmentedFontFace::LoadFontCallback {
     52 public:
     53     static PassRefPtr<LoadFontPromiseResolver> create(const FontFamily& family, ScriptPromise promise, ExecutionContext* context)
     54     {
     55         int numFamilies = 0;
     56         for (const FontFamily* f = &family; f; f = f->next())
     57             numFamilies++;
     58         return adoptRef<LoadFontPromiseResolver>(new LoadFontPromiseResolver(numFamilies, promise, context));
     59     }
     60 
     61     virtual void notifyLoaded(CSSSegmentedFontFace*) OVERRIDE;
     62     virtual void notifyError(CSSSegmentedFontFace*) OVERRIDE;
     63     void loaded(Document*);
     64     void error(Document*);
     65 
     66 private:
     67     LoadFontPromiseResolver(int numLoading, ScriptPromise promise, ExecutionContext* context)
     68         : m_numLoading(numLoading)
     69         , m_errorOccured(false)
     70         , m_scriptState(ScriptState::current())
     71         , m_resolver(ScriptPromiseResolver::create(promise, context))
     72     { }
     73 
     74     int m_numLoading;
     75     bool m_errorOccured;
     76     ScriptState* m_scriptState;
     77     RefPtr<ScriptPromiseResolver> m_resolver;
     78 };
     79 
     80 void LoadFontPromiseResolver::loaded(Document* document)
     81 {
     82     m_numLoading--;
     83     if (m_numLoading || !document)
     84         return;
     85 
     86     ScriptScope scope(m_scriptState);
     87     if (m_errorOccured)
     88         m_resolver->reject(ScriptValue::createNull());
     89     else
     90         m_resolver->resolve(ScriptValue::createNull());
     91 }
     92 
     93 void LoadFontPromiseResolver::error(Document* document)
     94 {
     95     m_errorOccured = true;
     96     loaded(document);
     97 }
     98 
     99 void LoadFontPromiseResolver::notifyLoaded(CSSSegmentedFontFace* face)
    100 {
    101     loaded(face->fontSelector()->document());
    102 }
    103 
    104 void LoadFontPromiseResolver::notifyError(CSSSegmentedFontFace* face)
    105 {
    106     error(face->fontSelector()->document());
    107 }
    108 
    109 class FontsReadyPromiseResolver {
    110 public:
    111     static PassOwnPtr<FontsReadyPromiseResolver> create(ScriptPromise promise, ExecutionContext* context)
    112     {
    113         return adoptPtr(new FontsReadyPromiseResolver(promise, context));
    114     }
    115 
    116     void resolve(PassRefPtr<FontFaceSet> fontFaceSet)
    117     {
    118         ScriptScope scope(m_scriptState);
    119         m_resolver->resolve(fontFaceSet);
    120     }
    121 
    122 private:
    123     FontsReadyPromiseResolver(ScriptPromise promise, ExecutionContext* context)
    124         : m_scriptState(ScriptState::current())
    125         , m_resolver(ScriptPromiseResolver::create(promise, context))
    126     { }
    127     ScriptState* m_scriptState;
    128     RefPtr<ScriptPromiseResolver> m_resolver;
    129 };
    130 
    131 FontFaceSet::FontFaceSet(Document* document)
    132     : ActiveDOMObject(document)
    133     , m_loadingCount(0)
    134     , m_shouldFireLoadingEvent(false)
    135     , m_asyncRunner(this, &FontFaceSet::handlePendingEventsAndPromises)
    136 {
    137     suspendIfNeeded();
    138 }
    139 
    140 FontFaceSet::~FontFaceSet()
    141 {
    142 }
    143 
    144 Document* FontFaceSet::document() const
    145 {
    146     return toDocument(executionContext());
    147 }
    148 
    149 const AtomicString& FontFaceSet::interfaceName() const
    150 {
    151     return EventTargetNames::FontFaceSet;
    152 }
    153 
    154 ExecutionContext* FontFaceSet::executionContext() const
    155 {
    156     return ActiveDOMObject::executionContext();
    157 }
    158 
    159 AtomicString FontFaceSet::status() const
    160 {
    161     DEFINE_STATIC_LOCAL(AtomicString, loading, ("loading", AtomicString::ConstructFromLiteral));
    162     DEFINE_STATIC_LOCAL(AtomicString, loaded, ("loaded", AtomicString::ConstructFromLiteral));
    163     return (m_loadingCount > 0 || hasLoadedFonts()) ? loading : loaded;
    164 }
    165 
    166 void FontFaceSet::handlePendingEventsAndPromisesSoon()
    167 {
    168     // setPendingActivity() is unnecessary because m_asyncRunner will be
    169     // automatically stopped on destruction.
    170     m_asyncRunner.runAsync();
    171 }
    172 
    173 void FontFaceSet::didLayout()
    174 {
    175     if (document()->frame()->isMainFrame())
    176         m_histogram.record();
    177     if (!RuntimeEnabledFeatures::fontLoadEventsEnabled())
    178         return;
    179     if (m_loadingCount || (!hasLoadedFonts() && m_readyResolvers.isEmpty()))
    180         return;
    181     handlePendingEventsAndPromisesSoon();
    182 }
    183 
    184 void FontFaceSet::handlePendingEventsAndPromises()
    185 {
    186     fireLoadingEvent();
    187     fireDoneEventIfPossible();
    188 }
    189 
    190 void FontFaceSet::fireLoadingEvent()
    191 {
    192     if (m_shouldFireLoadingEvent) {
    193         m_shouldFireLoadingEvent = false;
    194         dispatchEvent(CSSFontFaceLoadEvent::createForFontFaces(EventTypeNames::loading));
    195     }
    196 }
    197 
    198 void FontFaceSet::suspend()
    199 {
    200     m_asyncRunner.suspend();
    201 }
    202 
    203 void FontFaceSet::resume()
    204 {
    205     m_asyncRunner.resume();
    206 }
    207 
    208 void FontFaceSet::stop()
    209 {
    210     m_asyncRunner.stop();
    211 }
    212 
    213 void FontFaceSet::beginFontLoading(FontFace* fontFace)
    214 {
    215     m_histogram.incrementCount();
    216     if (!RuntimeEnabledFeatures::fontLoadEventsEnabled())
    217         return;
    218 
    219     if (!m_loadingCount && !hasLoadedFonts()) {
    220         ASSERT(!m_shouldFireLoadingEvent);
    221         m_shouldFireLoadingEvent = true;
    222         handlePendingEventsAndPromisesSoon();
    223     }
    224     ++m_loadingCount;
    225 }
    226 
    227 void FontFaceSet::fontLoaded(FontFace* fontFace)
    228 {
    229     if (!RuntimeEnabledFeatures::fontLoadEventsEnabled())
    230         return;
    231     m_loadedFonts.append(fontFace);
    232     queueDoneEvent(fontFace);
    233 }
    234 
    235 void FontFaceSet::loadError(FontFace* fontFace)
    236 {
    237     if (!RuntimeEnabledFeatures::fontLoadEventsEnabled())
    238         return;
    239     m_failedFonts.append(fontFace);
    240     queueDoneEvent(fontFace);
    241 }
    242 
    243 void FontFaceSet::queueDoneEvent(FontFace* fontFace)
    244 {
    245     ASSERT(m_loadingCount > 0);
    246     --m_loadingCount;
    247     if (!m_loadingCount)
    248         handlePendingEventsAndPromisesSoon();
    249 }
    250 
    251 ScriptPromise FontFaceSet::ready()
    252 {
    253     ScriptPromise promise = ScriptPromise::createPending(executionContext());
    254     OwnPtr<FontsReadyPromiseResolver> resolver = FontsReadyPromiseResolver::create(promise, executionContext());
    255     m_readyResolvers.append(resolver.release());
    256     handlePendingEventsAndPromisesSoon();
    257     return promise;
    258 }
    259 
    260 void FontFaceSet::fireDoneEventIfPossible()
    261 {
    262     if (m_shouldFireLoadingEvent)
    263         return;
    264     if (m_loadingCount || (!hasLoadedFonts() && m_readyResolvers.isEmpty()))
    265         return;
    266 
    267     // If the layout was invalidated in between when we thought layout
    268     // was updated and when we're ready to fire the event, just wait
    269     // until after the next layout before firing events.
    270     Document* d = document();
    271     if (!d->view() || d->view()->needsLayout())
    272         return;
    273 
    274     if (hasLoadedFonts()) {
    275         RefPtr<CSSFontFaceLoadEvent> doneEvent;
    276         RefPtr<CSSFontFaceLoadEvent> errorEvent;
    277         doneEvent = CSSFontFaceLoadEvent::createForFontFaces(EventTypeNames::loadingdone, m_loadedFonts);
    278         m_loadedFonts.clear();
    279         if (!m_failedFonts.isEmpty()) {
    280             errorEvent = CSSFontFaceLoadEvent::createForFontFaces(EventTypeNames::loadingerror, m_failedFonts);
    281             m_failedFonts.clear();
    282         }
    283         dispatchEvent(doneEvent);
    284         if (errorEvent)
    285             dispatchEvent(errorEvent);
    286     }
    287 
    288     if (!m_readyResolvers.isEmpty()) {
    289         Vector<OwnPtr<FontsReadyPromiseResolver> > resolvers;
    290         m_readyResolvers.swap(resolvers);
    291         for (size_t index = 0; index < resolvers.size(); ++index)
    292             resolvers[index]->resolve(this);
    293     }
    294 }
    295 
    296 static const String& nullToSpace(const String& s)
    297 {
    298     DEFINE_STATIC_LOCAL(String, space, (" "));
    299     return s.isNull() ? space : s;
    300 }
    301 
    302 Vector<RefPtr<FontFace> > FontFaceSet::match(const String& fontString, const String& text, ExceptionState& exceptionState)
    303 {
    304     Vector<RefPtr<FontFace> > matchedFonts;
    305 
    306     Font font;
    307     if (!resolveFontStyle(fontString, font)) {
    308         exceptionState.throwUninformativeAndGenericDOMException(SyntaxError);
    309         return matchedFonts;
    310     }
    311 
    312     for (const FontFamily* f = &font.family(); f; f = f->next()) {
    313         CSSSegmentedFontFace* face = document()->styleEngine()->fontSelector()->getFontFace(font.fontDescription(), f->family());
    314         if (face)
    315             matchedFonts.append(face->fontFaces(nullToSpace(text)));
    316     }
    317     return matchedFonts;
    318 }
    319 
    320 ScriptPromise FontFaceSet::load(const String& fontString, const String& text, ExceptionState& exceptionState)
    321 {
    322     Font font;
    323     if (!resolveFontStyle(fontString, font)) {
    324         exceptionState.throwUninformativeAndGenericDOMException(SyntaxError);
    325         return ScriptPromise();
    326     }
    327 
    328     Document* d = document();
    329     ScriptPromise promise = ScriptPromise::createPending(executionContext());
    330     RefPtr<LoadFontPromiseResolver> resolver = LoadFontPromiseResolver::create(font.family(), promise, executionContext());
    331     for (const FontFamily* f = &font.family(); f; f = f->next()) {
    332         CSSSegmentedFontFace* face = d->styleEngine()->fontSelector()->getFontFace(font.fontDescription(), f->family());
    333         if (!face) {
    334             resolver->error(d);
    335             continue;
    336         }
    337         face->loadFont(font.fontDescription(), nullToSpace(text), resolver);
    338     }
    339     return promise;
    340 }
    341 
    342 bool FontFaceSet::check(const String& fontString, const String& text, ExceptionState& exceptionState)
    343 {
    344     Font font;
    345     if (!resolveFontStyle(fontString, font)) {
    346         exceptionState.throwUninformativeAndGenericDOMException(SyntaxError);
    347         return false;
    348     }
    349 
    350     for (const FontFamily* f = &font.family(); f; f = f->next()) {
    351         CSSSegmentedFontFace* face = document()->styleEngine()->fontSelector()->getFontFace(font.fontDescription(), f->family());
    352         if (!face || !face->checkFont(nullToSpace(text)))
    353             return false;
    354     }
    355     return true;
    356 }
    357 
    358 bool FontFaceSet::resolveFontStyle(const String& fontString, Font& font)
    359 {
    360     if (fontString.isEmpty())
    361         return false;
    362 
    363     // Interpret fontString in the same way as the 'font' attribute of CanvasRenderingContext2D.
    364     RefPtr<MutableStylePropertySet> parsedStyle = MutableStylePropertySet::create();
    365     CSSParser::parseValue(parsedStyle.get(), CSSPropertyFont, fontString, true, HTMLStandardMode, 0);
    366     if (parsedStyle->isEmpty())
    367         return false;
    368 
    369     String fontValue = parsedStyle->getPropertyValue(CSSPropertyFont);
    370     if (fontValue == "inherit" || fontValue == "initial")
    371         return false;
    372 
    373     RefPtr<RenderStyle> style = RenderStyle::create();
    374 
    375     FontFamily fontFamily;
    376     fontFamily.setFamily(defaultFontFamily);
    377 
    378     FontDescription defaultFontDescription;
    379     defaultFontDescription.setFamily(fontFamily);
    380     defaultFontDescription.setSpecifiedSize(defaultFontSize);
    381     defaultFontDescription.setComputedSize(defaultFontSize);
    382 
    383     style->setFontDescription(defaultFontDescription);
    384 
    385     style->font().update(style->font().fontSelector());
    386 
    387     // Now map the font property longhands into the style.
    388     CSSPropertyValue properties[] = {
    389         CSSPropertyValue(CSSPropertyFontFamily, *parsedStyle),
    390         CSSPropertyValue(CSSPropertyFontStyle, *parsedStyle),
    391         CSSPropertyValue(CSSPropertyFontVariant, *parsedStyle),
    392         CSSPropertyValue(CSSPropertyFontWeight, *parsedStyle),
    393         CSSPropertyValue(CSSPropertyFontSize, *parsedStyle),
    394         CSSPropertyValue(CSSPropertyLineHeight, *parsedStyle),
    395     };
    396     StyleResolver& styleResolver = document()->ensureStyleResolver();
    397     styleResolver.applyPropertiesToStyle(properties, WTF_ARRAY_LENGTH(properties), style.get());
    398 
    399     font = style->font();
    400     font.update(document()->styleEngine()->fontSelector());
    401     return true;
    402 }
    403 
    404 void FontFaceSet::FontLoadHistogram::record()
    405 {
    406     if (m_recorded)
    407         return;
    408     m_recorded = true;
    409     blink::Platform::current()->histogramCustomCounts("WebFont.WebFontsInPage", m_count, 1, 100, 50);
    410 }
    411 
    412 static const char* supplementName()
    413 {
    414     return "FontFaceSet";
    415 }
    416 
    417 PassRefPtr<FontFaceSet> FontFaceSet::from(Document* document)
    418 {
    419     RefPtr<FontFaceSet> fonts = static_cast<FontFaceSet*>(SupplementType::from(document, supplementName()));
    420     if (!fonts) {
    421         fonts = FontFaceSet::create(document);
    422         SupplementType::provideTo(document, supplementName(), fonts);
    423     }
    424 
    425     return fonts.release();
    426 }
    427 
    428 void FontFaceSet::didLayout(Document* document)
    429 {
    430     if (FontFaceSet* fonts = static_cast<FontFaceSet*>(SupplementType::from(document, supplementName())))
    431         fonts->didLayout();
    432 }
    433 
    434 
    435 } // namespace WebCore
    436