/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "mozilla/dom/FontFace.h"

#include <algorithm>

#include "gfxFontUtils.h"
#include "mozilla/CycleCollectedJSContext.h"
#include "mozilla/ServoBindings.h"
#include "mozilla/ServoCSSParser.h"
#include "mozilla/ServoStyleSet.h"
#include "mozilla/ServoUtils.h"
#include "mozilla/StaticPrefs_layout.h"
#include "mozilla/dom/CSSFontFaceRule.h"
#include "mozilla/dom/FontFaceBinding.h"
#include "mozilla/dom/FontFaceImpl.h"
#include "mozilla/dom/FontFaceSet.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/TypedArray.h"
#include "mozilla/dom/UnionTypes.h"
#include "nsStyleUtil.h"

namespace mozilla::dom {

// -- Utility functions ------------------------------------------------------

template <typename T>
static void GetDataFrom(const T& aObject, uint8_t*& aBuffer,
                        uint32_t& aLength) {
  MOZ_ASSERT(!aBuffer);
  // We need to use malloc here because the gfxUserFontEntry will be calling
  // free on it, so the Vector's default AllocPolicy (MallocAllocPolicy) is
  // fine.
  Maybe<Vector<uint8_t>> buffer =
      aObject.template CreateFromData<Vector<uint8_t>>();
  if (buffer.isNothing()) {
    return;
  }
  aLength = buffer->length();
  aBuffer = buffer->extractOrCopyRawBuffer();
}

// -- FontFace ---------------------------------------------------------------

NS_IMPL_CYCLE_COLLECTION_CLASS(FontFace)

NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(FontFace)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLoaded)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END

NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(FontFace)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mLoaded)
  tmp->Destroy();
  NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
NS_IMPL_CYCLE_COLLECTION_UNLINK_END

NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(FontFace)
  NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
NS_IMPL_CYCLE_COLLECTION_TRACE_END

NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(FontFace)
  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
  NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END

NS_IMPL_CYCLE_COLLECTING_ADDREF(FontFace)
NS_IMPL_CYCLE_COLLECTING_RELEASE(FontFace)

FontFace::FontFace(nsIGlobalObject* aParent) { BindToGlobal(aParent); }

FontFace::~FontFace() {
  // Assert that we don't drop any FontFace objects during a Servo traversal,
  // since PostTraversalTask objects can hold raw pointers to FontFaces.
  MOZ_ASSERT(!gfxFontUtils::IsInServoTraversal());
  Destroy();
}

void FontFace::Destroy() { mImpl->Destroy(); }

void FontFace::DisconnectFromOwner() {
  GlobalTeardownObserver::DisconnectFromOwner();
  if (mImpl) {
    mImpl->StopKeepingOwnerAlive();
  }
}

JSObject* FontFace::WrapObject(JSContext* aCx,
                               JS::Handle<JSObject*> aGivenProto) {
  return FontFace_Binding::Wrap(aCx, this, aGivenProto);
}

already_AddRefed<FontFace> FontFace::CreateForRule(
    nsIGlobalObject* aGlobal, FontFaceSet* aFontFaceSet,
    StyleLockedFontFaceRule* aRule) {
  FontFaceSetImpl* setImpl = aFontFaceSet->GetImpl();
  MOZ_ASSERT(setImpl);

  RefPtr<FontFace> obj = do_AddRef(new FontFace(aGlobal));
  obj->mImpl = FontFaceImpl::CreateForRule(obj, setImpl, aRule);
  return obj.forget();
}

already_AddRefed<FontFace> FontFace::Constructor(
    const GlobalObject& aGlobal, const nsACString& aFamily,
    const UTF8StringOrArrayBufferOrArrayBufferView& aSource,
    const FontFaceDescriptors& aDescriptors, ErrorResult& aRv) {
  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());

  FontFaceSet* set = global->GetFonts();
  if (NS_WARN_IF(!set)) {
    aRv.Throw(NS_ERROR_FAILURE);
    return nullptr;
  }

  FontFaceSetImpl* setImpl = set->GetImpl();
  if (NS_WARN_IF(!setImpl)) {
    aRv.Throw(NS_ERROR_FAILURE);
    return nullptr;
  }

  RefPtr<FontFace> obj = do_AddRef(new FontFace((global)));
  obj->mImpl = MakeRefPtr<FontFaceImpl>(obj, setImpl);
  if (!obj->mImpl->SetDescriptors(aFamily, aDescriptors)) {
    return obj.forget();
  }

  if (aSource.IsUTF8String()) {
    obj->mImpl->InitializeSourceURL(aSource.GetAsUTF8String());
  } else {
    uint8_t* buffer = nullptr;
    uint32_t length = 0;
    if (aSource.IsArrayBuffer()) {
      GetDataFrom(aSource.GetAsArrayBuffer(), buffer, length);
    } else if (aSource.IsArrayBufferView()) {
      GetDataFrom(aSource.GetAsArrayBufferView(), buffer, length);
    } else {
      MOZ_ASSERT_UNREACHABLE("Unhandled source type!");
      return nullptr;
    }

    obj->mImpl->InitializeSourceBuffer(buffer, length);
  }

  return obj.forget();
}

void FontFace::GetFamily(nsACString& aResult) { mImpl->GetFamily(aResult); }

void FontFace::SetFamily(const nsACString& aValue, ErrorResult& aRv) {
  mImpl->SetFamily(aValue, aRv);
}

void FontFace::GetStyle(nsACString& aResult) { mImpl->GetStyle(aResult); }

void FontFace::SetStyle(const nsACString& aValue, ErrorResult& aRv) {
  mImpl->SetStyle(aValue, aRv);
}

void FontFace::GetWeight(nsACString& aResult) { mImpl->GetWeight(aResult); }

void FontFace::SetWeight(const nsACString& aValue, ErrorResult& aRv) {
  mImpl->SetWeight(aValue, aRv);
}

void FontFace::GetStretch(nsACString& aResult) { mImpl->GetStretch(aResult); }

void FontFace::SetStretch(const nsACString& aValue, ErrorResult& aRv) {
  mImpl->SetStretch(aValue, aRv);
}

void FontFace::GetUnicodeRange(nsACString& aResult) {
  mImpl->GetUnicodeRange(aResult);
}

void FontFace::SetUnicodeRange(const nsACString& aValue, ErrorResult& aRv) {
  mImpl->SetUnicodeRange(aValue, aRv);
}

void FontFace::GetVariant(nsACString& aResult) { mImpl->GetVariant(aResult); }

void FontFace::SetVariant(const nsACString& aValue, ErrorResult& aRv) {
  mImpl->SetVariant(aValue, aRv);
}

void FontFace::GetFeatureSettings(nsACString& aResult) {
  mImpl->GetFeatureSettings(aResult);
}

void FontFace::SetFeatureSettings(const nsACString& aValue, ErrorResult& aRv) {
  mImpl->SetFeatureSettings(aValue, aRv);
}

void FontFace::GetVariationSettings(nsACString& aResult) {
  mImpl->GetVariationSettings(aResult);
}

void FontFace::SetVariationSettings(const nsACString& aValue,
                                    ErrorResult& aRv) {
  mImpl->SetVariationSettings(aValue, aRv);
}

void FontFace::GetDisplay(nsACString& aResult) { mImpl->GetDisplay(aResult); }

void FontFace::SetDisplay(const nsACString& aValue, ErrorResult& aRv) {
  mImpl->SetDisplay(aValue, aRv);
}

void FontFace::GetAscentOverride(nsACString& aResult) {
  mImpl->GetAscentOverride(aResult);
}

void FontFace::SetAscentOverride(const nsACString& aValue, ErrorResult& aRv) {
  mImpl->SetAscentOverride(aValue, aRv);
}

void FontFace::GetDescentOverride(nsACString& aResult) {
  mImpl->GetDescentOverride(aResult);
}

void FontFace::SetDescentOverride(const nsACString& aValue, ErrorResult& aRv) {
  mImpl->SetDescentOverride(aValue, aRv);
}

void FontFace::GetLineGapOverride(nsACString& aResult) {
  mImpl->GetLineGapOverride(aResult);
}

void FontFace::SetLineGapOverride(const nsACString& aValue, ErrorResult& aRv) {
  mImpl->SetLineGapOverride(aValue, aRv);
}

void FontFace::GetSizeAdjust(nsACString& aResult) {
  mImpl->GetSizeAdjust(aResult);
}

void FontFace::SetSizeAdjust(const nsACString& aValue, ErrorResult& aRv) {
  mImpl->SetSizeAdjust(aValue, aRv);
}

FontFaceLoadStatus FontFace::Status() { return mImpl->Status(); }

Promise* FontFace::Load(ErrorResult& aRv) {
  EnsurePromise();

  if (!mLoaded) {
    aRv.Throw(NS_ERROR_FAILURE);
    return nullptr;
  }

  mImpl->Load();
  mImpl->UpdateOwnerKeepAlive();
  return mLoaded;
}

Promise* FontFace::GetLoaded(ErrorResult& aRv) {
  EnsurePromise();

  if (!mLoaded) {
    aRv.Throw(NS_ERROR_FAILURE);
    return nullptr;
  }

  if (mImpl) {
    mImpl->UpdateOwnerKeepAlive();
  }

  return mLoaded;
}

void FontFace::MaybeResolve() {
  gfxFontUtils::AssertSafeThreadOrServoFontMetricsLocked();
  MOZ_ASSERT(!NS_IsMainThread() || nsContentUtils::IsSafeToRunScript());
  MOZ_ASSERT(!gfxFontUtils::CurrentServoStyleSet());

  if (RefPtr loaded = mLoaded) {
    loaded->MaybeResolve(this);
  }
}

void FontFace::MaybeReject(FontFaceLoadedRejectReason aReason,
                           nsCString&& aMessage) {
  gfxFontUtils::AssertSafeThreadOrServoFontMetricsLocked();
  MOZ_ASSERT(!NS_IsMainThread() || nsContentUtils::IsSafeToRunScript());
  MOZ_ASSERT(!gfxFontUtils::CurrentServoStyleSet());

  if (mLoaded) {
    switch (aReason) {
      case FontFaceLoadedRejectReason::Network:
        mLoaded->MaybeRejectWithNetworkError(aMessage);
        break;
      case FontFaceLoadedRejectReason::Syntax:
        mLoaded->MaybeRejectWithSyntaxError(aMessage);
        break;
    }
  } else if (!mLoadedRejection) {
    mLoadedRejection = MakeUnique<FontFaceLoadedRejection>(
        FontFaceLoadedRejection{aReason, std::move(aMessage)});
  }
}

void FontFace::EnsurePromise() {
  if (mLoaded || !mImpl || !GetRelevantGlobal()) {
    return;
  }

  mLoaded = Promise::CreateInfallible(GetRelevantGlobal());

  if (mImpl->Status() == FontFaceLoadStatus::Loaded) {
    mLoaded->MaybeResolve(this);
  } else if (mLoadedRejection) {
    auto rejection = std::move(mLoadedRejection);
    MaybeReject(rejection->mReason, std::move(rejection->mMessage));
  }
}

}  // namespace mozilla::dom
