1/*
2 * Copyright (C) 2016 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "FontFaceSet.h"
28
29#include "Document.h"
30#include "FontFace.h"
31#include "JSDOMBinding.h"
32#include "JSFontFace.h"
33#include "JSFontFaceSet.h"
34#include <wtf/IsoMallocInlines.h>
35
36namespace WebCore {
37
38WTF_MAKE_ISO_ALLOCATED_IMPL(FontFaceSet);
39
40Ref<FontFaceSet> FontFaceSet::create(Document& document, const Vector<RefPtr<FontFace>>& initialFaces)
41{
42 Ref<FontFaceSet> result = adoptRef(*new FontFaceSet(document, initialFaces));
43 result->suspendIfNeeded();
44 return result;
45}
46
47Ref<FontFaceSet> FontFaceSet::create(Document& document, CSSFontFaceSet& backing)
48{
49 Ref<FontFaceSet> result = adoptRef(*new FontFaceSet(document, backing));
50 result->suspendIfNeeded();
51 return result;
52}
53
54FontFaceSet::FontFaceSet(Document& document, const Vector<RefPtr<FontFace>>& initialFaces)
55 : ActiveDOMObject(document)
56 , m_backing(CSSFontFaceSet::create())
57 , m_readyPromise(*this, &FontFaceSet::readyPromiseResolve)
58{
59 m_backing->addClient(*this);
60 for (auto& face : initialFaces)
61 add(*face);
62}
63
64FontFaceSet::FontFaceSet(Document& document, CSSFontFaceSet& backing)
65 : ActiveDOMObject(document)
66 , m_backing(backing)
67 , m_readyPromise(*this, &FontFaceSet::readyPromiseResolve)
68{
69 if (!backing.hasActiveFontFaces())
70 m_readyPromise.resolve(*this);
71 m_backing->addClient(*this);
72}
73
74FontFaceSet::~FontFaceSet()
75{
76 m_backing->removeClient(*this);
77}
78
79FontFaceSet::Iterator::Iterator(FontFaceSet& set)
80 : m_target(set)
81{
82}
83
84RefPtr<FontFace> FontFaceSet::Iterator::next()
85{
86 if (m_index == m_target->size())
87 return nullptr;
88 return m_target->backing()[m_index++].wrapper();
89}
90
91FontFaceSet::PendingPromise::PendingPromise(LoadPromise&& promise)
92 : promise(WTFMove(promise))
93{
94}
95
96FontFaceSet::PendingPromise::~PendingPromise() = default;
97
98bool FontFaceSet::has(FontFace& face) const
99{
100 return m_backing->hasFace(face.backing());
101}
102
103size_t FontFaceSet::size() const
104{
105 return m_backing->faceCount();
106}
107
108FontFaceSet& FontFaceSet::add(FontFace& face)
109{
110 if (!m_backing->hasFace(face.backing()))
111 m_backing->add(face.backing());
112 return *this;
113}
114
115bool FontFaceSet::remove(FontFace& face)
116{
117 bool result = m_backing->hasFace(face.backing());
118 if (result)
119 m_backing->remove(face.backing());
120 return result;
121}
122
123void FontFaceSet::clear()
124{
125 while (m_backing->faceCount())
126 m_backing->remove(m_backing.get()[0]);
127}
128
129void FontFaceSet::load(const String& font, const String& text, LoadPromise&& promise)
130{
131 auto matchingFacesResult = m_backing->matchingFacesExcludingPreinstalledFonts(font, text);
132 if (matchingFacesResult.hasException()) {
133 promise.reject(matchingFacesResult.releaseException());
134 return;
135 }
136 auto matchingFaces = matchingFacesResult.releaseReturnValue();
137
138 if (matchingFaces.isEmpty()) {
139 promise.resolve({ });
140 return;
141 }
142
143 for (auto& face : matchingFaces)
144 face.get().load();
145
146 for (auto& face : matchingFaces) {
147 if (face.get().status() == CSSFontFace::Status::Failure) {
148 promise.reject(NetworkError);
149 return;
150 }
151 }
152
153 auto pendingPromise = PendingPromise::create(WTFMove(promise));
154 bool waiting = false;
155
156 for (auto& face : matchingFaces) {
157 pendingPromise->faces.append(face.get().wrapper());
158 if (face.get().status() == CSSFontFace::Status::Success)
159 continue;
160 waiting = true;
161 ASSERT(face.get().existingWrapper());
162 m_pendingPromises.add(face.get().existingWrapper(), Vector<Ref<PendingPromise>>()).iterator->value.append(pendingPromise.copyRef());
163 }
164
165 if (!waiting)
166 pendingPromise->promise.resolve(pendingPromise->faces);
167}
168
169ExceptionOr<bool> FontFaceSet::check(const String& family, const String& text)
170{
171 return m_backing->check(family, text);
172}
173
174auto FontFaceSet::status() const -> LoadStatus
175{
176 switch (m_backing->status()) {
177 case CSSFontFaceSet::Status::Loading:
178 return LoadStatus::Loading;
179 case CSSFontFaceSet::Status::Loaded:
180 return LoadStatus::Loaded;
181 }
182 ASSERT_NOT_REACHED();
183 return LoadStatus::Loaded;
184}
185
186bool FontFaceSet::canSuspendForDocumentSuspension() const
187{
188 return m_backing->status() == CSSFontFaceSet::Status::Loaded;
189}
190
191void FontFaceSet::startedLoading()
192{
193 // FIXME: Fire a "loading" event asynchronously.
194 m_readyPromise.clear();
195}
196
197void FontFaceSet::completedLoading()
198{
199 m_readyPromise.resolve(*this);
200}
201
202void FontFaceSet::faceFinished(CSSFontFace& face, CSSFontFace::Status newStatus)
203{
204 if (!face.existingWrapper())
205 return;
206
207 auto iterator = m_pendingPromises.find(face.existingWrapper());
208 if (iterator == m_pendingPromises.end())
209 return;
210
211 for (auto& pendingPromise : iterator->value) {
212 if (pendingPromise->hasReachedTerminalState)
213 continue;
214 if (newStatus == CSSFontFace::Status::Success) {
215 if (pendingPromise->hasOneRef()) {
216 pendingPromise->promise.resolve(pendingPromise->faces);
217 pendingPromise->hasReachedTerminalState = true;
218 }
219 } else {
220 ASSERT(newStatus == CSSFontFace::Status::Failure);
221 pendingPromise->promise.reject(NetworkError);
222 pendingPromise->hasReachedTerminalState = true;
223 }
224 }
225
226 m_pendingPromises.remove(iterator);
227}
228
229FontFaceSet& FontFaceSet::readyPromiseResolve()
230{
231 return *this;
232}
233
234}
235