FontFaceSet binding does not handle null correctly
[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);
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 Optional<WTF::KeyValuePair<RefPtr<FontFace>, RefPtr<FontFace>>> FontFaceSet::Iterator::next(JSC::ExecState& state)
86 {
87     if (m_index == m_target->size())
88         return Nullopt;
89     RefPtr<FontFace> item = m_target->backing()[m_index++].wrapper(state);
90     return WTF::KeyValuePair<RefPtr<FontFace>, RefPtr<FontFace>>(item, item);
91 }
92
93 FontFaceSet::PendingPromise::PendingPromise(Promise&& promise)
94     : promise(WTFMove(promise))
95 {
96 }
97
98 FontFaceSet::PendingPromise::~PendingPromise()
99 {
100 }
101
102 bool FontFaceSet::has(FontFace& face) const
103 {
104     return m_backing->hasFace(face.backing());
105 }
106
107 size_t FontFaceSet::size() const
108 {
109     return m_backing->faceCount();
110 }
111
112 FontFaceSet& FontFaceSet::add(FontFace& face)
113 {
114     if (!m_backing->hasFace(face.backing()))
115         m_backing->add(face.backing());
116     return *this;
117 }
118
119 bool FontFaceSet::remove(FontFace& face)
120 {
121     bool result = m_backing->hasFace(face.backing());
122     if (result)
123         m_backing->remove(face.backing());
124     return result;
125 }
126
127 void FontFaceSet::clear()
128 {
129     while (m_backing->faceCount())
130         m_backing->remove(m_backing.get()[0]);
131 }
132
133 void FontFaceSet::load(JSC::ExecState& execState, const String& font, const String& text, DeferredWrapper&& promise, ExceptionCode& ec)
134 {
135     ec = 0;
136     auto matchingFaces = m_backing->matchingFaces(font, text, ec);
137     if (ec)
138         return;
139
140     if (matchingFaces.isEmpty()) {
141         promise.resolve(Vector<RefPtr<FontFace>>());
142         return;
143     }
144
145     for (auto& face : matchingFaces)
146         face.get().load();
147
148     for (auto& face : matchingFaces) {
149         if (face.get().status() == CSSFontFace::Status::Failure) {
150             promise.reject(DOMCoreException::create(ExceptionCodeDescription(NETWORK_ERR)).ptr());
151             return;
152         }
153     }
154
155     auto pendingPromise = PendingPromise::create(WTFMove(promise));
156     bool waiting = false;
157
158     for (auto& face : matchingFaces) {
159         pendingPromise->faces.append(face.get().wrapper(execState));
160         if (face.get().status() == CSSFontFace::Status::Success)
161             continue;
162         waiting = true;
163         m_pendingPromises.add(&face.get(), Vector<Ref<PendingPromise>>()).iterator->value.append(pendingPromise.copyRef());
164     }
165
166     if (!waiting)
167         pendingPromise->promise.resolve(pendingPromise->faces);
168 }
169
170 bool FontFaceSet::check(const String& family, const String& text, ExceptionCode& ec)
171 {
172     return m_backing->check(family, text, ec);
173 }
174
175 auto FontFaceSet::promise(JSC::ExecState& execState) -> Promise&
176 {
177     if (!m_promise) {
178         m_promise = createPromise(execState);
179         if (m_backing->status() == CSSFontFaceSet::Status::Loaded)
180             fulfillPromise();
181     }
182     return m_promise.value();
183 }
184     
185 String FontFaceSet::status() const
186 {
187     switch (m_backing->status()) {
188     case CSSFontFaceSet::Status::Loading:
189         return ASCIILiteral("loading");
190     case CSSFontFaceSet::Status::Loaded:
191         return ASCIILiteral("loaded");
192     }
193     ASSERT_NOT_REACHED();
194     return ASCIILiteral("loaded");
195 }
196
197 bool FontFaceSet::canSuspendForDocumentSuspension() const
198 {
199     return m_backing->status() == CSSFontFaceSet::Status::Loaded;
200 }
201
202 void FontFaceSet::startedLoading()
203 {
204     // FIXME: Fire a "loading" event asynchronously.
205 }
206
207 void FontFaceSet::completedLoading()
208 {
209     if (m_promise)
210         fulfillPromise();
211     m_promise = Nullopt;
212     // FIXME: Fire a "loadingdone" and possibly a "loadingerror" event asynchronously.
213 }
214
215 void FontFaceSet::fulfillPromise()
216 {
217     // Normally, DeferredWrapper::callFunction resets the reference to the promise.
218     // However, API semantics require our promise to live for the entire lifetime of the FontFace.
219     // Let's make sure it stays alive.
220
221     Promise guard(m_promise.value());
222     m_promise.value().resolve(*this);
223     m_promise = guard;
224 }
225
226 void FontFaceSet::faceFinished(CSSFontFace& face, CSSFontFace::Status newStatus)
227 {
228     auto iterator = m_pendingPromises.find(&face);
229     if (iterator == m_pendingPromises.end())
230         return;
231
232     for (auto& pendingPromise : iterator->value) {
233         if (pendingPromise->hasReachedTerminalState)
234             continue;
235         if (newStatus == CSSFontFace::Status::Success) {
236             if (pendingPromise->hasOneRef()) {
237                 pendingPromise->promise.resolve(pendingPromise->faces);
238                 pendingPromise->hasReachedTerminalState = true;
239             }
240         } else {
241             ASSERT(newStatus == CSSFontFace::Status::Failure);
242             pendingPromise->promise.reject(DOMCoreException::create(ExceptionCodeDescription(NETWORK_ERR)));
243             pendingPromise->hasReachedTerminalState = true;
244         }
245     }
246
247     m_pendingPromises.remove(iterator);
248 }
249
250 void FontFaceSet::load(JSC::ExecState& state, const String& font, DeferredWrapper&& promise, ExceptionCode& ec)
251 {
252     load(state, font, ASCIILiteral(" "), WTFMove(promise), ec);
253 }
254
255 bool FontFaceSet::check(const String& font, ExceptionCode& ec)
256 {
257     return check(font, ASCIILiteral(" "), ec);
258 }
259
260 }