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
24namespace content {
25
26namespace {
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.
33class MessageThrottlingFilter : public IPC::MessageFilter {
34 public:
35  explicit MessageThrottlingFilter(RenderThreadImpl* sender)
36      : pending_count_(0), sender_(sender) {}
37
38  void SendThrottled(IPC::Message* message);
39  void Shutdown() { sender_ = NULL; }
40
41 private:
42  virtual ~MessageThrottlingFilter() {}
43
44  virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE;
45
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_;
53  }
54
55  base::Lock lock_;
56  int pending_count_;
57  RenderThreadImpl* sender_;
58};
59
60void 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
84bool 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.
97class 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_;
134    CachedAreaHolder() : open_count_(0) {}
135    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
142  virtual ~ProxyImpl() {
143  }
144
145  // Sudden termination is disabled when there are callbacks pending
146  // to more reliably commit changes during shutdown.
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
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
161  std::string GetCachedAreaKey(int64 namespace_id, const GURL& origin) {
162    return base::Int64ToString(namespace_id) + origin.spec();
163  }
164
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
178DomStorageDispatcher::ProxyImpl::ProxyImpl(RenderThreadImpl* sender)
179    : sender_(sender),
180      throttling_filter_(new MessageThrottlingFilter(sender)) {
181  sender_->AddFilter(throttling_filter_.get());
182}
183
184DOMStorageCachedArea* 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
197void 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
209DOMStorageCachedArea* 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
218void 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
227void DomStorageDispatcher::ProxyImpl::CompleteOnePendingCallback(bool success) {
228  PopPendingCallback().Run(success);
229}
230
231void 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
239void 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
247void 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
256void 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
262void 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
270void 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
280DomStorageDispatcher::DomStorageDispatcher()
281    : proxy_(new ProxyImpl(RenderThreadImpl::current())) {
282}
283
284DomStorageDispatcher::~DomStorageDispatcher() {
285  proxy_->Shutdown();
286}
287
288scoped_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
296void DomStorageDispatcher::CloseCachedArea(
297    int connection_id, DOMStorageCachedArea* area) {
298  RenderThreadImpl::current()->Send(
299      new DOMStorageHostMsg_CloseStorageArea(connection_id));
300  proxy_->CloseCachedArea(area);
301}
302
303bool 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
316void 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
356void DomStorageDispatcher::OnAsyncOperationComplete(bool success) {
357  proxy_->CompleteOnePendingCallback(success);
358}
359
360void DomStorageDispatcher::OnResetCachedValues(int64 namespace_id) {
361  proxy_->ResetAllCachedAreas(namespace_id);
362}
363
364}  // namespace content
365