blob: f7dfa5469925268e55c1c6da05d9b271233d1d0e [file] [log] [blame]
/*
* Copyright (C) 2013 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*/
#include "config.h"
#include "core/css/FontFaceSet.h"
#include "RuntimeEnabledFeatures.h"
#include "V8FontFaceSet.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/CSSFontFaceSource.h"
#include "core/css/CSSFontSelector.h"
#include "core/css/CSSParser.h"
#include "core/css/CSSSegmentedFontFace.h"
#include "core/css/StylePropertySet.h"
#include "core/css/resolver/StyleResolver.h"
#include "core/dom/Document.h"
#include "core/frame/FrameView.h"
#include "public/platform/Platform.h"
namespace WebCore {
static const int defaultFontSize = 10;
static const char defaultFontFamily[] = "sans-serif";
class LoadFontPromiseResolver : public CSSSegmentedFontFace::LoadFontCallback {
public:
static PassRefPtr<LoadFontPromiseResolver> create(const FontFamily& family, ScriptPromise promise, ExecutionContext* context)
{
int numFamilies = 0;
for (const FontFamily* f = &family; f; f = f->next())
numFamilies++;
return adoptRef<LoadFontPromiseResolver>(new LoadFontPromiseResolver(numFamilies, promise, context));
}
virtual void notifyLoaded(CSSSegmentedFontFace*) OVERRIDE;
virtual void notifyError(CSSSegmentedFontFace*) OVERRIDE;
void loaded(Document*);
void error(Document*);
void resolve();
private:
LoadFontPromiseResolver(int numLoading, ScriptPromise promise, ExecutionContext* context)
: m_numLoading(numLoading)
, m_errorOccured(false)
, m_scriptState(ScriptState::current())
, m_resolver(ScriptPromiseResolver::create(promise, context))
{ }
int m_numLoading;
bool m_errorOccured;
ScriptState* m_scriptState;
RefPtr<ScriptPromiseResolver> m_resolver;
};
void LoadFontPromiseResolver::loaded(Document* document)
{
m_numLoading--;
if (m_numLoading || !document)
return;
FontFaceSet::from(document)->scheduleResolve(this);
}
void LoadFontPromiseResolver::error(Document* document)
{
m_errorOccured = true;
loaded(document);
}
void LoadFontPromiseResolver::notifyLoaded(CSSSegmentedFontFace* face)
{
loaded(face->fontSelector()->document());
}
void LoadFontPromiseResolver::notifyError(CSSSegmentedFontFace* face)
{
error(face->fontSelector()->document());
}
void LoadFontPromiseResolver::resolve()
{
ScriptScope scope(m_scriptState);
if (m_errorOccured)
m_resolver->reject(ScriptValue::createNull());
else
m_resolver->resolve(ScriptValue::createNull());
}
class FontsReadyPromiseResolver {
public:
static PassOwnPtr<FontsReadyPromiseResolver> create(ScriptPromise promise, ExecutionContext* context)
{
return adoptPtr(new FontsReadyPromiseResolver(promise, context));
}
void call(PassRefPtr<FontFaceSet> fontFaceSet)
{
ScriptScope scope(m_scriptState);
m_resolver->resolve(fontFaceSet);
}
private:
FontsReadyPromiseResolver(ScriptPromise promise, ExecutionContext* context)
: m_scriptState(ScriptState::current())
, m_resolver(ScriptPromiseResolver::create(promise, context))
{ }
ScriptState* m_scriptState;
RefPtr<ScriptPromiseResolver> m_resolver;
};
FontFaceSet::FontFaceSet(Document* document)
: ActiveDOMObject(document)
, m_loadingCount(0)
, m_asyncRunner(this, &FontFaceSet::handlePendingEventsAndPromises)
{
suspendIfNeeded();
}
FontFaceSet::~FontFaceSet()
{
}
Document* FontFaceSet::document() const
{
return toDocument(executionContext());
}
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_loadingCount > 0 || hasLoadedFonts()) ? loading : loaded;
}
void FontFaceSet::handlePendingEventsAndPromisesSoon()
{
// setPendingActivity() is unnecessary because m_asyncRunner will be
// automatically stopped on destruction.
m_asyncRunner.runAsync();
}
void FontFaceSet::didLayout()
{
Document* d = document();
if (d->page() && d->page()->mainFrame() == d->frame())
m_histogram.record();
if (!RuntimeEnabledFeatures::fontLoadEventsEnabled())
return;
if (m_loadingCount || (!hasLoadedFonts() && m_readyResolvers.isEmpty()))
return;
handlePendingEventsAndPromisesSoon();
}
void FontFaceSet::handlePendingEventsAndPromises()
{
firePendingEvents();
resolvePendingLoadPromises();
fireDoneEventIfPossible();
}
void FontFaceSet::scheduleEvent(PassRefPtr<Event> event)
{
m_pendingEvents.append(event);
handlePendingEventsAndPromisesSoon();
}
void FontFaceSet::firePendingEvents()
{
if (m_pendingEvents.isEmpty())
return;
Vector<RefPtr<Event> > pendingEvents;
m_pendingEvents.swap(pendingEvents);
for (size_t index = 0; index < pendingEvents.size(); ++index)
dispatchEvent(pendingEvents[index].release());
}
void FontFaceSet::suspend()
{
m_asyncRunner.suspend();
}
void FontFaceSet::resume()
{
m_asyncRunner.resume();
}
void FontFaceSet::stop()
{
m_asyncRunner.stop();
}
void FontFaceSet::scheduleResolve(LoadFontPromiseResolver* resolver)
{
m_pendingLoadResolvers.append(resolver);
handlePendingEventsAndPromisesSoon();
}
void FontFaceSet::resolvePendingLoadPromises()
{
if (m_pendingLoadResolvers.isEmpty())
return;
Vector<RefPtr<LoadFontPromiseResolver> > resolvers;
m_pendingLoadResolvers.swap(resolvers);
for (size_t index = 0; index < resolvers.size(); ++index)
resolvers[index]->resolve();
}
void FontFaceSet::beginFontLoading(FontFace* fontFace)
{
m_histogram.incrementCount();
if (!RuntimeEnabledFeatures::fontLoadEventsEnabled())
return;
if (!m_loadingCount && !hasLoadedFonts())
scheduleEvent(CSSFontFaceLoadEvent::createForFontFaces(EventTypeNames::loading));
++m_loadingCount;
}
void FontFaceSet::fontLoaded(FontFace* fontFace)
{
if (!RuntimeEnabledFeatures::fontLoadEventsEnabled())
return;
m_loadedFonts.append(fontFace);
queueDoneEvent(fontFace);
}
void FontFaceSet::loadError(FontFace* fontFace)
{
if (!RuntimeEnabledFeatures::fontLoadEventsEnabled())
return;
m_failedFonts.append(fontFace);
queueDoneEvent(fontFace);
}
void FontFaceSet::queueDoneEvent(FontFace* fontFace)
{
ASSERT(m_loadingCount > 0);
--m_loadingCount;
if (!m_loadingCount)
handlePendingEventsAndPromisesSoon();
}
ScriptPromise FontFaceSet::ready()
{
ScriptPromise promise = ScriptPromise::createPending(executionContext());
OwnPtr<FontsReadyPromiseResolver> resolver = FontsReadyPromiseResolver::create(promise, executionContext());
m_readyResolvers.append(resolver.release());
handlePendingEventsAndPromisesSoon();
return promise;
}
void FontFaceSet::fireDoneEventIfPossible()
{
if (!m_pendingEvents.isEmpty() || !m_pendingLoadResolvers.isEmpty())
return;
if (m_loadingCount || (!hasLoadedFonts() && m_readyResolvers.isEmpty()))
return;
// If the layout was invalidated in between when we thought layout
// was updated and when we're ready to fire the event, just wait
// until after the next layout before firing events.
Document* d = document();
if (!d->view() || d->view()->needsLayout())
return;
if (hasLoadedFonts()) {
RefPtr<CSSFontFaceLoadEvent> doneEvent;
RefPtr<CSSFontFaceLoadEvent> errorEvent;
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]->call(this);
}
}
static const String& nullToSpace(const String& s)
{
DEFINE_STATIC_LOCAL(String, space, (" "));
return s.isNull() ? space : s;
}
Vector<RefPtr<FontFace> > FontFaceSet::match(const String& fontString, const String& text, ExceptionState& es)
{
Vector<RefPtr<FontFace> > matchedFonts;
Font font;
if (!resolveFontStyle(fontString, font)) {
es.throwUninformativeAndGenericDOMException(SyntaxError);
return matchedFonts;
}
for (const FontFamily* f = &font.family(); f; f = f->next()) {
CSSSegmentedFontFace* face = document()->styleResolver()->fontSelector()->getFontFace(font.fontDescription(), f->family());
if (face)
matchedFonts.append(face->fontFaces(nullToSpace(text)));
}
return matchedFonts;
}
ScriptPromise FontFaceSet::load(const String& fontString, const String& text, ExceptionState& es)
{
Font font;
if (!resolveFontStyle(fontString, font)) {
es.throwUninformativeAndGenericDOMException(SyntaxError);
return ScriptPromise();
}
Document* d = document();
ScriptPromise promise = ScriptPromise::createPending(executionContext());
RefPtr<LoadFontPromiseResolver> resolver = LoadFontPromiseResolver::create(font.family(), promise, executionContext());
for (const FontFamily* f = &font.family(); f; f = f->next()) {
CSSSegmentedFontFace* face = d->styleResolver()->fontSelector()->getFontFace(font.fontDescription(), f->family());
if (!face) {
resolver->error(d);
continue;
}
face->loadFont(font.fontDescription(), nullToSpace(text), resolver);
}
return promise;
}
bool FontFaceSet::check(const String& fontString, const String& text, ExceptionState& es)
{
Font font;
if (!resolveFontStyle(fontString, font)) {
es.throwUninformativeAndGenericDOMException(SyntaxError);
return false;
}
for (const FontFamily* f = &font.family(); f; f = f->next()) {
CSSSegmentedFontFace* face = document()->styleResolver()->fontSelector()->getFontFace(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;
// Interpret fontString in the same way as the 'font' attribute of CanvasRenderingContext2D.
RefPtr<MutableStylePropertySet> parsedStyle = MutableStylePropertySet::create();
CSSParser::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());
// Now map the font property longhands into the style.
CSSPropertyValue properties[] = {
CSSPropertyValue(CSSPropertyFontFamily, *parsedStyle),
CSSPropertyValue(CSSPropertyFontStyle, *parsedStyle),
CSSPropertyValue(CSSPropertyFontVariant, *parsedStyle),
CSSPropertyValue(CSSPropertyFontWeight, *parsedStyle),
CSSPropertyValue(CSSPropertyFontSize, *parsedStyle),
CSSPropertyValue(CSSPropertyLineHeight, *parsedStyle),
};
StyleResolver* styleResolver = document()->styleResolver();
styleResolver->applyPropertiesToStyle(properties, WTF_ARRAY_LENGTH(properties), style.get());
font = style->font();
font.update(styleResolver->fontSelector());
return true;
}
void FontFaceSet::FontLoadHistogram::record()
{
if (m_recorded)
return;
m_recorded = true;
WebKit::Platform::current()->histogramCustomCounts("WebFont.WebFontsInPage", m_count, 1, 100, 50);
}
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();
}
} // namespace WebCore