1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "content/renderer/dom_storage/dom_storage_dispatcher.h"
10 #include "base/strings/string_number_conversions.h"
11 #include "base/synchronization/lock.h"
12 #include "content/common/dom_storage/dom_storage_messages.h"
13 #include "content/common/dom_storage/dom_storage_types.h"
14 #include "content/renderer/dom_storage/dom_storage_cached_area.h"
15 #include "content/renderer/dom_storage/dom_storage_proxy.h"
16 #include "content/renderer/dom_storage/webstoragearea_impl.h"
17 #include "content/renderer/dom_storage/webstoragenamespace_impl.h"
18 #include "content/renderer/render_thread_impl.h"
19 #include "third_party/WebKit/public/platform/Platform.h"
20 #include "third_party/WebKit/public/web/WebKit.h"
21 #include "third_party/WebKit/public/web/WebStorageEventDispatcher.h"
26 // MessageThrottlingFilter -------------------------------------------
27 // Used to limit the number of ipc messages pending completion so we
28 // don't overwhelm the main browser process. When the limit is reached,
29 // a synchronous message is sent to flush all pending messages thru.
30 // We expect to receive an 'ack' for each message sent. This object
31 // observes receipt of the acks on the IPC thread to decrement a counter.
32 class MessageThrottlingFilter
: public IPC::ChannelProxy::MessageFilter
{
34 explicit MessageThrottlingFilter(RenderThreadImpl
* sender
)
35 : pending_count_(0), sender_(sender
) {}
37 void SendThrottled(IPC::Message
* message
);
38 void Shutdown() { sender_
= NULL
; }
41 virtual ~MessageThrottlingFilter() {}
43 virtual bool OnMessageReceived(const IPC::Message
& message
) OVERRIDE
;
45 int GetPendingCount() { return IncrementPendingCountN(0); }
46 int IncrementPendingCount() { return IncrementPendingCountN(1); }
47 int DecrementPendingCount() { return IncrementPendingCountN(-1); }
48 int IncrementPendingCountN(int increment
) {
49 base::AutoLock
locker(lock_
);
50 pending_count_
+= increment
;
51 return pending_count_
;
56 RenderThreadImpl
* sender_
;
59 void MessageThrottlingFilter::SendThrottled(IPC::Message
* message
) {
60 // Should only be used for sending of messages which will be acknowledged
61 // with a separate DOMStorageMsg_AsyncOperationComplete message.
62 DCHECK(message
->type() == DOMStorageHostMsg_LoadStorageArea::ID
||
63 message
->type() == DOMStorageHostMsg_SetItem::ID
||
64 message
->type() == DOMStorageHostMsg_RemoveItem::ID
||
65 message
->type() == DOMStorageHostMsg_Clear::ID
);
71 const int kMaxPendingMessages
= 1000;
72 bool need_to_flush
= (IncrementPendingCount() > kMaxPendingMessages
) &&
74 sender_
->Send(message
);
76 sender_
->Send(new DOMStorageHostMsg_FlushMessages
);
77 DCHECK_EQ(0, GetPendingCount());
79 DCHECK_LE(0, GetPendingCount());
83 bool MessageThrottlingFilter::OnMessageReceived(const IPC::Message
& message
) {
84 if (message
.type() == DOMStorageMsg_AsyncOperationComplete::ID
) {
85 DecrementPendingCount();
86 DCHECK_LE(0, GetPendingCount());
92 // ProxyImpl -----------------------------------------------------
93 // An implementation of the DOMStorageProxy interface in terms of IPC.
94 // This class also manages the collection of cached areas and pending
95 // operations awaiting completion callbacks.
96 class DomStorageDispatcher::ProxyImpl
: public DOMStorageProxy
{
98 explicit ProxyImpl(RenderThreadImpl
* sender
);
100 // Methods for use by DomStorageDispatcher directly.
101 DOMStorageCachedArea
* OpenCachedArea(
102 int64 namespace_id
, const GURL
& origin
);
103 void CloseCachedArea(DOMStorageCachedArea
* area
);
104 DOMStorageCachedArea
* LookupCachedArea(
105 int64 namespace_id
, const GURL
& origin
);
106 void ResetAllCachedAreas(int64 namespace_id
);
107 void CompleteOnePendingCallback(bool success
);
110 // DOMStorageProxy interface for use by DOMStorageCachedArea.
111 virtual void LoadArea(int connection_id
, DOMStorageValuesMap
* values
,
112 bool* send_log_get_messages
,
113 const CompletionCallback
& callback
) OVERRIDE
;
114 virtual void SetItem(int connection_id
, const base::string16
& key
,
115 const base::string16
& value
, const GURL
& page_url
,
116 const CompletionCallback
& callback
) OVERRIDE
;
117 virtual void LogGetItem(int connection_id
, const base::string16
& key
,
118 const base::NullableString16
& value
) OVERRIDE
;
119 virtual void RemoveItem(int connection_id
, const base::string16
& key
,
120 const GURL
& page_url
,
121 const CompletionCallback
& callback
) OVERRIDE
;
122 virtual void ClearArea(int connection_id
,
123 const GURL
& page_url
,
124 const CompletionCallback
& callback
) OVERRIDE
;
127 // Struct to hold references to our contained areas and
128 // to keep track of how many tabs have a given area open.
129 struct CachedAreaHolder
{
130 scoped_refptr
<DOMStorageCachedArea
> area_
;
133 CachedAreaHolder() : open_count_(0) {}
134 CachedAreaHolder(DOMStorageCachedArea
* area
, int count
,
136 : area_(area
), open_count_(count
), namespace_id_(namespace_id
) {}
138 typedef std::map
<std::string
, CachedAreaHolder
> CachedAreaMap
;
139 typedef std::list
<CompletionCallback
> CallbackList
;
141 virtual ~ProxyImpl() {
144 // Sudden termination is disabled when there are callbacks pending
145 // to more reliably commit changes during shutdown.
146 void PushPendingCallback(const CompletionCallback
& callback
) {
147 if (pending_callbacks_
.empty())
148 blink::Platform::current()->suddenTerminationChanged(false);
149 pending_callbacks_
.push_back(callback
);
152 CompletionCallback
PopPendingCallback() {
153 CompletionCallback callback
= pending_callbacks_
.front();
154 pending_callbacks_
.pop_front();
155 if (pending_callbacks_
.empty())
156 blink::Platform::current()->suddenTerminationChanged(true);
160 std::string
GetCachedAreaKey(int64 namespace_id
, const GURL
& origin
) {
161 return base::Int64ToString(namespace_id
) + origin
.spec();
164 CachedAreaHolder
* GetAreaHolder(const std::string
& key
) {
165 CachedAreaMap::iterator found
= cached_areas_
.find(key
);
166 if (found
== cached_areas_
.end())
168 return &(found
->second
);
171 RenderThreadImpl
* sender_
;
172 CachedAreaMap cached_areas_
;
173 CallbackList pending_callbacks_
;
174 scoped_refptr
<MessageThrottlingFilter
> throttling_filter_
;
177 DomStorageDispatcher::ProxyImpl::ProxyImpl(RenderThreadImpl
* sender
)
179 throttling_filter_(new MessageThrottlingFilter(sender
)) {
180 sender_
->AddFilter(throttling_filter_
.get());
183 DOMStorageCachedArea
* DomStorageDispatcher::ProxyImpl::OpenCachedArea(
184 int64 namespace_id
, const GURL
& origin
) {
185 std::string key
= GetCachedAreaKey(namespace_id
, origin
);
186 if (CachedAreaHolder
* holder
= GetAreaHolder(key
)) {
187 ++(holder
->open_count_
);
188 return holder
->area_
.get();
190 scoped_refptr
<DOMStorageCachedArea
> area
=
191 new DOMStorageCachedArea(namespace_id
, origin
, this);
192 cached_areas_
[key
] = CachedAreaHolder(area
.get(), 1, namespace_id
);
196 void DomStorageDispatcher::ProxyImpl::CloseCachedArea(
197 DOMStorageCachedArea
* area
) {
198 std::string key
= GetCachedAreaKey(area
->namespace_id(), area
->origin());
199 CachedAreaHolder
* holder
= GetAreaHolder(key
);
201 DCHECK_EQ(holder
->area_
.get(), area
);
202 DCHECK_GT(holder
->open_count_
, 0);
203 if (--(holder
->open_count_
) == 0) {
204 cached_areas_
.erase(key
);
208 DOMStorageCachedArea
* DomStorageDispatcher::ProxyImpl::LookupCachedArea(
209 int64 namespace_id
, const GURL
& origin
) {
210 std::string key
= GetCachedAreaKey(namespace_id
, origin
);
211 CachedAreaHolder
* holder
= GetAreaHolder(key
);
214 return holder
->area_
.get();
217 void DomStorageDispatcher::ProxyImpl::ResetAllCachedAreas(int64 namespace_id
) {
218 for (CachedAreaMap::iterator it
= cached_areas_
.begin();
219 it
!= cached_areas_
.end();
221 if (it
->second
.namespace_id_
== namespace_id
)
222 it
->second
.area_
->Reset();
226 void DomStorageDispatcher::ProxyImpl::CompleteOnePendingCallback(bool success
) {
227 PopPendingCallback().Run(success
);
230 void DomStorageDispatcher::ProxyImpl::Shutdown() {
231 throttling_filter_
->Shutdown();
232 sender_
->RemoveFilter(throttling_filter_
.get());
234 cached_areas_
.clear();
235 pending_callbacks_
.clear();
238 void DomStorageDispatcher::ProxyImpl::LoadArea(
239 int connection_id
, DOMStorageValuesMap
* values
, bool* send_log_get_messages
,
240 const CompletionCallback
& callback
) {
241 PushPendingCallback(callback
);
242 throttling_filter_
->SendThrottled(new DOMStorageHostMsg_LoadStorageArea(
243 connection_id
, values
, send_log_get_messages
));
246 void DomStorageDispatcher::ProxyImpl::SetItem(
247 int connection_id
, const base::string16
& key
,
248 const base::string16
& value
, const GURL
& page_url
,
249 const CompletionCallback
& callback
) {
250 PushPendingCallback(callback
);
251 throttling_filter_
->SendThrottled(new DOMStorageHostMsg_SetItem(
252 connection_id
, key
, value
, page_url
));
255 void DomStorageDispatcher::ProxyImpl::LogGetItem(
256 int connection_id
, const base::string16
& key
,
257 const base::NullableString16
& value
) {
258 sender_
->Send(new DOMStorageHostMsg_LogGetItem(connection_id
, key
, value
));
261 void DomStorageDispatcher::ProxyImpl::RemoveItem(
262 int connection_id
, const base::string16
& key
, const GURL
& page_url
,
263 const CompletionCallback
& callback
) {
264 PushPendingCallback(callback
);
265 throttling_filter_
->SendThrottled(new DOMStorageHostMsg_RemoveItem(
266 connection_id
, key
, page_url
));
269 void DomStorageDispatcher::ProxyImpl::ClearArea(int connection_id
,
270 const GURL
& page_url
,
271 const CompletionCallback
& callback
) {
272 PushPendingCallback(callback
);
273 throttling_filter_
->SendThrottled(new DOMStorageHostMsg_Clear(
274 connection_id
, page_url
));
277 // DomStorageDispatcher ------------------------------------------------
279 DomStorageDispatcher::DomStorageDispatcher()
280 : proxy_(new ProxyImpl(RenderThreadImpl::current())) {
283 DomStorageDispatcher::~DomStorageDispatcher() {
287 scoped_refptr
<DOMStorageCachedArea
> DomStorageDispatcher::OpenCachedArea(
288 int connection_id
, int64 namespace_id
, const GURL
& origin
) {
289 RenderThreadImpl::current()->Send(
290 new DOMStorageHostMsg_OpenStorageArea(
291 connection_id
, namespace_id
, origin
));
292 return proxy_
->OpenCachedArea(namespace_id
, origin
);
295 void DomStorageDispatcher::CloseCachedArea(
296 int connection_id
, DOMStorageCachedArea
* area
) {
297 RenderThreadImpl::current()->Send(
298 new DOMStorageHostMsg_CloseStorageArea(connection_id
));
299 proxy_
->CloseCachedArea(area
);
302 bool DomStorageDispatcher::OnMessageReceived(const IPC::Message
& msg
) {
304 IPC_BEGIN_MESSAGE_MAP(DomStorageDispatcher
, msg
)
305 IPC_MESSAGE_HANDLER(DOMStorageMsg_Event
, OnStorageEvent
)
306 IPC_MESSAGE_HANDLER(DOMStorageMsg_AsyncOperationComplete
,
307 OnAsyncOperationComplete
)
308 IPC_MESSAGE_HANDLER(DOMStorageMsg_ResetCachedValues
,
310 IPC_MESSAGE_UNHANDLED(handled
= false)
311 IPC_END_MESSAGE_MAP()
315 void DomStorageDispatcher::OnStorageEvent(
316 const DOMStorageMsg_Event_Params
& params
) {
317 RenderThreadImpl::current()->EnsureWebKitInitialized();
319 bool originated_in_process
= params
.connection_id
!= 0;
320 WebStorageAreaImpl
* originating_area
= NULL
;
321 if (originated_in_process
) {
322 originating_area
= WebStorageAreaImpl::FromConnectionId(
323 params
.connection_id
);
325 DOMStorageCachedArea
* cached_area
= proxy_
->LookupCachedArea(
326 params
.namespace_id
, params
.origin
);
328 cached_area
->ApplyMutation(params
.key
, params
.new_value
);
331 if (params
.namespace_id
== kLocalStorageNamespaceId
) {
332 blink::WebStorageEventDispatcher::dispatchLocalStorageEvent(
339 originated_in_process
);
341 WebStorageNamespaceImpl
342 session_namespace_for_event_dispatch(params
.namespace_id
);
343 blink::WebStorageEventDispatcher::dispatchSessionStorageEvent(
349 session_namespace_for_event_dispatch
,
351 originated_in_process
);
355 void DomStorageDispatcher::OnAsyncOperationComplete(bool success
) {
356 proxy_
->CompleteOnePendingCallback(success
);
359 void DomStorageDispatcher::OnResetCachedValues(int64 namespace_id
) {
360 proxy_
->ResetAllCachedAreas(namespace_id
);
363 } // namespace content