// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "extensions/browser/extension_error.h"

#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "extensions/common/constants.h"
#include "url/gurl.h"

namespace extensions {

////////////////////////////////////////////////////////////////////////////////
// ExtensionError

ExtensionError::ExtensionError(Type type,
                               const std::string& extension_id,
                               bool from_incognito,
                               logging::LogSeverity level,
                               const base::string16& source,
                               const base::string16& message)
    : type_(type),
      extension_id_(extension_id),
      id_(0),
      from_incognito_(from_incognito),
      level_(level),
      source_(source),
      message_(message),
      occurrences_(1u) {
}

ExtensionError::~ExtensionError() {
}

std::string ExtensionError::GetDebugString() const {
  return std::string("Extension Error:") +
         "\n  OTR:     " + std::string(from_incognito_ ? "true" : "false") +
         "\n  Level:   " + base::IntToString(static_cast<int>(level_)) +
         "\n  Source:  " + base::UTF16ToUTF8(source_) +
         "\n  Message: " + base::UTF16ToUTF8(message_) +
         "\n  ID:      " + extension_id_;
}

bool ExtensionError::IsEqual(const ExtensionError* rhs) const {
  // We don't check |source_| or |level_| here, since they are constant for
  // manifest errors. Check them in RuntimeError::IsEqualImpl() instead.
  return type_ == rhs->type_ &&
         extension_id_ == rhs->extension_id_ &&
         message_ == rhs->message_ &&
         IsEqualImpl(rhs);
}

////////////////////////////////////////////////////////////////////////////////
// ManifestError

ManifestError::ManifestError(const std::string& extension_id,
                             const base::string16& message,
                             const base::string16& manifest_key,
                             const base::string16& manifest_specific)
    : ExtensionError(ExtensionError::MANIFEST_ERROR,
                     extension_id,
                     false,  // extensions can't be installed while incognito.
                     logging::LOG_WARNING,  // All manifest errors are warnings.
                     base::FilePath(kManifestFilename).AsUTF16Unsafe(),
                     message),
      manifest_key_(manifest_key),
      manifest_specific_(manifest_specific) {
}

ManifestError::~ManifestError() {
}

std::string ManifestError::GetDebugString() const {
  return ExtensionError::GetDebugString() +
         "\n  Type:    ManifestError";
}

bool ManifestError::IsEqualImpl(const ExtensionError* rhs) const {
  // If two manifest errors have the same extension id and message (which are
  // both checked in ExtensionError::IsEqual), then they are equal.
  return true;
}

////////////////////////////////////////////////////////////////////////////////
// RuntimeError

RuntimeError::RuntimeError(const std::string& extension_id,
                           bool from_incognito,
                           const base::string16& source,
                           const base::string16& message,
                           const StackTrace& stack_trace,
                           const GURL& context_url,
                           logging::LogSeverity level,
                           int render_frame_id,
                           int render_process_id)
    : ExtensionError(ExtensionError::RUNTIME_ERROR,
                     !extension_id.empty() ? extension_id : GURL(source).host(),
                     from_incognito,
                     level,
                     source,
                     message),
      context_url_(context_url),
      stack_trace_(stack_trace),
      render_frame_id_(render_frame_id),
      render_process_id_(render_process_id) {
  CleanUpInit();
}

RuntimeError::~RuntimeError() {
}

std::string RuntimeError::GetDebugString() const {
  std::string result = ExtensionError::GetDebugString() +
         "\n  Type:    RuntimeError"
         "\n  Context: " + context_url_.spec() +
         "\n  Stack Trace: ";
  for (StackTrace::const_iterator iter = stack_trace_.begin();
       iter != stack_trace_.end(); ++iter) {
    // The "NL" comments are to force clang-format to choose the right layout.
    result += "\n    {";
    result +=
        "\n      Line:     " + base::SizeTToString(iter->line_number) +    // NL
        "\n      Column:   " + base::SizeTToString(iter->column_number) +  // NL
        "\n      URL:      " + base::UTF16ToUTF8(iter->source) +           // NL
        "\n      Function: " + base::UTF16ToUTF8(iter->function) +         // NL
        "\n    }";                                                         // NL
  }
  return result;
}

bool RuntimeError::IsEqualImpl(const ExtensionError* rhs) const {
  const RuntimeError* error = static_cast<const RuntimeError*>(rhs);

  // Only look at the first frame of a stack trace to save time and group
  // nearly-identical errors. The most recent error is kept, so there's no risk
  // of displaying an old and inaccurate stack trace.
  return level_ == error->level_ &&
         source_ == error->source_ &&
         context_url_ == error->context_url_ &&
         stack_trace_.size() == error->stack_trace_.size() &&
         (stack_trace_.empty() || stack_trace_[0] == error->stack_trace_[0]);
}

void RuntimeError::CleanUpInit() {
  // If the error came from a generated background page, the "context" is empty
  // because there's no visible URL. We should set context to be the generated
  // background page in this case.
  GURL source_url = GURL(source_);
  if (context_url_.is_empty() &&
      source_url.path_piece() ==
          std::string("/") + kGeneratedBackgroundPageFilename) {
    context_url_ = source_url;
  }

  // In some instances (due to the fact that we're reusing error reporting from
  // other systems), the source won't match up with the final entry in the stack
  // trace. (For instance, in a browser action error, the source is the page -
  // sometimes the background page - but the error is thrown from the script.)
  // Make the source match the stack trace, since that is more likely the cause
  // of the error.
  if (!stack_trace_.empty() && source_ != stack_trace_[0].source)
    source_ = stack_trace_[0].source;
}

////////////////////////////////////////////////////////////////////////////////
// InternalError

InternalError::InternalError(const std::string& extension_id,
                             const base::string16& message,
                             logging::LogSeverity level)
    : ExtensionError(ExtensionError::INTERNAL_ERROR,
                     extension_id,
                     false,  // not incognito.
                     level,
                     base::string16(),
                     message) {
}

InternalError::~InternalError() {
}

std::string InternalError::GetDebugString() const {
  return ExtensionError::GetDebugString() +
         "\n  Type:    InternalError";
}

bool InternalError::IsEqualImpl(const ExtensionError* rhs) const {
  // ExtensionError logic is sufficient for comparison.
  return true;
}

}  // namespace extensions
