blob: 1ecac340a460056eaaee19e0ca6cde0cd02fcaa2 [file] [log] [blame]
/*
* Copyright (C) 2016-2024 Apple 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. ``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
* 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 "FontFace.h"
#include "CSSFontFaceSource.h"
#include "CSSFontSelector.h"
#include "CSSPrimitiveValueMappings.h"
#include "CSSPropertyParserConsumer+Font.h"
#include "CSSValueList.h"
#include "CSSValuePool.h"
#include "DOMPromiseProxy.h"
#include "JSFontFace.h"
#include "TrustedFonts.h"
#include <JavaScriptCore/ArrayBuffer.h>
#include <JavaScriptCore/ArrayBufferView.h>
#include <JavaScriptCore/JSCInlines.h>
namespace WebCore {
static bool populateFontFaceWithArrayBuffer(CSSFontFace& fontFace, Ref<JSC::ArrayBufferView>&& arrayBufferView)
{
auto source = makeUniqueWithoutRefCountedCheck<CSSFontFaceSource>(fontFace, WTF::move(arrayBufferView));
fontFace.adoptSource(WTF::move(source));
return false;
}
void FontFace::setErrorState()
{
m_loadedPromise->reject(Exception { ExceptionCode::SyntaxError });
m_backing->setErrorState();
}
Ref<FontFace> FontFace::create(ScriptExecutionContext& context, const String& family, Source&& source, const Descriptors& descriptors)
{
ASSERT(context.cssFontSelector());
auto result = adoptRef(*new FontFace(*context.cssFontSelector()));
result->suspendIfNeeded();
#if COMPILER(GCC) && (CPU(ARM) || CPU(ARM64))
// FIXME: Workaround for GCC bug https://gcc.gnu.org/bugzilla/show_bug.cgi?id=115033
// that is related to https://gcc.gnu.org/bugzilla/show_bug.cgi?id=115135 as well.
volatile
#endif
bool dataRequiresAsynchronousLoading = true;
auto setFamilyResult = result->setFamily(family);
if (setFamilyResult.hasException()) {
result->setErrorState();
return result;
}
auto fontTrustedTypes = context.settingsValues().downloadableBinaryFontTrustedTypes;
auto sourceConversionResult = WTF::switchOn(source,
[&] (String& string) -> ExceptionOr<void> {
auto value = CSSPropertyParserHelpers::parseFontFaceSrc(string, context);
if (!value)
return Exception { ExceptionCode::SyntaxError };
CSSFontFace::appendSources(result->backing(), *value, &context, false);
return { };
},
[&, fontTrustedTypes] (RefPtr<ArrayBufferView>& arrayBufferView) -> ExceptionOr<void> {
if (!arrayBufferView || fontBinaryParsingPolicy(arrayBufferView->span(), fontTrustedTypes) == FontParsingPolicy::Deny)
return { };
dataRequiresAsynchronousLoading = populateFontFaceWithArrayBuffer(result->backing(), arrayBufferView.releaseNonNull());
return { };
},
[&, fontTrustedTypes] (RefPtr<ArrayBuffer>& arrayBuffer) -> ExceptionOr<void> {
if (!arrayBuffer || fontBinaryParsingPolicy(arrayBuffer->span(), fontTrustedTypes) == FontParsingPolicy::Deny)
return { };
unsigned byteLength = arrayBuffer->byteLength();
auto arrayBufferView = JSC::Uint8Array::create(WTF::move(arrayBuffer), 0, byteLength);
dataRequiresAsynchronousLoading = populateFontFaceWithArrayBuffer(result->backing(), WTF::move(arrayBufferView));
return { };
}
);
if (sourceConversionResult.hasException()) {
result->setErrorState();
return result;
}
// These ternaries match the default strings inside the FontFaceDescriptors dictionary inside FontFace.idl.
auto setStyleResult = result->setStyle(context, descriptors.style.isEmpty() ? "normal"_s : descriptors.style);
if (setStyleResult.hasException()) {
result->setErrorState();
return result;
}
auto setWeightResult = result->setWeight(context, descriptors.weight.isEmpty() ? "normal"_s : descriptors.weight);
if (setWeightResult.hasException()) {
result->setErrorState();
return result;
}
auto setWidthResult = result->setWidth(context, descriptors.width.isEmpty() ? "normal"_s : descriptors.width);
if (setWidthResult.hasException()) {
result->setErrorState();
return result;
}
auto setUnicodeRangeResult = result->setUnicodeRange(context, descriptors.unicodeRange.isEmpty() ? "U+0-10FFFF"_s : descriptors.unicodeRange);
if (setUnicodeRangeResult.hasException()) {
result->setErrorState();
return result;
}
auto setFeatureSettingsResult = result->setFeatureSettings(context, descriptors.featureSettings.isEmpty() ? "normal"_s : descriptors.featureSettings);
if (setFeatureSettingsResult.hasException()) {
result->setErrorState();
return result;
}
auto setDisplayResult = result->setDisplay(context, descriptors.display.isEmpty() ? "auto"_s : descriptors.display);
if (setDisplayResult.hasException()) {
result->setErrorState();
return result;
}
auto setSizeAdjustResult = result->setSizeAdjust(context, descriptors.sizeAdjust.isEmpty() ? "100%"_s : descriptors.sizeAdjust);
if (setSizeAdjustResult.hasException()) {
result->setErrorState();
return result;
}
if (!dataRequiresAsynchronousLoading) {
result->backing().load();
auto status = result->backing().status();
ASSERT_UNUSED(status, status == CSSFontFace::Status::Success || status == CSSFontFace::Status::Failure);
}
return result;
}
Ref<FontFace> FontFace::create(ScriptExecutionContext* context, CSSFontFace& face)
{
auto fontFace = adoptRef(*new FontFace(context, face));
fontFace->suspendIfNeeded();
return fontFace;
}
FontFace::FontFace(CSSFontSelector& fontSelector)
: ActiveDOMObject(fontSelector.scriptExecutionContext())
, m_backing(CSSFontFace::create(fontSelector, nullptr, this))
, m_loadedPromise(makeUniqueRef<LoadedPromise>(*this, &FontFace::loadedPromiseResolve))
{
m_backing->addClient(*this);
}
FontFace::FontFace(ScriptExecutionContext* context, CSSFontFace& face)
: ActiveDOMObject(context)
, m_backing(face)
, m_loadedPromise(makeUniqueRef<LoadedPromise>(*this, &FontFace::loadedPromiseResolve))
{
m_backing->addClient(*this);
}
FontFace::~FontFace()
{
m_backing->removeClient(*this);
}
ExceptionOr<void> FontFace::setFamily(const String& family)
{
if (family.isEmpty())
return Exception { ExceptionCode::SyntaxError };
m_backing->setFamily(CSSPrimitiveValue::createFontFamily(family));
return { };
}
ExceptionOr<void> FontFace::setStyle(ScriptExecutionContext& context, const String& style)
{
if (auto value = CSSPropertyParserHelpers::parseFontFaceFontStyle(style, context)) {
m_backing->setStyle(*value);
return { };
}
return Exception { ExceptionCode::SyntaxError };
}
ExceptionOr<void> FontFace::setWeight(ScriptExecutionContext& context, const String& weight)
{
if (auto value = CSSPropertyParserHelpers::parseFontFaceFontWeight(weight, context)) {
m_backing->setWeight(*value);
return { };
}
return Exception { ExceptionCode::SyntaxError };
}
ExceptionOr<void> FontFace::setWidth(ScriptExecutionContext& context, const String& width)
{
if (auto value = CSSPropertyParserHelpers::parseFontFaceFontWidth(width, context)) {
m_backing->setWidth(*value);
return { };
}
return Exception { ExceptionCode::SyntaxError };
}
ExceptionOr<void> FontFace::setUnicodeRange(ScriptExecutionContext& context, const String& unicodeRange)
{
if (auto value = CSSPropertyParserHelpers::parseFontFaceUnicodeRange(unicodeRange, context)) {
m_backing->setUnicodeRange(*value);
return { };
}
return Exception { ExceptionCode::SyntaxError };
}
ExceptionOr<void> FontFace::setFeatureSettings(ScriptExecutionContext& context, const String& featureSettings)
{
if (auto value = CSSPropertyParserHelpers::parseFontFaceFeatureSettings(featureSettings, context)) {
m_backing->setFeatureSettings(*value);
return { };
}
return Exception { ExceptionCode::SyntaxError };
}
ExceptionOr<void> FontFace::setDisplay(ScriptExecutionContext& context, const String& display)
{
if (auto value = CSSPropertyParserHelpers::parseFontFaceDisplay(display, context)) {
m_backing->setDisplay(*value);
return { };
}
return Exception { ExceptionCode::SyntaxError };
}
ExceptionOr<void> FontFace::setSizeAdjust(ScriptExecutionContext& context, const String& sizeAdjust)
{
if (auto value = CSSPropertyParserHelpers::parseFontFaceSizeAdjust(sizeAdjust, context)) {
m_backing->setSizeAdjust(*value);
return { };
}
return Exception { ExceptionCode::SyntaxError };
}
String FontFace::family() const
{
if (auto value = m_backing->family(); !value.isNull())
return value;
return "normal"_s;
}
String FontFace::style() const
{
if (auto value = m_backing->style(); !value.isNull())
return value;
return "normal"_s;
}
String FontFace::weight() const
{
if (auto value = m_backing->weight(); !value.isNull())
return value;
return "normal"_s;
}
String FontFace::width() const
{
if (auto value = m_backing->width(); !value.isNull())
return value;
return "normal"_s;
}
String FontFace::unicodeRange() const
{
if (auto value = m_backing->unicodeRange(); !value.isNull())
return value;
return "U+0-10FFFF"_s;
}
String FontFace::featureSettings() const
{
if (auto value = m_backing->featureSettings(); !value.isNull())
return value;
return "normal"_s;
}
String FontFace::sizeAdjust() const
{
if (auto value = m_backing->sizeAdjust(); !value.isNull())
return value;
return "100%"_s;
}
String FontFace::display() const
{
if (auto value = m_backing->display(); !value.isNull())
return value;
return autoAtom();
}
auto FontFace::status() const -> LoadStatus
{
switch (m_backing->status()) {
case CSSFontFace::Status::Pending:
return LoadStatus::Unloaded;
case CSSFontFace::Status::Loading:
return LoadStatus::Loading;
case CSSFontFace::Status::TimedOut:
return LoadStatus::Error;
case CSSFontFace::Status::Success:
return LoadStatus::Loaded;
case CSSFontFace::Status::Failure:
return LoadStatus::Error;
}
ASSERT_NOT_REACHED();
return LoadStatus::Error;
}
void FontFace::adopt(CSSFontFace& newFace)
{
m_backing->removeClient(*this);
m_backing = newFace;
m_backing->addClient(*this);
newFace.setWrapper(*this);
}
void FontFace::fontStateChanged(CSSFontFace& face, CSSFontFace::Status, CSSFontFace::Status newState)
{
ASSERT_UNUSED(face, &face == m_backing.ptr());
switch (newState) {
case CSSFontFace::Status::Loading:
break;
case CSSFontFace::Status::TimedOut:
break;
case CSSFontFace::Status::Success:
// FIXME: This check should not be needed, but because FontFace's are sometimes adopted after they have already
// gone through a load cycle, we can sometimes come back through here and try to resolve the promise again.
if (!m_loadedPromise->isFulfilled())
m_loadedPromise->resolve(*this);
return;
case CSSFontFace::Status::Failure:
// FIXME: This check should not be needed, but because FontFace's are sometimes adopted after they have already
// gone through a load cycle, we can sometimes come back through here and try to resolve the promise again.
if (!m_loadedPromise->isFulfilled())
m_loadedPromise->reject(Exception { ExceptionCode::NetworkError });
return;
case CSSFontFace::Status::Pending:
ASSERT_NOT_REACHED();
return;
}
}
auto FontFace::loadForBindings() -> LoadedPromise&
{
m_mayLoadedPromiseBeScriptObservable = true;
m_backing->load();
return m_loadedPromise.get();
}
auto FontFace::loadedForBindings() -> LoadedPromise&
{
m_mayLoadedPromiseBeScriptObservable = true;
return m_loadedPromise.get();
}
FontFace& FontFace::loadedPromiseResolve()
{
return *this;
}
bool FontFace::virtualHasPendingActivity() const
{
return m_mayLoadedPromiseBeScriptObservable && !m_loadedPromise->isFulfilled();
}
}