blob: 6764a9bc0177705f2e6f2d3fe09c4c695ad0d309 [file] [log] [blame]
// 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 <list>
#include <map>
#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:
virtual ~MessageThrottlingFilter() {}
virtual 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 namespace_id, const GURL& origin);
void CloseCachedArea(DOMStorageCachedArea* area);
DOMStorageCachedArea* LookupCachedArea(
int64 namespace_id, const GURL& origin);
void ResetAllCachedAreas(int64 namespace_id);
void CompleteOnePendingCallback(bool success);
void Shutdown();
// DOMStorageProxy interface for use by DOMStorageCachedArea.
virtual void LoadArea(int connection_id, DOMStorageValuesMap* values,
bool* send_log_get_messages,
const CompletionCallback& callback) OVERRIDE;
virtual void SetItem(int connection_id, const base::string16& key,
const base::string16& value, const GURL& page_url,
const CompletionCallback& callback) OVERRIDE;
virtual void LogGetItem(int connection_id, const base::string16& key,
const base::NullableString16& value) OVERRIDE;
virtual void RemoveItem(int connection_id, const base::string16& key,
const GURL& page_url,
const CompletionCallback& callback) OVERRIDE;
virtual 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<DOMStorageCachedArea> area_;
int open_count_;
int64 namespace_id_;
CachedAreaHolder() : open_count_(0) {}
CachedAreaHolder(DOMStorageCachedArea* area, int count,
int64 namespace_id)
: area_(area), open_count_(count), namespace_id_(namespace_id) {}
};
typedef std::map<std::string, CachedAreaHolder> CachedAreaMap;
typedef std::list<CompletionCallback> CallbackList;
virtual ~ProxyImpl() {
}
// 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 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<MessageThrottlingFilter> throttling_filter_;
};
DomStorageDispatcher::ProxyImpl::ProxyImpl(RenderThreadImpl* sender)
: sender_(sender),
throttling_filter_(new MessageThrottlingFilter(sender)) {
sender_->AddFilter(throttling_filter_.get());
}
DOMStorageCachedArea* DomStorageDispatcher::ProxyImpl::OpenCachedArea(
int64 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<DOMStorageCachedArea> area =
new DOMStorageCachedArea(namespace_id, origin, this);
cached_areas_[key] = CachedAreaHolder(area.get(), 1, namespace_id);
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 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::ResetAllCachedAreas(int64 namespace_id) {
for (CachedAreaMap::iterator it = cached_areas_.begin();
it != cached_areas_.end();
++it) {
if (it->second.namespace_id_ == namespace_id)
it->second.area_->Reset();
}
}
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, bool* send_log_get_messages,
const CompletionCallback& callback) {
PushPendingCallback(callback);
throttling_filter_->SendThrottled(new DOMStorageHostMsg_LoadStorageArea(
connection_id, values, send_log_get_messages));
}
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::LogGetItem(
int connection_id, const base::string16& key,
const base::NullableString16& value) {
sender_->Send(new DOMStorageHostMsg_LogGetItem(connection_id, key, value));
}
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<DOMStorageCachedArea> DomStorageDispatcher::OpenCachedArea(
int connection_id, int64 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_HANDLER(DOMStorageMsg_ResetCachedValues,
OnResetCachedValues)
IPC_MESSAGE_UNHANDLED(handled = false)
IPC_END_MESSAGE_MAP()
return handled;
}
void DomStorageDispatcher::OnStorageEvent(
const DOMStorageMsg_Event_Params& params) {
RenderThreadImpl::current()->EnsureWebKitInitialized();
bool originated_in_process = params.connection_id != 0;
WebStorageAreaImpl* originating_area = NULL;
if (originated_in_process) {
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,
originated_in_process);
} 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,
originated_in_process);
}
}
void DomStorageDispatcher::OnAsyncOperationComplete(bool success) {
proxy_->CompleteOnePendingCallback(success);
}
void DomStorageDispatcher::OnResetCachedValues(int64 namespace_id) {
proxy_->ResetAllCachedAreas(namespace_id);
}
} // namespace content