/* -*- 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/. */
#ifndef nsJSEnvironment_h
#define nsJSEnvironment_h

#include "nsIScriptContext.h"
#include "nsIScriptGlobalObject.h"
#include "nsCOMPtr.h"
#include "nsIObserver.h"
#include "prtime.h"
#include "nsCycleCollectionParticipant.h"
#include "nsIXPConnect.h"
#include "nsIArray.h"
#include "mozilla/Attributes.h"
#include "mozilla/TimeStamp.h"
#include "nsThreadUtils.h"
#include "xpcpublic.h"

class nsICycleCollectorListener;
class nsScriptNameSpaceManager;
class nsIDocShell;

namespace JS {
class AutoValueVector;
} // namespace JS

namespace mozilla {
template <class> class Maybe;
struct CycleCollectorResults;
} // namespace mozilla

// The amount of time we wait between a request to GC (due to leaving
// a page) and doing the actual GC.
#define NS_GC_DELAY                 4000 // ms

#define NS_MAJOR_FORGET_SKIPPABLE_CALLS 5

class nsJSContext : public nsIScriptContext
{
public:
  nsJSContext(bool aGCOnDestruction, nsIScriptGlobalObject* aGlobalObject);

  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(nsJSContext,
                                                         nsIScriptContext)

  virtual nsIScriptGlobalObject *GetGlobalObject() override;
  inline nsIScriptGlobalObject *GetGlobalObjectRef() { return mGlobalObjectRef; }

  virtual nsresult InitContext() override;
  virtual bool IsContextInitialized() override;

  virtual nsresult SetProperty(JS::Handle<JSObject*> aTarget, const char* aPropName, nsISupports* aVal) override;

  virtual bool GetProcessingScriptTag() override;
  virtual void SetProcessingScriptTag(bool aResult) override;

  virtual nsresult InitClasses(JS::Handle<JSObject*> aGlobalObj) override;

  virtual void WillInitializeContext() override;
  virtual void DidInitializeContext() override;

  virtual void SetWindowProxy(JS::Handle<JSObject*> aWindowProxy) override;
  virtual JSObject* GetWindowProxy() override;

  static void LoadStart();
  static void LoadEnd();

  enum IsShrinking {
    ShrinkingGC,
    NonShrinkingGC
  };

  enum IsIncremental {
    IncrementalGC,
    NonIncrementalGC
  };

  // Setup all the statics etc - safe to call multiple times after Startup().
  void EnsureStatics();

  static void GarbageCollectNow(JS::gcreason::Reason reason,
                                IsIncremental aIncremental = NonIncrementalGC,
                                IsShrinking aShrinking = NonShrinkingGC,
                                int64_t aSliceMillis = 0);

  static void CycleCollectNow(nsICycleCollectorListener *aListener = nullptr);

  // Run a cycle collector slice, using a heuristic to decide how long to run it.
  static void RunCycleCollectorSlice(mozilla::TimeStamp aDeadline);

  // Run a cycle collector slice, using the given work budget.
  static void RunCycleCollectorWorkSlice(int64_t aWorkBudget);

  static void BeginCycleCollectionCallback();
  static void EndCycleCollectionCallback(mozilla::CycleCollectorResults &aResults);

  // Return the longest CC slice time since ClearMaxCCSliceTime() was last called.
  static uint32_t GetMaxCCSliceTimeSinceClear();
  static void ClearMaxCCSliceTime();

  // If there is some pending CC or GC timer/runner, this will run it.
  static void RunNextCollectorTimer(JS::gcreason::Reason aReason,
                                    mozilla::TimeStamp aDeadline = mozilla::TimeStamp());
  // If user has been idle and aDocShell is for an iframe being loaded in an
  // already loaded top level docshell, this will run a CC or GC
  // timer/runner if there is such pending.
  static void MaybeRunNextCollectorSlice(nsIDocShell* aDocShell,
                                         JS::gcreason::Reason aReason);

  // The GC should probably run soon, in the zone of object aObj (if given).
  static void PokeGC(JS::gcreason::Reason aReason, JSObject* aObj, int aDelay = 0);
  static void KillGCTimer();

  static void PokeShrinkingGC();
  static void KillShrinkingGCTimer();

  static void MaybePokeCC();
  static void KillCCRunner();
  static void KillICCRunner();
  static void KillFullGCTimer();
  static void KillInterSliceGCRunner();

  // Calling LikelyShortLivingObjectCreated() makes a GC more likely.
  static void LikelyShortLivingObjectCreated();

  static uint32_t CleanupsSinceLastGC();

  nsIScriptGlobalObject* GetCachedGlobalObject()
  {
    // Verify that we have a global so that this
    // does always return a null when GetGlobalObject() is null.
    JSObject* global = GetWindowProxy();
    return global ? mGlobalObjectRef.get() : nullptr;
  }

protected:
  virtual ~nsJSContext();

  // Helper to convert xpcom datatypes to jsvals.
  nsresult ConvertSupportsTojsvals(nsISupports *aArgs,
                                   JS::Handle<JSObject*> aScope,
                                   JS::AutoValueVector &aArgsOut);

  nsresult AddSupportsPrimitiveTojsvals(nsISupports *aArg, JS::Value *aArgv);

private:
  void Destroy();

  JS::Heap<JSObject*> mWindowProxy;

  bool mIsInitialized;
  bool mGCOnDestruction;
  bool mProcessingScriptTag;

  PRTime mModalStateTime;
  uint32_t mModalStateDepth;

  // mGlobalObjectRef ensures that the outer window stays alive as long as the
  // context does. It is eventually collected by the cycle collector.
  nsCOMPtr<nsIScriptGlobalObject> mGlobalObjectRef;

  static bool DOMOperationCallback(JSContext *cx);
};

namespace mozilla {
namespace dom {

void StartupJSEnvironment();
void ShutdownJSEnvironment();

// Get the NameSpaceManager, creating if necessary
nsScriptNameSpaceManager* GetNameSpaceManager();

// Peek the NameSpaceManager, without creating it.
nsScriptNameSpaceManager* PeekNameSpaceManager();

// Runnable that's used to do async error reporting
class AsyncErrorReporter final : public mozilla::Runnable
{
public:
  // aWindow may be null if this error report is not associated with a window
  explicit AsyncErrorReporter(xpc::ErrorReport* aReport)
    : Runnable("dom::AsyncErrorReporter")
    , mReport(aReport)
  {}

  NS_IMETHOD Run() override
  {
    mReport->LogToConsole();
    return NS_OK;
  }

protected:
  RefPtr<xpc::ErrorReport> mReport;
};

} // namespace dom
} // namespace mozilla

// An interface for fast and native conversion to/from nsIArray. If an object
// supports this interface, JS can reach directly in for the argv, and avoid
// nsISupports conversion. If this interface is not supported, the object will
// be queried for nsIArray, and everything converted via xpcom objects.
#define NS_IJSARGARRAY_IID \
{ 0xb6acdac8, 0xf5c6, 0x432c, \
  { 0xa8, 0x6e, 0x33, 0xee, 0xb1, 0xb0, 0xcd, 0xdc } }

class nsIJSArgArray : public nsIArray
{
public:
  NS_DECLARE_STATIC_IID_ACCESSOR(NS_IJSARGARRAY_IID)
  // Bug 312003 describes why this must be "void **", but after calling argv
  // may be cast to JS::Value* and the args found at:
  //    ((JS::Value*)argv)[0], ..., ((JS::Value*)argv)[argc - 1]
  virtual nsresult GetArgs(uint32_t *argc, void **argv) = 0;
};

NS_DEFINE_STATIC_IID_ACCESSOR(nsIJSArgArray, NS_IJSARGARRAY_IID)

#endif /* nsJSEnvironment_h */
