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