| /* |
| * Copyright (C) 2016 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "config.h" |
| #include "FontFaceSet.h" |
| |
| #include "DOMPromiseProxy.h" |
| #include "Document.h" |
| #include "FontFace.h" |
| #include "FrameLoader.h" |
| #include "JSDOMBinding.h" |
| #include "JSDOMPromiseDeferred.h" |
| #include "JSFontFace.h" |
| #include "JSFontFaceSet.h" |
| #include <wtf/IsoMallocInlines.h> |
| |
| namespace WebCore { |
| |
| WTF_MAKE_ISO_ALLOCATED_IMPL(FontFaceSet); |
| |
| Ref<FontFaceSet> FontFaceSet::create(Document& document, const Vector<RefPtr<FontFace>>& initialFaces) |
| { |
| Ref<FontFaceSet> result = adoptRef(*new FontFaceSet(document, initialFaces)); |
| result->suspendIfNeeded(); |
| return result; |
| } |
| |
| Ref<FontFaceSet> FontFaceSet::create(Document& document, CSSFontFaceSet& backing) |
| { |
| Ref<FontFaceSet> result = adoptRef(*new FontFaceSet(document, backing)); |
| result->suspendIfNeeded(); |
| return result; |
| } |
| |
| FontFaceSet::FontFaceSet(Document& document, const Vector<RefPtr<FontFace>>& initialFaces) |
| : ActiveDOMObject(document) |
| , m_backing(CSSFontFaceSet::create()) |
| , m_readyPromise(makeUniqueRef<ReadyPromise>(*this, &FontFaceSet::readyPromiseResolve)) |
| , m_taskQueue(SuspendableTaskQueue::create(document)) |
| { |
| m_backing->addClient(*this); |
| for (auto& face : initialFaces) |
| add(*face); |
| } |
| |
| FontFaceSet::FontFaceSet(Document& document, CSSFontFaceSet& backing) |
| : ActiveDOMObject(document) |
| , m_backing(backing) |
| , m_readyPromise(makeUniqueRef<ReadyPromise>(*this, &FontFaceSet::readyPromiseResolve)) |
| , m_taskQueue(SuspendableTaskQueue::create(document)) |
| { |
| if (document.frame()) |
| m_isFirstLayoutDone = document.frame()->loader().stateMachine().firstLayoutDone(); |
| |
| if (m_isFirstLayoutDone && !backing.hasActiveFontFaces()) |
| m_readyPromise->resolve(*this); |
| |
| m_backing->addClient(*this); |
| } |
| |
| FontFaceSet::~FontFaceSet() |
| { |
| m_backing->removeClient(*this); |
| } |
| |
| FontFaceSet::Iterator::Iterator(FontFaceSet& set) |
| : m_target(set) |
| { |
| } |
| |
| RefPtr<FontFace> FontFaceSet::Iterator::next() |
| { |
| if (m_index == m_target->size()) |
| return nullptr; |
| return m_target->backing()[m_index++].wrapper(); |
| } |
| |
| FontFaceSet::PendingPromise::PendingPromise(LoadPromise&& promise) |
| : promise(makeUniqueRef<LoadPromise>(WTFMove(promise))) |
| { |
| } |
| |
| FontFaceSet::PendingPromise::~PendingPromise() = default; |
| |
| bool FontFaceSet::has(FontFace& face) const |
| { |
| return m_backing->hasFace(face.backing()); |
| } |
| |
| size_t FontFaceSet::size() const |
| { |
| return m_backing->faceCount(); |
| } |
| |
| FontFaceSet& FontFaceSet::add(FontFace& face) |
| { |
| if (!m_backing->hasFace(face.backing())) |
| m_backing->add(face.backing()); |
| return *this; |
| } |
| |
| bool FontFaceSet::remove(FontFace& face) |
| { |
| bool result = m_backing->hasFace(face.backing()); |
| if (result) |
| m_backing->remove(face.backing()); |
| return result; |
| } |
| |
| void FontFaceSet::clear() |
| { |
| while (m_backing->faceCount()) |
| m_backing->remove(m_backing.get()[0]); |
| } |
| |
| void FontFaceSet::load(const String& font, const String& text, LoadPromise&& promise) |
| { |
| auto matchingFacesResult = m_backing->matchingFacesExcludingPreinstalledFonts(font, text); |
| if (matchingFacesResult.hasException()) { |
| promise.reject(matchingFacesResult.releaseException()); |
| return; |
| } |
| auto matchingFaces = matchingFacesResult.releaseReturnValue(); |
| |
| if (matchingFaces.isEmpty()) { |
| promise.resolve({ }); |
| return; |
| } |
| |
| for (auto& face : matchingFaces) |
| face.get().load(); |
| |
| for (auto& face : matchingFaces) { |
| if (face.get().status() == CSSFontFace::Status::Failure) { |
| promise.reject(NetworkError); |
| return; |
| } |
| } |
| |
| auto pendingPromise = PendingPromise::create(WTFMove(promise)); |
| bool waiting = false; |
| |
| for (auto& face : matchingFaces) { |
| pendingPromise->faces.append(face.get().wrapper()); |
| if (face.get().status() == CSSFontFace::Status::Success) |
| continue; |
| waiting = true; |
| ASSERT(face.get().existingWrapper()); |
| m_pendingPromises.add(face.get().existingWrapper(), Vector<Ref<PendingPromise>>()).iterator->value.append(pendingPromise.copyRef()); |
| } |
| |
| if (!waiting) |
| pendingPromise->promise->resolve(pendingPromise->faces); |
| } |
| |
| ExceptionOr<bool> FontFaceSet::check(const String& family, const String& text) |
| { |
| return m_backing->check(family, text); |
| } |
| |
| auto FontFaceSet::status() const -> LoadStatus |
| { |
| switch (m_backing->status()) { |
| case CSSFontFaceSet::Status::Loading: |
| return LoadStatus::Loading; |
| case CSSFontFaceSet::Status::Loaded: |
| return LoadStatus::Loaded; |
| } |
| ASSERT_NOT_REACHED(); |
| return LoadStatus::Loaded; |
| } |
| |
| bool FontFaceSet::canSuspendForDocumentSuspension() const |
| { |
| return true; |
| } |
| |
| void FontFaceSet::startedLoading() |
| { |
| // FIXME: Fire a "loading" event asynchronously. |
| } |
| |
| void FontFaceSet::didFirstLayout() |
| { |
| m_isFirstLayoutDone = true; |
| if (!m_backing->hasActiveFontFaces() && !m_readyPromise->isFulfilled()) { |
| m_taskQueue->enqueueTask([this] { |
| if (!m_readyPromise->isFulfilled()) |
| m_readyPromise->resolve(*this); |
| }); |
| } |
| } |
| |
| void FontFaceSet::completedLoading() |
| { |
| if (m_isFirstLayoutDone && !m_readyPromise->isFulfilled()) { |
| m_taskQueue->enqueueTask([this] { |
| if (!m_readyPromise->isFulfilled()) |
| m_readyPromise->resolve(*this); |
| }); |
| } |
| } |
| |
| void FontFaceSet::faceFinished(CSSFontFace& face, CSSFontFace::Status newStatus) |
| { |
| if (!face.existingWrapper()) |
| return; |
| |
| auto pendingPromises = m_pendingPromises.take(face.existingWrapper()); |
| if (pendingPromises.isEmpty()) |
| return; |
| |
| m_taskQueue->enqueueTask([pendingPromises = WTFMove(pendingPromises), newStatus] { |
| for (auto& pendingPromise : pendingPromises) { |
| if (pendingPromise->hasReachedTerminalState) |
| continue; |
| if (newStatus == CSSFontFace::Status::Success) { |
| if (pendingPromise->hasOneRef()) { |
| pendingPromise->promise->resolve(pendingPromise->faces); |
| pendingPromise->hasReachedTerminalState = true; |
| } |
| } else { |
| ASSERT(newStatus == CSSFontFace::Status::Failure); |
| pendingPromise->promise->reject(NetworkError); |
| pendingPromise->hasReachedTerminalState = true; |
| } |
| } |
| }); |
| } |
| |
| FontFaceSet& FontFaceSet::readyPromiseResolve() |
| { |
| return *this; |
| } |
| |
| } |