- add sources.
[platform/framework/web/crosswalk.git] / src / content / renderer / dom_storage / dom_storage_dispatcher.cc
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.
4
5 #include "content/renderer/dom_storage/dom_storage_dispatcher.h"
6
7 #include <list>
8 #include <map>
9
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"
22
23 namespace content {
24
25 namespace {
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 {
33  public:
34   explicit MessageThrottlingFilter(RenderThreadImpl* sender)
35       : pending_count_(0), sender_(sender) {}
36
37   void SendThrottled(IPC::Message* message);
38   void Shutdown() { sender_ = NULL; }
39
40  private:
41   virtual ~MessageThrottlingFilter() {}
42
43   virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE;
44
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_;
52   }
53
54   base::Lock lock_;
55   int pending_count_;
56   RenderThreadImpl* sender_;
57 };
58
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);
66   DCHECK(sender_);
67   if (!sender_) {
68     delete message;
69     return;
70   }
71   const int kMaxPendingMessages = 1000;
72   bool need_to_flush = (IncrementPendingCount() > kMaxPendingMessages) &&
73                        !message->is_sync();
74   sender_->Send(message);
75   if (need_to_flush) {
76     sender_->Send(new DOMStorageHostMsg_FlushMessages);
77     DCHECK_EQ(0, GetPendingCount());
78   } else {
79     DCHECK_LE(0, GetPendingCount());
80   }
81 }
82
83 bool MessageThrottlingFilter::OnMessageReceived(const IPC::Message& message) {
84   if (message.type() == DOMStorageMsg_AsyncOperationComplete::ID) {
85     DecrementPendingCount();
86     DCHECK_LE(0, GetPendingCount());
87   }
88   return false;
89 }
90 }  // namespace
91
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 {
97  public:
98   explicit ProxyImpl(RenderThreadImpl* sender);
99
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 CompleteOnePendingCallback(bool success);
107   void Shutdown();
108
109   // DOMStorageProxy interface for use by DOMStorageCachedArea.
110   virtual void LoadArea(int connection_id, DOMStorageValuesMap* values,
111                         bool* send_log_get_messages,
112                         const CompletionCallback& callback) OVERRIDE;
113   virtual void SetItem(int connection_id, const string16& key,
114                        const string16& value, const GURL& page_url,
115                        const CompletionCallback& callback) OVERRIDE;
116   virtual void LogGetItem(int connection_id, const string16& key,
117                           const base::NullableString16& value) OVERRIDE;
118   virtual void RemoveItem(int connection_id, const string16& key,
119                           const GURL& page_url,
120                           const CompletionCallback& callback) OVERRIDE;
121   virtual void ClearArea(int connection_id,
122                         const GURL& page_url,
123                         const CompletionCallback& callback) OVERRIDE;
124
125  private:
126   // Struct to hold references to our contained areas and
127   // to keep track of how many tabs have a given area open.
128   struct CachedAreaHolder {
129     scoped_refptr<DOMStorageCachedArea> area_;
130     int open_count_;
131     CachedAreaHolder() : open_count_(0) {}
132     CachedAreaHolder(DOMStorageCachedArea* area, int count)
133         : area_(area), open_count_(count) {}
134   };
135   typedef std::map<std::string, CachedAreaHolder> CachedAreaMap;
136   typedef std::list<CompletionCallback> CallbackList;
137
138   virtual ~ProxyImpl() {
139   }
140
141   // Sudden termination is disabled when there are callbacks pending
142   // to more reliably commit changes during shutdown.
143   void PushPendingCallback(const CompletionCallback& callback) {
144     if (pending_callbacks_.empty())
145       WebKit::Platform::current()->suddenTerminationChanged(false);
146     pending_callbacks_.push_back(callback);
147   }
148
149   CompletionCallback PopPendingCallback() {
150     CompletionCallback callback = pending_callbacks_.front();
151     pending_callbacks_.pop_front();
152     if (pending_callbacks_.empty())
153       WebKit::Platform::current()->suddenTerminationChanged(true);
154     return callback;
155   }
156
157   std::string GetCachedAreaKey(int64 namespace_id, const GURL& origin) {
158     return base::Int64ToString(namespace_id) + origin.spec();
159   }
160
161   CachedAreaHolder* GetAreaHolder(const std::string& key) {
162     CachedAreaMap::iterator found = cached_areas_.find(key);
163     if (found == cached_areas_.end())
164       return NULL;
165     return &(found->second);
166   }
167
168   RenderThreadImpl* sender_;
169   CachedAreaMap cached_areas_;
170   CallbackList pending_callbacks_;
171   scoped_refptr<MessageThrottlingFilter> throttling_filter_;
172 };
173
174 DomStorageDispatcher::ProxyImpl::ProxyImpl(RenderThreadImpl* sender)
175     : sender_(sender),
176       throttling_filter_(new MessageThrottlingFilter(sender)) {
177   sender_->AddFilter(throttling_filter_.get());
178 }
179
180 DOMStorageCachedArea* DomStorageDispatcher::ProxyImpl::OpenCachedArea(
181     int64 namespace_id, const GURL& origin) {
182   std::string key = GetCachedAreaKey(namespace_id, origin);
183   if (CachedAreaHolder* holder = GetAreaHolder(key)) {
184     ++(holder->open_count_);
185     return holder->area_.get();
186   }
187   scoped_refptr<DOMStorageCachedArea> area =
188       new DOMStorageCachedArea(namespace_id, origin, this);
189   cached_areas_[key] = CachedAreaHolder(area.get(), 1);
190   return area.get();
191 }
192
193 void DomStorageDispatcher::ProxyImpl::CloseCachedArea(
194     DOMStorageCachedArea* area) {
195   std::string key = GetCachedAreaKey(area->namespace_id(), area->origin());
196   CachedAreaHolder* holder = GetAreaHolder(key);
197   DCHECK(holder);
198   DCHECK_EQ(holder->area_.get(), area);
199   DCHECK_GT(holder->open_count_, 0);
200   if (--(holder->open_count_) == 0) {
201     cached_areas_.erase(key);
202   }
203 }
204
205 DOMStorageCachedArea* DomStorageDispatcher::ProxyImpl::LookupCachedArea(
206     int64 namespace_id, const GURL& origin) {
207   std::string key = GetCachedAreaKey(namespace_id, origin);
208   CachedAreaHolder* holder = GetAreaHolder(key);
209   if (!holder)
210     return NULL;
211   return holder->area_.get();
212 }
213
214 void DomStorageDispatcher::ProxyImpl::CompleteOnePendingCallback(bool success) {
215   PopPendingCallback().Run(success);
216 }
217
218 void DomStorageDispatcher::ProxyImpl::Shutdown() {
219   throttling_filter_->Shutdown();
220   sender_->RemoveFilter(throttling_filter_.get());
221   sender_ = NULL;
222   cached_areas_.clear();
223   pending_callbacks_.clear();
224 }
225
226 void DomStorageDispatcher::ProxyImpl::LoadArea(
227     int connection_id, DOMStorageValuesMap* values, bool* send_log_get_messages,
228     const CompletionCallback& callback) {
229   PushPendingCallback(callback);
230   throttling_filter_->SendThrottled(new DOMStorageHostMsg_LoadStorageArea(
231       connection_id, values, send_log_get_messages));
232 }
233
234 void DomStorageDispatcher::ProxyImpl::SetItem(
235     int connection_id, const string16& key,
236     const string16& value, const GURL& page_url,
237     const CompletionCallback& callback) {
238   PushPendingCallback(callback);
239   throttling_filter_->SendThrottled(new DOMStorageHostMsg_SetItem(
240       connection_id, key, value, page_url));
241 }
242
243 void DomStorageDispatcher::ProxyImpl::LogGetItem(
244     int connection_id, const string16& key,
245     const base::NullableString16& value) {
246   throttling_filter_->SendThrottled(new DOMStorageHostMsg_LogGetItem(
247       connection_id, key, value));
248 }
249
250 void DomStorageDispatcher::ProxyImpl::RemoveItem(
251     int connection_id, const string16& key,  const GURL& page_url,
252     const CompletionCallback& callback) {
253   PushPendingCallback(callback);
254   throttling_filter_->SendThrottled(new DOMStorageHostMsg_RemoveItem(
255       connection_id, key, page_url));
256 }
257
258 void DomStorageDispatcher::ProxyImpl::ClearArea(int connection_id,
259                       const GURL& page_url,
260                       const CompletionCallback& callback) {
261   PushPendingCallback(callback);
262   throttling_filter_->SendThrottled(new DOMStorageHostMsg_Clear(
263       connection_id, page_url));
264 }
265
266 // DomStorageDispatcher ------------------------------------------------
267
268 DomStorageDispatcher::DomStorageDispatcher()
269     : proxy_(new ProxyImpl(RenderThreadImpl::current())) {
270 }
271
272 DomStorageDispatcher::~DomStorageDispatcher() {
273   proxy_->Shutdown();
274 }
275
276 scoped_refptr<DOMStorageCachedArea> DomStorageDispatcher::OpenCachedArea(
277     int connection_id, int64 namespace_id, const GURL& origin) {
278   RenderThreadImpl::current()->Send(
279       new DOMStorageHostMsg_OpenStorageArea(
280           connection_id, namespace_id, origin));
281   return proxy_->OpenCachedArea(namespace_id, origin);
282 }
283
284 void DomStorageDispatcher::CloseCachedArea(
285     int connection_id, DOMStorageCachedArea* area) {
286   RenderThreadImpl::current()->Send(
287       new DOMStorageHostMsg_CloseStorageArea(connection_id));
288   proxy_->CloseCachedArea(area);
289 }
290
291 bool DomStorageDispatcher::OnMessageReceived(const IPC::Message& msg) {
292   bool handled = true;
293   IPC_BEGIN_MESSAGE_MAP(DomStorageDispatcher, msg)
294     IPC_MESSAGE_HANDLER(DOMStorageMsg_Event, OnStorageEvent)
295     IPC_MESSAGE_HANDLER(DOMStorageMsg_AsyncOperationComplete,
296                         OnAsyncOperationComplete)
297     IPC_MESSAGE_UNHANDLED(handled = false)
298   IPC_END_MESSAGE_MAP()
299   return handled;
300 }
301
302 void DomStorageDispatcher::OnStorageEvent(
303     const DOMStorageMsg_Event_Params& params) {
304   RenderThreadImpl::current()->EnsureWebKitInitialized();
305
306   bool originated_in_process = params.connection_id != 0;
307   WebStorageAreaImpl* originating_area = NULL;
308   if (originated_in_process) {
309     originating_area = WebStorageAreaImpl::FromConnectionId(
310         params.connection_id);
311   } else {
312     DOMStorageCachedArea* cached_area = proxy_->LookupCachedArea(
313         params.namespace_id, params.origin);
314     if (cached_area)
315       cached_area->ApplyMutation(params.key, params.new_value);
316   }
317
318   if (params.namespace_id == kLocalStorageNamespaceId) {
319     WebKit::WebStorageEventDispatcher::dispatchLocalStorageEvent(
320         params.key,
321         params.old_value,
322         params.new_value,
323         params.origin,
324         params.page_url,
325         originating_area,
326         originated_in_process);
327   } else {
328     WebStorageNamespaceImpl
329         session_namespace_for_event_dispatch(params.namespace_id);
330     WebKit::WebStorageEventDispatcher::dispatchSessionStorageEvent(
331         params.key,
332         params.old_value,
333         params.new_value,
334         params.origin,
335         params.page_url,
336         session_namespace_for_event_dispatch,
337         originating_area,
338         originated_in_process);
339   }
340 }
341
342 void DomStorageDispatcher::OnAsyncOperationComplete(bool success) {
343   proxy_->CompleteOnePendingCallback(success);
344 }
345
346 }  // namespace content