This source file includes following definitions.
- create
- promise
- m_resolver
- loadFonts
- notifyLoaded
- notifyError
- create
- resolve
- promise
- m_resolver
- m_asyncRunner
- document
- inActiveDocumentContext
- addFontFacesToFontFaceCache
- interfaceName
- executionContext
- status
- handlePendingEventsAndPromisesSoon
- didLayout
- handlePendingEventsAndPromises
- fireLoadingEvent
- suspend
- resume
- stop
- beginFontLoading
- fontLoaded
- loadError
- addToLoadingFonts
- removeFromLoadingFonts
- ready
- add
- clear
- remove
- has
- cssConnectedFontFaceList
- isCSSConnectedFontFace
- forEach
- forEach
- forEachInternal
- size
- fireDoneEventIfPossible
- nullToSpace
- load
- check
- resolveFontStyle
- updateStatus
- record
- supplementName
- from
- didLayout
#include "config.h"
#include "core/css/FontFaceSet.h"
#include "RuntimeEnabledFeatures.h"
#include "bindings/v8/Dictionary.h"
#include "bindings/v8/ScriptPromiseResolver.h"
#include "bindings/v8/ScriptScope.h"
#include "bindings/v8/ScriptState.h"
#include "core/css/CSSFontFaceLoadEvent.h"
#include "core/css/CSSFontSelector.h"
#include "core/css/parser/BisonCSSParser.h"
#include "core/css/CSSSegmentedFontFace.h"
#include "core/css/FontFaceCache.h"
#include "core/css/StylePropertySet.h"
#include "core/css/resolver/StyleResolver.h"
#include "core/dom/Document.h"
#include "core/dom/StyleEngine.h"
#include "core/frame/FrameView.h"
#include "core/frame/LocalFrame.h"
#include "public/platform/Platform.h"
namespace WebCore {
static const int defaultFontSize = 10;
static const char defaultFontFamily[] = "sans-serif";
class LoadFontPromiseResolver : public FontFace::LoadFontCallback {
public:
static PassRefPtr<LoadFontPromiseResolver> create(FontFaceArray faces, ExecutionContext* context)
{
return adoptRef(new LoadFontPromiseResolver(faces, context));
}
void loadFonts(ExecutionContext*);
ScriptPromise promise() { return m_resolver->promise(); }
virtual void notifyLoaded(FontFace*) OVERRIDE;
virtual void notifyError(FontFace*) OVERRIDE;
private:
LoadFontPromiseResolver(FontFaceArray faces, ExecutionContext* context)
: m_numLoading(faces.size())
, m_errorOccured(false)
, m_scriptState(ScriptState::current())
, m_resolver(ScriptPromiseResolver::create(context))
{
m_fontFaces.swap(faces);
}
FontFaceArray m_fontFaces;
int m_numLoading;
bool m_errorOccured;
ScriptState* m_scriptState;
RefPtr<ScriptPromiseResolver> m_resolver;
};
void LoadFontPromiseResolver::loadFonts(ExecutionContext* context)
{
if (!m_numLoading) {
m_resolver->resolve(m_fontFaces);
return;
}
for (size_t i = 0; i < m_fontFaces.size(); i++)
m_fontFaces[i]->loadWithCallback(this, context);
}
void LoadFontPromiseResolver::notifyLoaded(FontFace* fontFace)
{
m_numLoading--;
if (m_numLoading || m_errorOccured)
return;
ScriptScope scope(m_scriptState);
m_resolver->resolve(m_fontFaces);
}
void LoadFontPromiseResolver::notifyError(FontFace* fontFace)
{
m_numLoading--;
if (!m_errorOccured) {
m_errorOccured = true;
ScriptScope scope(m_scriptState);
m_resolver->reject(fontFace->error());
}
}
class FontsReadyPromiseResolver {
public:
static PassOwnPtr<FontsReadyPromiseResolver> create(ExecutionContext* context)
{
return adoptPtr(new FontsReadyPromiseResolver(context));
}
void resolve(PassRefPtr<FontFaceSet> fontFaceSet)
{
ScriptScope scope(m_scriptState);
m_resolver->resolve(fontFaceSet);
}
ScriptPromise promise() { return m_resolver->promise(); }
private:
FontsReadyPromiseResolver(ExecutionContext* context)
: m_scriptState(ScriptState::current())
, m_resolver(ScriptPromiseResolver::create(context))
{ }
ScriptState* m_scriptState;
RefPtr<ScriptPromiseResolver> m_resolver;
};
FontFaceSet::FontFaceSet(Document& document)
: ActiveDOMObject(&document)
, m_shouldFireLoadingEvent(false)
, m_asyncRunner(this, &FontFaceSet::handlePendingEventsAndPromises)
{
suspendIfNeeded();
}
FontFaceSet::~FontFaceSet()
{
}
Document* FontFaceSet::document() const
{
return toDocument(executionContext());
}
bool FontFaceSet::inActiveDocumentContext() const
{
ExecutionContext* context = executionContext();
return context && toDocument(context)->isActive();
}
void FontFaceSet::addFontFacesToFontFaceCache(FontFaceCache* fontFaceCache, CSSFontSelector* fontSelector)
{
for (ListHashSet<RefPtr<FontFace> >::iterator it = m_nonCSSConnectedFaces.begin(); it != m_nonCSSConnectedFaces.end(); ++it)
fontFaceCache->addFontFace(fontSelector, *it, false);
}
const AtomicString& FontFaceSet::interfaceName() const
{
return EventTargetNames::FontFaceSet;
}
ExecutionContext* FontFaceSet::executionContext() const
{
return ActiveDOMObject::executionContext();
}
AtomicString FontFaceSet::status() const
{
DEFINE_STATIC_LOCAL(AtomicString, loading, ("loading", AtomicString::ConstructFromLiteral));
DEFINE_STATIC_LOCAL(AtomicString, loaded, ("loaded", AtomicString::ConstructFromLiteral));
return (!m_loadingFonts.isEmpty() || hasLoadedFonts()) ? loading : loaded;
}
void FontFaceSet::handlePendingEventsAndPromisesSoon()
{
m_asyncRunner.runAsync();
}
void FontFaceSet::didLayout()
{
if (document()->frame()->isMainFrame() && m_loadingFonts.isEmpty())
m_histogram.record();
if (!RuntimeEnabledFeatures::fontLoadEventsEnabled())
return;
if (!m_loadingFonts.isEmpty() || (!hasLoadedFonts() && m_readyResolvers.isEmpty()))
return;
handlePendingEventsAndPromisesSoon();
}
void FontFaceSet::handlePendingEventsAndPromises()
{
fireLoadingEvent();
fireDoneEventIfPossible();
}
void FontFaceSet::fireLoadingEvent()
{
if (m_shouldFireLoadingEvent) {
m_shouldFireLoadingEvent = false;
dispatchEvent(CSSFontFaceLoadEvent::createForFontFaces(EventTypeNames::loading));
}
}
void FontFaceSet::suspend()
{
m_asyncRunner.suspend();
}
void FontFaceSet::resume()
{
m_asyncRunner.resume();
}
void FontFaceSet::stop()
{
m_asyncRunner.stop();
}
void FontFaceSet::beginFontLoading(FontFace* fontFace)
{
m_histogram.incrementCount();
addToLoadingFonts(fontFace);
}
void FontFaceSet::fontLoaded(FontFace* fontFace)
{
m_histogram.updateStatus(fontFace);
if (RuntimeEnabledFeatures::fontLoadEventsEnabled())
m_loadedFonts.append(fontFace);
removeFromLoadingFonts(fontFace);
}
void FontFaceSet::loadError(FontFace* fontFace)
{
m_histogram.updateStatus(fontFace);
if (RuntimeEnabledFeatures::fontLoadEventsEnabled())
m_failedFonts.append(fontFace);
removeFromLoadingFonts(fontFace);
}
void FontFaceSet::addToLoadingFonts(PassRefPtr<FontFace> fontFace)
{
if (RuntimeEnabledFeatures::fontLoadEventsEnabled() && m_loadingFonts.isEmpty() && !hasLoadedFonts()) {
m_shouldFireLoadingEvent = true;
handlePendingEventsAndPromisesSoon();
}
m_loadingFonts.add(fontFace);
}
void FontFaceSet::removeFromLoadingFonts(PassRefPtr<FontFace> fontFace)
{
m_loadingFonts.remove(fontFace);
if (RuntimeEnabledFeatures::fontLoadEventsEnabled() && m_loadingFonts.isEmpty())
handlePendingEventsAndPromisesSoon();
}
ScriptPromise FontFaceSet::ready()
{
if (!inActiveDocumentContext())
return ScriptPromise();
OwnPtr<FontsReadyPromiseResolver> resolver = FontsReadyPromiseResolver::create(executionContext());
ScriptPromise promise = resolver->promise();
m_readyResolvers.append(resolver.release());
handlePendingEventsAndPromisesSoon();
return promise;
}
void FontFaceSet::add(FontFace* fontFace, ExceptionState& exceptionState)
{
if (!inActiveDocumentContext())
return;
if (!fontFace) {
exceptionState.throwTypeError("The argument is not a FontFace.");
return;
}
if (m_nonCSSConnectedFaces.contains(fontFace))
return;
if (isCSSConnectedFontFace(fontFace)) {
exceptionState.throwDOMException(InvalidModificationError, "Cannot add a CSS-connected FontFace.");
return;
}
CSSFontSelector* fontSelector = document()->styleEngine()->fontSelector();
m_nonCSSConnectedFaces.add(fontFace);
fontSelector->fontFaceCache()->addFontFace(fontSelector, fontFace, false);
if (fontFace->loadStatus() == FontFace::Loading)
addToLoadingFonts(fontFace);
}
void FontFaceSet::clear()
{
if (!inActiveDocumentContext())
return;
FontFaceCache* fontFaceCache = document()->styleEngine()->fontSelector()->fontFaceCache();
for (ListHashSet<RefPtr<FontFace> >::iterator it = m_nonCSSConnectedFaces.begin(); it != m_nonCSSConnectedFaces.end(); ++it) {
fontFaceCache->removeFontFace(it->get(), false);
if ((*it)->loadStatus() == FontFace::Loading)
removeFromLoadingFonts(*it);
}
m_nonCSSConnectedFaces.clear();
}
bool FontFaceSet::remove(FontFace* fontFace, ExceptionState& exceptionState)
{
if (!inActiveDocumentContext())
return false;
if (!fontFace) {
exceptionState.throwTypeError("The argument is not a FontFace.");
return false;
}
ListHashSet<RefPtr<FontFace> >::iterator it = m_nonCSSConnectedFaces.find(fontFace);
if (it != m_nonCSSConnectedFaces.end()) {
m_nonCSSConnectedFaces.remove(it);
document()->styleEngine()->fontSelector()->fontFaceCache()->removeFontFace(fontFace, false);
if (fontFace->loadStatus() == FontFace::Loading)
removeFromLoadingFonts(fontFace);
return true;
}
if (isCSSConnectedFontFace(fontFace))
exceptionState.throwDOMException(InvalidModificationError, "Cannot delete a CSS-connected FontFace.");
return false;
}
bool FontFaceSet::has(FontFace* fontFace, ExceptionState& exceptionState) const
{
if (!inActiveDocumentContext())
return false;
if (!fontFace) {
exceptionState.throwTypeError("The argument is not a FontFace.");
return false;
}
return m_nonCSSConnectedFaces.contains(fontFace) || isCSSConnectedFontFace(fontFace);
}
const ListHashSet<RefPtr<FontFace> >& FontFaceSet::cssConnectedFontFaceList() const
{
Document* d = document();
d->ensureStyleResolver();
return d->styleEngine()->fontSelector()->fontFaceCache()->cssConnectedFontFaces();
}
bool FontFaceSet::isCSSConnectedFontFace(FontFace* fontFace) const
{
return cssConnectedFontFaceList().contains(fontFace);
}
void FontFaceSet::forEach(PassOwnPtr<FontFaceSetForEachCallback> callback, ScriptValue& thisArg) const
{
forEachInternal(callback, &thisArg);
}
void FontFaceSet::forEach(PassOwnPtr<FontFaceSetForEachCallback> callback) const
{
forEachInternal(callback, 0);
}
void FontFaceSet::forEachInternal(PassOwnPtr<FontFaceSetForEachCallback> callback, ScriptValue* thisArg) const
{
if (!inActiveDocumentContext())
return;
const ListHashSet<RefPtr<FontFace> >& cssConnectedFaces = cssConnectedFontFaceList();
Vector<RefPtr<FontFace> > fontFaces;
fontFaces.reserveInitialCapacity(cssConnectedFaces.size() + m_nonCSSConnectedFaces.size());
for (ListHashSet<RefPtr<FontFace> >::const_iterator it = cssConnectedFaces.begin(); it != cssConnectedFaces.end(); ++it)
fontFaces.append(*it);
for (ListHashSet<RefPtr<FontFace> >::const_iterator it = m_nonCSSConnectedFaces.begin(); it != m_nonCSSConnectedFaces.end(); ++it)
fontFaces.append(*it);
for (size_t i = 0; i < fontFaces.size(); ++i) {
FontFace* face = fontFaces[i].get();
if (thisArg)
callback->handleItem(*thisArg, face, face, const_cast<FontFaceSet*>(this));
else
callback->handleItem(face, face, const_cast<FontFaceSet*>(this));
}
}
unsigned long FontFaceSet::size() const
{
if (!inActiveDocumentContext())
return m_nonCSSConnectedFaces.size();
return cssConnectedFontFaceList().size() + m_nonCSSConnectedFaces.size();
}
void FontFaceSet::fireDoneEventIfPossible()
{
if (m_shouldFireLoadingEvent)
return;
if (!m_loadingFonts.isEmpty() || (!hasLoadedFonts() && m_readyResolvers.isEmpty()))
return;
Document* d = document();
if (!d->view() || d->view()->needsLayout())
return;
if (hasLoadedFonts()) {
RefPtrWillBeRawPtr<CSSFontFaceLoadEvent> doneEvent = nullptr;
RefPtrWillBeRawPtr<CSSFontFaceLoadEvent> errorEvent = nullptr;
doneEvent = CSSFontFaceLoadEvent::createForFontFaces(EventTypeNames::loadingdone, m_loadedFonts);
m_loadedFonts.clear();
if (!m_failedFonts.isEmpty()) {
errorEvent = CSSFontFaceLoadEvent::createForFontFaces(EventTypeNames::loadingerror, m_failedFonts);
m_failedFonts.clear();
}
dispatchEvent(doneEvent);
if (errorEvent)
dispatchEvent(errorEvent);
}
if (!m_readyResolvers.isEmpty()) {
Vector<OwnPtr<FontsReadyPromiseResolver> > resolvers;
m_readyResolvers.swap(resolvers);
for (size_t index = 0; index < resolvers.size(); ++index)
resolvers[index]->resolve(this);
}
}
static const String& nullToSpace(const String& s)
{
DEFINE_STATIC_LOCAL(String, space, (" "));
return s.isNull() ? space : s;
}
ScriptPromise FontFaceSet::load(const String& fontString, const String& text)
{
if (!inActiveDocumentContext())
return ScriptPromise();
Font font;
if (!resolveFontStyle(fontString, font)) {
RefPtr<ScriptPromiseResolver> resolver = ScriptPromiseResolver::create(executionContext());
ScriptPromise promise = resolver->promise();
resolver->reject(DOMError::create(SyntaxError, "Could not resolve '" + fontString + "' as a font."));
return promise;
}
FontFaceCache* fontFaceCache = document()->styleEngine()->fontSelector()->fontFaceCache();
FontFaceArray faces;
for (const FontFamily* f = &font.fontDescription().family(); f; f = f->next()) {
CSSSegmentedFontFace* segmentedFontFace = fontFaceCache->get(font.fontDescription(), f->family());
if (segmentedFontFace)
segmentedFontFace->match(nullToSpace(text), faces);
}
RefPtr<LoadFontPromiseResolver> resolver = LoadFontPromiseResolver::create(faces, executionContext());
ScriptPromise promise = resolver->promise();
resolver->loadFonts(executionContext());
return promise;
}
bool FontFaceSet::check(const String& fontString, const String& text, ExceptionState& exceptionState)
{
if (!inActiveDocumentContext())
return false;
Font font;
if (!resolveFontStyle(fontString, font)) {
exceptionState.throwDOMException(SyntaxError, "Could not resolve '" + fontString + "' as a font.");
return false;
}
FontFaceCache* fontFaceCache = document()->styleEngine()->fontSelector()->fontFaceCache();
for (const FontFamily* f = &font.fontDescription().family(); f; f = f->next()) {
CSSSegmentedFontFace* face = fontFaceCache->get(font.fontDescription(), f->family());
if (face && !face->checkFont(nullToSpace(text)))
return false;
}
return true;
}
bool FontFaceSet::resolveFontStyle(const String& fontString, Font& font)
{
if (fontString.isEmpty())
return false;
RefPtrWillBeRawPtr<MutableStylePropertySet> parsedStyle = MutableStylePropertySet::create();
BisonCSSParser::parseValue(parsedStyle.get(), CSSPropertyFont, fontString, true, HTMLStandardMode, 0);
if (parsedStyle->isEmpty())
return false;
String fontValue = parsedStyle->getPropertyValue(CSSPropertyFont);
if (fontValue == "inherit" || fontValue == "initial")
return false;
RefPtr<RenderStyle> style = RenderStyle::create();
FontFamily fontFamily;
fontFamily.setFamily(defaultFontFamily);
FontDescription defaultFontDescription;
defaultFontDescription.setFamily(fontFamily);
defaultFontDescription.setSpecifiedSize(defaultFontSize);
defaultFontDescription.setComputedSize(defaultFontSize);
style->setFontDescription(defaultFontDescription);
style->font().update(style->font().fontSelector());
CSSPropertyValue properties[] = {
CSSPropertyValue(CSSPropertyFontFamily, *parsedStyle),
CSSPropertyValue(CSSPropertyFontStyle, *parsedStyle),
CSSPropertyValue(CSSPropertyFontVariant, *parsedStyle),
CSSPropertyValue(CSSPropertyFontWeight, *parsedStyle),
CSSPropertyValue(CSSPropertyFontSize, *parsedStyle),
CSSPropertyValue(CSSPropertyLineHeight, *parsedStyle),
};
StyleResolver& styleResolver = document()->ensureStyleResolver();
styleResolver.applyPropertiesToStyle(properties, WTF_ARRAY_LENGTH(properties), style.get());
font = style->font();
font.update(document()->styleEngine()->fontSelector());
return true;
}
void FontFaceSet::FontLoadHistogram::updateStatus(FontFace* fontFace)
{
if (m_status == Reported)
return;
if (fontFace->hadBlankText())
m_status = HadBlankText;
else if (m_status == NoWebFonts)
m_status = DidNotHaveBlankText;
}
void FontFaceSet::FontLoadHistogram::record()
{
if (!m_recorded) {
m_recorded = true;
blink::Platform::current()->histogramCustomCounts("WebFont.WebFontsInPage", m_count, 1, 100, 50);
}
if (m_status == HadBlankText || m_status == DidNotHaveBlankText) {
blink::Platform::current()->histogramEnumeration("WebFont.HadBlankText", m_status == HadBlankText ? 1 : 0, 2);
m_status = Reported;
}
}
static const char* supplementName()
{
return "FontFaceSet";
}
PassRefPtr<FontFaceSet> FontFaceSet::from(Document& document)
{
RefPtr<FontFaceSet> fonts = static_cast<FontFaceSet*>(SupplementType::from(document, supplementName()));
if (!fonts) {
fonts = FontFaceSet::create(document);
SupplementType::provideTo(document, supplementName(), fonts);
}
return fonts.release();
}
void FontFaceSet::didLayout(Document& document)
{
if (FontFaceSet* fonts = static_cast<FontFaceSet*>(SupplementType::from(document, supplementName())))
fonts->didLayout();
}
}