/* 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/. */

#ifndef nsDOMNavigationTiming_h_
#define nsDOMNavigationTiming_h_

#include "mozilla/BaseProfilerMarkersPrerequisites.h"
#include "mozilla/RelativeTimeline.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/WeakPtr.h"
#include "nsCOMArray.h"
#include "nsCOMPtr.h"
#include "nsITimer.h"

class nsDocShell;
class nsIURI;
class SharedLcpMarkerState;

using DOMTimeMilliSec = unsigned long long;
using DOMHighResTimeStamp = double;

class PickleIterator;
namespace IPC {
class Message;
class MessageReader;
class MessageWriter;
template <typename>
struct ParamTraits;
}  // namespace IPC

class nsDOMNavigationTiming final : public mozilla::RelativeTimeline {
 public:
  enum Type {
    TYPE_NAVIGATE = 0,
    TYPE_RELOAD = 1,
    TYPE_BACK_FORWARD = 2,
    TYPE_RESERVED = 255,
  };

  explicit nsDOMNavigationTiming(nsDocShell* aDocShell);
  nsDOMNavigationTiming(const nsDOMNavigationTiming&) = delete;

  NS_INLINE_DECL_REFCOUNTING(nsDOMNavigationTiming)

  Type GetType() const { return mNavigationType; }

  inline DOMHighResTimeStamp GetNavigationStartHighRes() const {
    return mNavigationStartHighRes;
  }

  DOMTimeMilliSec GetNavigationStart() const {
    return static_cast<int64_t>(GetNavigationStartHighRes());
  }

  mozilla::TimeStamp GetNavigationStartTimeStamp() const {
    return mNavigationStart;
  }

  mozilla::TimeStamp GetLoadEventStartTimeStamp() const {
    return mLoadEventStart;
  }

  mozilla::TimeStamp GetDOMContentLoadedEventStartTimeStamp() const {
    return mDOMContentLoadedEventStart;
  }

  mozilla::TimeStamp GetFirstContentfulCompositeTimeStamp() const {
    return mContentfulComposite;
  }

  mozilla::TimeStamp GetLargestContentfulRenderTimeStamp() const {
    return mLargestContentfulRender;
  }

  DOMTimeMilliSec GetUnloadEventStart() {
    return TimeStampToDOM(GetUnloadEventStartTimeStamp());
  }

  DOMTimeMilliSec GetUnloadEventEnd() {
    return TimeStampToDOM(GetUnloadEventEndTimeStamp());
  }

  DOMTimeMilliSec GetDomLoading() const { return TimeStampToDOM(mDOMLoading); }
  DOMTimeMilliSec GetDomInteractive() const {
    return TimeStampToDOM(mDOMInteractive);
  }
  DOMTimeMilliSec GetDomContentLoadedEventStart() const {
    return TimeStampToDOM(mDOMContentLoadedEventStart);
  }
  DOMTimeMilliSec GetDomContentLoadedEventEnd() const {
    return TimeStampToDOM(mDOMContentLoadedEventEnd);
  }
  DOMTimeMilliSec GetDomComplete() const {
    return TimeStampToDOM(mDOMComplete);
  }
  DOMTimeMilliSec GetLoadEventStart() const {
    return TimeStampToDOM(mLoadEventStart);
  }
  DOMTimeMilliSec GetLoadEventEnd() const {
    return TimeStampToDOM(mLoadEventEnd);
  }
  DOMTimeMilliSec GetTimeToNonBlankPaint() const {
    return TimeStampToDOM(mNonBlankPaint);
  }
  DOMTimeMilliSec GetTimeToContentfulComposite() const {
    return TimeStampToDOM(mContentfulComposite);
  }
  DOMTimeMilliSec GetTimeToLargestContentfulRender() const {
    return TimeStampToDOM(mLargestContentfulRender);
  }
  DOMTimeMilliSec GetTimeToTTFI() const { return TimeStampToDOM(mTTFI); }

  DOMHighResTimeStamp GetUnloadEventStartHighRes() {
    mozilla::TimeStamp stamp = GetUnloadEventStartTimeStamp();
    if (stamp.IsNull()) {
      return 0;
    }
    return TimeStampToDOMHighRes(stamp);
  }
  DOMHighResTimeStamp GetUnloadEventEndHighRes() {
    mozilla::TimeStamp stamp = GetUnloadEventEndTimeStamp();
    if (stamp.IsNull()) {
      return 0;
    }
    return TimeStampToDOMHighRes(stamp);
  }
  DOMHighResTimeStamp GetDomInteractiveHighRes() const {
    return TimeStampToDOMHighRes(mDOMInteractive);
  }
  DOMHighResTimeStamp GetDomContentLoadedEventStartHighRes() const {
    return TimeStampToDOMHighRes(mDOMContentLoadedEventStart);
  }
  DOMHighResTimeStamp GetDomContentLoadedEventEndHighRes() const {
    return TimeStampToDOMHighRes(mDOMContentLoadedEventEnd);
  }
  DOMHighResTimeStamp GetDomCompleteHighRes() const {
    return TimeStampToDOMHighRes(mDOMComplete);
  }
  DOMHighResTimeStamp GetLoadEventStartHighRes() const {
    return TimeStampToDOMHighRes(mLoadEventStart);
  }
  DOMHighResTimeStamp GetLoadEventEndHighRes() const {
    return TimeStampToDOMHighRes(mLoadEventEnd);
  }

  enum class DocShellState : uint8_t { eActive, eInactive };

  void NotifyNavigationStart(DocShellState aDocShellState);
  void NotifyFetchStart(nsIURI* aURI, Type aNavigationType);
  // A restoration occurs when the document is loaded from the
  // bfcache. This method sets the appropriate parameters of the
  // navigation timing object in this case.
  void NotifyRestoreStart();
  void NotifyBeforeUnload();
  void NotifyUnloadAccepted(nsIURI* aOldURI);
  void NotifyUnloadEventStart();
  void NotifyUnloadEventEnd();
  void NotifyLoadEventStart();
  void NotifyLoadEventEnd();

  // Document changes state to 'loading' before connecting to timing
  void SetDOMLoadingTimeStamp(nsIURI* aURI, mozilla::TimeStamp aValue);
  void NotifyDOMLoading(nsIURI* aURI);
  void NotifyDOMInteractive(nsIURI* aURI);
  void NotifyDOMComplete(nsIURI* aURI);
  void NotifyDOMContentLoadedStart(nsIURI* aURI);
  void NotifyDOMContentLoadedEnd(nsIURI* aURI);

  static void TTITimeoutCallback(nsITimer* aTimer, void* aClosure);
  void TTITimeout(nsITimer* aTimer);

  void NotifyLongTask(mozilla::TimeStamp aWhen);
  void NotifyNonBlankPaintForRootContentDocument();
  void NotifyContentfulCompositeForRootContentDocument(
      const mozilla::TimeStamp& aCompositeEndTime);
  void NotifyLargestContentfulRenderForRootContentDocument(
      const DOMHighResTimeStamp& aRenderTime, const nsAString& aElement,
      const nsACString& aImageURL);
  void NotifyDocShellStateChanged(DocShellState aDocShellState);

  RefPtr<SharedLcpMarkerState> GetSharedLcpMarkerState() const;

  DOMTimeMilliSec TimeStampToDOM(mozilla::TimeStamp aStamp) const;

  inline DOMHighResTimeStamp TimeStampToDOMHighRes(
      mozilla::TimeStamp aStamp) const {
    if (aStamp.IsNull()) {
      return 0;
    }
    mozilla::TimeDuration duration = aStamp - mNavigationStart;
    return duration.ToMilliseconds();
  }

  // Called by the DocumentLoadListener before sending the timing information
  // to the new content process.
  void Anonymize(nsIURI* aFinalURI);

  inline already_AddRefed<nsDOMNavigationTiming> CloneNavigationTime(
      nsDocShell* aDocShell) const {
    RefPtr<nsDOMNavigationTiming> timing = new nsDOMNavigationTiming(aDocShell);
    timing->mNavigationStartHighRes = mNavigationStartHighRes;
    timing->mNavigationStart = mNavigationStart;
    return timing.forget();
  }

  bool DocShellHasBeenActiveSinceNavigationStart() const {
    return mDocShellHasBeenActiveSinceNavigationStart;
  }

  mozilla::TimeStamp LoadEventEnd() { return mLoadEventEnd; }

 private:
  friend class nsDocShell;
  nsDOMNavigationTiming(nsDocShell* aDocShell, nsDOMNavigationTiming* aOther);
  ~nsDOMNavigationTiming();

  void Clear();

  mozilla::TimeStamp GetUnloadEventStartTimeStamp() const;
  mozilla::TimeStamp GetUnloadEventEndTimeStamp() const;

  bool IsTopLevelContentDocumentInContentProcess() const;

  // Should those be amended, the IPC serializer should be updated
  // accordingly.
  mozilla::WeakPtr<nsDocShell> mDocShell;

  nsCOMPtr<nsIURI> mUnloadedURI;
  nsCOMPtr<nsIURI> mLoadedURI;
  nsCOMPtr<nsITimer> mTTITimer;

  Type mNavigationType;
  DOMHighResTimeStamp mNavigationStartHighRes;
  mozilla::TimeStamp mNavigationStart;
  mozilla::TimeStamp mNonBlankPaint;
  mozilla::TimeStamp mContentfulComposite;
  mozilla::TimeStamp mLargestContentfulRender;
  RefPtr<SharedLcpMarkerState> mSharedLcpMarkerState;

  mozilla::TimeStamp mBeforeUnloadStart;
  mozilla::TimeStamp mUnloadStart;
  mozilla::TimeStamp mUnloadEnd;
  mozilla::TimeStamp mLoadEventStart;
  mozilla::TimeStamp mLoadEventEnd;

  mozilla::TimeStamp mDOMLoading;
  mozilla::TimeStamp mDOMInteractive;
  mozilla::TimeStamp mDOMContentLoadedEventStart;
  mozilla::TimeStamp mDOMContentLoadedEventEnd;
  mozilla::TimeStamp mDOMComplete;

  mozilla::TimeStamp mTTFI;

  bool mDocShellHasBeenActiveSinceNavigationStart;

  friend struct IPC::ParamTraits<nsDOMNavigationTiming*>;
};

// IPDL serializer. Please be aware of the caveats in sending across
// the information and the potential resulting data leakage.
// For now, this serializer is to only be used under a very narrowed scope
// so that only the starting times are ever set.
namespace IPC {

template <>
struct ParamTraits<nsDOMNavigationTiming*> {
  static void Write(MessageWriter* aWriter, nsDOMNavigationTiming* aParam);
  static bool Read(MessageReader* aReader,
                   RefPtr<nsDOMNavigationTiming>* aResult);
};

}  // namespace IPC

#endif /* nsDOMNavigationTiming_h_ */
