blob: 001de4fb870fbd7aad0f40ec80dc54597ab8bb53 [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_ = nullptr; }
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(const std::string& namespace_id,
const GURL& origin);
void CloseCachedArea(DOMStorageCachedArea* area);
DOMStorageCachedArea* LookupCachedArea(const std::string& namespace_id,
const GURL& origin);
void CompleteOnePendingCallback(bool success);
void Shutdown();
// DOMStorageProxy interface for use by DOMStorageCachedArea.
void LoadArea(int connection_id,
DOMStorageValuesMap* values,
CompletionCallback callback) override;
void SetItem(int connection_id,
const base::string16& key,
const base::string16& value,
const base::NullableString16& old_value,
const GURL& page_url,
CompletionCallback callback) override;
void RemoveItem(int connection_id,
const base::string16& key,
const base::NullableString16& old_value,
const GURL& page_url,
CompletionCallback callback) override;
void ClearArea(int connection_id,
const GURL& page_url,
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_;
CachedAreaHolder() : open_count_(0) {}
CachedAreaHolder(DOMStorageCachedArea* area, int count)
: area_(area), open_count_(count) {}
};
typedef std::map<std::string, CachedAreaHolder> CachedAreaMap;
typedef std::list<CompletionCallback> CallbackList;
~ProxyImpl() override {}
void PushPendingCallback(CompletionCallback callback) {
// Terminate the renderer if an excessive number of calls are made,
// This is indicative of script in an infinite loop or being malicious.
// It's better to crash intentionally than by running the system OOM
// and interfering with everything else running in the system.
const int kMaxPendingCompletionCallbacks = 1000000;
if (pending_callbacks_.size() > kMaxPendingCompletionCallbacks)
CHECK(false) << "Too many pending DOMStorage calls.";
// Sudden termination is disabled when there are callbacks pending
// to more reliably commit changes during shutdown.
if (pending_callbacks_.empty())
blink::Platform::Current()->SuddenTerminationChanged(false);
pending_callbacks_.push_back(std::move(callback));
}
CompletionCallback PopPendingCallback() {
CompletionCallback callback = std::move(pending_callbacks_.front());
pending_callbacks_.pop_front();
if (pending_callbacks_.empty())
blink::Platform::Current()->SuddenTerminationChanged(true);
return callback;
}
std::string GetCachedAreaKey(const std::string& namespace_id,
const GURL& origin) {
return namespace_id + origin.spec();
}
CachedAreaHolder* GetAreaHolder(const std::string& key) {
CachedAreaMap::iterator found = cached_areas_.find(key);
if (found == cached_areas_.end())
return nullptr;
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(
const std::string& 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,
content::RenderThreadImpl::current()->GetRendererScheduler());
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(
const std::string& namespace_id,
const GURL& origin) {
std::string key = GetCachedAreaKey(namespace_id, origin);
CachedAreaHolder* holder = GetAreaHolder(key);
if (!holder)
return nullptr;
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_ = nullptr;
cached_areas_.clear();
pending_callbacks_.clear();
}
void DomStorageDispatcher::ProxyImpl::LoadArea(int connection_id,
DOMStorageValuesMap* values,
CompletionCallback callback) {
PushPendingCallback(std::move(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 base::NullableString16& old_value,
const GURL& page_url,
CompletionCallback callback) {
PushPendingCallback(std::move(callback));
throttling_filter_->SendThrottled(new DOMStorageHostMsg_SetItem(
connection_id, key, value, old_value, page_url));
}
void DomStorageDispatcher::ProxyImpl::RemoveItem(
int connection_id,
const base::string16& key,
const base::NullableString16& old_value,
const GURL& page_url,
CompletionCallback callback) {
PushPendingCallback(std::move(callback));
throttling_filter_->SendThrottled(new DOMStorageHostMsg_RemoveItem(
connection_id, key, old_value, page_url));
}
void DomStorageDispatcher::ProxyImpl::ClearArea(int connection_id,
const GURL& page_url,
CompletionCallback callback) {
PushPendingCallback(std::move(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,
const std::string& 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) {
WebStorageAreaImpl* originating_area = nullptr;
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);
}
DCHECK(!params.namespace_id.empty());
WebStorageNamespaceImpl session_namespace_for_event_dispatch(
params.namespace_id);
blink::WebStorageEventDispatcher::DispatchSessionStorageEvent(
blink::WebString::FromUTF16(params.key),
blink::WebString::FromUTF16(params.old_value),
blink::WebString::FromUTF16(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