// 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 "content/renderer/web_database_observer_impl.h"

#include "base/bind.h"
#include "base/metrics/histogram_macros.h"
#include "base/single_thread_task_runner.h"
#include "base/strings/string16.h"
#include "base/threading/thread_task_runner_handle.h"
#include "third_party/blink/public/platform/web_security_origin.h"
#include "third_party/blink/public/platform/web_string.h"
#include "third_party/sqlite/sqlite3.h"

using blink::WebSecurityOrigin;
using blink::WebString;

namespace content {

namespace {

const int kResultHistogramSize = 50;
const int kCallsiteHistogramSize = 10;
const int kWebSQLSuccess = -1;

int DetermineHistogramResult(int websql_error, int sqlite_error) {
  // If we have a sqlite error, log it after trimming the extended bits.
  // There are 26 possible values, but we leave room for some new ones.
  if (sqlite_error)
    return std::min(sqlite_error & 0xff, 30);

  // Otherwise, websql_error may be an SQLExceptionCode, SQLErrorCode
  // or a DOMExceptionCode, or -1 for success.
  if (websql_error == kWebSQLSuccess)
    return 0;  // no error

  // SQLExceptionCode starts at 1000
  if (websql_error >= 1000)
    websql_error -= 1000;

  return std::min(websql_error + 30, kResultHistogramSize - 1);
}

#define UMA_HISTOGRAM_WEBSQL_RESULT(name, callsite, websql_error, sqlite_error) \
  do { \
    DCHECK(callsite < kCallsiteHistogramSize); \
    int result = DetermineHistogramResult(websql_error, sqlite_error); \
    UMA_HISTOGRAM_ENUMERATION("websql.Async." name, \
                              result, kResultHistogramSize); \
    if (result) { \
      UMA_HISTOGRAM_ENUMERATION("websql.Async." name ".ErrorSite", \
                                callsite, kCallsiteHistogramSize); \
    } \
  } while (0)


}  // namespace

WebDatabaseObserverImpl::WebDatabaseObserverImpl(
    scoped_refptr<blink::mojom::ThreadSafeWebDatabaseHostPtr> web_database_host)
    : web_database_host_(std::move(web_database_host)),
      main_thread_task_runner_(base::ThreadTaskRunnerHandle::Get()) {
  DCHECK(main_thread_task_runner_);
}

WebDatabaseObserverImpl::~WebDatabaseObserverImpl() = default;

void WebDatabaseObserverImpl::DatabaseOpened(
    const WebSecurityOrigin& origin,
    const WebString& database_name,
    const WebString& database_display_name,
    unsigned long estimated_size) {
  GetWebDatabaseHost().Opened(origin, database_name.Utf16(),
                              database_display_name.Utf16(), estimated_size);
}

void WebDatabaseObserverImpl::DatabaseModified(const WebSecurityOrigin& origin,
                                               const WebString& database_name) {
  GetWebDatabaseHost().Modified(origin, database_name.Utf16());
}

void WebDatabaseObserverImpl::DatabaseClosed(const WebSecurityOrigin& origin,
                                             const WebString& database_name) {
  DCHECK(!main_thread_task_runner_->RunsTasksInCurrentSequence());
  GetWebDatabaseHost().Closed(origin, database_name.Utf16());
}

void WebDatabaseObserverImpl::ReportOpenDatabaseResult(
    const WebSecurityOrigin& origin,
    const WebString& database_name,
    int callsite,
    int websql_error,
    int sqlite_error,
    base::TimeDelta call_time) {
  UMA_HISTOGRAM_WEBSQL_RESULT("OpenResult", callsite,
                              websql_error, sqlite_error);
  HandleSqliteError(origin, database_name, sqlite_error);

  if (websql_error == kWebSQLSuccess && sqlite_error == SQLITE_OK) {
    UMA_HISTOGRAM_TIMES("websql.Async.OpenTime.Success", call_time);
  } else {
    UMA_HISTOGRAM_TIMES("websql.Async.OpenTime.Error", call_time);
  }
}

void WebDatabaseObserverImpl::ReportChangeVersionResult(
    const WebSecurityOrigin& origin,
    const WebString& database_name,
    int callsite,
    int websql_error,
    int sqlite_error) {
  UMA_HISTOGRAM_WEBSQL_RESULT("ChangeVersionResult", callsite,
                              websql_error, sqlite_error);
  HandleSqliteError(origin, database_name, sqlite_error);
}

void WebDatabaseObserverImpl::ReportStartTransactionResult(
    const WebSecurityOrigin& origin,
    const WebString& database_name,
    int callsite,
    int websql_error,
    int sqlite_error) {
  UMA_HISTOGRAM_WEBSQL_RESULT("BeginResult", callsite,
                              websql_error, sqlite_error);
  HandleSqliteError(origin, database_name, sqlite_error);
}

void WebDatabaseObserverImpl::ReportCommitTransactionResult(
    const WebSecurityOrigin& origin,
    const WebString& database_name,
    int callsite,
    int websql_error,
    int sqlite_error) {
  UMA_HISTOGRAM_WEBSQL_RESULT("CommitResult", callsite,
                              websql_error, sqlite_error);
  HandleSqliteError(origin, database_name, sqlite_error);
}

void WebDatabaseObserverImpl::ReportExecuteStatementResult(
    const WebSecurityOrigin& origin,
    const WebString& database_name,
    int callsite,
    int websql_error,
    int sqlite_error) {
  UMA_HISTOGRAM_WEBSQL_RESULT("StatementResult", callsite,
                              websql_error, sqlite_error);
  HandleSqliteError(origin, database_name, sqlite_error);
}

void WebDatabaseObserverImpl::ReportVacuumDatabaseResult(
    const WebSecurityOrigin& origin,
    const WebString& database_name,
    int sqlite_error) {
  int result = DetermineHistogramResult(-1, sqlite_error);
  UMA_HISTOGRAM_ENUMERATION("websql.Async.VacuumResult",
                              result, kResultHistogramSize);
  HandleSqliteError(origin, database_name, sqlite_error);
}

void WebDatabaseObserverImpl::HandleSqliteError(const WebSecurityOrigin& origin,
                                                const WebString& database_name,
                                                int error) {
  // We filter out errors which the backend doesn't act on to avoid
  // a unnecessary ipc traffic, this method can get called at a fairly
  // high frequency (per-sqlstatement).
  if (error == SQLITE_CORRUPT || error == SQLITE_NOTADB) {
    GetWebDatabaseHost().HandleSqliteError(origin, database_name.Utf16(),
                                           error);
  }
}

blink::mojom::WebDatabaseHost& WebDatabaseObserverImpl::GetWebDatabaseHost() {
  return **web_database_host_;
}

}  // namespace content
