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