1 // Copyright (c) 2013 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/indexed_db/indexed_db_internals_ui.h"
6
7 #include <string>
8 #include <utility>
9
10 #include "base/bind.h"
11 #include "base/bind_helpers.h"
12 #include "base/files/file_util.h"
13 #include "base/macros.h"
14 #include "base/task/post_task.h"
15 #include "base/task/thread_pool.h"
16 #include "base/threading/platform_thread.h"
17 #include "base/values.h"
18 #include "content/grit/dev_ui_content_resources.h"
19 #include "content/public/browser/browser_context.h"
20 #include "content/public/browser/browser_task_traits.h"
21 #include "content/public/browser/browser_thread.h"
22 #include "content/public/browser/download_manager.h"
23 #include "content/public/browser/download_request_utils.h"
24 #include "content/public/browser/storage_partition.h"
25 #include "content/public/browser/web_contents.h"
26 #include "content/public/browser/web_ui.h"
27 #include "content/public/browser/web_ui_data_source.h"
28 #include "content/public/common/url_constants.h"
29 #include "net/traffic_annotation/network_traffic_annotation.h"
30 #include "storage/common/database/database_identifier.h"
31 #include "ui/base/text/bytes_formatting.h"
32 #include "url/origin.h"
33
34 using url::Origin;
35
36 namespace content {
37
IndexedDBInternalsUI(WebUI * web_ui)38 IndexedDBInternalsUI::IndexedDBInternalsUI(WebUI* web_ui)
39 : WebUIController(web_ui) {
40 web_ui->RegisterMessageCallback(
41 "getAllOrigins", base::BindRepeating(&IndexedDBInternalsUI::GetAllOrigins,
42 base::Unretained(this)));
43
44 web_ui->RegisterMessageCallback(
45 "downloadOriginData",
46 base::BindRepeating(&IndexedDBInternalsUI::DownloadOriginData,
47 base::Unretained(this)));
48 web_ui->RegisterMessageCallback(
49 "forceClose", base::BindRepeating(&IndexedDBInternalsUI::ForceCloseOrigin,
50 base::Unretained(this)));
51 WebUIDataSource* source =
52 WebUIDataSource::Create(kChromeUIIndexedDBInternalsHost);
53 source->OverrideContentSecurityPolicyScriptSrc(
54 "script-src chrome://resources 'self' 'unsafe-eval';");
55 source->UseStringsJs();
56 source->AddResourcePath("indexeddb_internals.js",
57 IDR_INDEXED_DB_INTERNALS_JS);
58 source->AddResourcePath("indexeddb_internals.css",
59 IDR_INDEXED_DB_INTERNALS_CSS);
60 source->SetDefaultResource(IDR_INDEXED_DB_INTERNALS_HTML);
61
62 BrowserContext* browser_context =
63 web_ui->GetWebContents()->GetBrowserContext();
64 WebUIDataSource::Add(browser_context, source);
65 }
66
~IndexedDBInternalsUI()67 IndexedDBInternalsUI::~IndexedDBInternalsUI() {}
68
GetAllOrigins(const base::ListValue * args)69 void IndexedDBInternalsUI::GetAllOrigins(const base::ListValue* args) {
70 DCHECK_CURRENTLY_ON(BrowserThread::UI);
71
72 BrowserContext* browser_context =
73 web_ui()->GetWebContents()->GetBrowserContext();
74
75 BrowserContext::ForEachStoragePartition(
76 browser_context,
77 base::BindRepeating(
78 [](base::WeakPtr<IndexedDBInternalsUI> ui,
79 StoragePartition* partition) {
80 if (!ui)
81 return;
82 auto& control = partition->GetIndexedDBControl();
83 control.GetAllOriginsDetails(base::BindOnce(
84 [](base::WeakPtr<IndexedDBInternalsUI> ui,
85 base::FilePath partition_path, bool incognito,
86 base::Value info_list) {
87 if (!ui)
88 return;
89
90 ui->OnOriginsReady(
91 info_list, incognito ? base::FilePath() : partition_path);
92 },
93 ui, partition->GetPath()));
94 },
95 weak_factory_.GetWeakPtr()));
96 }
97
OnOriginsReady(const base::Value & origins,const base::FilePath & path)98 void IndexedDBInternalsUI::OnOriginsReady(const base::Value& origins,
99 const base::FilePath& path) {
100 DCHECK_CURRENTLY_ON(BrowserThread::UI);
101 web_ui()->CallJavascriptFunctionUnsafe("indexeddb.onOriginsReady", origins,
102 base::Value(path.value()));
103 }
104
FindControl(const base::FilePath & partition_path,StoragePartition ** result_partition,storage::mojom::IndexedDBControl ** result_control,StoragePartition * storage_partition)105 static void FindControl(const base::FilePath& partition_path,
106 StoragePartition** result_partition,
107 storage::mojom::IndexedDBControl** result_control,
108 StoragePartition* storage_partition) {
109 if (storage_partition->GetPath() == partition_path) {
110 *result_partition = storage_partition;
111 *result_control = &storage_partition->GetIndexedDBControl();
112 }
113 }
114
GetOriginData(const base::ListValue * args,base::FilePath * partition_path,Origin * origin,storage::mojom::IndexedDBControl ** control)115 bool IndexedDBInternalsUI::GetOriginData(
116 const base::ListValue* args,
117 base::FilePath* partition_path,
118 Origin* origin,
119 storage::mojom::IndexedDBControl** control) {
120 base::FilePath::StringType path_string;
121 if (!args->GetString(0, &path_string))
122 return false;
123 *partition_path = base::FilePath(path_string);
124
125 std::string url_string;
126 if (!args->GetString(1, &url_string))
127 return false;
128
129 *origin = Origin::Create(GURL(url_string));
130
131 return GetOriginControl(*partition_path, *origin, control);
132 }
133
GetOriginControl(const base::FilePath & path,const Origin & origin,storage::mojom::IndexedDBControl ** control)134 bool IndexedDBInternalsUI::GetOriginControl(
135 const base::FilePath& path,
136 const Origin& origin,
137 storage::mojom::IndexedDBControl** control) {
138 // search the origins to find the right context
139 BrowserContext* browser_context =
140 web_ui()->GetWebContents()->GetBrowserContext();
141
142 StoragePartition* result_partition = nullptr;
143 *control = nullptr;
144 BrowserContext::ForEachStoragePartition(
145 browser_context,
146 base::BindRepeating(&FindControl, path, &result_partition, control));
147
148 if (!result_partition || !control)
149 return false;
150
151 return true;
152 }
153
DownloadOriginData(const base::ListValue * args)154 void IndexedDBInternalsUI::DownloadOriginData(const base::ListValue* args) {
155 DCHECK_CURRENTLY_ON(BrowserThread::UI);
156
157 base::FilePath partition_path;
158 Origin origin;
159 storage::mojom::IndexedDBControl* control;
160 if (!GetOriginData(args, &partition_path, &origin, &control))
161 return;
162
163 DCHECK(control);
164 control->ForceClose(
165 origin, storage::mojom::ForceCloseReason::FORCE_CLOSE_INTERNALS_PAGE,
166 base::BindOnce(
167 [](base::WeakPtr<IndexedDBInternalsUI> ui, Origin origin,
168 base::FilePath partition_path,
169 storage::mojom::IndexedDBControl* control) {
170 // Is the connection count always zero after closing,
171 // such that this can be simplified?
172 control->GetConnectionCount(
173 origin,
174 base::BindOnce(
175 [](base::WeakPtr<IndexedDBInternalsUI> ui, Origin origin,
176 base::FilePath partition_path,
177 storage::mojom::IndexedDBControl* control,
178 uint64_t connection_count) {
179 if (!ui)
180 return;
181
182 control->DownloadOriginData(
183 origin,
184 base::BindOnce(
185 &IndexedDBInternalsUI::OnDownloadDataReady, ui,
186 partition_path, origin, connection_count));
187 },
188 ui, origin, partition_path, control));
189 },
190 weak_factory_.GetWeakPtr(), origin, partition_path, control));
191 }
192
ForceCloseOrigin(const base::ListValue * args)193 void IndexedDBInternalsUI::ForceCloseOrigin(const base::ListValue* args) {
194 DCHECK_CURRENTLY_ON(BrowserThread::UI);
195
196 base::FilePath partition_path;
197 Origin origin;
198 storage::mojom::IndexedDBControl* control;
199 if (!GetOriginData(args, &partition_path, &origin, &control))
200 return;
201
202 control->ForceClose(
203 origin, storage::mojom::ForceCloseReason::FORCE_CLOSE_INTERNALS_PAGE,
204 base::BindOnce(
205 [](base::WeakPtr<IndexedDBInternalsUI> ui,
206 base::FilePath partition_path, Origin origin,
207 storage::mojom::IndexedDBControl* control) {
208 if (!ui)
209 return;
210 control->GetConnectionCount(
211 origin, base::BindOnce(&IndexedDBInternalsUI::OnForcedClose, ui,
212 partition_path, origin));
213 },
214 weak_factory_.GetWeakPtr(), partition_path, origin, control));
215 }
216
OnForcedClose(const base::FilePath & partition_path,const Origin & origin,uint64_t connection_count)217 void IndexedDBInternalsUI::OnForcedClose(const base::FilePath& partition_path,
218 const Origin& origin,
219 uint64_t connection_count) {
220 web_ui()->CallJavascriptFunctionUnsafe(
221 "indexeddb.onForcedClose", base::Value(partition_path.value()),
222 base::Value(origin.Serialize()),
223 base::Value(static_cast<double>(connection_count)));
224 }
225
OnDownloadDataReady(const base::FilePath & partition_path,const Origin & origin,uint64_t connection_count,bool success,const base::FilePath & temp_path,const base::FilePath & zip_path)226 void IndexedDBInternalsUI::OnDownloadDataReady(
227 const base::FilePath& partition_path,
228 const Origin& origin,
229 uint64_t connection_count,
230 bool success,
231 const base::FilePath& temp_path,
232 const base::FilePath& zip_path) {
233 DCHECK_CURRENTLY_ON(BrowserThread::UI);
234 if (!success)
235 return;
236
237 const GURL url = GURL(FILE_PATH_LITERAL("file://") + zip_path.value());
238 WebContents* web_contents = web_ui()->GetWebContents();
239 net::NetworkTrafficAnnotationTag traffic_annotation =
240 net::DefineNetworkTrafficAnnotation("indexed_db_internals_handler", R"(
241 semantics {
242 sender: "Indexed DB Internals"
243 description:
244 "This is an internal Chrome webpage that displays debug "
245 "information about IndexedDB usage and data, used by developers."
246 trigger: "When a user navigates to chrome://indexeddb-internals/."
247 data: "None."
248 destination: LOCAL
249 }
250 policy {
251 cookies_allowed: NO
252 setting:
253 "This feature cannot be disabled by settings, but it's only "
254 "triggered by navigating to the specified URL."
255 policy_exception_justification:
256 "Not implemented. Indexed DB is Chrome's internal local data "
257 "storage."
258 })");
259 std::unique_ptr<download::DownloadUrlParameters> dl_params(
260 DownloadRequestUtils::CreateDownloadForWebContentsMainFrame(
261 web_contents, url, traffic_annotation));
262 content::Referrer referrer = content::Referrer::SanitizeForRequest(
263 url, content::Referrer(web_contents->GetLastCommittedURL(),
264 network::mojom::ReferrerPolicy::kDefault));
265 dl_params->set_referrer(referrer.url);
266 dl_params->set_referrer_policy(
267 Referrer::ReferrerPolicyForUrlRequest(referrer.policy));
268
269 // This is how to watch for the download to finish: first wait for it
270 // to start, then attach a download::DownloadItem::Observer to observe the
271 // state change to the finished state.
272 dl_params->set_callback(base::BindOnce(
273 &IndexedDBInternalsUI::OnDownloadStarted, base::Unretained(this),
274 partition_path, origin, temp_path, connection_count));
275
276 BrowserContext* context = web_contents->GetBrowserContext();
277 BrowserContext::GetDownloadManager(context)->DownloadUrl(
278 std::move(dl_params));
279 }
280
281 // The entire purpose of this class is to delete the temp file after
282 // the download is complete.
283 class FileDeleter : public download::DownloadItem::Observer {
284 public:
FileDeleter(const base::FilePath & temp_dir)285 explicit FileDeleter(const base::FilePath& temp_dir) : temp_dir_(temp_dir) {}
286 ~FileDeleter() override;
287
288 void OnDownloadUpdated(download::DownloadItem* download) override;
289 void OnDownloadOpened(download::DownloadItem* item) override {}
290 void OnDownloadRemoved(download::DownloadItem* item) override {}
291 void OnDownloadDestroyed(download::DownloadItem* item) override {}
292
293 private:
294 const base::FilePath temp_dir_;
295
296 DISALLOW_COPY_AND_ASSIGN(FileDeleter);
297 };
298
299 void FileDeleter::OnDownloadUpdated(download::DownloadItem* item) {
300 switch (item->GetState()) {
301 case download::DownloadItem::IN_PROGRESS:
302 break;
303 case download::DownloadItem::COMPLETE:
304 case download::DownloadItem::CANCELLED:
305 case download::DownloadItem::INTERRUPTED: {
306 item->RemoveObserver(this);
307 delete this;
308 break;
309 }
310 default:
311 NOTREACHED();
312 }
313 }
314
315 FileDeleter::~FileDeleter() {
316 base::ThreadPool::PostTask(
317 FROM_HERE,
318 {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
319 base::TaskShutdownBehavior::BLOCK_SHUTDOWN},
320 base::BindOnce(base::IgnoreResult(&base::DeleteFile),
321 std::move(temp_dir_), true));
322 }
323
324 void IndexedDBInternalsUI::OnDownloadStarted(
325 const base::FilePath& partition_path,
326 const Origin& origin,
327 const base::FilePath& temp_path,
328 size_t connection_count,
329 download::DownloadItem* item,
330 download::DownloadInterruptReason interrupt_reason) {
331 if (interrupt_reason != download::DOWNLOAD_INTERRUPT_REASON_NONE) {
332 LOG(ERROR) << "Error downloading database dump: "
333 << DownloadInterruptReasonToString(interrupt_reason);
334 return;
335 }
336
337 item->AddObserver(new FileDeleter(temp_path));
338 web_ui()->CallJavascriptFunctionUnsafe(
339 "indexeddb.onOriginDownloadReady", base::Value(partition_path.value()),
340 base::Value(origin.Serialize()),
341 base::Value(static_cast<double>(connection_count)));
342 }
343
344 } // namespace content
345