/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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/ChildSHistory.h"
#include "mozilla/dom/ChildSHistoryBinding.h"
#include "mozilla/dom/ContentChild.h"
#include "mozilla/dom/ContentFrameMessageManager.h"
#include "mozilla/StaticPrefs_fission.h"
#include "nsComponentManagerUtils.h"
#include "nsSHEntry.h"
#include "nsSHistory.h"
#include "nsDocShell.h"
#include "nsXULAppAPI.h"

namespace mozilla {
namespace dom {

ChildSHistory::ChildSHistory(BrowsingContext* aBrowsingContext)
    : mBrowsingContext(aBrowsingContext) {}

ChildSHistory::~ChildSHistory() {
  if (mHistory) {
    static_cast<nsSHistory*>(mHistory.get())->SetBrowsingContext(nullptr);
  }
}

void ChildSHistory::SetIsInProcess(bool aIsInProcess) {
  if (!aIsInProcess) {
    if (mHistory) {
      static_cast<nsSHistory*>(mHistory.get())->SetBrowsingContext(nullptr);
      mHistory = nullptr;
    }

    return;
  }

  if (mHistory) {
    return;
  }

  mHistory = new nsSHistory(mBrowsingContext);
}

int32_t ChildSHistory::Count() {
  if (StaticPrefs::fission_sessionHistoryInParent()) {
    return mLength;
  }
  return mHistory->GetCount();
}

int32_t ChildSHistory::Index() {
  int32_t index;
  mHistory->GetIndex(&index);
  return index;
}

void ChildSHistory::Reload(uint32_t aReloadFlags, ErrorResult& aRv) {
  aRv = mHistory->Reload(aReloadFlags);
}

bool ChildSHistory::CanGo(int32_t aOffset) {
  CheckedInt<int32_t> index = Index();
  index += aOffset;
  if (!index.isValid()) {
    return false;
  }
  return index.value() < Count() && index.value() >= 0;
}

void ChildSHistory::Go(int32_t aOffset, ErrorResult& aRv) {
  CheckedInt<int32_t> index = Index();
  index += aOffset;
  if (!index.isValid()) {
    aRv.Throw(NS_ERROR_FAILURE);
    return;
  }
  if (StaticPrefs::fission_sessionHistoryInParent()) {
    nsCOMPtr<nsISHistory> shistory = mHistory;
    ContentChild::GetSingleton()->SendHistoryGo(
        mBrowsingContext, index.value(),
        [shistory](int32_t&& aRequestedIndex) {
          // FIXME Should probably only do this for non-fission.
          shistory->InternalSetRequestedIndex(aRequestedIndex);
        },
        [](mozilla::ipc::
               ResponseRejectReason) { /* FIXME Is ignoring this fine? */ });
  } else {
    aRv = mHistory->GotoIndex(index.value());
  }
}

void ChildSHistory::AsyncGo(int32_t aOffset) {
  if (!CanGo(aOffset)) {
    return;
  }

  RefPtr<PendingAsyncHistoryNavigation> asyncNav =
      new PendingAsyncHistoryNavigation(this, aOffset);
  mPendingNavigations.insertBack(asyncNav);
  NS_DispatchToCurrentThread(asyncNav.forget());
}

void ChildSHistory::RemovePendingHistoryNavigations() {
  mPendingNavigations.clear();
}

void ChildSHistory::EvictLocalContentViewers() {
  mHistory->EvictAllContentViewers();
}

nsISHistory* ChildSHistory::LegacySHistory() {
  MOZ_RELEASE_ASSERT(mHistory);
  return mHistory;
}

NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ChildSHistory)
  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
  NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END

NS_IMPL_CYCLE_COLLECTING_ADDREF(ChildSHistory)
NS_IMPL_CYCLE_COLLECTING_RELEASE(ChildSHistory)

NS_IMPL_CYCLE_COLLECTION_CLASS(ChildSHistory)

NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ChildSHistory)
  if (tmp->mHistory) {
    static_cast<nsSHistory*>(tmp->mHistory.get())->SetBrowsingContext(nullptr);
  }
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mBrowsingContext, mHistory)
  NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
NS_IMPL_CYCLE_COLLECTION_UNLINK_END

NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(ChildSHistory)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBrowsingContext, mHistory)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END

NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(ChildSHistory)

JSObject* ChildSHistory::WrapObject(JSContext* cx,
                                    JS::Handle<JSObject*> aGivenProto) {
  return ChildSHistory_Binding::Wrap(cx, this, aGivenProto);
}

nsISupports* ChildSHistory::GetParentObject() const {
  return xpc::NativeGlobal(xpc::PrivilegedJunkScope());
}

}  // namespace dom
}  // namespace mozilla
