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 "ipc/message_filter.h"
20 #include "third_party/WebKit/public/platform/Platform.h"
21 #include "third_party/WebKit/public/web/WebKit.h"
22 #include "third_party/WebKit/public/web/WebStorageEventDispatcher.h"
27 // MessageThrottlingFilter -------------------------------------------
28 // Used to limit the number of ipc messages pending completion so we
29 // don't overwhelm the main browser process. When the limit is reached,
30 // a synchronous message is sent to flush all pending messages thru.
31 // We expect to receive an 'ack' for each message sent. This object
32 // observes receipt of the acks on the IPC thread to decrement a counter.
33 class MessageThrottlingFilter
: public IPC::MessageFilter
{
35 explicit MessageThrottlingFilter(RenderThreadImpl
* sender
)
36 : pending_count_(0), sender_(sender
) {}
38 void SendThrottled(IPC::Message
* message
);
39 void Shutdown() { sender_
= NULL
; }
42 ~MessageThrottlingFilter() override
{}
44 bool OnMessageReceived(const IPC::Message
& message
) override
;
46 int GetPendingCount() { return IncrementPendingCountN(0); }
47 int IncrementPendingCount() { return IncrementPendingCountN(1); }
48 int DecrementPendingCount() { return IncrementPendingCountN(-1); }
49 int IncrementPendingCountN(int increment
) {
50 base::AutoLock
locker(lock_
);
51 pending_count_
+= increment
;
52 return pending_count_
;
57 RenderThreadImpl
* sender_
;
60 void MessageThrottlingFilter::SendThrottled(IPC::Message
* message
) {
61 // Should only be used for sending of messages which will be acknowledged
62 // with a separate DOMStorageMsg_AsyncOperationComplete message.
63 DCHECK(message
->type() == DOMStorageHostMsg_LoadStorageArea::ID
||
64 message
->type() == DOMStorageHostMsg_SetItem::ID
||
65 message
->type() == DOMStorageHostMsg_RemoveItem::ID
||
66 message
->type() == DOMStorageHostMsg_Clear::ID
);
72 const int kMaxPendingMessages
= 1000;
73 bool need_to_flush
= (IncrementPendingCount() > kMaxPendingMessages
) &&
75 sender_
->Send(message
);
77 sender_
->Send(new DOMStorageHostMsg_FlushMessages
);
78 DCHECK_EQ(0, GetPendingCount());
80 DCHECK_LE(0, GetPendingCount());
84 bool MessageThrottlingFilter::OnMessageReceived(const IPC::Message
& message
) {
85 if (message
.type() == DOMStorageMsg_AsyncOperationComplete::ID
) {
86 DecrementPendingCount();
87 DCHECK_LE(0, GetPendingCount());
93 // ProxyImpl -----------------------------------------------------
94 // An implementation of the DOMStorageProxy interface in terms of IPC.
95 // This class also manages the collection of cached areas and pending
96 // operations awaiting completion callbacks.
97 class DomStorageDispatcher::ProxyImpl
: public DOMStorageProxy
{
99 explicit ProxyImpl(RenderThreadImpl
* sender
);
101 // Methods for use by DomStorageDispatcher directly.
102 DOMStorageCachedArea
* OpenCachedArea(
103 int64 namespace_id
, const GURL
& origin
);
104 void CloseCachedArea(DOMStorageCachedArea
* area
);
105 DOMStorageCachedArea
* LookupCachedArea(
106 int64 namespace_id
, const GURL
& origin
);
107 void CompleteOnePendingCallback(bool success
);
110 // DOMStorageProxy interface for use by DOMStorageCachedArea.
111 void LoadArea(int connection_id
,
112 DOMStorageValuesMap
* values
,
113 const CompletionCallback
& callback
) override
;
114 void SetItem(int connection_id
,
115 const base::string16
& key
,
116 const base::string16
& value
,
117 const GURL
& page_url
,
118 const CompletionCallback
& callback
) override
;
119 void RemoveItem(int connection_id
,
120 const base::string16
& key
,
121 const GURL
& page_url
,
122 const CompletionCallback
& callback
) override
;
123 void ClearArea(int connection_id
,
124 const GURL
& page_url
,
125 const CompletionCallback
& callback
) override
;
128 // Struct to hold references to our contained areas and
129 // to keep track of how many tabs have a given area open.
130 struct CachedAreaHolder
{
131 scoped_refptr
<DOMStorageCachedArea
> area_
;
133 CachedAreaHolder() : open_count_(0) {}
134 CachedAreaHolder(DOMStorageCachedArea
* area
, int count
)
135 : area_(area
), open_count_(count
) {}
137 typedef std::map
<std::string
, CachedAreaHolder
> CachedAreaMap
;
138 typedef std::list
<CompletionCallback
> CallbackList
;
140 ~ProxyImpl() override
{}
142 // Sudden termination is disabled when there are callbacks pending
143 // to more reliably commit changes during shutdown.
144 void PushPendingCallback(const CompletionCallback
& callback
) {
145 if (pending_callbacks_
.empty())
146 blink::Platform::current()->suddenTerminationChanged(false);
147 pending_callbacks_
.push_back(callback
);
150 CompletionCallback
PopPendingCallback() {
151 CompletionCallback callback
= pending_callbacks_
.front();
152 pending_callbacks_
.pop_front();
153 if (pending_callbacks_
.empty())
154 blink::Platform::current()->suddenTerminationChanged(true);
158 std::string
GetCachedAreaKey(int64 namespace_id
, const GURL
& origin
) {
159 return base::Int64ToString(namespace_id
) + origin
.spec();
162 CachedAreaHolder
* GetAreaHolder(const std::string
& key
) {
163 CachedAreaMap::iterator found
= cached_areas_
.find(key
);
164 if (found
== cached_areas_
.end())
166 return &(found
->second
);
169 RenderThreadImpl
* sender_
;
170 CachedAreaMap cached_areas_
;
171 CallbackList pending_callbacks_
;
172 scoped_refptr
<MessageThrottlingFilter
> throttling_filter_
;
175 DomStorageDispatcher::ProxyImpl::ProxyImpl(RenderThreadImpl
* sender
)
177 throttling_filter_(new MessageThrottlingFilter(sender
)) {
178 sender_
->AddFilter(throttling_filter_
.get());
181 DOMStorageCachedArea
* DomStorageDispatcher::ProxyImpl::OpenCachedArea(
182 int64 namespace_id
, const GURL
& origin
) {
183 std::string key
= GetCachedAreaKey(namespace_id
, origin
);
184 if (CachedAreaHolder
* holder
= GetAreaHolder(key
)) {
185 ++(holder
->open_count_
);
186 return holder
->area_
.get();
188 scoped_refptr
<DOMStorageCachedArea
> area
=
189 new DOMStorageCachedArea(namespace_id
, origin
, this);
190 cached_areas_
[key
] = CachedAreaHolder(area
.get(), 1);
194 void DomStorageDispatcher::ProxyImpl::CloseCachedArea(
195 DOMStorageCachedArea
* area
) {
196 std::string key
= GetCachedAreaKey(area
->namespace_id(), area
->origin());
197 CachedAreaHolder
* holder
= GetAreaHolder(key
);
199 DCHECK_EQ(holder
->area_
.get(), area
);
200 DCHECK_GT(holder
->open_count_
, 0);
201 if (--(holder
->open_count_
) == 0) {
202 cached_areas_
.erase(key
);
206 DOMStorageCachedArea
* DomStorageDispatcher::ProxyImpl::LookupCachedArea(
207 int64 namespace_id
, const GURL
& origin
) {
208 std::string key
= GetCachedAreaKey(namespace_id
, origin
);
209 CachedAreaHolder
* holder
= GetAreaHolder(key
);
212 return holder
->area_
.get();
215 void DomStorageDispatcher::ProxyImpl::CompleteOnePendingCallback(bool success
) {
216 PopPendingCallback().Run(success
);
219 void DomStorageDispatcher::ProxyImpl::Shutdown() {
220 throttling_filter_
->Shutdown();
221 sender_
->RemoveFilter(throttling_filter_
.get());
223 cached_areas_
.clear();
224 pending_callbacks_
.clear();
227 void DomStorageDispatcher::ProxyImpl::LoadArea(
228 int connection_id
, DOMStorageValuesMap
* values
,
229 const CompletionCallback
& callback
) {
230 PushPendingCallback(callback
);
231 throttling_filter_
->SendThrottled(new DOMStorageHostMsg_LoadStorageArea(
232 connection_id
, values
));
235 void DomStorageDispatcher::ProxyImpl::SetItem(
236 int connection_id
, const base::string16
& key
,
237 const base::string16
& value
, const GURL
& page_url
,
238 const CompletionCallback
& callback
) {
239 PushPendingCallback(callback
);
240 throttling_filter_
->SendThrottled(new DOMStorageHostMsg_SetItem(
241 connection_id
, key
, value
, page_url
));
244 void DomStorageDispatcher::ProxyImpl::RemoveItem(
245 int connection_id
, const base::string16
& key
, const GURL
& page_url
,
246 const CompletionCallback
& callback
) {
247 PushPendingCallback(callback
);
248 throttling_filter_
->SendThrottled(new DOMStorageHostMsg_RemoveItem(
249 connection_id
, key
, page_url
));
252 void DomStorageDispatcher::ProxyImpl::ClearArea(int connection_id
,
253 const GURL
& page_url
,
254 const CompletionCallback
& callback
) {
255 PushPendingCallback(callback
);
256 throttling_filter_
->SendThrottled(new DOMStorageHostMsg_Clear(
257 connection_id
, page_url
));
260 // DomStorageDispatcher ------------------------------------------------
262 DomStorageDispatcher::DomStorageDispatcher()
263 : proxy_(new ProxyImpl(RenderThreadImpl::current())) {
266 DomStorageDispatcher::~DomStorageDispatcher() {
270 scoped_refptr
<DOMStorageCachedArea
> DomStorageDispatcher::OpenCachedArea(
271 int connection_id
, int64 namespace_id
, const GURL
& origin
) {
272 RenderThreadImpl::current()->Send(
273 new DOMStorageHostMsg_OpenStorageArea(
274 connection_id
, namespace_id
, origin
));
275 return proxy_
->OpenCachedArea(namespace_id
, origin
);
278 void DomStorageDispatcher::CloseCachedArea(
279 int connection_id
, DOMStorageCachedArea
* area
) {
280 RenderThreadImpl::current()->Send(
281 new DOMStorageHostMsg_CloseStorageArea(connection_id
));
282 proxy_
->CloseCachedArea(area
);
285 bool DomStorageDispatcher::OnMessageReceived(const IPC::Message
& msg
) {
287 IPC_BEGIN_MESSAGE_MAP(DomStorageDispatcher
, msg
)
288 IPC_MESSAGE_HANDLER(DOMStorageMsg_Event
, OnStorageEvent
)
289 IPC_MESSAGE_HANDLER(DOMStorageMsg_AsyncOperationComplete
,
290 OnAsyncOperationComplete
)
291 IPC_MESSAGE_UNHANDLED(handled
= false)
292 IPC_END_MESSAGE_MAP()
296 void DomStorageDispatcher::OnStorageEvent(
297 const DOMStorageMsg_Event_Params
& params
) {
298 RenderThreadImpl::current()->EnsureWebKitInitialized();
300 bool originated_in_process
= params
.connection_id
!= 0;
301 WebStorageAreaImpl
* originating_area
= NULL
;
302 if (originated_in_process
) {
303 originating_area
= WebStorageAreaImpl::FromConnectionId(
304 params
.connection_id
);
306 DOMStorageCachedArea
* cached_area
= proxy_
->LookupCachedArea(
307 params
.namespace_id
, params
.origin
);
309 cached_area
->ApplyMutation(params
.key
, params
.new_value
);
312 if (params
.namespace_id
== kLocalStorageNamespaceId
) {
313 blink::WebStorageEventDispatcher::dispatchLocalStorageEvent(
320 originated_in_process
);
322 WebStorageNamespaceImpl
323 session_namespace_for_event_dispatch(params
.namespace_id
);
324 blink::WebStorageEventDispatcher::dispatchSessionStorageEvent(
330 session_namespace_for_event_dispatch
,
332 originated_in_process
);
336 void DomStorageDispatcher::OnAsyncOperationComplete(bool success
) {
337 proxy_
->CompleteOnePendingCallback(success
);
340 } // namespace content