Home | History | Annotate | Download | only in dom_storage
      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:
     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 
     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 
     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_;
    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 
    178 DomStorageDispatcher::ProxyImpl::ProxyImpl(RenderThreadImpl* sender)
    179     : sender_(sender),
    180       throttling_filter_(new MessageThrottlingFilter(sender)) {
    181   sender_->AddFilter(throttling_filter_.get());
    182 }
    183 
    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 
    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 
    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 
    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 
    227 void DomStorageDispatcher::ProxyImpl::CompleteOnePendingCallback(bool success) {
    228   PopPendingCallback().Run(success);
    229 }
    230 
    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 
    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 
    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 
    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 
    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 
    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 
    280 DomStorageDispatcher::DomStorageDispatcher()
    281     : proxy_(new ProxyImpl(RenderThreadImpl::current())) {
    282 }
    283 
    284 DomStorageDispatcher::~DomStorageDispatcher() {
    285   proxy_->Shutdown();
    286 }
    287 
    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 
    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 
    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 
    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 
    356 void DomStorageDispatcher::OnAsyncOperationComplete(bool success) {
    357   proxy_->CompleteOnePendingCallback(success);
    358 }
    359 
    360 void DomStorageDispatcher::OnResetCachedValues(int64 namespace_id) {
    361   proxy_->ResetAllCachedAreas(namespace_id);
    362 }
    363 
    364 }  // namespace content
    365