1 // Copyright 2016 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/browser/devtools/protocol/storage_handler.h"
6
7 #include <memory>
8 #include <unordered_set>
9 #include <utility>
10 #include <vector>
11
12 #include "base/bind.h"
13 #include "base/strings/string_split.h"
14 #include "base/strings/utf_string_conversions.h"
15 #include "base/task/post_task.h"
16 #include "content/browser/cache_storage/cache_storage_context_impl.h"
17 #include "content/browser/devtools/protocol/browser_handler.h"
18 #include "content/browser/devtools/protocol/network.h"
19 #include "content/browser/devtools/protocol/network_handler.h"
20 #include "content/browser/storage_partition_impl.h"
21 #include "content/public/browser/browser_context.h"
22 #include "content/public/browser/browser_task_traits.h"
23 #include "content/public/browser/browser_thread.h"
24 #include "content/public/browser/render_process_host.h"
25 #include "content/public/browser/storage_partition.h"
26 #include "storage/browser/quota/quota_client.h"
27 #include "storage/browser/quota/quota_manager.h"
28 #include "third_party/blink/public/mojom/quota/quota_types.mojom.h"
29 #include "url/gurl.h"
30 #include "url/origin.h"
31
32 namespace content {
33 namespace protocol {
34
35 using ClearCookiesCallback = Storage::Backend::ClearCookiesCallback;
36 using GetCookiesCallback = Storage::Backend::GetCookiesCallback;
37 using SetCookiesCallback = Storage::Backend::SetCookiesCallback;
38
39 struct UsageListInitializer {
40 const char* type;
41 int64_t blink::mojom::UsageBreakdown::*usage_member;
42 };
43
44 UsageListInitializer initializers[] = {
45 {Storage::StorageTypeEnum::File_systems,
46 &blink::mojom::UsageBreakdown::fileSystem},
47 {Storage::StorageTypeEnum::Websql, &blink::mojom::UsageBreakdown::webSql},
48 {Storage::StorageTypeEnum::Appcache,
49 &blink::mojom::UsageBreakdown::appcache},
50 {Storage::StorageTypeEnum::Indexeddb,
51 &blink::mojom::UsageBreakdown::indexedDatabase},
52 {Storage::StorageTypeEnum::Cache_storage,
53 &blink::mojom::UsageBreakdown::serviceWorkerCache},
54 {Storage::StorageTypeEnum::Service_workers,
55 &blink::mojom::UsageBreakdown::serviceWorker},
56 };
57
58 namespace {
59
ReportUsageAndQuotaDataOnUIThread(std::unique_ptr<StorageHandler::GetUsageAndQuotaCallback> callback,blink::mojom::QuotaStatusCode code,int64_t usage,int64_t quota,blink::mojom::UsageBreakdownPtr usage_breakdown)60 void ReportUsageAndQuotaDataOnUIThread(
61 std::unique_ptr<StorageHandler::GetUsageAndQuotaCallback> callback,
62 blink::mojom::QuotaStatusCode code,
63 int64_t usage,
64 int64_t quota,
65 blink::mojom::UsageBreakdownPtr usage_breakdown) {
66 DCHECK_CURRENTLY_ON(BrowserThread::UI);
67 if (code != blink::mojom::QuotaStatusCode::kOk) {
68 return callback->sendFailure(
69 Response::ServerError("Quota information is not available"));
70 }
71
72 auto usageList = std::make_unique<Array<Storage::UsageForType>>();
73
74 blink::mojom::UsageBreakdown* breakdown_ptr = usage_breakdown.get();
75 for (const auto initializer : initializers) {
76 std::unique_ptr<Storage::UsageForType> entry =
77 Storage::UsageForType::Create()
78 .SetStorageType(initializer.type)
79 .SetUsage(breakdown_ptr->*(initializer.usage_member))
80 .Build();
81 usageList->emplace_back(std::move(entry));
82 }
83
84 callback->sendSuccess(usage, quota, std::move(usageList));
85 }
86
GotUsageAndQuotaDataCallback(std::unique_ptr<StorageHandler::GetUsageAndQuotaCallback> callback,blink::mojom::QuotaStatusCode code,int64_t usage,int64_t quota,blink::mojom::UsageBreakdownPtr usage_breakdown)87 void GotUsageAndQuotaDataCallback(
88 std::unique_ptr<StorageHandler::GetUsageAndQuotaCallback> callback,
89 blink::mojom::QuotaStatusCode code,
90 int64_t usage,
91 int64_t quota,
92 blink::mojom::UsageBreakdownPtr usage_breakdown) {
93 DCHECK_CURRENTLY_ON(BrowserThread::IO);
94 base::PostTask(
95 FROM_HERE, {BrowserThread::UI},
96 base::BindOnce(ReportUsageAndQuotaDataOnUIThread, std::move(callback),
97 code, usage, quota, std::move(usage_breakdown)));
98 }
99
GetUsageAndQuotaOnIOThread(storage::QuotaManager * manager,const url::Origin & origin,std::unique_ptr<StorageHandler::GetUsageAndQuotaCallback> callback)100 void GetUsageAndQuotaOnIOThread(
101 storage::QuotaManager* manager,
102 const url::Origin& origin,
103 std::unique_ptr<StorageHandler::GetUsageAndQuotaCallback> callback) {
104 DCHECK_CURRENTLY_ON(BrowserThread::IO);
105 manager->GetUsageAndQuotaWithBreakdown(
106 origin, blink::mojom::StorageType::kTemporary,
107 base::BindOnce(&GotUsageAndQuotaDataCallback, std::move(callback)));
108 }
109
110 } // namespace
111
112 // Observer that listens on the IO thread for cache storage notifications and
113 // informs the StorageHandler on the UI thread for origins of interest.
114 // Created on the UI thread but predominantly used and deleted on the IO thread.
115 // Registered on creation as an observer in CacheStorageContextImpl,
116 // unregistered on destruction.
117 class StorageHandler::CacheStorageObserver : CacheStorageContextImpl::Observer {
118 public:
CacheStorageObserver(base::WeakPtr<StorageHandler> owner_storage_handler,CacheStorageContextImpl * cache_storage_context)119 CacheStorageObserver(base::WeakPtr<StorageHandler> owner_storage_handler,
120 CacheStorageContextImpl* cache_storage_context)
121 : owner_(owner_storage_handler), context_(cache_storage_context) {
122 DCHECK_CURRENTLY_ON(BrowserThread::UI);
123 context_->AddObserver(this);
124 }
125
~CacheStorageObserver()126 ~CacheStorageObserver() override {
127 DCHECK_CURRENTLY_ON(BrowserThread::UI);
128 context_->RemoveObserver(this);
129 }
130
TrackOrigin(const url::Origin & origin)131 void TrackOrigin(const url::Origin& origin) {
132 DCHECK_CURRENTLY_ON(BrowserThread::UI);
133 if (origins_.find(origin) != origins_.end())
134 return;
135 origins_.insert(origin);
136 }
137
UntrackOrigin(const url::Origin & origin)138 void UntrackOrigin(const url::Origin& origin) {
139 DCHECK_CURRENTLY_ON(BrowserThread::UI);
140 origins_.erase(origin);
141 }
142
OnCacheListChanged(const url::Origin & origin)143 void OnCacheListChanged(const url::Origin& origin) override {
144 DCHECK_CURRENTLY_ON(BrowserThread::UI);
145 auto found = origins_.find(origin);
146 if (found == origins_.end())
147 return;
148 owner_->NotifyCacheStorageListChanged(origin.Serialize());
149 }
150
OnCacheContentChanged(const url::Origin & origin,const std::string & cache_name)151 void OnCacheContentChanged(const url::Origin& origin,
152 const std::string& cache_name) override {
153 DCHECK_CURRENTLY_ON(BrowserThread::UI);
154 if (origins_.find(origin) == origins_.end())
155 return;
156 owner_->NotifyCacheStorageContentChanged(origin.Serialize(), cache_name);
157 }
158
159 private:
160 // Maintained on the IO thread to avoid thread contention.
161 base::flat_set<url::Origin> origins_;
162
163 base::WeakPtr<StorageHandler> owner_;
164 scoped_refptr<CacheStorageContextImpl> context_;
165
166 DISALLOW_COPY_AND_ASSIGN(CacheStorageObserver);
167 };
168
169 // Observer that listens on the IDB thread for IndexedDB notifications and
170 // informs the StorageHandler on the UI thread for origins of interest.
171 // Created on the UI thread but predominantly used and deleted on the IDB
172 // thread.
173 class StorageHandler::IndexedDBObserver
174 : public storage::mojom::IndexedDBObserver {
175 public:
IndexedDBObserver(base::WeakPtr<StorageHandler> owner_storage_handler)176 explicit IndexedDBObserver(
177 base::WeakPtr<StorageHandler> owner_storage_handler)
178 : owner_(owner_storage_handler), receiver_(this) {
179 DCHECK_CURRENTLY_ON(BrowserThread::UI);
180
181 ReconnectObserver();
182 }
183
~IndexedDBObserver()184 ~IndexedDBObserver() override { DCHECK_CURRENTLY_ON(BrowserThread::UI); }
185
TrackOrigin(const url::Origin & origin)186 void TrackOrigin(const url::Origin& origin) {
187 DCHECK_CURRENTLY_ON(BrowserThread::UI);
188 if (origins_.find(origin) != origins_.end())
189 return;
190 origins_.insert(origin);
191 }
192
UntrackOrigin(const url::Origin & origin)193 void UntrackOrigin(const url::Origin& origin) {
194 DCHECK_CURRENTLY_ON(BrowserThread::UI);
195 origins_.erase(origin);
196 }
197
OnIndexedDBListChanged(const url::Origin & origin)198 void OnIndexedDBListChanged(const url::Origin& origin) override {
199 DCHECK_CURRENTLY_ON(BrowserThread::UI);
200 if (!owner_)
201 return;
202 auto found = origins_.find(origin);
203 if (found == origins_.end())
204 return;
205 owner_->NotifyIndexedDBListChanged(origin.Serialize());
206 }
207
OnIndexedDBContentChanged(const url::Origin & origin,const base::string16 & database_name,const base::string16 & object_store_name)208 void OnIndexedDBContentChanged(
209 const url::Origin& origin,
210 const base::string16& database_name,
211 const base::string16& object_store_name) override {
212 DCHECK_CURRENTLY_ON(BrowserThread::UI);
213 if (!owner_)
214 return;
215 auto found = origins_.find(origin);
216 if (found == origins_.end())
217 return;
218 owner_->NotifyIndexedDBContentChanged(origin.Serialize(), database_name,
219 object_store_name);
220 }
221
222 private:
ReconnectObserver()223 void ReconnectObserver() {
224 DCHECK(!receiver_.is_bound());
225 if (!owner_)
226 return;
227
228 auto& control = owner_->storage_partition_->GetIndexedDBControl();
229 mojo::PendingRemote<storage::mojom::IndexedDBObserver> remote;
230 receiver_.Bind(remote.InitWithNewPipeAndPassReceiver());
231 receiver_.set_disconnect_handler(base::BindOnce(
232 [](IndexedDBObserver* observer) {
233 // If this observer disconnects because IndexedDB or the storage
234 // service goes away, reconnect again.
235 observer->ReconnectObserver();
236 },
237 this));
238 control.AddObserver(std::move(remote));
239 }
240
241 base::flat_set<url::Origin> origins_;
242 base::WeakPtr<StorageHandler> owner_;
243 mojo::Receiver<storage::mojom::IndexedDBObserver> receiver_;
244
245 DISALLOW_COPY_AND_ASSIGN(IndexedDBObserver);
246 };
247
StorageHandler()248 StorageHandler::StorageHandler()
249 : DevToolsDomainHandler(Storage::Metainfo::domainName),
250 storage_partition_(nullptr) {}
251
~StorageHandler()252 StorageHandler::~StorageHandler() {
253 DCHECK(!cache_storage_observer_);
254 DCHECK(!indexed_db_observer_);
255 }
256
Wire(UberDispatcher * dispatcher)257 void StorageHandler::Wire(UberDispatcher* dispatcher) {
258 frontend_ = std::make_unique<Storage::Frontend>(dispatcher->channel());
259 Storage::Dispatcher::wire(dispatcher, this);
260 }
261
SetRenderer(int process_host_id,RenderFrameHostImpl * frame_host)262 void StorageHandler::SetRenderer(int process_host_id,
263 RenderFrameHostImpl* frame_host) {
264 RenderProcessHost* process = RenderProcessHost::FromID(process_host_id);
265 storage_partition_ = process ? process->GetStoragePartition() : nullptr;
266 }
267
Disable()268 Response StorageHandler::Disable() {
269 cache_storage_observer_.reset();
270 indexed_db_observer_.reset();
271 return Response::Success();
272 }
273
GetCookies(Maybe<std::string> browser_context_id,std::unique_ptr<GetCookiesCallback> callback)274 void StorageHandler::GetCookies(Maybe<std::string> browser_context_id,
275 std::unique_ptr<GetCookiesCallback> callback) {
276 StoragePartition* storage_partition = nullptr;
277 Response response = StorageHandler::FindStoragePartition(browser_context_id,
278 &storage_partition);
279 if (!response.IsSuccess()) {
280 callback->sendFailure(std::move(response));
281 return;
282 }
283
284 storage_partition->GetCookieManagerForBrowserProcess()->GetAllCookies(
285 base::BindOnce(
286 [](std::unique_ptr<GetCookiesCallback> callback,
287 const std::vector<net::CanonicalCookie>& cookies) {
288 callback->sendSuccess(NetworkHandler::BuildCookieArray(cookies));
289 },
290 std::move(callback)));
291 }
292
SetCookies(std::unique_ptr<protocol::Array<Network::CookieParam>> cookies,Maybe<std::string> browser_context_id,std::unique_ptr<SetCookiesCallback> callback)293 void StorageHandler::SetCookies(
294 std::unique_ptr<protocol::Array<Network::CookieParam>> cookies,
295 Maybe<std::string> browser_context_id,
296 std::unique_ptr<SetCookiesCallback> callback) {
297 StoragePartition* storage_partition = nullptr;
298 Response response = StorageHandler::FindStoragePartition(browser_context_id,
299 &storage_partition);
300 if (!response.IsSuccess()) {
301 callback->sendFailure(std::move(response));
302 return;
303 }
304
305 NetworkHandler::SetCookies(
306 storage_partition, std::move(cookies),
307 base::BindOnce(
308 [](std::unique_ptr<SetCookiesCallback> callback, bool success) {
309 if (success) {
310 callback->sendSuccess();
311 } else {
312 callback->sendFailure(
313 Response::InvalidParams("Invalid cookie fields"));
314 }
315 },
316 std::move(callback)));
317 }
318
ClearCookies(Maybe<std::string> browser_context_id,std::unique_ptr<ClearCookiesCallback> callback)319 void StorageHandler::ClearCookies(
320 Maybe<std::string> browser_context_id,
321 std::unique_ptr<ClearCookiesCallback> callback) {
322 StoragePartition* storage_partition = nullptr;
323 Response response = StorageHandler::FindStoragePartition(browser_context_id,
324 &storage_partition);
325 if (!response.IsSuccess()) {
326 callback->sendFailure(std::move(response));
327 return;
328 }
329
330 storage_partition->GetCookieManagerForBrowserProcess()->DeleteCookies(
331 network::mojom::CookieDeletionFilter::New(),
332 base::BindOnce([](std::unique_ptr<ClearCookiesCallback> callback,
333 uint32_t) { callback->sendSuccess(); },
334 std::move(callback)));
335 }
336
ClearDataForOrigin(const std::string & origin,const std::string & storage_types,std::unique_ptr<ClearDataForOriginCallback> callback)337 void StorageHandler::ClearDataForOrigin(
338 const std::string& origin,
339 const std::string& storage_types,
340 std::unique_ptr<ClearDataForOriginCallback> callback) {
341 if (!storage_partition_)
342 return callback->sendFailure(Response::InternalError());
343
344 std::vector<std::string> types = base::SplitString(
345 storage_types, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
346 std::unordered_set<std::string> set(types.begin(), types.end());
347 uint32_t remove_mask = 0;
348 if (set.count(Storage::StorageTypeEnum::Appcache))
349 remove_mask |= StoragePartition::REMOVE_DATA_MASK_APPCACHE;
350 if (set.count(Storage::StorageTypeEnum::Cookies))
351 remove_mask |= StoragePartition::REMOVE_DATA_MASK_COOKIES;
352 if (set.count(Storage::StorageTypeEnum::File_systems))
353 remove_mask |= StoragePartition::REMOVE_DATA_MASK_FILE_SYSTEMS;
354 if (set.count(Storage::StorageTypeEnum::Indexeddb))
355 remove_mask |= StoragePartition::REMOVE_DATA_MASK_INDEXEDDB;
356 if (set.count(Storage::StorageTypeEnum::Local_storage))
357 remove_mask |= StoragePartition::REMOVE_DATA_MASK_LOCAL_STORAGE;
358 if (set.count(Storage::StorageTypeEnum::Shader_cache))
359 remove_mask |= StoragePartition::REMOVE_DATA_MASK_SHADER_CACHE;
360 if (set.count(Storage::StorageTypeEnum::Websql))
361 remove_mask |= StoragePartition::REMOVE_DATA_MASK_WEBSQL;
362 if (set.count(Storage::StorageTypeEnum::Service_workers))
363 remove_mask |= StoragePartition::REMOVE_DATA_MASK_SERVICE_WORKERS;
364 if (set.count(Storage::StorageTypeEnum::Cache_storage))
365 remove_mask |= StoragePartition::REMOVE_DATA_MASK_CACHE_STORAGE;
366 if (set.count(Storage::StorageTypeEnum::All))
367 remove_mask |= StoragePartition::REMOVE_DATA_MASK_ALL;
368
369 if (!remove_mask) {
370 return callback->sendFailure(
371 Response::InvalidParams("No valid storage type specified"));
372 }
373
374 storage_partition_->ClearData(
375 remove_mask, StoragePartition::QUOTA_MANAGED_STORAGE_MASK_ALL,
376 GURL(origin), base::Time(), base::Time::Max(),
377 base::BindOnce(&ClearDataForOriginCallback::sendSuccess,
378 std::move(callback)));
379 }
380
GetUsageAndQuota(const String & origin,std::unique_ptr<GetUsageAndQuotaCallback> callback)381 void StorageHandler::GetUsageAndQuota(
382 const String& origin,
383 std::unique_ptr<GetUsageAndQuotaCallback> callback) {
384 if (!storage_partition_)
385 return callback->sendFailure(Response::InternalError());
386
387 GURL origin_url(origin);
388 if (!origin_url.is_valid()) {
389 return callback->sendFailure(
390 Response::ServerError(origin + " is not a valid URL"));
391 }
392
393 storage::QuotaManager* manager = storage_partition_->GetQuotaManager();
394 base::PostTask(
395 FROM_HERE, {BrowserThread::IO},
396 base::BindOnce(&GetUsageAndQuotaOnIOThread, base::RetainedRef(manager),
397 url::Origin::Create(origin_url), std::move(callback)));
398 }
399
TrackCacheStorageForOrigin(const std::string & origin)400 Response StorageHandler::TrackCacheStorageForOrigin(const std::string& origin) {
401 if (!storage_partition_)
402 return Response::InternalError();
403
404 GURL origin_url(origin);
405 if (!origin_url.is_valid())
406 return Response::InvalidParams(origin + " is not a valid URL");
407
408 GetCacheStorageObserver()->TrackOrigin(url::Origin::Create(origin_url));
409 return Response::Success();
410 }
411
UntrackCacheStorageForOrigin(const std::string & origin)412 Response StorageHandler::UntrackCacheStorageForOrigin(
413 const std::string& origin) {
414 if (!storage_partition_)
415 return Response::InternalError();
416
417 GURL origin_url(origin);
418 if (!origin_url.is_valid())
419 return Response::InvalidParams(origin + " is not a valid URL");
420
421 GetCacheStorageObserver()->UntrackOrigin(url::Origin::Create(origin_url));
422 return Response::Success();
423 }
424
TrackIndexedDBForOrigin(const std::string & origin)425 Response StorageHandler::TrackIndexedDBForOrigin(const std::string& origin) {
426 if (!storage_partition_)
427 return Response::InternalError();
428
429 GURL origin_url(origin);
430 if (!origin_url.is_valid())
431 return Response::InvalidParams(origin + " is not a valid URL");
432
433 GetIndexedDBObserver()->TrackOrigin(url::Origin::Create(origin_url));
434 return Response::Success();
435 }
436
UntrackIndexedDBForOrigin(const std::string & origin)437 Response StorageHandler::UntrackIndexedDBForOrigin(const std::string& origin) {
438 if (!storage_partition_)
439 return Response::InternalError();
440
441 GURL origin_url(origin);
442 if (!origin_url.is_valid())
443 return Response::InvalidParams(origin + " is not a valid URL");
444
445 GetIndexedDBObserver()->UntrackOrigin(url::Origin::Create(origin_url));
446 return Response::Success();
447 }
448
449 StorageHandler::CacheStorageObserver*
GetCacheStorageObserver()450 StorageHandler::GetCacheStorageObserver() {
451 DCHECK_CURRENTLY_ON(BrowserThread::UI);
452 if (!cache_storage_observer_) {
453 cache_storage_observer_ = std::make_unique<CacheStorageObserver>(
454 weak_ptr_factory_.GetWeakPtr(),
455 static_cast<CacheStorageContextImpl*>(
456 storage_partition_->GetCacheStorageContext()));
457 }
458 return cache_storage_observer_.get();
459 }
460
GetIndexedDBObserver()461 StorageHandler::IndexedDBObserver* StorageHandler::GetIndexedDBObserver() {
462 DCHECK_CURRENTLY_ON(BrowserThread::UI);
463 if (!indexed_db_observer_) {
464 indexed_db_observer_ =
465 std::make_unique<IndexedDBObserver>(weak_ptr_factory_.GetWeakPtr());
466 }
467 return indexed_db_observer_.get();
468 }
469
NotifyCacheStorageListChanged(const std::string & origin)470 void StorageHandler::NotifyCacheStorageListChanged(const std::string& origin) {
471 DCHECK_CURRENTLY_ON(BrowserThread::UI);
472 frontend_->CacheStorageListUpdated(origin);
473 }
474
NotifyCacheStorageContentChanged(const std::string & origin,const std::string & name)475 void StorageHandler::NotifyCacheStorageContentChanged(const std::string& origin,
476 const std::string& name) {
477 DCHECK_CURRENTLY_ON(BrowserThread::UI);
478 frontend_->CacheStorageContentUpdated(origin, name);
479 }
480
NotifyIndexedDBListChanged(const std::string & origin)481 void StorageHandler::NotifyIndexedDBListChanged(const std::string& origin) {
482 DCHECK_CURRENTLY_ON(BrowserThread::UI);
483 frontend_->IndexedDBListUpdated(origin);
484 }
485
NotifyIndexedDBContentChanged(const std::string & origin,const base::string16 & database_name,const base::string16 & object_store_name)486 void StorageHandler::NotifyIndexedDBContentChanged(
487 const std::string& origin,
488 const base::string16& database_name,
489 const base::string16& object_store_name) {
490 DCHECK_CURRENTLY_ON(BrowserThread::UI);
491 frontend_->IndexedDBContentUpdated(origin, base::UTF16ToUTF8(database_name),
492 base::UTF16ToUTF8(object_store_name));
493 }
494
FindStoragePartition(const Maybe<std::string> & browser_context_id,StoragePartition ** storage_partition)495 Response StorageHandler::FindStoragePartition(
496 const Maybe<std::string>& browser_context_id,
497 StoragePartition** storage_partition) {
498 BrowserContext* browser_context = nullptr;
499 Response response =
500 BrowserHandler::FindBrowserContext(browser_context_id, &browser_context);
501 if (!response.IsSuccess())
502 return response;
503 *storage_partition =
504 BrowserContext::GetDefaultStoragePartition(browser_context);
505 if (!*storage_partition)
506 return Response::InternalError();
507 return Response::Success();
508 }
509
510 } // namespace protocol
511 } // namespace content
512