[Font Loading] Implement FontFace JavaScript object
https://bugs.webkit.org/show_bug.cgi?id=153345
Reviewed by Antti Koivisto.
Source/WebCore:
Test: fast/text/font-face-javascript.html
This patch implements the FontFace Javascript object. This object mostly consists of
style getters / setters, which we implement by parsing input strings and generating
output strings similarly to getComputedStyle(). This object also has a load() function
which returns a promise which will be fulfilled or rejected depending on the load.
There is also a "loaded" attribute which exposes this promise directly. Also, a status
field is exposed so script knows what the state of the load is.
Currently, loading depends on our CachedResourceLoader which is part of the Document,
so this API is not available in a non-document context.
Another caveat is that immediate-mode font loading (where the content provides an
ArrayBuffer containing the bytes of the font file) is forthcoming. This requires
changing the relationship between CSSFontFaceSource and CachedFont.
CSSFontFace has been modified to keep a strong reference to the CSSFontSelector. This
is because the lifetime of the CSSFontFace can now outlive the CSSFontSelector. When
the CSSFontSelector is removed from the Document, it explicitly clears its constituent
CSSFontFaces, thereby breaking the reference cycle.
Test: fast/text/font-face-javascript-expected.html
* CMakeLists.txt: Add new files.
* DerivedSources.cpp: Ditto.
* DerivedSources.make: Ditto.
* WebCore.vcxproj/WebCore.vcxproj: Ditto.
* WebCore.vcxproj/WebCore.vcxproj.filters: Ditto.
* WebCore.xcodeproj/project.pbxproj: Ditto.
* bindings/js/JSDOMPromise.cpp:
(WebCore::DeferredWrapper::globalObject): Remove whitespace.
(WebCore::DeferredWrapper::deferred): Allow access to the inner JSC object.
* bindings/js/JSDOMPromise.h:
(WebCore::DOMPromise::deferred): Ditto.
* bindings/js/JSFontFaceCustom.cpp: Copied from Source/WebCore/bindings/js/JSDOMPromise.cpp.
(WebCore::JSFontFace::loaded):
(WebCore::JSFontFace::load):
* css/CSSFontFace.cpp:
(WebCore::CSSFontFace::CSSFontFace):
(WebCore::CSSFontFace::adoptSource):
(WebCore::CSSFontFace::updateStatus): Enforce the state machine's transitions.
(WebCore::CSSFontFace::fontLoaded):
(WebCore::CSSFontFace::pump):
(WebCore::CSSFontFace::load):
* css/CSSFontFace.h:
(WebCore::CSSFontFaceClient::~CSSFontFaceClient):
(WebCore::CSSFontFace::create):
(WebCore::CSSFontFace::status):
* css/CSSFontSelector.cpp:
(WebCore::CSSFontSelector::appendSources): Update for new CSSFontFace API.
(WebCore::CSSFontSelector::registerLocalFontFacesForFamily): Ditto.
(WebCore::CSSFontSelector::addFontFaceRule): Ditto.
(WebCore::CSSFontSelector::kick): Ditto.
(WebCore::appendSources): Deleted.
(WebCore::registerLocalFontFacesForFamily): Deleted.
* css/CSSFontSelector.h:
* css/CSSUnicodeRangeValue.cpp: Use for serializing the "unicodeRange" property.
* css/FontFace.cpp:
(WebCore::createPromise): Implement the remaining Javascript API functions.
(WebCore::valueFromDictionary):
(WebCore::FontFace::create):
(WebCore::FontFace::FontFace):
(WebCore::FontFace::parseString):
(WebCore::FontFace::status):
(WebCore::FontFace::kick):
(WebCore::FontFace::load):
(WebCore::FontFace::fulfillPromise):
(WebCore::FontFace::rejectPromise):
(WebCore::parseString): Deleted.
* css/FontFace.h:
(WebCore::FontFace::promise):
(WebCore::FontFace::backing):
(WebCore::FontFace::create): Deleted.
* css/FontFace.idl: Copied from Source/WebCore/bindings/js/JSDOMPromise.cpp.
LayoutTests:
* fast/text/font-face-javascript-expected.txt: Added.
* fast/text/font-face-javascript.html: Added.
* js/dom/global-constructors-attributes-expected.txt:
* platform/efl/js/dom/global-constructors-attributes-expected.txt:
* platform/gtk/js/dom/global-constructors-attributes-expected.txt:
* platform/mac-mavericks/js/dom/global-constructors-attributes-expected.txt:
* platform/mac-yosemite/js/dom/global-constructors-attributes-expected.txt:
* platform/mac/js/dom/global-constructors-attributes-expected.txt:
* platform/win/js/dom/global-constructors-attributes-expected.txt:
git-svn-id: http://svn.webkit.org/repository/webkit/trunk@196604 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/Source/WebCore/css/FontFace.cpp b/Source/WebCore/css/FontFace.cpp
index 5d4a5a6..bcb2cd0 100644
--- a/Source/WebCore/css/FontFace.cpp
+++ b/Source/WebCore/css/FontFace.cpp
@@ -28,16 +28,91 @@
#include "CSSFontFace.h"
#include "CSSFontFeatureValue.h"
+#include "CSSFontSelector.h"
#include "CSSUnicodeRangeValue.h"
#include "CSSValue.h"
+#include "CSSValuePool.h"
+#include "Dictionary.h"
+#include "Document.h"
+#include "ExceptionCodeDescription.h"
#include "FontVariantBuilder.h"
+#include "JSDOMCoreException.h"
+#include "JSFontFace.h"
+#include "ScriptExecutionContext.h"
#include "StyleProperties.h"
#include <wtf/text/StringBuilder.h>
namespace WebCore {
-FontFace::FontFace()
- : m_backing(CSSFontFace::create())
+static FontFace::Promise createPromise(JSC::ExecState& exec)
+{
+ JSDOMGlobalObject& globalObject = *JSC::jsCast<JSDOMGlobalObject*>(exec.lexicalGlobalObject());
+ return FontFace::Promise(DeferredWrapper(&exec, &globalObject, JSC::JSPromiseDeferred::create(&exec, &globalObject)));
+}
+
+static inline Optional<String> valueFromDictionary(const Dictionary& dictionary, const char* key)
+{
+ String result;
+ dictionary.get(key, result);
+ return result.isNull() ? Nullopt : Optional<String>(result);
+}
+
+RefPtr<FontFace> FontFace::create(JSC::ExecState& execState, ScriptExecutionContext& context, const String& family, const Deprecated::ScriptValue& source, const Dictionary& descriptors, ExceptionCode& ec)
+{
+ if (!context.isDocument()) {
+ ec = TypeError;
+ return nullptr;
+ }
+
+ Ref<FontFace> result = adoptRef(*new FontFace(execState, downcast<Document>(context).fontSelector()));
+
+ result->setFamily(family, ec);
+ if (ec)
+ return nullptr;
+
+ if (source.jsValue().isString()) {
+ String sourceString = source.jsValue().toString(&execState)->value(&execState);
+ auto value = FontFace::parseString(sourceString, CSSPropertySrc);
+ if (is<CSSValueList>(value.get())) {
+ CSSValueList& srcList = downcast<CSSValueList>(*value);
+ CSSFontSelector::appendSources(result->backing(), srcList, &downcast<Document>(context), false);
+ } else {
+ ec = SYNTAX_ERR;
+ return nullptr;
+ }
+ }
+
+ if (auto style = valueFromDictionary(descriptors, "style"))
+ result->setStyle(style.value(), ec);
+ if (ec)
+ return nullptr;
+ if (auto style = valueFromDictionary(descriptors, "weight"))
+ result->setWeight(style.value(), ec);
+ if (ec)
+ return nullptr;
+ if (auto style = valueFromDictionary(descriptors, "stretch"))
+ result->setStretch(style.value(), ec);
+ if (ec)
+ return nullptr;
+ if (auto style = valueFromDictionary(descriptors, "unicodeRange"))
+ result->setUnicodeRange(style.value(), ec);
+ if (ec)
+ return nullptr;
+ if (auto style = valueFromDictionary(descriptors, "variant"))
+ result->setVariant(style.value(), ec);
+ if (ec)
+ return nullptr;
+ if (auto style = valueFromDictionary(descriptors, "featureSettings"))
+ result->setFeatureSettings(style.value(), ec);
+ if (ec)
+ return nullptr;
+
+ return result.ptr();
+}
+
+FontFace::FontFace(JSC::ExecState& execState, CSSFontSelector& fontSelector)
+ : m_backing(CSSFontFace::create(*this, fontSelector))
+ , m_promise(createPromise(execState))
{
}
@@ -45,7 +120,7 @@
{
}
-static inline RefPtr<CSSValue> parseString(const String& string, CSSPropertyID propertyID)
+RefPtr<CSSValue> FontFace::parseString(const String& string, CSSPropertyID propertyID)
{
Ref<MutableStyleProperties> style = MutableStyleProperties::create();
auto result = CSSParser::parseValue(style.ptr(), propertyID, string, true, CSSStrictMode, nullptr);
@@ -101,19 +176,38 @@
auto result = CSSParser::parseValue(style.ptr(), CSSPropertyFontVariant, variant, true, CSSStrictMode, nullptr);
if (result != CSSParser::ParseResult::Error) {
FontVariantSettings backup = m_backing->variantSettings();
+ auto normal = CSSValuePool::singleton().createIdentifierValue(CSSValueNormal);
bool success = true;
if (auto value = style->getPropertyCSSValue(CSSPropertyFontVariantLigatures))
success &= m_backing->setVariantLigatures(*value);
+ else
+ m_backing->setVariantLigatures(normal);
+
if (auto value = style->getPropertyCSSValue(CSSPropertyFontVariantPosition))
success &= m_backing->setVariantPosition(*value);
+ else
+ m_backing->setVariantPosition(normal);
+
if (auto value = style->getPropertyCSSValue(CSSPropertyFontVariantCaps))
success &= m_backing->setVariantCaps(*value);
+ else
+ m_backing->setVariantCaps(normal);
+
if (auto value = style->getPropertyCSSValue(CSSPropertyFontVariantNumeric))
success &= m_backing->setVariantNumeric(*value);
+ else
+ m_backing->setVariantNumeric(normal);
+
if (auto value = style->getPropertyCSSValue(CSSPropertyFontVariantAlternates))
success &= m_backing->setVariantAlternates(*value);
+ else
+ m_backing->setVariantAlternates(normal);
+
if (auto value = style->getPropertyCSSValue(CSSPropertyFontVariantEastAsian))
success &= m_backing->setVariantEastAsian(*value);
+ else
+ m_backing->setVariantEastAsian(normal);
+
if (success)
return;
m_backing->setVariantSettings(backup);
@@ -180,6 +274,8 @@
String FontFace::unicodeRange() const
{
+ if (!m_backing->ranges().size())
+ return "U+0-10FFFF";
RefPtr<CSSValueList> values = CSSValueList::createCommaSeparated();
for (auto& range : m_backing->ranges())
values->append(CSSUnicodeRangeValue::create(range.from(), range.to()));
@@ -201,4 +297,67 @@
return list->cssText();
}
+String FontFace::status() const
+{
+ switch (m_backing->status()) {
+ case CSSFontFace::Status::Pending:
+ return String("unloaded", String::ConstructFromLiteral);
+ case CSSFontFace::Status::Loading:
+ return String("loading", String::ConstructFromLiteral);
+ case CSSFontFace::Status::TimedOut:
+ return String("error", String::ConstructFromLiteral);
+ case CSSFontFace::Status::Success:
+ return String("loaded", String::ConstructFromLiteral);
+ case CSSFontFace::Status::Failure:
+ return String("error", String::ConstructFromLiteral);
+ }
+ ASSERT_NOT_REACHED();
+ return String("error", String::ConstructFromLiteral);
+}
+
+void FontFace::kick(CSSFontFace& face)
+{
+ ASSERT_UNUSED(face, &face == m_backing.ptr());
+ switch (m_backing->status()) {
+ case CSSFontFace::Status::TimedOut:
+ rejectPromise(NETWORK_ERR);
+ return;
+ case CSSFontFace::Status::Success:
+ fulfillPromise();
+ return;
+ case CSSFontFace::Status::Failure:
+ rejectPromise(NETWORK_ERR);
+ return;
+ default:
+ return;
+ }
+}
+
+void FontFace::load()
+{
+ m_backing->load();
+}
+
+void FontFace::fulfillPromise()
+{
+ // Normally, DeferredWrapper::callFunction resets the reference to the promise.
+ // However, API semantics require our promise to live for the entire lifetime of the FontFace.
+ // Let's make sure it stays alive.
+
+ Promise guard(m_promise);
+ m_promise.resolve(*this);
+ m_promise = guard;
+}
+
+void FontFace::rejectPromise(ExceptionCode code)
+{
+ // Normally, DeferredWrapper::callFunction resets the reference to the promise.
+ // However, API semantics require our promise to live for the entire lifetime of the FontFace.
+ // Let's make sure it stays alive.
+
+ Promise guard(m_promise);
+ m_promise.reject(DOMCoreException::create(ExceptionCodeDescription(code)).get());
+ m_promise = guard;
+}
+
}