[Font Loading] Split CSSFontSelector into a FontFaceSet implementation and the rest...
[WebKit-https.git] / Source / WebCore / css / FontFaceSet.cpp
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 "ExceptionCodeDescription.h"
31 #include "FontFace.h"
32 #include "JSDOMBinding.h"
33 #include "JSDOMCoreException.h"
34 #include "JSFontFace.h"
35 #include "JSFontFaceSet.h"
36
37 namespace WebCore {
38
39 static FontFaceSet::Promise createPromise(JSC::ExecState& exec)
40 {
41     JSDOMGlobalObject& globalObject = *JSC::jsCast<JSDOMGlobalObject*>(exec.lexicalGlobalObject());
42     return FontFaceSet::Promise(DeferredWrapper(&exec, &globalObject, JSC::JSPromiseDeferred::create(&exec, &globalObject)));
43 }
44
45 Ref<FontFaceSet> FontFaceSet::create(Document& document, const Vector<RefPtr<FontFace>>& initialFaces)
46 {
47     Ref<FontFaceSet> result = adoptRef(*new FontFaceSet(document, initialFaces));
48     result->suspendIfNeeded();
49     return result;
50 }
51
52 Ref<FontFaceSet> FontFaceSet::create(Document& document, CSSFontFaceSet& backing)
53 {
54     Ref<FontFaceSet> result = adoptRef(*new FontFaceSet(document, backing));
55     result->suspendIfNeeded();
56     return result;
57 }
58
59 FontFaceSet::FontFaceSet(Document& document, const Vector<RefPtr<FontFace>>& initialFaces)
60     : ActiveDOMObject(&document)
61     , m_backing(CSSFontFaceSet::create())
62 {
63     m_backing->addClient(*this);
64     for (auto& face : initialFaces)
65         add(face.get());
66 }
67
68 FontFaceSet::FontFaceSet(Document& document, CSSFontFaceSet& backing)
69     : ActiveDOMObject(&document)
70     , m_backing(backing)
71 {
72     m_backing->addClient(*this);
73 }
74
75 FontFaceSet::~FontFaceSet()
76 {
77     m_backing->removeClient(*this);
78 }
79
80 FontFaceSet::Iterator::Iterator(FontFaceSet& set)
81     : m_target(set)
82 {
83 }
84
85 bool FontFaceSet::Iterator::next(JSC::ExecState& execState, RefPtr<FontFace>& key, RefPtr<FontFace>& value)
86 {
87     if (m_index == m_target->size())
88         return true;
89     key = m_target->backing()[m_index++].wrapper(execState);
90     value = key;
91     return false;
92 }
93
94 FontFaceSet::PendingPromise::PendingPromise(Promise&& promise)
95     : promise(WTFMove(promise))
96 {
97 }
98
99 FontFaceSet::PendingPromise::~PendingPromise()
100 {
101 }
102
103 bool FontFaceSet::has(RefPtr<WebCore::FontFace> face) const
104 {
105     if (!face)
106         return false;
107     return m_backing->hasFace(face->backing());
108 }
109
110 size_t FontFaceSet::size() const
111 {
112     return m_backing->faceCount();
113 }
114
115 FontFaceSet& FontFaceSet::add(RefPtr<WebCore::FontFace> face)
116 {
117     if (face && !m_backing->hasFace(face->backing()))
118         m_backing->add(face->backing());
119     return *this;
120 }
121
122 bool FontFaceSet::remove(RefPtr<WebCore::FontFace> face)
123 {
124     if (!face)
125         return false;
126
127     bool result = m_backing->hasFace(face->backing());
128     if (result)
129         m_backing->remove(face->backing());
130     return result;
131 }
132
133 void FontFaceSet::clear()
134 {
135     while (m_backing->faceCount())
136         m_backing->remove(m_backing.get()[0]);
137 }
138
139 void FontFaceSet::load(JSC::ExecState& execState, const String& font, const String& text, DeferredWrapper&& promise, ExceptionCode& ec)
140 {
141     auto matchingFaces = m_backing->matchingFaces(font, text, ec);
142     if (ec)
143         return;
144
145     if (matchingFaces.isEmpty()) {
146         promise.resolve(Vector<RefPtr<FontFace>>());
147         return;
148     }
149
150     for (auto& face : matchingFaces)
151         face.get().load();
152
153     auto pendingPromise = PendingPromise::create(WTFMove(promise));
154     bool waiting = false;
155
156     for (auto& face : matchingFaces) {
157         if (face.get().status() == CSSFontFace::Status::Failure) {
158             pendingPromise->promise.reject(DOMCoreException::create(ExceptionCodeDescription(NETWORK_ERR)));
159             return;
160         }
161     }
162
163     for (auto& face : matchingFaces) {
164         pendingPromise->faces.append(face.get().wrapper(execState));
165         if (face.get().status() == CSSFontFace::Status::Success)
166             continue;
167         waiting = true;
168         auto& vector = m_pendingPromises.add(RefPtr<CSSFontFace>(&face.get()), Vector<Ref<PendingPromise>>()).iterator->value;
169         vector.append(pendingPromise.copyRef());
170     }
171
172     if (!waiting)
173         pendingPromise->promise.resolve(pendingPromise->faces);
174 }
175
176 bool FontFaceSet::check(const String& family, const String& text, ExceptionCode& ec)
177 {
178     return m_backing->check(family, text, ec);
179 }
180
181 auto FontFaceSet::promise(JSC::ExecState& execState) -> Promise&
182 {
183     if (!m_promise) {
184         m_promise = createPromise(execState);
185         if (m_backing->status() == CSSFontFaceSet::Status::Loaded)
186             fulfillPromise();
187     }
188     return m_promise.value();
189 }
190     
191 String FontFaceSet::status() const
192 {
193     switch (m_backing->status()) {
194     case CSSFontFaceSet::Status::Loading:
195         return String("loading", String::ConstructFromLiteral);
196     case CSSFontFaceSet::Status::Loaded:
197         return String("loaded", String::ConstructFromLiteral);
198     }
199     ASSERT_NOT_REACHED();
200     return String("loaded", String::ConstructFromLiteral);
201 }
202
203 bool FontFaceSet::canSuspendForDocumentSuspension() const
204 {
205     return m_backing->status() == CSSFontFaceSet::Status::Loaded;
206 }
207
208 void FontFaceSet::startedLoading()
209 {
210     // FIXME: Fire a "loading" event asynchronously.
211 }
212
213 void FontFaceSet::completedLoading()
214 {
215     if (m_promise)
216         fulfillPromise();
217     m_promise = Nullopt;
218     // FIXME: Fire a "loadingdone" and possibly a "loadingerror" event asynchronously.
219 }
220
221 void FontFaceSet::fulfillPromise()
222 {
223     // Normally, DeferredWrapper::callFunction resets the reference to the promise.
224     // However, API semantics require our promise to live for the entire lifetime of the FontFace.
225     // Let's make sure it stays alive.
226
227     Promise guard(m_promise.value());
228     m_promise.value().resolve(*this);
229     m_promise = guard;
230 }
231
232 void FontFaceSet::faceFinished(CSSFontFace& face, CSSFontFace::Status newStatus)
233 {
234     auto iterator = m_pendingPromises.find(&face);
235     if (iterator == m_pendingPromises.end())
236         return;
237
238     for (auto& pendingPromise : iterator->value) {
239         if (newStatus == CSSFontFace::Status::Success) {
240             if (pendingPromise->hasOneRef())
241                 pendingPromise->promise.resolve(pendingPromise->faces);
242         } else {
243             ASSERT(newStatus == CSSFontFace::Status::Failure);
244             // The first resolution wins, so we can just reject early now.
245             pendingPromise->promise.reject(DOMCoreException::create(ExceptionCodeDescription(NETWORK_ERR)));
246         }
247     }
248
249     m_pendingPromises.remove(iterator);
250 }
251
252 }