/* * 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 create(int numLoading, FontLoader& fontLoader, PassRefPtr loadCallback, PassRefPtr errorCallback) { return adoptRef(*new LoadFontCallback(numLoading, fontLoader, loadCallback, errorCallback)); } static PassRefPtr createFromParams(const Dictionary& params, FontLoader& fontLoader, const FontCascade& font) { RefPtr onsuccess; RefPtr 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 loadCallback, PassRefPtr 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 m_loadCallback; RefPtr 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::canSuspendForDocumentSuspension() const { return !m_numLoadingFromCSS && !m_numLoadingFromJS; } void FontLoader::scheduleEvent(Ref&& event) { m_pendingEvents.append(WTFMove(event)); if (!m_pendingEventsTimer.isActive()) m_pendingEventsTimer.startOneShot(0); } void FontLoader::firePendingEvents() { if (m_pendingEvents.isEmpty() && !m_loadingDoneEvent && !m_callbacks.isEmpty()) return; Vector> pendingEvents; m_pendingEvents.swap(pendingEvents); bool loadingDone = false; if (m_loadingDoneEvent) { pendingEvents.append(m_loadingDoneEvent.releaseNonNull()); loadingDone = true; } for (size_t index = 0; index < pendingEvents.size(); ++index) dispatchEvent(pendingEvents[index]); if (loadingDone && !m_callbacks.isEmpty()) { Vector> 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 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 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& 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 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 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)