| // Copyright (c) 2012 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/indexed_db/indexed_db_dispatcher_host.h" |
| |
| #include "base/bind.h" |
| #include "base/command_line.h" |
| #include "base/files/file_path.h" |
| #include "base/guid.h" |
| #include "base/memory/scoped_vector.h" |
| #include "base/process/process.h" |
| #include "base/stl_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "content/browser/child_process_security_policy_impl.h" |
| #include "content/browser/indexed_db/indexed_db_callbacks.h" |
| #include "content/browser/indexed_db/indexed_db_connection.h" |
| #include "content/browser/indexed_db/indexed_db_context_impl.h" |
| #include "content/browser/indexed_db/indexed_db_cursor.h" |
| #include "content/browser/indexed_db/indexed_db_database_callbacks.h" |
| #include "content/browser/indexed_db/indexed_db_metadata.h" |
| #include "content/browser/indexed_db/indexed_db_pending_connection.h" |
| #include "content/browser/indexed_db/indexed_db_value.h" |
| #include "content/browser/renderer_host/render_message_filter.h" |
| #include "content/common/indexed_db/indexed_db_messages.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/user_metrics.h" |
| #include "content/public/common/content_switches.h" |
| #include "content/public/common/result_codes.h" |
| #include "storage/browser/blob/blob_data_builder.h" |
| #include "storage/browser/blob/blob_storage_context.h" |
| #include "storage/browser/database/database_util.h" |
| #include "storage/common/database/database_identifier.h" |
| #include "third_party/WebKit/public/platform/WebIDBDatabaseException.h" |
| #include "url/gurl.h" |
| |
| using storage::DatabaseUtil; |
| using blink::WebIDBKey; |
| |
| namespace content { |
| |
| IndexedDBDispatcherHost::IndexedDBDispatcherHost( |
| int ipc_process_id, |
| net::URLRequestContextGetter* request_context_getter, |
| IndexedDBContextImpl* indexed_db_context, |
| ChromeBlobStorageContext* blob_storage_context) |
| : BrowserMessageFilter(IndexedDBMsgStart), |
| request_context_getter_(request_context_getter), |
| request_context_(NULL), |
| indexed_db_context_(indexed_db_context), |
| blob_storage_context_(blob_storage_context), |
| database_dispatcher_host_(new DatabaseDispatcherHost(this)), |
| cursor_dispatcher_host_(new CursorDispatcherHost(this)), |
| ipc_process_id_(ipc_process_id) { |
| DCHECK(indexed_db_context_.get()); |
| } |
| |
| IndexedDBDispatcherHost::IndexedDBDispatcherHost( |
| int ipc_process_id, |
| net::URLRequestContext* request_context, |
| IndexedDBContextImpl* indexed_db_context, |
| ChromeBlobStorageContext* blob_storage_context) |
| : BrowserMessageFilter(IndexedDBMsgStart), |
| request_context_(request_context), |
| indexed_db_context_(indexed_db_context), |
| blob_storage_context_(blob_storage_context), |
| database_dispatcher_host_(new DatabaseDispatcherHost(this)), |
| cursor_dispatcher_host_(new CursorDispatcherHost(this)), |
| ipc_process_id_(ipc_process_id) { |
| DCHECK(indexed_db_context_.get()); |
| } |
| |
| IndexedDBDispatcherHost::~IndexedDBDispatcherHost() { |
| for (auto& iter : blob_data_handle_map_) |
| delete iter.second.first; |
| } |
| |
| void IndexedDBDispatcherHost::OnChannelConnected(int32 peer_pid) { |
| BrowserMessageFilter::OnChannelConnected(peer_pid); |
| |
| if (request_context_getter_.get()) { |
| DCHECK(!request_context_); |
| request_context_ = request_context_getter_->GetURLRequestContext(); |
| request_context_getter_ = NULL; |
| DCHECK(request_context_); |
| } |
| } |
| |
| void IndexedDBDispatcherHost::OnChannelClosing() { |
| bool success = indexed_db_context_->TaskRunner()->PostTask( |
| FROM_HERE, |
| base::Bind(&IndexedDBDispatcherHost::ResetDispatcherHosts, this)); |
| |
| if (!success) |
| ResetDispatcherHosts(); |
| } |
| |
| void IndexedDBDispatcherHost::OnDestruct() const { |
| // The last reference to the dispatcher may be a posted task, which would |
| // be destructed on the IndexedDB thread. Without this override, that would |
| // take the dispatcher with it. Since the dispatcher may be keeping the |
| // IndexedDBContext alive, it might be destructed to on its own thread, |
| // which is not supported. Ensure destruction runs on the IO thread instead. |
| BrowserThread::DeleteOnIOThread::Destruct(this); |
| } |
| |
| void IndexedDBDispatcherHost::ResetDispatcherHosts() { |
| // It is important that the various *_dispatcher_host_ members are reset |
| // on the IndexedDB 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(indexed_db_context_->TaskRunner()->RunsTasksOnCurrentThread()); |
| |
| // Note that we explicitly separate CloseAll() from destruction of the |
| // DatabaseDispatcherHost, since CloseAll() can invoke callbacks which need to |
| // be dispatched through database_dispatcher_host_. |
| database_dispatcher_host_->CloseAll(); |
| database_dispatcher_host_.reset(); |
| cursor_dispatcher_host_.reset(); |
| } |
| |
| base::TaskRunner* IndexedDBDispatcherHost::OverrideTaskRunnerForMessage( |
| const IPC::Message& message) { |
| if (IPC_MESSAGE_CLASS(message) != IndexedDBMsgStart) |
| return NULL; |
| |
| switch (message.type()) { |
| case IndexedDBHostMsg_DatabasePut::ID: |
| case IndexedDBHostMsg_AckReceivedBlobs::ID: |
| return NULL; |
| default: |
| return indexed_db_context_->TaskRunner(); |
| } |
| } |
| |
| bool IndexedDBDispatcherHost::OnMessageReceived(const IPC::Message& message) { |
| if (IPC_MESSAGE_CLASS(message) != IndexedDBMsgStart) |
| return false; |
| |
| DCHECK(indexed_db_context_->TaskRunner()->RunsTasksOnCurrentThread() || |
| (message.type() == IndexedDBHostMsg_DatabasePut::ID || |
| message.type() == IndexedDBHostMsg_AckReceivedBlobs::ID)); |
| |
| bool handled = database_dispatcher_host_->OnMessageReceived(message) || |
| cursor_dispatcher_host_->OnMessageReceived(message); |
| |
| if (!handled) { |
| handled = true; |
| IPC_BEGIN_MESSAGE_MAP(IndexedDBDispatcherHost, message) |
| IPC_MESSAGE_HANDLER(IndexedDBHostMsg_FactoryGetDatabaseNames, |
| OnIDBFactoryGetDatabaseNames) |
| IPC_MESSAGE_HANDLER(IndexedDBHostMsg_FactoryOpen, OnIDBFactoryOpen) |
| IPC_MESSAGE_HANDLER(IndexedDBHostMsg_FactoryDeleteDatabase, |
| OnIDBFactoryDeleteDatabase) |
| IPC_MESSAGE_HANDLER(IndexedDBHostMsg_AckReceivedBlobs, OnAckReceivedBlobs) |
| IPC_MESSAGE_UNHANDLED(handled = false) |
| IPC_END_MESSAGE_MAP() |
| } |
| return handled; |
| } |
| |
| int32 IndexedDBDispatcherHost::Add(IndexedDBCursor* cursor) { |
| if (!cursor_dispatcher_host_) { |
| return 0; |
| } |
| return cursor_dispatcher_host_->map_.Add(cursor); |
| } |
| |
| int32 IndexedDBDispatcherHost::Add(IndexedDBConnection* connection, |
| int32 ipc_thread_id, |
| const GURL& origin_url) { |
| if (!database_dispatcher_host_) { |
| connection->Close(); |
| delete connection; |
| return -1; |
| } |
| int32 ipc_database_id = database_dispatcher_host_->map_.Add(connection); |
| Context()->ConnectionOpened(origin_url, connection); |
| database_dispatcher_host_->database_url_map_[ipc_database_id] = origin_url; |
| return ipc_database_id; |
| } |
| |
| void IndexedDBDispatcherHost::RegisterTransactionId(int64 host_transaction_id, |
| const GURL& url) { |
| if (!database_dispatcher_host_) |
| return; |
| database_dispatcher_host_->transaction_url_map_[host_transaction_id] = url; |
| } |
| |
| int64 IndexedDBDispatcherHost::HostTransactionId(int64 transaction_id) { |
| // Inject the renderer process id into the transaction id, to |
| // uniquely identify this transaction, and effectively bind it to |
| // the renderer that initiated it. The lower 32 bits of |
| // transaction_id are guaranteed to be unique within that renderer. |
| base::ProcessId pid = peer_pid(); |
| DCHECK(!(transaction_id >> 32)) << "Transaction ids can only be 32 bits"; |
| static_assert(sizeof(base::ProcessId) <= sizeof(int32), |
| "Process ID must fit in 32 bits"); |
| |
| return transaction_id | (static_cast<uint64>(pid) << 32); |
| } |
| |
| int64 IndexedDBDispatcherHost::RendererTransactionId( |
| int64 host_transaction_id) { |
| DCHECK(host_transaction_id >> 32 == peer_pid()) |
| << "Invalid renderer target for transaction id"; |
| return host_transaction_id & 0xffffffff; |
| } |
| |
| // static |
| uint32 IndexedDBDispatcherHost::TransactionIdToRendererTransactionId( |
| int64 host_transaction_id) { |
| return host_transaction_id & 0xffffffff; |
| } |
| |
| // static |
| uint32 IndexedDBDispatcherHost::TransactionIdToProcessId( |
| int64 host_transaction_id) { |
| return (host_transaction_id >> 32) & 0xffffffff; |
| } |
| |
| std::string IndexedDBDispatcherHost::HoldBlobData( |
| const IndexedDBBlobInfo& blob_info) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| std::string uuid = blob_info.uuid(); |
| storage::BlobStorageContext* context = blob_storage_context_->context(); |
| scoped_ptr<storage::BlobDataHandle> blob_data_handle; |
| if (uuid.empty()) { |
| uuid = base::GenerateGUID(); |
| storage::BlobDataBuilder blob_data_builder(uuid); |
| blob_data_builder.set_content_type(base::UTF16ToUTF8(blob_info.type())); |
| blob_data_builder.AppendFile(blob_info.file_path(), 0, blob_info.size(), |
| blob_info.last_modified()); |
| blob_data_handle = context->AddFinishedBlob(&blob_data_builder); |
| } else { |
| auto iter = blob_data_handle_map_.find(uuid); |
| if (iter != blob_data_handle_map_.end()) { |
| iter->second.second += 1; |
| return uuid; |
| } |
| blob_data_handle = context->GetBlobDataFromUUID(uuid); |
| } |
| |
| DCHECK(!ContainsKey(blob_data_handle_map_, uuid)); |
| blob_data_handle_map_[uuid] = std::make_pair(blob_data_handle.release(), 1); |
| return uuid; |
| } |
| |
| void IndexedDBDispatcherHost::DropBlobData(const std::string& uuid) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| BlobDataHandleMap::iterator iter = blob_data_handle_map_.find(uuid); |
| if (iter != blob_data_handle_map_.end()) { |
| DCHECK_GE(iter->second.second, 1); |
| if (iter->second.second == 1) { |
| delete iter->second.first; |
| blob_data_handle_map_.erase(iter); |
| } else { |
| iter->second.second -= 1; |
| } |
| } else { |
| DLOG(FATAL) << "Failed to find blob UUID in map:" << uuid; |
| } |
| } |
| |
| IndexedDBCursor* IndexedDBDispatcherHost::GetCursorFromId(int32 ipc_cursor_id) { |
| DCHECK(indexed_db_context_->TaskRunner()->RunsTasksOnCurrentThread()); |
| return cursor_dispatcher_host_->map_.Lookup(ipc_cursor_id); |
| } |
| |
| ::IndexedDBDatabaseMetadata IndexedDBDispatcherHost::ConvertMetadata( |
| const content::IndexedDBDatabaseMetadata& web_metadata) { |
| ::IndexedDBDatabaseMetadata metadata; |
| metadata.id = web_metadata.id; |
| metadata.name = web_metadata.name; |
| metadata.version = web_metadata.version; |
| metadata.int_version = web_metadata.int_version; |
| metadata.max_object_store_id = web_metadata.max_object_store_id; |
| |
| for (const auto& iter : web_metadata.object_stores) { |
| const content::IndexedDBObjectStoreMetadata& web_store_metadata = |
| iter.second; |
| ::IndexedDBObjectStoreMetadata idb_store_metadata; |
| idb_store_metadata.id = web_store_metadata.id; |
| idb_store_metadata.name = web_store_metadata.name; |
| idb_store_metadata.key_path = web_store_metadata.key_path; |
| idb_store_metadata.auto_increment = web_store_metadata.auto_increment; |
| idb_store_metadata.max_index_id = web_store_metadata.max_index_id; |
| |
| for (const auto& index_iter : web_store_metadata.indexes) { |
| const content::IndexedDBIndexMetadata& web_index_metadata = |
| index_iter.second; |
| ::IndexedDBIndexMetadata idb_index_metadata; |
| idb_index_metadata.id = web_index_metadata.id; |
| idb_index_metadata.name = web_index_metadata.name; |
| idb_index_metadata.key_path = web_index_metadata.key_path; |
| idb_index_metadata.unique = web_index_metadata.unique; |
| idb_index_metadata.multi_entry = web_index_metadata.multi_entry; |
| idb_store_metadata.indexes.push_back(idb_index_metadata); |
| } |
| metadata.object_stores.push_back(idb_store_metadata); |
| } |
| return metadata; |
| } |
| |
| void IndexedDBDispatcherHost::OnIDBFactoryGetDatabaseNames( |
| const IndexedDBHostMsg_FactoryGetDatabaseNames_Params& params) { |
| DCHECK(indexed_db_context_->TaskRunner()->RunsTasksOnCurrentThread()); |
| base::FilePath indexed_db_path = indexed_db_context_->data_path(); |
| |
| GURL origin_url = |
| storage::GetOriginFromIdentifier(params.database_identifier); |
| |
| Context()->GetIDBFactory()->GetDatabaseNames( |
| new IndexedDBCallbacks( |
| this, params.ipc_thread_id, params.ipc_callbacks_id), |
| origin_url, |
| indexed_db_path, |
| request_context_); |
| } |
| |
| void IndexedDBDispatcherHost::OnIDBFactoryOpen( |
| const IndexedDBHostMsg_FactoryOpen_Params& params) { |
| DCHECK(indexed_db_context_->TaskRunner()->RunsTasksOnCurrentThread()); |
| base::TimeTicks begin_time = base::TimeTicks::Now(); |
| base::FilePath indexed_db_path = indexed_db_context_->data_path(); |
| |
| GURL origin_url = |
| storage::GetOriginFromIdentifier(params.database_identifier); |
| |
| int64 host_transaction_id = HostTransactionId(params.transaction_id); |
| |
| // TODO(dgrogan): Don't let a non-existing database be opened (and therefore |
| // created) if this origin is already over quota. |
| scoped_refptr<IndexedDBCallbacks> callbacks = |
| new IndexedDBCallbacks(this, |
| params.ipc_thread_id, |
| params.ipc_callbacks_id, |
| params.ipc_database_callbacks_id, |
| host_transaction_id, |
| origin_url); |
| callbacks->SetConnectionOpenStartTime(begin_time); |
| scoped_refptr<IndexedDBDatabaseCallbacks> database_callbacks = |
| new IndexedDBDatabaseCallbacks( |
| this, params.ipc_thread_id, params.ipc_database_callbacks_id); |
| IndexedDBPendingConnection connection(callbacks, |
| database_callbacks, |
| ipc_process_id_, |
| host_transaction_id, |
| params.version); |
| DCHECK(request_context_); |
| Context()->GetIDBFactory()->Open( |
| params.name, connection, request_context_, origin_url, indexed_db_path); |
| } |
| |
| void IndexedDBDispatcherHost::OnIDBFactoryDeleteDatabase( |
| const IndexedDBHostMsg_FactoryDeleteDatabase_Params& params) { |
| DCHECK(indexed_db_context_->TaskRunner()->RunsTasksOnCurrentThread()); |
| GURL origin_url = |
| storage::GetOriginFromIdentifier(params.database_identifier); |
| base::FilePath indexed_db_path = indexed_db_context_->data_path(); |
| DCHECK(request_context_); |
| Context()->GetIDBFactory()->DeleteDatabase( |
| params.name, |
| request_context_, |
| new IndexedDBCallbacks( |
| this, params.ipc_thread_id, params.ipc_callbacks_id), |
| origin_url, |
| indexed_db_path); |
| } |
| |
| // OnPutHelper exists only to allow us to hop threads while holding a reference |
| // to the IndexedDBDispatcherHost. |
| void IndexedDBDispatcherHost::OnPutHelper( |
| const IndexedDBHostMsg_DatabasePut_Params& params, |
| std::vector<storage::BlobDataHandle*> handles) { |
| database_dispatcher_host_->OnPut(params, handles); |
| } |
| |
| void IndexedDBDispatcherHost::OnAckReceivedBlobs( |
| const std::vector<std::string>& uuids) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| for (const auto& uuid : uuids) |
| DropBlobData(uuid); |
| } |
| |
| void IndexedDBDispatcherHost::FinishTransaction(int64 host_transaction_id, |
| bool committed) { |
| DCHECK(indexed_db_context_->TaskRunner()->RunsTasksOnCurrentThread()); |
| if (!database_dispatcher_host_) |
| return; |
| TransactionIDToURLMap& transaction_url_map = |
| database_dispatcher_host_->transaction_url_map_; |
| TransactionIDToSizeMap& transaction_size_map = |
| database_dispatcher_host_->transaction_size_map_; |
| TransactionIDToDatabaseIDMap& transaction_database_map = |
| database_dispatcher_host_->transaction_database_map_; |
| if (committed) |
| Context()->TransactionComplete(transaction_url_map[host_transaction_id]); |
| transaction_url_map.erase(host_transaction_id); |
| transaction_size_map.erase(host_transaction_id); |
| transaction_database_map.erase(host_transaction_id); |
| } |
| |
| ////////////////////////////////////////////////////////////////////// |
| // Helper templates. |
| // |
| |
| template <typename ObjectType> |
| ObjectType* IndexedDBDispatcherHost::GetOrTerminateProcess( |
| IDMap<ObjectType, IDMapOwnPointer>* map, |
| int32 ipc_return_object_id) { |
| DCHECK(indexed_db_context_->TaskRunner()->RunsTasksOnCurrentThread()); |
| ObjectType* return_object = map->Lookup(ipc_return_object_id); |
| if (!return_object) { |
| NOTREACHED() << "Uh oh, couldn't find object with id " |
| << ipc_return_object_id; |
| RecordAction(base::UserMetricsAction("BadMessageTerminate_IDBMF")); |
| BadMessageReceived(); |
| } |
| return return_object; |
| } |
| |
| template <typename ObjectType> |
| ObjectType* IndexedDBDispatcherHost::GetOrTerminateProcess( |
| RefIDMap<ObjectType>* map, |
| int32 ipc_return_object_id) { |
| DCHECK(indexed_db_context_->TaskRunner()->RunsTasksOnCurrentThread()); |
| ObjectType* return_object = map->Lookup(ipc_return_object_id); |
| if (!return_object) { |
| NOTREACHED() << "Uh oh, couldn't find object with id " |
| << ipc_return_object_id; |
| RecordAction(base::UserMetricsAction("BadMessageTerminate_IDBMF")); |
| BadMessageReceived(); |
| } |
| return return_object; |
| } |
| |
| template <typename MapType> |
| void IndexedDBDispatcherHost::DestroyObject(MapType* map, int32 ipc_object_id) { |
| GetOrTerminateProcess(map, ipc_object_id); |
| map->Remove(ipc_object_id); |
| } |
| |
| ////////////////////////////////////////////////////////////////////// |
| // IndexedDBDispatcherHost::DatabaseDispatcherHost |
| // |
| |
| IndexedDBDispatcherHost::DatabaseDispatcherHost::DatabaseDispatcherHost( |
| IndexedDBDispatcherHost* parent) |
| : parent_(parent) { |
| map_.set_check_on_null_data(true); |
| } |
| |
| IndexedDBDispatcherHost::DatabaseDispatcherHost::~DatabaseDispatcherHost() { |
| // TODO(alecflett): uncomment these when we find the source of these leaks. |
| // DCHECK(transaction_size_map_.empty()); |
| // DCHECK(transaction_url_map_.empty()); |
| } |
| |
| void IndexedDBDispatcherHost::DatabaseDispatcherHost::CloseAll() { |
| DCHECK( |
| parent_->indexed_db_context_->TaskRunner()->RunsTasksOnCurrentThread()); |
| // Abort outstanding transactions started by connections in the associated |
| // front-end to unblock later transactions. This should only occur on unclean |
| // (crash) or abrupt (process-kill) shutdowns. |
| for (TransactionIDToDatabaseIDMap::iterator iter = |
| transaction_database_map_.begin(); |
| iter != transaction_database_map_.end();) { |
| int64 transaction_id = iter->first; |
| int32 ipc_database_id = iter->second; |
| ++iter; |
| IndexedDBConnection* connection = map_.Lookup(ipc_database_id); |
| if (connection && connection->IsConnected()) { |
| connection->database()->Abort( |
| transaction_id, |
| IndexedDBDatabaseError(blink::WebIDBDatabaseExceptionUnknownError)); |
| } |
| } |
| DCHECK(transaction_database_map_.empty()); |
| |
| for (const auto& iter : database_url_map_) { |
| IndexedDBConnection* connection = map_.Lookup(iter.first); |
| if (connection && connection->IsConnected()) { |
| connection->Close(); |
| parent_->Context()->ConnectionClosed(iter.second, connection); |
| } |
| } |
| } |
| |
| bool IndexedDBDispatcherHost::DatabaseDispatcherHost::OnMessageReceived( |
| const IPC::Message& message) { |
| DCHECK( |
| (message.type() == IndexedDBHostMsg_DatabasePut::ID || |
| message.type() == IndexedDBHostMsg_AckReceivedBlobs::ID) || |
| parent_->indexed_db_context_->TaskRunner()->RunsTasksOnCurrentThread()); |
| |
| bool handled = true; |
| IPC_BEGIN_MESSAGE_MAP( |
| IndexedDBDispatcherHost::DatabaseDispatcherHost, message) |
| IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabaseCreateObjectStore, |
| OnCreateObjectStore) |
| IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabaseDeleteObjectStore, |
| OnDeleteObjectStore) |
| IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabaseCreateTransaction, |
| OnCreateTransaction) |
| IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabaseClose, OnClose) |
| IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabaseVersionChangeIgnored, |
| OnVersionChangeIgnored) |
| IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabaseDestroyed, OnDestroyed) |
| IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabaseGet, OnGet) |
| IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabasePut, OnPutWrapper) |
| IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabaseSetIndexKeys, OnSetIndexKeys) |
| IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabaseSetIndexesReady, |
| OnSetIndexesReady) |
| IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabaseOpenCursor, OnOpenCursor) |
| IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabaseCount, OnCount) |
| IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabaseDeleteRange, OnDeleteRange) |
| IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabaseClear, OnClear) |
| IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabaseCreateIndex, OnCreateIndex) |
| IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabaseDeleteIndex, OnDeleteIndex) |
| IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabaseAbort, OnAbort) |
| IPC_MESSAGE_HANDLER(IndexedDBHostMsg_DatabaseCommit, OnCommit) |
| IPC_MESSAGE_UNHANDLED(handled = false) |
| IPC_END_MESSAGE_MAP() |
| |
| return handled; |
| } |
| |
| void IndexedDBDispatcherHost::DatabaseDispatcherHost::OnCreateObjectStore( |
| const IndexedDBHostMsg_DatabaseCreateObjectStore_Params& params) { |
| DCHECK( |
| parent_->indexed_db_context_->TaskRunner()->RunsTasksOnCurrentThread()); |
| IndexedDBConnection* connection = |
| parent_->GetOrTerminateProcess(&map_, params.ipc_database_id); |
| if (!connection || !connection->IsConnected()) |
| return; |
| |
| int64 host_transaction_id = parent_->HostTransactionId(params.transaction_id); |
| connection->database()->CreateObjectStore(host_transaction_id, |
| params.object_store_id, |
| params.name, |
| params.key_path, |
| params.auto_increment); |
| if (parent_->Context()->IsOverQuota( |
| database_url_map_[params.ipc_database_id])) { |
| connection->database()->Abort( |
| host_transaction_id, |
| IndexedDBDatabaseError(blink::WebIDBDatabaseExceptionQuotaError)); |
| } |
| } |
| |
| void IndexedDBDispatcherHost::DatabaseDispatcherHost::OnDeleteObjectStore( |
| int32 ipc_database_id, |
| int64 transaction_id, |
| int64 object_store_id) { |
| DCHECK( |
| parent_->indexed_db_context_->TaskRunner()->RunsTasksOnCurrentThread()); |
| IndexedDBConnection* connection = |
| parent_->GetOrTerminateProcess(&map_, ipc_database_id); |
| if (!connection || !connection->IsConnected()) |
| return; |
| |
| connection->database()->DeleteObjectStore( |
| parent_->HostTransactionId(transaction_id), object_store_id); |
| } |
| |
| void IndexedDBDispatcherHost::DatabaseDispatcherHost::OnCreateTransaction( |
| const IndexedDBHostMsg_DatabaseCreateTransaction_Params& params) { |
| DCHECK( |
| parent_->indexed_db_context_->TaskRunner()->RunsTasksOnCurrentThread()); |
| IndexedDBConnection* connection = |
| parent_->GetOrTerminateProcess(&map_, params.ipc_database_id); |
| if (!connection || !connection->IsConnected()) |
| return; |
| |
| int64 host_transaction_id = parent_->HostTransactionId(params.transaction_id); |
| |
| if (transaction_database_map_.find(host_transaction_id) != |
| transaction_database_map_.end()) { |
| DLOG(ERROR) << "Duplicate host_transaction_id."; |
| return; |
| } |
| |
| connection->database()->CreateTransaction( |
| host_transaction_id, connection, params.object_store_ids, params.mode); |
| transaction_database_map_[host_transaction_id] = params.ipc_database_id; |
| parent_->RegisterTransactionId(host_transaction_id, |
| database_url_map_[params.ipc_database_id]); |
| } |
| |
| void IndexedDBDispatcherHost::DatabaseDispatcherHost::OnClose( |
| int32 ipc_database_id) { |
| DCHECK( |
| parent_->indexed_db_context_->TaskRunner()->RunsTasksOnCurrentThread()); |
| IndexedDBConnection* connection = |
| parent_->GetOrTerminateProcess(&map_, ipc_database_id); |
| if (!connection || !connection->IsConnected()) |
| return; |
| connection->Close(); |
| } |
| |
| void IndexedDBDispatcherHost::DatabaseDispatcherHost::OnVersionChangeIgnored( |
| int32 ipc_database_id) { |
| DCHECK( |
| parent_->indexed_db_context_->TaskRunner()->RunsTasksOnCurrentThread()); |
| IndexedDBConnection* connection = |
| parent_->GetOrTerminateProcess(&map_, ipc_database_id); |
| if (!connection || !connection->IsConnected()) |
| return; |
| connection->VersionChangeIgnored(); |
| } |
| |
| void IndexedDBDispatcherHost::DatabaseDispatcherHost::OnDestroyed( |
| int32 ipc_object_id) { |
| DCHECK( |
| parent_->indexed_db_context_->TaskRunner()->RunsTasksOnCurrentThread()); |
| IndexedDBConnection* connection = |
| parent_->GetOrTerminateProcess(&map_, ipc_object_id); |
| if (!connection) |
| return; |
| if (connection->IsConnected()) |
| connection->Close(); |
| parent_->Context() |
| ->ConnectionClosed(database_url_map_[ipc_object_id], connection); |
| database_url_map_.erase(ipc_object_id); |
| parent_->DestroyObject(&map_, ipc_object_id); |
| } |
| |
| void IndexedDBDispatcherHost::DatabaseDispatcherHost::OnGet( |
| const IndexedDBHostMsg_DatabaseGet_Params& params) { |
| DCHECK( |
| parent_->indexed_db_context_->TaskRunner()->RunsTasksOnCurrentThread()); |
| IndexedDBConnection* connection = |
| parent_->GetOrTerminateProcess(&map_, params.ipc_database_id); |
| if (!connection || !connection->IsConnected()) |
| return; |
| |
| scoped_refptr<IndexedDBCallbacks> callbacks(new IndexedDBCallbacks( |
| parent_, params.ipc_thread_id, params.ipc_callbacks_id)); |
| connection->database()->Get( |
| parent_->HostTransactionId(params.transaction_id), |
| params.object_store_id, |
| params.index_id, |
| make_scoped_ptr(new IndexedDBKeyRange(params.key_range)), |
| params.key_only, |
| callbacks); |
| } |
| |
| void IndexedDBDispatcherHost::DatabaseDispatcherHost::OnPutWrapper( |
| const IndexedDBHostMsg_DatabasePut_Params& params) { |
| std::vector<storage::BlobDataHandle*> handles; |
| for (size_t i = 0; i < params.blob_or_file_info.size(); ++i) { |
| const IndexedDBMsg_BlobOrFileInfo& info = params.blob_or_file_info[i]; |
| handles.push_back(parent_->blob_storage_context_->context() |
| ->GetBlobDataFromUUID(info.uuid) |
| .release()); |
| } |
| parent_->indexed_db_context_->TaskRunner()->PostTask( |
| FROM_HERE, |
| base::Bind( |
| &IndexedDBDispatcherHost::OnPutHelper, parent_, params, handles)); |
| } |
| |
| void IndexedDBDispatcherHost::DatabaseDispatcherHost::OnPut( |
| const IndexedDBHostMsg_DatabasePut_Params& params, |
| std::vector<storage::BlobDataHandle*> handles) { |
| DCHECK( |
| parent_->indexed_db_context_->TaskRunner()->RunsTasksOnCurrentThread()); |
| |
| ScopedVector<storage::BlobDataHandle> scoped_handles; |
| scoped_handles.swap(handles); |
| |
| IndexedDBConnection* connection = |
| parent_->GetOrTerminateProcess(&map_, params.ipc_database_id); |
| if (!connection || !connection->IsConnected()) |
| return; |
| scoped_refptr<IndexedDBCallbacks> callbacks(new IndexedDBCallbacks( |
| parent_, params.ipc_thread_id, params.ipc_callbacks_id)); |
| |
| int64 host_transaction_id = parent_->HostTransactionId(params.transaction_id); |
| |
| std::vector<IndexedDBBlobInfo> blob_info(params.blob_or_file_info.size()); |
| |
| ChildProcessSecurityPolicyImpl* policy = |
| ChildProcessSecurityPolicyImpl::GetInstance(); |
| |
| for (size_t i = 0; i < params.blob_or_file_info.size(); ++i) { |
| const IndexedDBMsg_BlobOrFileInfo& info = params.blob_or_file_info[i]; |
| if (info.is_file) { |
| base::FilePath path; |
| if (!info.file_path.empty()) { |
| path = base::FilePath::FromUTF16Unsafe(info.file_path); |
| if (!policy->CanReadFile(parent_->ipc_process_id_, path)) { |
| parent_->BadMessageReceived(); |
| return; |
| } |
| } |
| blob_info[i] = |
| IndexedDBBlobInfo(info.uuid, path, info.file_name, info.mime_type); |
| if (info.size != static_cast<uint64_t>(-1)) { |
| blob_info[i].set_last_modified( |
| base::Time::FromDoubleT(info.last_modified)); |
| blob_info[i].set_size(info.size); |
| } |
| } else { |
| blob_info[i] = IndexedDBBlobInfo(info.uuid, info.mime_type, info.size); |
| } |
| } |
| |
| // TODO(alecflett): Avoid a copy here. |
| IndexedDBValue value; |
| value.bits = params.value; |
| value.blob_info.swap(blob_info); |
| connection->database()->Put(host_transaction_id, |
| params.object_store_id, |
| &value, |
| &scoped_handles, |
| make_scoped_ptr(new IndexedDBKey(params.key)), |
| params.put_mode, |
| callbacks, |
| params.index_keys); |
| TransactionIDToSizeMap* map = |
| &parent_->database_dispatcher_host_->transaction_size_map_; |
| // Size can't be big enough to overflow because it represents the |
| // actual bytes passed through IPC. |
| (*map)[host_transaction_id] += params.value.size(); |
| } |
| |
| void IndexedDBDispatcherHost::DatabaseDispatcherHost::OnSetIndexKeys( |
| const IndexedDBHostMsg_DatabaseSetIndexKeys_Params& params) { |
| DCHECK( |
| parent_->indexed_db_context_->TaskRunner()->RunsTasksOnCurrentThread()); |
| IndexedDBConnection* connection = |
| parent_->GetOrTerminateProcess(&map_, params.ipc_database_id); |
| if (!connection || !connection->IsConnected()) |
| return; |
| |
| int64 host_transaction_id = parent_->HostTransactionId(params.transaction_id); |
| connection->database()->SetIndexKeys( |
| host_transaction_id, |
| params.object_store_id, |
| make_scoped_ptr(new IndexedDBKey(params.primary_key)), |
| params.index_keys); |
| } |
| |
| void IndexedDBDispatcherHost::DatabaseDispatcherHost::OnSetIndexesReady( |
| int32 ipc_database_id, |
| int64 transaction_id, |
| int64 object_store_id, |
| const std::vector<int64>& index_ids) { |
| DCHECK( |
| parent_->indexed_db_context_->TaskRunner()->RunsTasksOnCurrentThread()); |
| IndexedDBConnection* connection = |
| parent_->GetOrTerminateProcess(&map_, ipc_database_id); |
| if (!connection || !connection->IsConnected()) |
| return; |
| |
| connection->database()->SetIndexesReady( |
| parent_->HostTransactionId(transaction_id), object_store_id, index_ids); |
| } |
| |
| void IndexedDBDispatcherHost::DatabaseDispatcherHost::OnOpenCursor( |
| const IndexedDBHostMsg_DatabaseOpenCursor_Params& params) { |
| DCHECK( |
| parent_->indexed_db_context_->TaskRunner()->RunsTasksOnCurrentThread()); |
| IndexedDBConnection* connection = |
| parent_->GetOrTerminateProcess(&map_, params.ipc_database_id); |
| if (!connection || !connection->IsConnected()) |
| return; |
| |
| scoped_refptr<IndexedDBCallbacks> callbacks(new IndexedDBCallbacks( |
| parent_, params.ipc_thread_id, params.ipc_callbacks_id, -1)); |
| connection->database()->OpenCursor( |
| parent_->HostTransactionId(params.transaction_id), |
| params.object_store_id, |
| params.index_id, |
| make_scoped_ptr(new IndexedDBKeyRange(params.key_range)), |
| params.direction, |
| params.key_only, |
| params.task_type, |
| callbacks); |
| } |
| |
| void IndexedDBDispatcherHost::DatabaseDispatcherHost::OnCount( |
| const IndexedDBHostMsg_DatabaseCount_Params& params) { |
| DCHECK( |
| parent_->indexed_db_context_->TaskRunner()->RunsTasksOnCurrentThread()); |
| IndexedDBConnection* connection = |
| parent_->GetOrTerminateProcess(&map_, params.ipc_database_id); |
| if (!connection || !connection->IsConnected()) |
| return; |
| |
| scoped_refptr<IndexedDBCallbacks> callbacks(new IndexedDBCallbacks( |
| parent_, params.ipc_thread_id, params.ipc_callbacks_id)); |
| connection->database()->Count( |
| parent_->HostTransactionId(params.transaction_id), |
| params.object_store_id, |
| params.index_id, |
| make_scoped_ptr(new IndexedDBKeyRange(params.key_range)), |
| callbacks); |
| } |
| |
| void IndexedDBDispatcherHost::DatabaseDispatcherHost::OnDeleteRange( |
| const IndexedDBHostMsg_DatabaseDeleteRange_Params& params) { |
| DCHECK( |
| parent_->indexed_db_context_->TaskRunner()->RunsTasksOnCurrentThread()); |
| IndexedDBConnection* connection = |
| parent_->GetOrTerminateProcess(&map_, params.ipc_database_id); |
| if (!connection || !connection->IsConnected()) |
| return; |
| |
| scoped_refptr<IndexedDBCallbacks> callbacks(new IndexedDBCallbacks( |
| parent_, params.ipc_thread_id, params.ipc_callbacks_id)); |
| connection->database()->DeleteRange( |
| parent_->HostTransactionId(params.transaction_id), |
| params.object_store_id, |
| make_scoped_ptr(new IndexedDBKeyRange(params.key_range)), |
| callbacks); |
| } |
| |
| void IndexedDBDispatcherHost::DatabaseDispatcherHost::OnClear( |
| int32 ipc_thread_id, |
| int32 ipc_callbacks_id, |
| int32 ipc_database_id, |
| int64 transaction_id, |
| int64 object_store_id) { |
| DCHECK( |
| parent_->indexed_db_context_->TaskRunner()->RunsTasksOnCurrentThread()); |
| IndexedDBConnection* connection = |
| parent_->GetOrTerminateProcess(&map_, ipc_database_id); |
| if (!connection || !connection->IsConnected()) |
| return; |
| |
| scoped_refptr<IndexedDBCallbacks> callbacks( |
| new IndexedDBCallbacks(parent_, ipc_thread_id, ipc_callbacks_id)); |
| |
| connection->database()->Clear( |
| parent_->HostTransactionId(transaction_id), object_store_id, callbacks); |
| } |
| |
| void IndexedDBDispatcherHost::DatabaseDispatcherHost::OnAbort( |
| int32 ipc_database_id, |
| int64 transaction_id) { |
| DCHECK( |
| parent_->indexed_db_context_->TaskRunner()->RunsTasksOnCurrentThread()); |
| IndexedDBConnection* connection = |
| parent_->GetOrTerminateProcess(&map_, ipc_database_id); |
| if (!connection || !connection->IsConnected()) |
| return; |
| |
| connection->database()->Abort(parent_->HostTransactionId(transaction_id)); |
| } |
| |
| void IndexedDBDispatcherHost::DatabaseDispatcherHost::OnCommit( |
| int32 ipc_database_id, |
| int64 transaction_id) { |
| DCHECK( |
| parent_->indexed_db_context_->TaskRunner()->RunsTasksOnCurrentThread()); |
| IndexedDBConnection* connection = |
| parent_->GetOrTerminateProcess(&map_, ipc_database_id); |
| if (!connection || !connection->IsConnected()) |
| return; |
| |
| int64 host_transaction_id = parent_->HostTransactionId(transaction_id); |
| int64 transaction_size = transaction_size_map_[host_transaction_id]; |
| if (transaction_size && |
| parent_->Context()->WouldBeOverQuota( |
| transaction_url_map_[host_transaction_id], transaction_size)) { |
| connection->database()->Abort( |
| host_transaction_id, |
| IndexedDBDatabaseError(blink::WebIDBDatabaseExceptionQuotaError)); |
| return; |
| } |
| |
| connection->database()->Commit(host_transaction_id); |
| } |
| |
| void IndexedDBDispatcherHost::DatabaseDispatcherHost::OnCreateIndex( |
| const IndexedDBHostMsg_DatabaseCreateIndex_Params& params) { |
| DCHECK( |
| parent_->indexed_db_context_->TaskRunner()->RunsTasksOnCurrentThread()); |
| IndexedDBConnection* connection = |
| parent_->GetOrTerminateProcess(&map_, params.ipc_database_id); |
| if (!connection || !connection->IsConnected()) |
| return; |
| |
| int64 host_transaction_id = parent_->HostTransactionId(params.transaction_id); |
| connection->database()->CreateIndex(host_transaction_id, |
| params.object_store_id, |
| params.index_id, |
| params.name, |
| params.key_path, |
| params.unique, |
| params.multi_entry); |
| if (parent_->Context()->IsOverQuota( |
| database_url_map_[params.ipc_database_id])) { |
| connection->database()->Abort( |
| host_transaction_id, |
| IndexedDBDatabaseError(blink::WebIDBDatabaseExceptionQuotaError)); |
| } |
| } |
| |
| void IndexedDBDispatcherHost::DatabaseDispatcherHost::OnDeleteIndex( |
| int32 ipc_database_id, |
| int64 transaction_id, |
| int64 object_store_id, |
| int64 index_id) { |
| DCHECK( |
| parent_->indexed_db_context_->TaskRunner()->RunsTasksOnCurrentThread()); |
| IndexedDBConnection* connection = |
| parent_->GetOrTerminateProcess(&map_, ipc_database_id); |
| if (!connection || !connection->IsConnected()) |
| return; |
| |
| connection->database()->DeleteIndex( |
| parent_->HostTransactionId(transaction_id), object_store_id, index_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 handled = true; |
| IPC_BEGIN_MESSAGE_MAP( |
| IndexedDBDispatcherHost::CursorDispatcherHost, message) |
| IPC_MESSAGE_HANDLER(IndexedDBHostMsg_CursorAdvance, OnAdvance) |
| IPC_MESSAGE_HANDLER(IndexedDBHostMsg_CursorContinue, OnContinue) |
| IPC_MESSAGE_HANDLER(IndexedDBHostMsg_CursorPrefetch, OnPrefetch) |
| IPC_MESSAGE_HANDLER(IndexedDBHostMsg_CursorPrefetchReset, OnPrefetchReset) |
| IPC_MESSAGE_HANDLER(IndexedDBHostMsg_CursorDestroyed, OnDestroyed) |
| IPC_MESSAGE_UNHANDLED(handled = false) |
| IPC_END_MESSAGE_MAP() |
| |
| DCHECK( |
| !handled || |
| parent_->indexed_db_context_->TaskRunner()->RunsTasksOnCurrentThread()); |
| |
| return handled; |
| } |
| |
| void IndexedDBDispatcherHost::CursorDispatcherHost::OnAdvance( |
| int32 ipc_cursor_id, |
| int32 ipc_thread_id, |
| int32 ipc_callbacks_id, |
| uint32 count) { |
| DCHECK( |
| parent_->indexed_db_context_->TaskRunner()->RunsTasksOnCurrentThread()); |
| IndexedDBCursor* idb_cursor = |
| parent_->GetOrTerminateProcess(&map_, ipc_cursor_id); |
| if (!idb_cursor) |
| return; |
| |
| idb_cursor->Advance( |
| count, |
| new IndexedDBCallbacks( |
| parent_, ipc_thread_id, ipc_callbacks_id, ipc_cursor_id)); |
| } |
| |
| void IndexedDBDispatcherHost::CursorDispatcherHost::OnContinue( |
| int32 ipc_cursor_id, |
| int32 ipc_thread_id, |
| int32 ipc_callbacks_id, |
| const IndexedDBKey& key, |
| const IndexedDBKey& primary_key) { |
| DCHECK( |
| parent_->indexed_db_context_->TaskRunner()->RunsTasksOnCurrentThread()); |
| IndexedDBCursor* idb_cursor = |
| parent_->GetOrTerminateProcess(&map_, ipc_cursor_id); |
| if (!idb_cursor) |
| return; |
| |
| idb_cursor->Continue( |
| key.IsValid() ? make_scoped_ptr(new IndexedDBKey(key)) |
| : scoped_ptr<IndexedDBKey>(), |
| primary_key.IsValid() ? make_scoped_ptr(new IndexedDBKey(primary_key)) |
| : scoped_ptr<IndexedDBKey>(), |
| new IndexedDBCallbacks( |
| parent_, ipc_thread_id, ipc_callbacks_id, ipc_cursor_id)); |
| } |
| |
| void IndexedDBDispatcherHost::CursorDispatcherHost::OnPrefetch( |
| int32 ipc_cursor_id, |
| int32 ipc_thread_id, |
| int32 ipc_callbacks_id, |
| int n) { |
| DCHECK( |
| parent_->indexed_db_context_->TaskRunner()->RunsTasksOnCurrentThread()); |
| IndexedDBCursor* idb_cursor = |
| parent_->GetOrTerminateProcess(&map_, ipc_cursor_id); |
| if (!idb_cursor) |
| return; |
| |
| idb_cursor->PrefetchContinue( |
| n, |
| new IndexedDBCallbacks( |
| parent_, ipc_thread_id, ipc_callbacks_id, ipc_cursor_id)); |
| } |
| |
| void IndexedDBDispatcherHost::CursorDispatcherHost::OnPrefetchReset( |
| int32 ipc_cursor_id, |
| int used_prefetches, |
| int unused_prefetches) { |
| DCHECK( |
| parent_->indexed_db_context_->TaskRunner()->RunsTasksOnCurrentThread()); |
| IndexedDBCursor* idb_cursor = |
| parent_->GetOrTerminateProcess(&map_, ipc_cursor_id); |
| if (!idb_cursor) |
| return; |
| |
| leveldb::Status s = |
| idb_cursor->PrefetchReset(used_prefetches, unused_prefetches); |
| // TODO(cmumford): Handle this error (crbug.com/363397) |
| if (!s.ok()) |
| DLOG(ERROR) << "Unable to reset prefetch"; |
| } |
| |
| void IndexedDBDispatcherHost::CursorDispatcherHost::OnDestroyed( |
| int32 ipc_object_id) { |
| DCHECK( |
| parent_->indexed_db_context_->TaskRunner()->RunsTasksOnCurrentThread()); |
| parent_->DestroyObject(&map_, ipc_object_id); |
| } |
| |
| } // namespace content |