// Copyright (c) 2011 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/browser/in_process_webkit/indexed_db_dispatcher_host.h"

#include "base/command_line.h"
#include "base/utf_string_conversions.h"
#include "content/browser/browser_thread.h"
#include "content/browser/in_process_webkit/indexed_db_callbacks.h"
#include "content/browser/in_process_webkit/indexed_db_database_callbacks.h"
#include "content/browser/in_process_webkit/indexed_db_transaction_callbacks.h"
#include "content/browser/renderer_host/render_message_filter.h"
#include "content/browser/user_metrics.h"
#include "content/common/content_switches.h"
#include "content/common/indexed_db_messages.h"
#include "content/common/result_codes.h"
#include "googleurl/src/gurl.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebDOMStringList.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebIDBCursor.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebIDBDatabase.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebIDBDatabaseError.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebIDBKeyRange.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebIDBIndex.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebIDBFactory.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebIDBObjectStore.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebIDBTransaction.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebSecurityOrigin.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebVector.h"
#include "webkit/glue/webkit_glue.h"

using WebKit::WebDOMStringList;
using WebKit::WebExceptionCode;
using WebKit::WebIDBCallbacks;
using WebKit::WebIDBCursor;
using WebKit::WebIDBDatabase;
using WebKit::WebIDBDatabaseError;
using WebKit::WebIDBIndex;
using WebKit::WebIDBKey;
using WebKit::WebIDBKeyRange;
using WebKit::WebIDBObjectStore;
using WebKit::WebIDBTransaction;
using WebKit::WebSecurityOrigin;
using WebKit::WebSerializedScriptValue;
using WebKit::WebVector;

namespace {

template <class T>
void DeleteOnWebKitThread(T* obj) {
  if (!BrowserThread::DeleteSoon(BrowserThread::WEBKIT, FROM_HERE, obj))
    delete obj;
}

}

IndexedDBDispatcherHost::IndexedDBDispatcherHost(
    int process_id, WebKitContext* webkit_context)
    : webkit_context_(webkit_context),
      ALLOW_THIS_IN_INITIALIZER_LIST(database_dispatcher_host_(
          new DatabaseDispatcherHost(this))),
      ALLOW_THIS_IN_INITIALIZER_LIST(index_dispatcher_host_(
          new IndexDispatcherHost(this))),
      ALLOW_THIS_IN_INITIALIZER_LIST(object_store_dispatcher_host_(
          new ObjectStoreDispatcherHost(this))),
      ALLOW_THIS_IN_INITIALIZER_LIST(cursor_dispatcher_host_(
          new CursorDispatcherHost(this))),
      ALLOW_THIS_IN_INITIALIZER_LIST(transaction_dispatcher_host_(
          new TransactionDispatcherHost(this))),
      process_id_(process_id) {
  DCHECK(webkit_context_.get());
}

IndexedDBDispatcherHost::~IndexedDBDispatcherHost() {
}

void IndexedDBDispatcherHost::OnChannelClosing() {
  BrowserMessageFilter::OnChannelClosing();

  bool success = BrowserThread::PostTask(
      BrowserThread::WEBKIT, FROM_HERE,
      NewRunnableMethod(this, &IndexedDBDispatcherHost::ResetDispatcherHosts));

  if (!success)
    ResetDispatcherHosts();
}

void IndexedDBDispatcherHost::ResetDispatcherHosts() {
  // It is important that the various *_dispatcher_host_ members are reset
  // on the WebKit thread, since there might be incoming messages on that
  // thread, and we must not reset the dispatcher hosts until after those
  // messages are processed.
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::WEBKIT) ||
         CommandLine::ForCurrentProcess()->HasSwitch(switches::kSingleProcess));

  database_dispatcher_host_.reset();
  index_dispatcher_host_.reset();
  object_store_dispatcher_host_.reset();
  cursor_dispatcher_host_.reset();
  transaction_dispatcher_host_.reset();
}

void IndexedDBDispatcherHost::OverrideThreadForMessage(
    const IPC::Message& message,
    BrowserThread::ID* thread) {
  if (IPC_MESSAGE_CLASS(message) == IndexedDBMsgStart)
    *thread = BrowserThread::WEBKIT;
}

bool IndexedDBDispatcherHost::OnMessageReceived(const IPC::Message& message,
                                                bool* message_was_ok) {
  if (IPC_MESSAGE_CLASS(message) != IndexedDBMsgStart)
    return false;

  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::WEBKIT));

  bool handled =
      database_dispatcher_host_->OnMessageReceived(message, message_was_ok) ||
      index_dispatcher_host_->OnMessageReceived(message, message_was_ok) ||
      object_store_dispatcher_host_->OnMessageReceived(
          message, message_was_ok) ||
      cursor_dispatcher_host_->OnMessageReceived(message, message_was_ok) ||
      transaction_dispatcher_host_->OnMessageReceived(message, message_was_ok);

  if (!handled) {
    handled = true;
    IPC_BEGIN_MESSAGE_MAP_EX(IndexedDBDispatcherHost, message, *message_was_ok)
      IPC_MESSAGE_HANDLER(IndexedDBHostMsg_FactoryGetDatabaseNames,
                          OnIDBFactoryGetDatabaseNames)
      IPC_MESSAGE_HANDLER(IndexedDBHostMsg_FactoryOpen, OnIDBFactoryOpen)
      IPC_MESSAGE_HANDLER(IndexedDBHostMsg_FactoryDeleteDatabase,
                          OnIDBFactoryDeleteDatabase)
      IPC_MESSAGE_UNHANDLED(handled = false)
    IPC_END_MESSAGE_MAP()
  }

  return handled;
}

int32 IndexedDBDispatcherHost::Add(WebIDBCursor* idb_cursor) {
  if (!cursor_dispatcher_host_.get()) {
    delete idb_cursor;
    return 0;
  }
  return cursor_dispatcher_host_->map_.Add(idb_cursor);
}

int32 IndexedDBDispatcherHost::Add(WebIDBDatabase* idb_database,
                                   const GURL& origin_url) {
  if (!database_dispatcher_host_.get()) {
    delete idb_database;
    return 0;
  }
  int32 idb_database_id = database_dispatcher_host_->map_.Add(idb_database);
  Context()->ConnectionOpened(origin_url);
  database_dispatcher_host_->database_url_map_[idb_database_id] = origin_url;
  return idb_database_id;
}

int32 IndexedDBDispatcherHost::Add(WebIDBIndex* idb_index) {
  if (!index_dispatcher_host_.get())  {
    delete idb_index;
    return 0;
  }
  if (!idb_index)
    return 0;
  return index_dispatcher_host_->map_.Add(idb_index);
}

int32 IndexedDBDispatcherHost::Add(WebIDBObjectStore* idb_object_store) {
  if (!object_store_dispatcher_host_.get()) {
    delete idb_object_store;
    return 0;
  }
  if (!idb_object_store)
    return 0;
  return object_store_dispatcher_host_->map_.Add(idb_object_store);
}

int32 IndexedDBDispatcherHost::Add(WebIDBTransaction* idb_transaction,
                                   const GURL& url) {
  if (!transaction_dispatcher_host_.get()) {
    delete idb_transaction;
    return 0;
  }
  int32 id = transaction_dispatcher_host_->map_.Add(idb_transaction);
  idb_transaction->setCallbacks(new IndexedDBTransactionCallbacks(this, id));
  transaction_dispatcher_host_->transaction_url_map_[id] = url;
  return id;
}

void IndexedDBDispatcherHost::OnIDBFactoryGetDatabaseNames(
    const IndexedDBHostMsg_FactoryGetDatabaseNames_Params& params) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::WEBKIT));
  FilePath base_path = webkit_context_->data_path();
  FilePath indexed_db_path;
  if (!base_path.empty()) {
    indexed_db_path = base_path.Append(
        IndexedDBContext::kIndexedDBDirectory);
  }

  // TODO(jorlow): This doesn't support file:/// urls properly. We probably need
  //               to add some toString method to WebSecurityOrigin that doesn't
  //               return null for them.  Look at
  //               DatabaseUtil::GetOriginFromIdentifier.
  WebSecurityOrigin origin(
      WebSecurityOrigin::createFromDatabaseIdentifier(params.origin));
  GURL origin_url(origin.toString());

  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::WEBKIT));

  WebKit::WebIDBFactory::BackingStoreType backingStoreType =
      WebKit::WebIDBFactory::LevelDBBackingStore;

  if (CommandLine::ForCurrentProcess()->HasSwitch(
      switches::kSQLiteIndexedDatabase)) {
    backingStoreType = WebKit::WebIDBFactory::SQLiteBackingStore;
  }

  // TODO(dgrogan): Delete this magic constant once we've removed sqlite.
  static const uint64 kIncognitoSqliteBackendQuota = 50 * 1024 * 1024;

  Context()->GetIDBFactory()->getDatabaseNames(
      new IndexedDBCallbacks<WebDOMStringList>(this, params.response_id),
      origin, NULL, webkit_glue::FilePathToWebString(indexed_db_path),
      kIncognitoSqliteBackendQuota, backingStoreType);
}

void IndexedDBDispatcherHost::OnIDBFactoryOpen(
    const IndexedDBHostMsg_FactoryOpen_Params& params) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::WEBKIT));
  FilePath base_path = webkit_context_->data_path();
  FilePath indexed_db_path;
  if (!base_path.empty()) {
    indexed_db_path = base_path.Append(
        IndexedDBContext::kIndexedDBDirectory);
  }

  // TODO(jorlow): This doesn't support file:/// urls properly. We probably need
  //               to add some toString method to WebSecurityOrigin that doesn't
  //               return null for them.  Look at
  //               DatabaseUtil::GetOriginFromIdentifier.
  WebSecurityOrigin origin(
      WebSecurityOrigin::createFromDatabaseIdentifier(params.origin));
  GURL origin_url(origin.toString());

  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::WEBKIT));

  WebKit::WebIDBFactory::BackingStoreType backingStoreType =
      WebKit::WebIDBFactory::LevelDBBackingStore;

  if (CommandLine::ForCurrentProcess()->HasSwitch(
      switches::kSQLiteIndexedDatabase)) {
    backingStoreType = WebKit::WebIDBFactory::SQLiteBackingStore;
  }

  // TODO(dgrogan): Delete this magic constant once we've removed sqlite.
  static const uint64 kIncognitoSqliteBackendQuota = 50 * 1024 * 1024;

  // TODO(dgrogan): Don't let a non-existing database be opened (and therefore
  // created) if this origin is already over quota.
  Context()->GetIDBFactory()->open(
      params.name,
      new IndexedDBCallbacks<WebIDBDatabase>(this, params.response_id,
                                             origin_url),
      origin, NULL, webkit_glue::FilePathToWebString(indexed_db_path),
      kIncognitoSqliteBackendQuota, backingStoreType);
}

void IndexedDBDispatcherHost::OnIDBFactoryDeleteDatabase(
    const IndexedDBHostMsg_FactoryDeleteDatabase_Params& params) {
  FilePath base_path = webkit_context_->data_path();
  FilePath indexed_db_path;
  if (!base_path.empty()) {
    indexed_db_path = base_path.Append(
        IndexedDBContext::kIndexedDBDirectory);
  }

  WebSecurityOrigin origin(
      WebSecurityOrigin::createFromDatabaseIdentifier(params.origin));
  GURL url(origin.toString());

  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::WEBKIT));
  Context()->GetIDBFactory()->deleteDatabase(
      params.name,
      new IndexedDBCallbacks<WebIDBDatabase>(this, params.response_id, url),
      WebSecurityOrigin::createFromDatabaseIdentifier(params.origin), NULL,
      webkit_glue::FilePathToWebString(indexed_db_path));
}

void IndexedDBDispatcherHost::TransactionComplete(int32 transaction_id) {
  Context()->TransactionComplete(
      transaction_dispatcher_host_->transaction_url_map_[transaction_id]);
}

//////////////////////////////////////////////////////////////////////
// Helper templates.
//

template <typename ObjectType>
ObjectType* IndexedDBDispatcherHost::GetOrTerminateProcess(
    IDMap<ObjectType, IDMapOwnPointer>* map, int32 return_object_id) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::WEBKIT));
  ObjectType* return_object = map->Lookup(return_object_id);
  if (!return_object) {
    UserMetrics::RecordAction(UserMetricsAction("BadMessageTerminate_IDBMF"));
    BadMessageReceived();
  }
  return return_object;
}

template <typename ReplyType, typename MapObjectType, typename Method>
void IndexedDBDispatcherHost::SyncGetter(
    IDMap<MapObjectType, IDMapOwnPointer>* map, int32 object_id,
    ReplyType* reply, Method method) {
  MapObjectType* object = GetOrTerminateProcess(map, object_id);
  if (!object)
      return;

  *reply = (object->*method)();
}

template <typename ObjectType>
void IndexedDBDispatcherHost::DestroyObject(
    IDMap<ObjectType, IDMapOwnPointer>* map, int32 object_id) {
  GetOrTerminateProcess(map, object_id);
  map->Remove(object_id);
}


//////////////////////////////////////////////////////////////////////
// IndexedDBDispatcherHost::DatabaseDispatcherHost
//

IndexedDBDispatcherHost::DatabaseDispatcherHost::DatabaseDispatcherHost(
    IndexedDBDispatcherHost* parent)
    : parent_(parent) {
  map_.set_check_on_null_data(true);
}

IndexedDBDispatcherHost::DatabaseDispatcherHost::~DatabaseDispatcherHost() {
  for (WebIDBObjectIDToURLMap::iterator iter = database_url_map_.begin();
       iter != database_url_map_.end(); iter++) {
    parent_->Context()->ConnectionClosed(iter->second);
  }
}

bool IndexedDBDispatcherHost::DatabaseDispatcherHost::OnMessageReceived(
    const IPC::Message& message, bool* msg_is_ok) {
  bool handled = true;
  IPC_BEGIN_MESSAGE_MAP_EX(IndexedDBDispatcherHost::DatabaseDispatcherHost,
                           message, *msg_is_ok)
    IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabaseName, OnName)
    IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabaseVersion, OnVersion)
    IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabaseObjectStoreNames,
                        OnObjectStoreNames)
    IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabaseCreateObjectStore,
                        OnCreateObjectStore)
    IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabaseDeleteObjectStore,
                        OnDeleteObjectStore)
    IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabaseSetVersion, OnSetVersion)
    IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabaseTransaction, OnTransaction)
    IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabaseClose, OnClose)
    IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabaseOpen, OnOpen)
    IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabaseDestroyed, OnDestroyed)
    IPC_MESSAGE_UNHANDLED(handled = false)
  IPC_END_MESSAGE_MAP()
  return handled;
}

void IndexedDBDispatcherHost::DatabaseDispatcherHost::Send(
    IPC::Message* message) {
  parent_->Send(message);
}

void IndexedDBDispatcherHost::DatabaseDispatcherHost::OnName(
    int32 object_id, string16* name) {
  parent_->SyncGetter<string16>(&map_, object_id, name, &WebIDBDatabase::name);
}

void IndexedDBDispatcherHost::DatabaseDispatcherHost::OnVersion(
    int32 object_id, string16* version) {
  parent_->SyncGetter<string16>(
      &map_, object_id, version, &WebIDBDatabase::version);
}

void IndexedDBDispatcherHost::DatabaseDispatcherHost::OnObjectStoreNames(
    int32 idb_database_id, std::vector<string16>* object_stores) {
  WebIDBDatabase* idb_database = parent_->GetOrTerminateProcess(
      &map_, idb_database_id);
  if (!idb_database)
    return;

  WebDOMStringList web_object_stores = idb_database->objectStoreNames();
  object_stores->reserve(web_object_stores.length());
  for (unsigned i = 0; i < web_object_stores.length(); ++i)
    object_stores->push_back(web_object_stores.item(i));
}

void IndexedDBDispatcherHost::DatabaseDispatcherHost::OnCreateObjectStore(
    const IndexedDBHostMsg_DatabaseCreateObjectStore_Params& params,
    int32* object_store_id, WebKit::WebExceptionCode* ec) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::WEBKIT));
  WebIDBDatabase* idb_database = parent_->GetOrTerminateProcess(
      &map_, params.idb_database_id);
  WebIDBTransaction* idb_transaction = parent_->GetOrTerminateProcess(
      &parent_->transaction_dispatcher_host_->map_, params.transaction_id);
  if (!idb_database || !idb_transaction)
    return;

  *ec = 0;
  WebIDBObjectStore* object_store = idb_database->createObjectStore(
      params.name, params.key_path, params.auto_increment,
      *idb_transaction, *ec);
  *object_store_id = *ec ? 0 : parent_->Add(object_store);
  if (parent_->Context()->IsOverQuota(
      database_url_map_[params.idb_database_id])) {
    idb_transaction->abort();
  }
}

void IndexedDBDispatcherHost::DatabaseDispatcherHost::OnDeleteObjectStore(
    int32 idb_database_id,
    const string16& name,
    int32 transaction_id,
    WebKit::WebExceptionCode* ec) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::WEBKIT));
  WebIDBDatabase* idb_database = parent_->GetOrTerminateProcess(
      &map_, idb_database_id);
  WebIDBTransaction* idb_transaction = parent_->GetOrTerminateProcess(
      &parent_->transaction_dispatcher_host_->map_, transaction_id);
  if (!idb_database || !idb_transaction)
    return;

  *ec = 0;
  idb_database->deleteObjectStore(name, *idb_transaction, *ec);
}

void IndexedDBDispatcherHost::DatabaseDispatcherHost::OnSetVersion(
    int32 idb_database_id,
    int32 response_id,
    const string16& version,
    WebKit::WebExceptionCode* ec) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::WEBKIT));
  WebIDBDatabase* idb_database = parent_->GetOrTerminateProcess(
      &map_, idb_database_id);
  if (!idb_database)
    return;

  *ec = 0;
  idb_database->setVersion(
      version,
      new IndexedDBCallbacks<WebIDBTransaction>(parent_, response_id,
          database_url_map_[idb_database_id]),
      *ec);
}

void IndexedDBDispatcherHost::DatabaseDispatcherHost::OnTransaction(
    int32 idb_database_id,
    const std::vector<string16>& names,
    int32 mode,
    int32* idb_transaction_id,
    WebKit::WebExceptionCode* ec) {
  WebIDBDatabase* database = parent_->GetOrTerminateProcess(
      &map_, idb_database_id);
  if (!database)
      return;

  WebDOMStringList object_stores;
  for (std::vector<string16>::const_iterator it = names.begin();
       it != names.end(); ++it) {
    object_stores.append(*it);
  }

  *ec = 0;
  WebIDBTransaction* transaction = database->transaction(
      object_stores, mode, *ec);
  DCHECK(!transaction != !*ec);
  *idb_transaction_id =
      *ec ? 0 : parent_->Add(transaction, database_url_map_[idb_database_id]);
}

void IndexedDBDispatcherHost::DatabaseDispatcherHost::OnOpen(
    int32 idb_database_id, int32 response_id) {
  WebIDBDatabase* database = parent_->GetOrTerminateProcess(
      &map_, idb_database_id);
  database->open(new IndexedDBDatabaseCallbacks(parent_, response_id));
}

void IndexedDBDispatcherHost::DatabaseDispatcherHost::OnClose(
    int32 idb_database_id) {
  WebIDBDatabase* database = parent_->GetOrTerminateProcess(
      &map_, idb_database_id);
  database->close();
}

void IndexedDBDispatcherHost::DatabaseDispatcherHost::OnDestroyed(
    int32 object_id) {
  parent_->Context()->ConnectionClosed(database_url_map_[object_id]);
  database_url_map_.erase(object_id);
  parent_->DestroyObject(&map_, object_id);
}


//////////////////////////////////////////////////////////////////////
// IndexedDBDispatcherHost::IndexDispatcherHost
//

IndexedDBDispatcherHost::IndexDispatcherHost::IndexDispatcherHost(
    IndexedDBDispatcherHost* parent)
    : parent_(parent) {
  map_.set_check_on_null_data(true);
}

IndexedDBDispatcherHost::IndexDispatcherHost::~IndexDispatcherHost() {
}

bool IndexedDBDispatcherHost::IndexDispatcherHost::OnMessageReceived(
    const IPC::Message& message, bool* msg_is_ok) {
  bool handled = true;
  IPC_BEGIN_MESSAGE_MAP_EX(IndexedDBDispatcherHost::IndexDispatcherHost,
                           message, *msg_is_ok)
    IPC_MESSAGE_HANDLER(IndexedDBHostMsg_IndexName, OnName)
    IPC_MESSAGE_HANDLER(IndexedDBHostMsg_IndexStoreName, OnStoreName)
    IPC_MESSAGE_HANDLER(IndexedDBHostMsg_IndexKeyPath, OnKeyPath)
    IPC_MESSAGE_HANDLER(IndexedDBHostMsg_IndexUnique, OnUnique)
    IPC_MESSAGE_HANDLER(IndexedDBHostMsg_IndexOpenObjectCursor,
                                    OnOpenObjectCursor)
    IPC_MESSAGE_HANDLER(IndexedDBHostMsg_IndexOpenKeyCursor, OnOpenKeyCursor)
    IPC_MESSAGE_HANDLER(IndexedDBHostMsg_IndexGetObject, OnGetObject)
    IPC_MESSAGE_HANDLER(IndexedDBHostMsg_IndexGetKey, OnGetKey)
    IPC_MESSAGE_HANDLER(IndexedDBHostMsg_IndexDestroyed, OnDestroyed)
    IPC_MESSAGE_UNHANDLED(handled = false)
  IPC_END_MESSAGE_MAP()
  return handled;
}

void IndexedDBDispatcherHost::IndexDispatcherHost::Send(
    IPC::Message* message) {
  parent_->Send(message);
}

void IndexedDBDispatcherHost::IndexDispatcherHost::OnName(
    int32 object_id, string16* name) {
  parent_->SyncGetter<string16>(&map_, object_id, name, &WebIDBIndex::name);
}

void IndexedDBDispatcherHost::IndexDispatcherHost::OnStoreName(
    int32 object_id, string16* store_name) {
  parent_->SyncGetter<string16>(
      &map_, object_id, store_name, &WebIDBIndex::storeName);
}

void IndexedDBDispatcherHost::IndexDispatcherHost::OnKeyPath(
    int32 object_id, NullableString16* key_path) {
  parent_->SyncGetter<NullableString16>(
      &map_, object_id, key_path, &WebIDBIndex::keyPath);
}

void IndexedDBDispatcherHost::IndexDispatcherHost::OnUnique(
    int32 object_id, bool* unique) {
  parent_->SyncGetter<bool>(&map_, object_id, unique, &WebIDBIndex::unique);
}

void IndexedDBDispatcherHost::IndexDispatcherHost::OnOpenObjectCursor(
    const IndexedDBHostMsg_IndexOpenCursor_Params& params,
    WebKit::WebExceptionCode* ec) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::WEBKIT));
  WebIDBIndex* idb_index = parent_->GetOrTerminateProcess(
      &map_, params.idb_index_id);
  WebIDBTransaction* idb_transaction = parent_->GetOrTerminateProcess(
      &parent_->transaction_dispatcher_host_->map_, params.transaction_id);
  if (!idb_transaction || !idb_index)
    return;

  *ec = 0;
  scoped_ptr<WebIDBCallbacks> callbacks(
      new IndexedDBCallbacks<WebIDBCursor>(parent_, params.response_id));
  idb_index->openObjectCursor(
      WebIDBKeyRange(params.lower_key, params.upper_key, params.lower_open,
                     params.upper_open),
      params.direction, callbacks.release(), *idb_transaction, *ec);
}

void IndexedDBDispatcherHost::IndexDispatcherHost::OnOpenKeyCursor(
    const IndexedDBHostMsg_IndexOpenCursor_Params& params,
    WebKit::WebExceptionCode* ec) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::WEBKIT));
  WebIDBIndex* idb_index = parent_->GetOrTerminateProcess(
      &map_, params.idb_index_id);
  WebIDBTransaction* idb_transaction = parent_->GetOrTerminateProcess(
      &parent_->transaction_dispatcher_host_->map_, params.transaction_id);
  if (!idb_transaction || !idb_index)
    return;

  *ec = 0;
  scoped_ptr<WebIDBCallbacks> callbacks(
      new IndexedDBCallbacks<WebIDBCursor>(parent_, params.response_id));
  idb_index->openKeyCursor(
      WebIDBKeyRange(params.lower_key, params.upper_key, params.lower_open,
                     params.upper_open),
      params.direction, callbacks.release(), *idb_transaction, *ec);
}

void IndexedDBDispatcherHost::IndexDispatcherHost::OnGetObject(
    int idb_index_id,
    int32 response_id,
    const IndexedDBKey& key,
    int32 transaction_id,
    WebKit::WebExceptionCode* ec) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::WEBKIT));
  WebIDBIndex* idb_index = parent_->GetOrTerminateProcess(
      &map_, idb_index_id);
  WebIDBTransaction* idb_transaction = parent_->GetOrTerminateProcess(
      &parent_->transaction_dispatcher_host_->map_, transaction_id);
  if (!idb_transaction || !idb_index)
    return;

  *ec = 0;
  scoped_ptr<WebIDBCallbacks> callbacks(
      new IndexedDBCallbacks<WebSerializedScriptValue>(parent_, response_id));
  idb_index->getObject(key, callbacks.release(), *idb_transaction, *ec);
}

void IndexedDBDispatcherHost::IndexDispatcherHost::OnGetKey(
    int idb_index_id,
    int32 response_id,
    const IndexedDBKey& key,
    int32 transaction_id,
    WebKit::WebExceptionCode* ec) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::WEBKIT));
  WebIDBIndex* idb_index = parent_->GetOrTerminateProcess(
      &map_, idb_index_id);
  WebIDBTransaction* idb_transaction = parent_->GetOrTerminateProcess(
      &parent_->transaction_dispatcher_host_->map_, transaction_id);
  if (!idb_transaction || !idb_index)
    return;

  *ec = 0;
  scoped_ptr<WebIDBCallbacks> callbacks(
      new IndexedDBCallbacks<WebIDBKey>(parent_, response_id));
  idb_index->getKey(key, callbacks.release(), *idb_transaction, *ec);
}

void IndexedDBDispatcherHost::IndexDispatcherHost::OnDestroyed(
    int32 object_id) {
  parent_->DestroyObject(&map_, object_id);
}

//////////////////////////////////////////////////////////////////////
// IndexedDBDispatcherHost::ObjectStoreDispatcherHost
//

IndexedDBDispatcherHost::ObjectStoreDispatcherHost::ObjectStoreDispatcherHost(
    IndexedDBDispatcherHost* parent)
    : parent_(parent) {
  map_.set_check_on_null_data(true);
}

IndexedDBDispatcherHost::
ObjectStoreDispatcherHost::~ObjectStoreDispatcherHost() {
}

bool IndexedDBDispatcherHost::ObjectStoreDispatcherHost::OnMessageReceived(
    const IPC::Message& message, bool* msg_is_ok) {
  bool handled = true;
  IPC_BEGIN_MESSAGE_MAP_EX(IndexedDBDispatcherHost::ObjectStoreDispatcherHost,
                           message, *msg_is_ok)
    IPC_MESSAGE_HANDLER(IndexedDBHostMsg_ObjectStoreName, OnName)
    IPC_MESSAGE_HANDLER(IndexedDBHostMsg_ObjectStoreKeyPath, OnKeyPath)
    IPC_MESSAGE_HANDLER(IndexedDBHostMsg_ObjectStoreIndexNames, OnIndexNames)
    IPC_MESSAGE_HANDLER(IndexedDBHostMsg_ObjectStoreGet, OnGet)
    IPC_MESSAGE_HANDLER(IndexedDBHostMsg_ObjectStorePut, OnPut)
    IPC_MESSAGE_HANDLER(IndexedDBHostMsg_ObjectStoreDelete, OnDelete)
    IPC_MESSAGE_HANDLER(IndexedDBHostMsg_ObjectStoreClear, OnClear)
    IPC_MESSAGE_HANDLER(IndexedDBHostMsg_ObjectStoreCreateIndex, OnCreateIndex)
    IPC_MESSAGE_HANDLER(IndexedDBHostMsg_ObjectStoreIndex, OnIndex)
    IPC_MESSAGE_HANDLER(IndexedDBHostMsg_ObjectStoreDeleteIndex, OnDeleteIndex)
    IPC_MESSAGE_HANDLER(IndexedDBHostMsg_ObjectStoreOpenCursor, OnOpenCursor)
    IPC_MESSAGE_HANDLER(IndexedDBHostMsg_ObjectStoreDestroyed, OnDestroyed)
    IPC_MESSAGE_UNHANDLED(handled = false)
  IPC_END_MESSAGE_MAP()
  return handled;
}

void IndexedDBDispatcherHost::ObjectStoreDispatcherHost::Send(
    IPC::Message* message) {
  parent_->Send(message);
}

void IndexedDBDispatcherHost::ObjectStoreDispatcherHost::OnName(
    int32 object_id, string16* name) {
  parent_->SyncGetter<string16>(
      &map_, object_id, name, &WebIDBObjectStore::name);
}

void IndexedDBDispatcherHost::ObjectStoreDispatcherHost::OnKeyPath(
    int32 object_id, NullableString16* keyPath) {
  parent_->SyncGetter<NullableString16>(
      &map_, object_id, keyPath, &WebIDBObjectStore::keyPath);
}

void IndexedDBDispatcherHost::ObjectStoreDispatcherHost::OnIndexNames(
    int32 idb_object_store_id, std::vector<string16>* index_names) {
  WebIDBObjectStore* idb_object_store = parent_->GetOrTerminateProcess(
      &map_, idb_object_store_id);
  if (!idb_object_store)
    return;

  WebDOMStringList web_index_names = idb_object_store->indexNames();
  index_names->reserve(web_index_names.length());
  for (unsigned i = 0; i < web_index_names.length(); ++i)
    index_names->push_back(web_index_names.item(i));
}

void IndexedDBDispatcherHost::ObjectStoreDispatcherHost::OnGet(
    int idb_object_store_id,
    int32 response_id,
    const IndexedDBKey& key,
    int32 transaction_id,
    WebKit::WebExceptionCode* ec) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::WEBKIT));
  WebIDBObjectStore* idb_object_store = parent_->GetOrTerminateProcess(
      &map_, idb_object_store_id);
  WebIDBTransaction* idb_transaction = parent_->GetOrTerminateProcess(
      &parent_->transaction_dispatcher_host_->map_, transaction_id);
  if (!idb_transaction || !idb_object_store)
    return;

  *ec = 0;
  scoped_ptr<WebIDBCallbacks> callbacks(
      new IndexedDBCallbacks<WebSerializedScriptValue>(parent_, response_id));
  idb_object_store->get(key, callbacks.release(), *idb_transaction, *ec);
}

void IndexedDBDispatcherHost::ObjectStoreDispatcherHost::OnPut(
    const IndexedDBHostMsg_ObjectStorePut_Params& params,
    WebKit::WebExceptionCode* ec) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::WEBKIT));
  WebIDBObjectStore* idb_object_store = parent_->GetOrTerminateProcess(
      &map_, params.idb_object_store_id);
  WebIDBTransaction* idb_transaction = parent_->GetOrTerminateProcess(
      &parent_->transaction_dispatcher_host_->map_, params.transaction_id);
  if (!idb_transaction || !idb_object_store)
    return;

  *ec = 0;
  scoped_ptr<WebIDBCallbacks> callbacks(
      new IndexedDBCallbacks<WebIDBKey>(parent_, params.response_id));
  idb_object_store->put(params.serialized_value, params.key, params.put_mode,
                        callbacks.release(), *idb_transaction, *ec);
  if (*ec)
    return;
  int64 size = UTF16ToUTF8(params.serialized_value.data()).size();
  WebIDBTransactionIDToSizeMap* map =
      &parent_->transaction_dispatcher_host_->transaction_size_map_;
  (*map)[params.transaction_id] += size;
}

void IndexedDBDispatcherHost::ObjectStoreDispatcherHost::OnDelete(
    int idb_object_store_id,
    int32 response_id,
    const IndexedDBKey& key,
    int32 transaction_id,
    WebKit::WebExceptionCode* ec) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::WEBKIT));
  WebIDBObjectStore* idb_object_store = parent_->GetOrTerminateProcess(
      &map_, idb_object_store_id);
  WebIDBTransaction* idb_transaction = parent_->GetOrTerminateProcess(
      &parent_->transaction_dispatcher_host_->map_, transaction_id);
  if (!idb_transaction || !idb_object_store)
    return;

  *ec = 0;
  scoped_ptr<WebIDBCallbacks> callbacks(
      new IndexedDBCallbacks<WebSerializedScriptValue>(parent_, response_id));
  idb_object_store->deleteFunction(
      key, callbacks.release(), *idb_transaction, *ec);
}

void IndexedDBDispatcherHost::ObjectStoreDispatcherHost::OnClear(
    int idb_object_store_id,
    int32 response_id,
    int32 transaction_id,
    WebKit::WebExceptionCode* ec) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::WEBKIT));
  WebIDBObjectStore* idb_object_store = parent_->GetOrTerminateProcess(
      &map_, idb_object_store_id);
  WebIDBTransaction* idb_transaction = parent_->GetOrTerminateProcess(
      &parent_->transaction_dispatcher_host_->map_, transaction_id);
  if (!idb_transaction || !idb_object_store)
    return;

  *ec = 0;
  scoped_ptr<WebIDBCallbacks> callbacks(
      new IndexedDBCallbacks<WebSerializedScriptValue>(parent_, response_id));
  idb_object_store->clear(callbacks.release(), *idb_transaction, *ec);
}

void IndexedDBDispatcherHost::ObjectStoreDispatcherHost::OnCreateIndex(
   const IndexedDBHostMsg_ObjectStoreCreateIndex_Params& params,
   int32* index_id, WebKit::WebExceptionCode* ec) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::WEBKIT));
  WebIDBObjectStore* idb_object_store = parent_->GetOrTerminateProcess(
      &map_, params.idb_object_store_id);
  WebIDBTransaction* idb_transaction = parent_->GetOrTerminateProcess(
      &parent_->transaction_dispatcher_host_->map_, params.transaction_id);
  if (!idb_object_store || !idb_transaction)
    return;

  *ec = 0;
  WebIDBIndex* index = idb_object_store->createIndex(
      params.name, params.key_path, params.unique, *idb_transaction, *ec);
  *index_id = *ec ? 0 : parent_->Add(index);
  WebIDBObjectIDToURLMap* transaction_url_map =
      &parent_->transaction_dispatcher_host_->transaction_url_map_;
  if (parent_->Context()->IsOverQuota(
      (*transaction_url_map)[params.transaction_id])) {
    idb_transaction->abort();
  }
}

void IndexedDBDispatcherHost::ObjectStoreDispatcherHost::OnIndex(
    int32 idb_object_store_id,
    const string16& name,
    int32* idb_index_id,
    WebKit::WebExceptionCode* ec) {
  WebIDBObjectStore* idb_object_store = parent_->GetOrTerminateProcess(
      &map_, idb_object_store_id);
  if (!idb_object_store)
    return;

  *ec = 0;
  WebIDBIndex* index = idb_object_store->index(name, *ec);
  *idb_index_id = parent_->Add(index);
}

void IndexedDBDispatcherHost::ObjectStoreDispatcherHost::OnDeleteIndex(
    int32 idb_object_store_id,
    const string16& name,
    int32 transaction_id,
    WebKit::WebExceptionCode* ec) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::WEBKIT));
  WebIDBObjectStore* idb_object_store = parent_->GetOrTerminateProcess(
      &map_, idb_object_store_id);
  WebIDBTransaction* idb_transaction = parent_->GetOrTerminateProcess(
      &parent_->transaction_dispatcher_host_->map_, transaction_id);
  if (!idb_object_store || !idb_transaction)
    return;

  *ec = 0;
  idb_object_store->deleteIndex(name, *idb_transaction, *ec);
}

void IndexedDBDispatcherHost::ObjectStoreDispatcherHost::OnOpenCursor(
    const IndexedDBHostMsg_ObjectStoreOpenCursor_Params& params,
    WebKit::WebExceptionCode* ec) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::WEBKIT));
  WebIDBObjectStore* idb_object_store = parent_->GetOrTerminateProcess(
      &parent_->object_store_dispatcher_host_->map_,
      params.idb_object_store_id);
  WebIDBTransaction* idb_transaction = parent_->GetOrTerminateProcess(
      &parent_->transaction_dispatcher_host_->map_, params.transaction_id);
  if (!idb_transaction || !idb_object_store)
    return;

  *ec = 0;
  scoped_ptr<WebIDBCallbacks> callbacks(
      new IndexedDBCallbacks<WebIDBCursor>(parent_, params.response_id));
  idb_object_store->openCursor(
      WebIDBKeyRange(params.lower_key, params.upper_key, params.lower_open,
                     params.upper_open),
      params.direction, callbacks.release(), *idb_transaction, *ec);
}

void IndexedDBDispatcherHost::ObjectStoreDispatcherHost::OnDestroyed(
    int32 object_id) {
  parent_->DestroyObject(&map_, object_id);
}

//////////////////////////////////////////////////////////////////////
// IndexedDBDispatcherHost::CursorDispatcherHost
//

IndexedDBDispatcherHost::CursorDispatcherHost::CursorDispatcherHost(
    IndexedDBDispatcherHost* parent)
    : parent_(parent) {
  map_.set_check_on_null_data(true);
}

IndexedDBDispatcherHost::CursorDispatcherHost::~CursorDispatcherHost() {
}

bool IndexedDBDispatcherHost::CursorDispatcherHost::OnMessageReceived(
    const IPC::Message& message, bool* msg_is_ok) {
  bool handled = true;
  IPC_BEGIN_MESSAGE_MAP_EX(IndexedDBDispatcherHost::CursorDispatcherHost,
                           message, *msg_is_ok)
    IPC_MESSAGE_HANDLER(IndexedDBHostMsg_CursorDirection, OnDirection)
    IPC_MESSAGE_HANDLER(IndexedDBHostMsg_CursorKey, OnKey)
    IPC_MESSAGE_HANDLER(IndexedDBHostMsg_CursorPrimaryKey, OnPrimaryKey)
    IPC_MESSAGE_HANDLER(IndexedDBHostMsg_CursorValue, OnValue)
    IPC_MESSAGE_HANDLER(IndexedDBHostMsg_CursorUpdate, OnUpdate)
    IPC_MESSAGE_HANDLER(IndexedDBHostMsg_CursorContinue, OnContinue)
    IPC_MESSAGE_HANDLER(IndexedDBHostMsg_CursorDelete, OnDelete)
    IPC_MESSAGE_HANDLER(IndexedDBHostMsg_CursorDestroyed, OnDestroyed)
    IPC_MESSAGE_UNHANDLED(handled = false)
  IPC_END_MESSAGE_MAP()
  return handled;
}


void IndexedDBDispatcherHost::CursorDispatcherHost::Send(
    IPC::Message* message) {
  parent_->Send(message);
}

void IndexedDBDispatcherHost::CursorDispatcherHost::OnDirection(
    int32 object_id, int32* direction) {
  WebIDBCursor* idb_cursor = parent_->GetOrTerminateProcess(&map_, object_id);
  if (!idb_cursor)
    return;

  *direction = idb_cursor->direction();
}

void IndexedDBDispatcherHost::CursorDispatcherHost::OnKey(
    int32 object_id, IndexedDBKey* key) {
  WebIDBCursor* idb_cursor = parent_->GetOrTerminateProcess(&map_, object_id);
  if (!idb_cursor)
    return;

  *key = IndexedDBKey(idb_cursor->key());
}

void IndexedDBDispatcherHost::CursorDispatcherHost::OnPrimaryKey(
    int32 object_id, IndexedDBKey* primary_key) {
  WebIDBCursor* idb_cursor = parent_->GetOrTerminateProcess(&map_, object_id);
  if (!idb_cursor)
    return;

  *primary_key = IndexedDBKey(idb_cursor->primaryKey());
}

void IndexedDBDispatcherHost::CursorDispatcherHost::OnValue(
    int32 object_id,
    SerializedScriptValue* script_value) {
  WebIDBCursor* idb_cursor = parent_->GetOrTerminateProcess(&map_, object_id);
  if (!idb_cursor)
    return;

  *script_value = SerializedScriptValue(idb_cursor->value());
}

void IndexedDBDispatcherHost::CursorDispatcherHost::OnUpdate(
    int32 cursor_id,
    int32 response_id,
    const SerializedScriptValue& value,
    WebKit::WebExceptionCode* ec) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::WEBKIT));
  WebIDBCursor* idb_cursor = parent_->GetOrTerminateProcess(&map_, cursor_id);
  if (!idb_cursor)
    return;

  *ec = 0;
  idb_cursor->update(
      value, new IndexedDBCallbacks<WebIDBKey>(parent_, response_id), *ec);
}

void IndexedDBDispatcherHost::CursorDispatcherHost::OnContinue(
    int32 cursor_id,
    int32 response_id,
    const IndexedDBKey& key,
    WebKit::WebExceptionCode* ec) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::WEBKIT));
  WebIDBCursor* idb_cursor = parent_->GetOrTerminateProcess(&map_, cursor_id);
  if (!idb_cursor)
    return;

  *ec = 0;
  idb_cursor->continueFunction(
      key, new IndexedDBCallbacks<WebIDBCursor>(parent_, response_id), *ec);
}

void IndexedDBDispatcherHost::CursorDispatcherHost::OnDelete(
    int32 cursor_id,
    int32 response_id,
    WebKit::WebExceptionCode* ec) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::WEBKIT));
  WebIDBCursor* idb_cursor = parent_->GetOrTerminateProcess(&map_, cursor_id);
  if (!idb_cursor)
    return;

  *ec = 0;
  idb_cursor->deleteFunction(
      new IndexedDBCallbacks<WebSerializedScriptValue>(parent_, response_id), *ec);
}

void IndexedDBDispatcherHost::CursorDispatcherHost::OnDestroyed(
    int32 object_id) {
  parent_->DestroyObject(&map_, object_id);
}

//////////////////////////////////////////////////////////////////////
// IndexedDBDispatcherHost::TransactionDispatcherHost
//

IndexedDBDispatcherHost::TransactionDispatcherHost::TransactionDispatcherHost(
    IndexedDBDispatcherHost* parent)
    : parent_(parent) {
  map_.set_check_on_null_data(true);
}

IndexedDBDispatcherHost::
    TransactionDispatcherHost::~TransactionDispatcherHost() {
  MapType::iterator it(&map_);
  while (!it.IsAtEnd()) {
    it.GetCurrentValue()->abort();
    it.Advance();
  }
}

bool IndexedDBDispatcherHost::TransactionDispatcherHost::OnMessageReceived(
    const IPC::Message& message, bool* msg_is_ok) {
  bool handled = true;
  IPC_BEGIN_MESSAGE_MAP_EX(IndexedDBDispatcherHost::TransactionDispatcherHost,
                           message, *msg_is_ok)
    IPC_MESSAGE_HANDLER(IndexedDBHostMsg_TransactionAbort, OnAbort)
    IPC_MESSAGE_HANDLER(IndexedDBHostMsg_TransactionMode, OnMode)
    IPC_MESSAGE_HANDLER(IndexedDBHostMsg_TransactionObjectStore, OnObjectStore)
    IPC_MESSAGE_HANDLER(IndexedDBHostMsg_TransactionDidCompleteTaskEvents,
                        OnDidCompleteTaskEvents)
    IPC_MESSAGE_HANDLER(IndexedDBHostMsg_TransactionDestroyed, OnDestroyed)
    IPC_MESSAGE_UNHANDLED(handled = false)
  IPC_END_MESSAGE_MAP()
  return handled;
}

void IndexedDBDispatcherHost::TransactionDispatcherHost::Send(
    IPC::Message* message) {
  parent_->Send(message);
}

void IndexedDBDispatcherHost::TransactionDispatcherHost::OnAbort(
    int32 transaction_id) {
  WebIDBTransaction* idb_transaction = parent_->GetOrTerminateProcess(
      &map_, transaction_id);
  if (!idb_transaction)
    return;

  idb_transaction->abort();
}

void IndexedDBDispatcherHost::TransactionDispatcherHost::OnMode(
    int32 transaction_id,
    int* mode) {
  WebIDBTransaction* idb_transaction = parent_->GetOrTerminateProcess(
      &map_, transaction_id);
  if (!idb_transaction)
    return;

  *mode = idb_transaction->mode();
}

void IndexedDBDispatcherHost::TransactionDispatcherHost::OnObjectStore(
    int32 transaction_id, const string16& name, int32* object_store_id,
    WebKit::WebExceptionCode* ec) {
  WebIDBTransaction* idb_transaction = parent_->GetOrTerminateProcess(
      &map_, transaction_id);
  if (!idb_transaction)
    return;

  *ec = 0;
  WebIDBObjectStore* object_store = idb_transaction->objectStore(name, *ec);
  *object_store_id = object_store ? parent_->Add(object_store) : 0;
}

void IndexedDBDispatcherHost::
    TransactionDispatcherHost::OnDidCompleteTaskEvents(int transaction_id) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::WEBKIT));
  WebIDBTransaction* idb_transaction = parent_->GetOrTerminateProcess(
      &map_, transaction_id);
  if (!idb_transaction)
    return;

  // TODO(dgrogan): Tell the page the transaction aborted because of quota.
  if (parent_->Context()->WouldBeOverQuota(
      transaction_url_map_[transaction_id],
      transaction_size_map_[transaction_id])) {
    idb_transaction->abort();
    return;
  }
  idb_transaction->didCompleteTaskEvents();
}

void IndexedDBDispatcherHost::TransactionDispatcherHost::OnDestroyed(
    int32 object_id) {
  transaction_size_map_.erase(object_id);
  transaction_url_map_.erase(object_id);
  parent_->DestroyObject(&map_, object_id);
}