// 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/renderer/dom_storage/dom_storage_dispatcher.h" #include #include #include "base/strings/string_number_conversions.h" #include "base/synchronization/lock.h" #include "content/common/dom_storage/dom_storage_messages.h" #include "content/common/dom_storage/dom_storage_types.h" #include "content/renderer/dom_storage/dom_storage_cached_area.h" #include "content/renderer/dom_storage/dom_storage_proxy.h" #include "content/renderer/dom_storage/webstoragearea_impl.h" #include "content/renderer/dom_storage/webstoragenamespace_impl.h" #include "content/renderer/render_thread_impl.h" #include "ipc/message_filter.h" #include "third_party/WebKit/public/platform/Platform.h" #include "third_party/WebKit/public/web/WebKit.h" #include "third_party/WebKit/public/web/WebStorageEventDispatcher.h" namespace content { namespace { // MessageThrottlingFilter ------------------------------------------- // Used to limit the number of ipc messages pending completion so we // don't overwhelm the main browser process. When the limit is reached, // a synchronous message is sent to flush all pending messages thru. // We expect to receive an 'ack' for each message sent. This object // observes receipt of the acks on the IPC thread to decrement a counter. class MessageThrottlingFilter : public IPC::MessageFilter { public: explicit MessageThrottlingFilter(RenderThreadImpl* sender) : pending_count_(0), sender_(sender) {} void SendThrottled(IPC::Message* message); void Shutdown() { sender_ = NULL; } private: ~MessageThrottlingFilter() override {} bool OnMessageReceived(const IPC::Message& message) override; int GetPendingCount() { return IncrementPendingCountN(0); } int IncrementPendingCount() { return IncrementPendingCountN(1); } int DecrementPendingCount() { return IncrementPendingCountN(-1); } int IncrementPendingCountN(int increment) { base::AutoLock locker(lock_); pending_count_ += increment; return pending_count_; } base::Lock lock_; int pending_count_; RenderThreadImpl* sender_; }; void MessageThrottlingFilter::SendThrottled(IPC::Message* message) { // Should only be used for sending of messages which will be acknowledged // with a separate DOMStorageMsg_AsyncOperationComplete message. DCHECK(message->type() == DOMStorageHostMsg_LoadStorageArea::ID || message->type() == DOMStorageHostMsg_SetItem::ID || message->type() == DOMStorageHostMsg_RemoveItem::ID || message->type() == DOMStorageHostMsg_Clear::ID); DCHECK(sender_); if (!sender_) { delete message; return; } const int kMaxPendingMessages = 1000; bool need_to_flush = (IncrementPendingCount() > kMaxPendingMessages) && !message->is_sync(); sender_->Send(message); if (need_to_flush) { sender_->Send(new DOMStorageHostMsg_FlushMessages); DCHECK_EQ(0, GetPendingCount()); } else { DCHECK_LE(0, GetPendingCount()); } } bool MessageThrottlingFilter::OnMessageReceived(const IPC::Message& message) { if (message.type() == DOMStorageMsg_AsyncOperationComplete::ID) { DecrementPendingCount(); DCHECK_LE(0, GetPendingCount()); } return false; } } // namespace // ProxyImpl ----------------------------------------------------- // An implementation of the DOMStorageProxy interface in terms of IPC. // This class also manages the collection of cached areas and pending // operations awaiting completion callbacks. class DomStorageDispatcher::ProxyImpl : public DOMStorageProxy { public: explicit ProxyImpl(RenderThreadImpl* sender); // Methods for use by DomStorageDispatcher directly. DOMStorageCachedArea* OpenCachedArea(int64_t namespace_id, const GURL& origin); void CloseCachedArea(DOMStorageCachedArea* area); DOMStorageCachedArea* LookupCachedArea(int64_t namespace_id, const GURL& origin); void CompleteOnePendingCallback(bool success); void Shutdown(); // DOMStorageProxy interface for use by DOMStorageCachedArea. void LoadArea(int connection_id, DOMStorageValuesMap* values, const CompletionCallback& callback) override; void SetItem(int connection_id, const base::string16& key, const base::string16& value, const GURL& page_url, const CompletionCallback& callback) override; void RemoveItem(int connection_id, const base::string16& key, const GURL& page_url, const CompletionCallback& callback) override; void ClearArea(int connection_id, const GURL& page_url, const CompletionCallback& callback) override; private: // Struct to hold references to our contained areas and // to keep track of how many tabs have a given area open. struct CachedAreaHolder { scoped_refptr area_; int open_count_; CachedAreaHolder() : open_count_(0) {} CachedAreaHolder(DOMStorageCachedArea* area, int count) : area_(area), open_count_(count) {} }; typedef std::map CachedAreaMap; typedef std::list CallbackList; ~ProxyImpl() override {} // Sudden termination is disabled when there are callbacks pending // to more reliably commit changes during shutdown. void PushPendingCallback(const CompletionCallback& callback) { if (pending_callbacks_.empty()) blink::Platform::current()->suddenTerminationChanged(false); pending_callbacks_.push_back(callback); } CompletionCallback PopPendingCallback() { CompletionCallback callback = pending_callbacks_.front(); pending_callbacks_.pop_front(); if (pending_callbacks_.empty()) blink::Platform::current()->suddenTerminationChanged(true); return callback; } std::string GetCachedAreaKey(int64_t namespace_id, const GURL& origin) { return base::Int64ToString(namespace_id) + origin.spec(); } CachedAreaHolder* GetAreaHolder(const std::string& key) { CachedAreaMap::iterator found = cached_areas_.find(key); if (found == cached_areas_.end()) return NULL; return &(found->second); } RenderThreadImpl* sender_; CachedAreaMap cached_areas_; CallbackList pending_callbacks_; scoped_refptr throttling_filter_; }; DomStorageDispatcher::ProxyImpl::ProxyImpl(RenderThreadImpl* sender) : sender_(sender), throttling_filter_(new MessageThrottlingFilter(sender)) { sender_->AddFilter(throttling_filter_.get()); } DOMStorageCachedArea* DomStorageDispatcher::ProxyImpl::OpenCachedArea( int64_t namespace_id, const GURL& origin) { std::string key = GetCachedAreaKey(namespace_id, origin); if (CachedAreaHolder* holder = GetAreaHolder(key)) { ++(holder->open_count_); return holder->area_.get(); } scoped_refptr area = new DOMStorageCachedArea(namespace_id, origin, this); cached_areas_[key] = CachedAreaHolder(area.get(), 1); return area.get(); } void DomStorageDispatcher::ProxyImpl::CloseCachedArea( DOMStorageCachedArea* area) { std::string key = GetCachedAreaKey(area->namespace_id(), area->origin()); CachedAreaHolder* holder = GetAreaHolder(key); DCHECK(holder); DCHECK_EQ(holder->area_.get(), area); DCHECK_GT(holder->open_count_, 0); if (--(holder->open_count_) == 0) { cached_areas_.erase(key); } } DOMStorageCachedArea* DomStorageDispatcher::ProxyImpl::LookupCachedArea( int64_t namespace_id, const GURL& origin) { std::string key = GetCachedAreaKey(namespace_id, origin); CachedAreaHolder* holder = GetAreaHolder(key); if (!holder) return NULL; return holder->area_.get(); } void DomStorageDispatcher::ProxyImpl::CompleteOnePendingCallback(bool success) { PopPendingCallback().Run(success); } void DomStorageDispatcher::ProxyImpl::Shutdown() { throttling_filter_->Shutdown(); sender_->RemoveFilter(throttling_filter_.get()); sender_ = NULL; cached_areas_.clear(); pending_callbacks_.clear(); } void DomStorageDispatcher::ProxyImpl::LoadArea( int connection_id, DOMStorageValuesMap* values, const CompletionCallback& callback) { PushPendingCallback(callback); throttling_filter_->SendThrottled(new DOMStorageHostMsg_LoadStorageArea( connection_id, values)); } void DomStorageDispatcher::ProxyImpl::SetItem( int connection_id, const base::string16& key, const base::string16& value, const GURL& page_url, const CompletionCallback& callback) { PushPendingCallback(callback); throttling_filter_->SendThrottled(new DOMStorageHostMsg_SetItem( connection_id, key, value, page_url)); } void DomStorageDispatcher::ProxyImpl::RemoveItem( int connection_id, const base::string16& key, const GURL& page_url, const CompletionCallback& callback) { PushPendingCallback(callback); throttling_filter_->SendThrottled(new DOMStorageHostMsg_RemoveItem( connection_id, key, page_url)); } void DomStorageDispatcher::ProxyImpl::ClearArea(int connection_id, const GURL& page_url, const CompletionCallback& callback) { PushPendingCallback(callback); throttling_filter_->SendThrottled(new DOMStorageHostMsg_Clear( connection_id, page_url)); } // DomStorageDispatcher ------------------------------------------------ DomStorageDispatcher::DomStorageDispatcher() : proxy_(new ProxyImpl(RenderThreadImpl::current())) { } DomStorageDispatcher::~DomStorageDispatcher() { proxy_->Shutdown(); } scoped_refptr DomStorageDispatcher::OpenCachedArea( int connection_id, int64_t namespace_id, const GURL& origin) { RenderThreadImpl::current()->Send( new DOMStorageHostMsg_OpenStorageArea( connection_id, namespace_id, origin)); return proxy_->OpenCachedArea(namespace_id, origin); } void DomStorageDispatcher::CloseCachedArea( int connection_id, DOMStorageCachedArea* area) { RenderThreadImpl::current()->Send( new DOMStorageHostMsg_CloseStorageArea(connection_id)); proxy_->CloseCachedArea(area); } bool DomStorageDispatcher::OnMessageReceived(const IPC::Message& msg) { bool handled = true; IPC_BEGIN_MESSAGE_MAP(DomStorageDispatcher, msg) IPC_MESSAGE_HANDLER(DOMStorageMsg_Event, OnStorageEvent) IPC_MESSAGE_HANDLER(DOMStorageMsg_AsyncOperationComplete, OnAsyncOperationComplete) IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP() return handled; } void DomStorageDispatcher::OnStorageEvent( const DOMStorageMsg_Event_Params& params) { RenderThreadImpl::current()->EnsureWebKitInitialized(); WebStorageAreaImpl* originating_area = NULL; if (params.connection_id) { originating_area = WebStorageAreaImpl::FromConnectionId( params.connection_id); } else { DOMStorageCachedArea* cached_area = proxy_->LookupCachedArea( params.namespace_id, params.origin); if (cached_area) cached_area->ApplyMutation(params.key, params.new_value); } if (params.namespace_id == kLocalStorageNamespaceId) { blink::WebStorageEventDispatcher::dispatchLocalStorageEvent( params.key, params.old_value, params.new_value, params.origin, params.page_url, originating_area); } else { WebStorageNamespaceImpl session_namespace_for_event_dispatch(params.namespace_id); blink::WebStorageEventDispatcher::dispatchSessionStorageEvent( params.key, params.old_value, params.new_value, params.origin, params.page_url, session_namespace_for_event_dispatch, originating_area); } } void DomStorageDispatcher::OnAsyncOperationComplete(bool success) { proxy_->CompleteOnePendingCallback(success); } } // namespace content