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

/*
 * nsIScriptError implementation.
 */

#include "nsScriptError.h"
#include "js/Printf.h"
#include "MainThreadUtils.h"
#include "mozilla/Assertions.h"
#include "nsContentUtils.h"
#include "nsGlobalWindow.h"
#include "nsNetUtil.h"
#include "nsPIDOMWindow.h"
#include "nsIMutableArray.h"
#include "nsIScriptError.h"
#include "mozilla/BasePrincipal.h"

nsScriptErrorBase::nsScriptErrorBase()
    : mMessage(),
      mMessageName(),
      mSourceName(),
      mCssSelectors(),
      mSourceId(0),
      mLineNumber(0),
      mSourceLine(),
      mColumnNumber(0),
      mFlags(0),
      mCategory(),
      mOuterWindowID(0),
      mInnerWindowID(0),
      mMicroSecondTimeStamp(0),
      mInitializedOnMainThread(false),
      mIsFromPrivateWindow(false),
      mIsFromChromeContext(false),
      mIsPromiseRejection(false),
      mIsForwardedFromContentProcess(false) {}

nsScriptErrorBase::~nsScriptErrorBase() = default;

void nsScriptErrorBase::AddNote(nsIScriptErrorNote* note) {
  mNotes.AppendObject(note);
}

void nsScriptErrorBase::InitializeOnMainThread() {
  MOZ_ASSERT(NS_IsMainThread());
  MOZ_ASSERT(!mInitializedOnMainThread);

  if (mInnerWindowID) {
    nsGlobalWindowInner* window =
        nsGlobalWindowInner::GetInnerWindowWithId(mInnerWindowID);
    if (window) {
      nsPIDOMWindowOuter* outer = window->GetOuterWindow();
      if (outer) mOuterWindowID = outer->WindowID();
      mIsFromChromeContext = ComputeIsFromChromeContext(window);
      mIsFromPrivateWindow = ComputeIsFromPrivateWindow(window);
    }
  }

  mInitializedOnMainThread = true;
}

NS_IMETHODIMP
nsScriptErrorBase::InitSourceId(uint32_t value) {
  mSourceId = value;
  return NS_OK;
}

// nsIConsoleMessage methods
NS_IMETHODIMP
nsScriptErrorBase::GetMessageMoz(nsAString& aMessage) {
  nsAutoCString message;
  nsresult rv = ToString(message);
  if (NS_FAILED(rv)) {
    return rv;
  }

  CopyUTF8toUTF16(message, aMessage);
  return NS_OK;
}

NS_IMETHODIMP
nsScriptErrorBase::GetLogLevel(uint32_t* aLogLevel) {
  if (mFlags & (uint32_t)nsIScriptError::infoFlag) {
    *aLogLevel = nsIConsoleMessage::info;
  } else if (mFlags & (uint32_t)nsIScriptError::warningFlag) {
    *aLogLevel = nsIConsoleMessage::warn;
  } else {
    *aLogLevel = nsIConsoleMessage::error;
  }
  return NS_OK;
}

// nsIScriptError methods
NS_IMETHODIMP
nsScriptErrorBase::GetErrorMessage(nsAString& aResult) {
  aResult.Assign(mMessage);
  return NS_OK;
}

NS_IMETHODIMP
nsScriptErrorBase::GetSourceName(nsAString& aResult) {
  aResult.Assign(mSourceName);
  return NS_OK;
}

NS_IMETHODIMP
nsScriptErrorBase::GetCssSelectors(nsAString& aResult) {
  aResult.Assign(mCssSelectors);
  return NS_OK;
}

NS_IMETHODIMP
nsScriptErrorBase::SetCssSelectors(const nsAString& aCssSelectors) {
  mCssSelectors = aCssSelectors;
  return NS_OK;
}

NS_IMETHODIMP
nsScriptErrorBase::GetSourceId(uint32_t* result) {
  *result = mSourceId;
  return NS_OK;
}

NS_IMETHODIMP
nsScriptErrorBase::GetSourceLine(nsAString& aResult) {
  aResult.Assign(mSourceLine);
  return NS_OK;
}

NS_IMETHODIMP
nsScriptErrorBase::GetLineNumber(uint32_t* result) {
  *result = mLineNumber;
  return NS_OK;
}

NS_IMETHODIMP
nsScriptErrorBase::GetColumnNumber(uint32_t* result) {
  *result = mColumnNumber;
  return NS_OK;
}

NS_IMETHODIMP
nsScriptErrorBase::GetFlags(uint32_t* result) {
  *result = mFlags;
  return NS_OK;
}

NS_IMETHODIMP
nsScriptErrorBase::GetCategory(char** result) {
  *result = ToNewCString(mCategory);
  return NS_OK;
}

NS_IMETHODIMP
nsScriptErrorBase::GetHasException(bool* aHasException) {
  *aHasException = false;
  return NS_OK;
}

NS_IMETHODIMP
nsScriptErrorBase::GetException(JS::MutableHandle<JS::Value> aException) {
  aException.setUndefined();
  return NS_OK;
}

NS_IMETHODIMP
nsScriptErrorBase::SetException(JS::Handle<JS::Value> aStack) {
  return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP
nsScriptErrorBase::GetStack(JS::MutableHandle<JS::Value> aStack) {
  aStack.setUndefined();
  return NS_OK;
}

NS_IMETHODIMP
nsScriptErrorBase::SetStack(JS::Handle<JS::Value> aStack) { return NS_OK; }

NS_IMETHODIMP
nsScriptErrorBase::GetStackGlobal(JS::MutableHandle<JS::Value> aStackGlobal) {
  aStackGlobal.setUndefined();
  return NS_OK;
}

NS_IMETHODIMP
nsScriptErrorBase::GetErrorMessageName(nsAString& aErrorMessageName) {
  aErrorMessageName = mMessageName;
  return NS_OK;
}

NS_IMETHODIMP
nsScriptErrorBase::SetErrorMessageName(const nsAString& aErrorMessageName) {
  mMessageName = aErrorMessageName;
  return NS_OK;
}

static void AssignSourceNameHelper(nsString& aSourceNameDest,
                                   const nsAString& aSourceNameSrc) {
  if (aSourceNameSrc.IsEmpty()) return;

  aSourceNameDest.Assign(aSourceNameSrc);

  nsCOMPtr<nsIURI> uri;
  nsAutoCString pass;
  if (NS_SUCCEEDED(NS_NewURI(getter_AddRefs(uri), aSourceNameSrc)) &&
      NS_SUCCEEDED(uri->GetPassword(pass)) && !pass.IsEmpty()) {
    NS_GetSanitizedURIStringFromURI(uri, aSourceNameDest);
  }
}

static void AssignSourceNameHelper(nsIURI* aSourceURI,
                                   nsString& aSourceNameDest) {
  if (!aSourceURI) return;

  if (NS_FAILED(NS_GetSanitizedURIStringFromURI(aSourceURI, aSourceNameDest))) {
    aSourceNameDest.AssignLiteral("[nsIURI::GetSpec failed]");
  }
}

NS_IMETHODIMP
nsScriptErrorBase::Init(const nsAString& message, const nsAString& sourceName,
                        const nsAString& sourceLine, uint32_t lineNumber,
                        uint32_t columnNumber, uint32_t flags,
                        const nsACString& category, bool fromPrivateWindow,
                        bool fromChromeContext) {
  InitializationHelper(message, sourceLine, lineNumber, columnNumber, flags,
                       category, 0 /* inner Window ID */, fromChromeContext);
  AssignSourceNameHelper(mSourceName, sourceName);

  mIsFromPrivateWindow = fromPrivateWindow;
  mIsFromChromeContext = fromChromeContext;
  return NS_OK;
}

void nsScriptErrorBase::InitializationHelper(
    const nsAString& message, const nsAString& sourceLine, uint32_t lineNumber,
    uint32_t columnNumber, uint32_t flags, const nsACString& category,
    uint64_t aInnerWindowID, bool aFromChromeContext) {
  mMessage.Assign(message);
  mLineNumber = lineNumber;
  mSourceLine.Assign(sourceLine);
  mColumnNumber = columnNumber;
  mFlags = flags;
  mCategory = category;
  mMicroSecondTimeStamp = JS_Now();
  mInnerWindowID = aInnerWindowID;
  mIsFromChromeContext = aFromChromeContext;
}

NS_IMETHODIMP
nsScriptErrorBase::InitWithWindowID(const nsAString& message,
                                    const nsAString& sourceName,
                                    const nsAString& sourceLine,
                                    uint32_t lineNumber, uint32_t columnNumber,
                                    uint32_t flags, const nsACString& category,
                                    uint64_t aInnerWindowID,
                                    bool aFromChromeContext) {
  InitializationHelper(message, sourceLine, lineNumber, columnNumber, flags,
                       category, aInnerWindowID, aFromChromeContext);
  AssignSourceNameHelper(mSourceName, sourceName);

  if (aInnerWindowID && NS_IsMainThread()) InitializeOnMainThread();

  return NS_OK;
}

NS_IMETHODIMP
nsScriptErrorBase::InitWithSanitizedSource(
    const nsAString& message, const nsAString& sourceName,
    const nsAString& sourceLine, uint32_t lineNumber, uint32_t columnNumber,
    uint32_t flags, const nsACString& category, uint64_t aInnerWindowID,
    bool aFromChromeContext) {
  InitializationHelper(message, sourceLine, lineNumber, columnNumber, flags,
                       category, aInnerWindowID, aFromChromeContext);
  mSourceName = sourceName;

  if (aInnerWindowID && NS_IsMainThread()) InitializeOnMainThread();

  return NS_OK;
}

NS_IMETHODIMP
nsScriptErrorBase::InitWithSourceURI(const nsAString& message,
                                     nsIURI* sourceURI,
                                     const nsAString& sourceLine,
                                     uint32_t lineNumber, uint32_t columnNumber,
                                     uint32_t flags, const nsACString& category,
                                     uint64_t aInnerWindowID,
                                     bool aFromChromeContext) {
  InitializationHelper(message, sourceLine, lineNumber, columnNumber, flags,
                       category, aInnerWindowID, aFromChromeContext);
  AssignSourceNameHelper(sourceURI, mSourceName);

  if (aInnerWindowID && NS_IsMainThread()) InitializeOnMainThread();

  return NS_OK;
}

static nsresult ToStringHelper(const char* aSeverity, const nsString& aMessage,
                               const nsString& aSourceName,
                               const nsString* aSourceLine,
                               uint32_t aLineNumber, uint32_t aColumnNumber,
                               nsACString& /*UTF8*/ aResult) {
  static const char format0[] =
      "[%s: \"%s\" {file: \"%s\" line: %d column: %d source: \"%s\"}]";
  static const char format1[] = "[%s: \"%s\" {file: \"%s\" line: %d}]";
  static const char format2[] = "[%s: \"%s\"]";

  JS::UniqueChars temp;
  char* tempMessage = nullptr;
  char* tempSourceName = nullptr;
  char* tempSourceLine = nullptr;

  if (!aMessage.IsEmpty()) tempMessage = ToNewUTF8String(aMessage);
  if (!aSourceName.IsEmpty())
    // Use at most 512 characters from mSourceName.
    tempSourceName = ToNewUTF8String(StringHead(aSourceName, 512));
  if (aSourceLine && !aSourceLine->IsEmpty())
    // Use at most 512 characters from mSourceLine.
    tempSourceLine = ToNewUTF8String(StringHead(*aSourceLine, 512));

  if (nullptr != tempSourceName && nullptr != tempSourceLine) {
    temp = JS_smprintf(format0, aSeverity, tempMessage, tempSourceName,
                       aLineNumber, aColumnNumber, tempSourceLine);
  } else if (!aSourceName.IsEmpty()) {
    temp = JS_smprintf(format1, aSeverity, tempMessage, tempSourceName,
                       aLineNumber);
  } else {
    temp = JS_smprintf(format2, aSeverity, tempMessage);
  }

  if (nullptr != tempMessage) free(tempMessage);
  if (nullptr != tempSourceName) free(tempSourceName);
  if (nullptr != tempSourceLine) free(tempSourceLine);

  if (!temp) return NS_ERROR_OUT_OF_MEMORY;

  aResult.Assign(temp.get());
  return NS_OK;
}

NS_IMETHODIMP
nsScriptErrorBase::ToString(nsACString& /*UTF8*/ aResult) {
  static const char error[] = "JavaScript Error";
  static const char warning[] = "JavaScript Warning";

  const char* severity =
      !(mFlags & nsIScriptError::warningFlag) ? error : warning;

  return ToStringHelper(severity, mMessage, mSourceName, &mSourceLine,
                        mLineNumber, mColumnNumber, aResult);
}

NS_IMETHODIMP
nsScriptErrorBase::GetOuterWindowID(uint64_t* aOuterWindowID) {
  NS_WARNING_ASSERTION(NS_IsMainThread() || mInitializedOnMainThread,
                       "This can't be safely determined off the main thread, "
                       "returning an inaccurate value!");

  if (!mInitializedOnMainThread && NS_IsMainThread()) {
    InitializeOnMainThread();
  }

  *aOuterWindowID = mOuterWindowID;
  return NS_OK;
}

NS_IMETHODIMP
nsScriptErrorBase::GetInnerWindowID(uint64_t* aInnerWindowID) {
  *aInnerWindowID = mInnerWindowID;
  return NS_OK;
}

NS_IMETHODIMP
nsScriptErrorBase::GetTimeStamp(int64_t* aTimeStamp) {
  *aTimeStamp = mMicroSecondTimeStamp / 1000;
  return NS_OK;
}

NS_IMETHODIMP
nsScriptErrorBase::GetMicroSecondTimeStamp(int64_t* aTimeStamp) {
  *aTimeStamp = mMicroSecondTimeStamp;
  return NS_OK;
}

NS_IMETHODIMP
nsScriptErrorBase::GetIsFromPrivateWindow(bool* aIsFromPrivateWindow) {
  NS_WARNING_ASSERTION(NS_IsMainThread() || mInitializedOnMainThread,
                       "This can't be safely determined off the main thread, "
                       "returning an inaccurate value!");

  if (!mInitializedOnMainThread && NS_IsMainThread()) {
    InitializeOnMainThread();
  }

  *aIsFromPrivateWindow = mIsFromPrivateWindow;
  return NS_OK;
}

NS_IMETHODIMP
nsScriptErrorBase::GetIsFromChromeContext(bool* aIsFromChromeContext) {
  NS_WARNING_ASSERTION(NS_IsMainThread() || mInitializedOnMainThread,
                       "This can't be safely determined off the main thread, "
                       "returning an inaccurate value!");
  if (!mInitializedOnMainThread && NS_IsMainThread()) {
    InitializeOnMainThread();
  }
  *aIsFromChromeContext = mIsFromChromeContext;
  return NS_OK;
}

NS_IMETHODIMP
nsScriptErrorBase::GetIsPromiseRejection(bool* aIsPromiseRejection) {
  *aIsPromiseRejection = mIsPromiseRejection;
  return NS_OK;
}

NS_IMETHODIMP
nsScriptErrorBase::InitIsPromiseRejection(bool aIsPromiseRejection) {
  mIsPromiseRejection = aIsPromiseRejection;
  return NS_OK;
}

NS_IMETHODIMP
nsScriptErrorBase::GetIsForwardedFromContentProcess(
    bool* aIsForwardedFromContentProcess) {
  *aIsForwardedFromContentProcess = mIsForwardedFromContentProcess;
  return NS_OK;
}

NS_IMETHODIMP
nsScriptErrorBase::SetIsForwardedFromContentProcess(
    bool aIsForwardedFromContentProcess) {
  mIsForwardedFromContentProcess = aIsForwardedFromContentProcess;
  return NS_OK;
}

NS_IMETHODIMP
nsScriptErrorBase::GetNotes(nsIArray** aNotes) {
  nsresult rv = NS_OK;
  nsCOMPtr<nsIMutableArray> array = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
  NS_ENSURE_SUCCESS(rv, rv);

  uint32_t len = mNotes.Length();
  for (uint32_t i = 0; i < len; i++) array->AppendElement(mNotes[i]);
  array.forget(aNotes);

  return NS_OK;
}

/* static */
bool nsScriptErrorBase::ComputeIsFromPrivateWindow(
    nsGlobalWindowInner* aWindow) {
  // Never mark exceptions from chrome windows as having come from private
  // windows, since we always want them to be reported.
  nsIPrincipal* winPrincipal = aWindow->GetPrincipal();
  return aWindow->IsPrivateBrowsing() && !winPrincipal->IsSystemPrincipal();
}

/* static */
bool nsScriptErrorBase::ComputeIsFromChromeContext(
    nsGlobalWindowInner* aWindow) {
  nsIPrincipal* winPrincipal = aWindow->GetPrincipal();
  return winPrincipal->IsSystemPrincipal();
}

NS_IMPL_ISUPPORTS(nsScriptError, nsIConsoleMessage, nsIScriptError)

nsScriptErrorNote::nsScriptErrorNote()
    : mMessage(),
      mSourceName(),
      mSourceId(0),
      mLineNumber(0),
      mColumnNumber(0) {}

nsScriptErrorNote::~nsScriptErrorNote() = default;

void nsScriptErrorNote::Init(const nsAString& message,
                             const nsAString& sourceName, uint32_t sourceId,
                             uint32_t lineNumber, uint32_t columnNumber) {
  mMessage.Assign(message);
  AssignSourceNameHelper(mSourceName, sourceName);
  mSourceId = sourceId;
  mLineNumber = lineNumber;
  mColumnNumber = columnNumber;
}

// nsIScriptErrorNote methods
NS_IMETHODIMP
nsScriptErrorNote::GetErrorMessage(nsAString& aResult) {
  aResult.Assign(mMessage);
  return NS_OK;
}

NS_IMETHODIMP
nsScriptErrorNote::GetSourceName(nsAString& aResult) {
  aResult.Assign(mSourceName);
  return NS_OK;
}

NS_IMETHODIMP
nsScriptErrorNote::GetSourceId(uint32_t* result) {
  *result = mSourceId;
  return NS_OK;
}

NS_IMETHODIMP
nsScriptErrorNote::GetLineNumber(uint32_t* result) {
  *result = mLineNumber;
  return NS_OK;
}

NS_IMETHODIMP
nsScriptErrorNote::GetColumnNumber(uint32_t* result) {
  *result = mColumnNumber;
  return NS_OK;
}

NS_IMETHODIMP
nsScriptErrorNote::ToString(nsACString& /*UTF8*/ aResult) {
  return ToStringHelper("JavaScript Note", mMessage, mSourceName, nullptr,
                        mLineNumber, mColumnNumber, aResult);
}

NS_IMPL_ISUPPORTS(nsScriptErrorNote, nsIScriptErrorNote)
