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