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