blob: dde133cc7c70144e2d10344fa77233c91f065fd4 [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 "FontLoader.h"
#if ENABLE(FONT_LOAD_EVENTS)
#include "CSSFontFaceLoadEvent.h"
#include "CSSFontFaceSource.h"
#include "CSSFontSelector.h"
#include "CSSParser.h"
#include "CSSSegmentedFontFace.h"
#include "Dictionary.h"
#include "Document.h"
#include "ExceptionCodeDescription.h"
#include "FontCascade.h"
#include "FrameView.h"
#include "StyleProperties.h"
#include "StyleResolver.h"
namespace WebCore {
static const int defaultFontSize = 10;
static const char* const defaultFontFamily = "sans-serif";
class LoadFontCallback : public CSSSegmentedFontFace::LoadFontCallback {
public:
static Ref<LoadFontCallback> create(int numLoading, FontLoader& fontLoader, PassRefPtr<VoidCallback> loadCallback, PassRefPtr<VoidCallback> errorCallback)
{
return adoptRef(*new LoadFontCallback(numLoading, fontLoader, loadCallback, errorCallback));
}
static PassRefPtr<LoadFontCallback> createFromParams(const Dictionary& params, FontLoader& fontLoader, const FontCascade& font)
{
RefPtr<VoidCallback> onsuccess;
RefPtr<VoidCallback> onerror;
params.get("onsuccess", onsuccess);
params.get("onerror", onerror);
if (!onsuccess && !onerror)
return 0;
int numFamilies = font.familyCount();
return LoadFontCallback::create(numFamilies, fontLoader, onsuccess, onerror);
}
virtual void notifyLoaded() override;
virtual void notifyError() override;
virtual ~LoadFontCallback() { };
int familyCount() const { return m_familyCount; }
private:
LoadFontCallback(int numLoading, FontLoader& fontLoader, PassRefPtr<VoidCallback> loadCallback, PassRefPtr<VoidCallback> errorCallback)
: m_familyCount(numLoading)
, m_numLoading(numLoading)
, m_errorOccured(false)
, m_fontLoader(fontLoader)
, m_loadCallback(loadCallback)
, m_errorCallback(errorCallback)
{ }
int m_familyCount;
int m_numLoading;
bool m_errorOccured;
FontLoader& m_fontLoader;
RefPtr<VoidCallback> m_loadCallback;
RefPtr<VoidCallback> m_errorCallback;
};
void LoadFontCallback::notifyLoaded()
{
m_numLoading--;
if (m_numLoading)
return;
m_fontLoader.loadFontDone(*this);
if (m_errorOccured) {
if (m_errorCallback)
m_errorCallback->handleEvent();
} else {
if (m_loadCallback)
m_loadCallback->handleEvent();
}
}
void LoadFontCallback::notifyError()
{
m_errorOccured = true;
notifyLoaded();
}
void FontLoader::loadFontDone(const LoadFontCallback& callback)
{
m_numLoadingFromJS -= callback.familyCount();
}
FontLoader::FontLoader(Document* document)
: ActiveDOMObject(document)
, m_document(document)
, m_numLoadingFromCSS(0)
, m_numLoadingFromJS(0)
, m_pendingEventsTimer(*this, &FontLoader::firePendingEvents)
{
suspendIfNeeded();
}
FontLoader::~FontLoader()
{
}
EventTargetData* FontLoader::eventTargetData()
{
return &m_eventTargetData;
}
EventTargetData& FontLoader::ensureEventTargetData()
{
return m_eventTargetData;
}
EventTargetInterface FontLoader::eventTargetInterface() const
{
return FontLoaderEventTargetInterfaceType;
}
ScriptExecutionContext* FontLoader::scriptExecutionContext() const
{
return ActiveDOMObject::scriptExecutionContext();
}
void FontLoader::didLayout()
{
loadingDone();
}
const char* FontLoader::activeDOMObjectName() const
{
return "FontLoader";
}
bool FontLoader::canSuspendForPageCache() const
{
return !m_numLoadingFromCSS && !m_numLoadingFromJS;
}
void FontLoader::scheduleEvent(PassRefPtr<Event> event)
{
m_pendingEvents.append(event);
if (!m_pendingEventsTimer.isActive())
m_pendingEventsTimer.startOneShot(0);
}
void FontLoader::firePendingEvents()
{
if (m_pendingEvents.isEmpty() && !m_loadingDoneEvent && !m_callbacks.isEmpty())
return;
Vector<RefPtr<Event>> pendingEvents;
m_pendingEvents.swap(pendingEvents);
bool loadingDone = false;
if (m_loadingDoneEvent) {
pendingEvents.append(m_loadingDoneEvent.release());
loadingDone = true;
}
for (size_t index = 0; index < pendingEvents.size(); ++index)
dispatchEvent(pendingEvents[index].release());
if (loadingDone && !m_callbacks.isEmpty()) {
Vector<RefPtr<VoidCallback>> callbacks;
m_callbacks.swap(callbacks);
for (size_t index = 0; index < callbacks.size(); ++index)
callbacks[index]->handleEvent();
}
}
void FontLoader::beginFontLoading(CSSFontFaceRule* rule)
{
++m_numLoadingFromCSS;
if (m_numLoadingFromCSS == 1 && !m_loadingDoneEvent)
scheduleEvent(CSSFontFaceLoadEvent::createForFontFaceRule(eventNames().loadingEvent, rule));
scheduleEvent(CSSFontFaceLoadEvent::createForFontFaceRule(eventNames().loadstartEvent, rule));
}
void FontLoader::fontLoaded(CSSFontFaceRule* rule)
{
ASSERT(m_numLoadingFromCSS > 0);
scheduleEvent(CSSFontFaceLoadEvent::createForFontFaceRule(eventNames().loadEvent, rule));
--m_numLoadingFromCSS;
if (!m_numLoadingFromCSS)
m_loadingDoneEvent = CSSFontFaceLoadEvent::createForFontFaceRule(eventNames().loadingdoneEvent, rule);
}
void FontLoader::loadError(CSSFontFaceRule* rule, CSSFontFaceSource* source)
{
ASSERT(m_numLoadingFromCSS > 0);
// FIXME: We should report NetworkError in case of timeout, etc.
String errorName = (source && source->isDecodeError()) ? "InvalidFontDataError" : ExceptionCodeDescription(NOT_FOUND_ERR).name;
scheduleEvent(CSSFontFaceLoadEvent::createForError(rule, DOMError::create(errorName)));
--m_numLoadingFromCSS;
if (!m_numLoadingFromCSS)
m_loadingDoneEvent = CSSFontFaceLoadEvent::createForFontFaceRule(eventNames().loadingdoneEvent, rule);
}
void FontLoader::notifyWhenFontsReady(PassRefPtr<VoidCallback> callback)
{
m_callbacks.append(callback);
}
void FontLoader::loadingDone()
{
if (loading() || !m_document->haveStylesheetsLoaded())
return;
if (!m_loadingDoneEvent && m_callbacks.isEmpty() && m_pendingEvents.isEmpty())
return;
if (FrameView* view = m_document->view()) {
if (view->isInLayout() || view->needsLayout())
return;
}
if (!m_pendingEventsTimer.isActive())
m_pendingEventsTimer.startOneShot(0);
}
void FontLoader::loadFont(const Dictionary& params)
{
// FIXME: The text member of params is ignored.
String fontString;
if (!params.get("font", fontString))
return;
FontCascade font;
if (!resolveFontStyle(fontString, font))
return;
RefPtr<LoadFontCallback> callback = LoadFontCallback::createFromParams(params, *this, font);
m_numLoadingFromJS += callback->familyCount();
for (unsigned i = 0; i < font.familyCount(); i++) {
CSSSegmentedFontFace* face = m_document->fontSelector().getFontFace(font.fontDescription(), font.familyAt(i));
if (!face) {
if (callback)
callback->notifyError();
continue;
}
face->loadFont(font.fontDescription(), callback);
}
}
bool FontLoader::checkFont(const String& fontString, const String&)
{
// FIXME: The second parameter (text) is ignored.
FontCascade font;
if (!resolveFontStyle(fontString, font))
return false;
for (unsigned i = 0; i < font.familyCount(); i++) {
CSSSegmentedFontFace* face = m_document->fontSelector().getFontFace(font.fontDescription(), font.familyAt(i));
if (!face || !face->checkFont())
return false;
}
return true;
}
static void applyPropertyToCurrentStyle(StyleResolver& styleResolver, CSSPropertyID id, const RefPtr<StyleProperties>& parsedStyle)
{
styleResolver.applyPropertyToCurrentStyle(id, parsedStyle->getPropertyCSSValue(id).get());
}
bool FontLoader::resolveFontStyle(const String& fontString, FontCascade& font)
{
// Interpret fontString in the same way as the 'font' attribute of CanvasRenderingContext2D.
RefPtr<MutableStyleProperties> parsedStyle = MutableStyleProperties::create();
CSSParser::parseValue(parsedStyle.get(), CSSPropertyFont, fontString, true, CSSStrictMode, nullptr);
if (parsedStyle->isEmpty())
return false;
String fontValue = parsedStyle->getPropertyValue(CSSPropertyFont);
if (fontValue == "inherit" || fontValue == "initial" || fontValue == "unset" || fontValue == "revert")
return false;
RefPtr<RenderStyle> style = RenderStyle::create();
FontDescription defaultFontDescription;
defaultFontDescription.setOneFamily(defaultFontFamily);
defaultFontDescription.setSpecifiedSize(defaultFontSize);
defaultFontDescription.setComputedSize(defaultFontSize);
style->setFontDescription(defaultFontDescription);
style->fontCascade().update(style->fontCascade().fontSelector());
// Now map the font property longhands into the style.
StyleResolver& styleResolver = m_document->ensureStyleResolver();
styleResolver.applyPropertyToStyle(CSSPropertyFontFamily, parsedStyle->getPropertyCSSValue(CSSPropertyFontFamily).get(), style.get());
applyPropertyToCurrentStyle(styleResolver, CSSPropertyFontStyle, parsedStyle);
applyPropertyToCurrentStyle(styleResolver, CSSPropertyFontVariant, parsedStyle);
applyPropertyToCurrentStyle(styleResolver, CSSPropertyFontWeight, parsedStyle);
// As described in BUG66291, setting font-size and line-height on a font may entail a CSSPrimitiveValue::computeLengthDouble call,
// which assumes the fontMetrics are available for the affected font, otherwise a crash occurs (see http://trac.webkit.org/changeset/96122).
// The updateFont() calls below update the fontMetrics and ensure the proper setting of font-size and line-height.
styleResolver.updateFont();
applyPropertyToCurrentStyle(styleResolver, CSSPropertyFontSize, parsedStyle);
styleResolver.updateFont();
applyPropertyToCurrentStyle(styleResolver, CSSPropertyLineHeight, parsedStyle);
font = style->fontCascade();
font.update(&m_document->fontSelector());
return true;
}
} // namespace WebCore
#endif // ENABLE(FONT_LOAD_EVENTS)