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 9 #include "base/bind.h" 10 #include "base/files/scoped_temp_dir.h" 11 #include "base/threading/platform_thread.h" 12 #include "base/values.h" 13 #include "content/browser/indexed_db/indexed_db_context_impl.h" 14 #include "content/grit/content_resources.h" 15 #include "content/public/browser/browser_context.h" 16 #include "content/public/browser/browser_thread.h" 17 #include "content/public/browser/download_manager.h" 18 #include "content/public/browser/download_url_parameters.h" 19 #include "content/public/browser/storage_partition.h" 20 #include "content/public/browser/web_contents.h" 21 #include "content/public/browser/web_ui.h" 22 #include "content/public/browser/web_ui_data_source.h" 23 #include "content/public/common/url_constants.h" 24 #include "storage/common/database/database_identifier.h" 25 #include "third_party/zlib/google/zip.h" 26 #include "ui/base/text/bytes_formatting.h" 27 28 namespace content { 29 30 IndexedDBInternalsUI::IndexedDBInternalsUI(WebUI* web_ui) 31 : WebUIController(web_ui) { 32 web_ui->RegisterMessageCallback( 33 "getAllOrigins", 34 base::Bind(&IndexedDBInternalsUI::GetAllOrigins, base::Unretained(this))); 35 36 web_ui->RegisterMessageCallback( 37 "downloadOriginData", 38 base::Bind(&IndexedDBInternalsUI::DownloadOriginData, 39 base::Unretained(this))); 40 web_ui->RegisterMessageCallback( 41 "forceClose", 42 base::Bind(&IndexedDBInternalsUI::ForceCloseOrigin, 43 base::Unretained(this))); 44 45 WebUIDataSource* source = 46 WebUIDataSource::Create(kChromeUIIndexedDBInternalsHost); 47 source->SetUseJsonJSFormatV2(); 48 source->SetJsonPath("strings.js"); 49 source->AddResourcePath("indexeddb_internals.js", 50 IDR_INDEXED_DB_INTERNALS_JS); 51 source->AddResourcePath("indexeddb_internals.css", 52 IDR_INDEXED_DB_INTERNALS_CSS); 53 source->SetDefaultResource(IDR_INDEXED_DB_INTERNALS_HTML); 54 55 BrowserContext* browser_context = 56 web_ui->GetWebContents()->GetBrowserContext(); 57 WebUIDataSource::Add(browser_context, source); 58 } 59 60 IndexedDBInternalsUI::~IndexedDBInternalsUI() {} 61 62 void IndexedDBInternalsUI::AddContextFromStoragePartition( 63 StoragePartition* partition) { 64 scoped_refptr<IndexedDBContext> context = partition->GetIndexedDBContext(); 65 context->TaskRunner()->PostTask( 66 FROM_HERE, 67 base::Bind(&IndexedDBInternalsUI::GetAllOriginsOnIndexedDBThread, 68 base::Unretained(this), 69 context, 70 partition->GetPath())); 71 } 72 73 void IndexedDBInternalsUI::GetAllOrigins(const base::ListValue* args) { 74 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 75 76 BrowserContext* browser_context = 77 web_ui()->GetWebContents()->GetBrowserContext(); 78 79 BrowserContext::StoragePartitionCallback cb = 80 base::Bind(&IndexedDBInternalsUI::AddContextFromStoragePartition, 81 base::Unretained(this)); 82 BrowserContext::ForEachStoragePartition(browser_context, cb); 83 } 84 85 void IndexedDBInternalsUI::GetAllOriginsOnIndexedDBThread( 86 scoped_refptr<IndexedDBContext> context, 87 const base::FilePath& context_path) { 88 DCHECK(context->TaskRunner()->RunsTasksOnCurrentThread()); 89 90 IndexedDBContextImpl* context_impl = 91 static_cast<IndexedDBContextImpl*>(context.get()); 92 93 scoped_ptr<base::ListValue> info_list(context_impl->GetAllOriginsDetails()); 94 bool is_incognito = context_impl->is_incognito(); 95 96 BrowserThread::PostTask( 97 BrowserThread::UI, 98 FROM_HERE, 99 base::Bind(&IndexedDBInternalsUI::OnOriginsReady, 100 base::Unretained(this), 101 base::Passed(&info_list), 102 is_incognito ? base::FilePath() : context_path)); 103 } 104 105 void IndexedDBInternalsUI::OnOriginsReady(scoped_ptr<base::ListValue> origins, 106 const base::FilePath& path) { 107 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 108 web_ui()->CallJavascriptFunction( 109 "indexeddb.onOriginsReady", *origins, base::StringValue(path.value())); 110 } 111 112 static void FindContext(const base::FilePath& partition_path, 113 StoragePartition** result_partition, 114 scoped_refptr<IndexedDBContextImpl>* result_context, 115 StoragePartition* storage_partition) { 116 if (storage_partition->GetPath() == partition_path) { 117 *result_partition = storage_partition; 118 *result_context = static_cast<IndexedDBContextImpl*>( 119 storage_partition->GetIndexedDBContext()); 120 } 121 } 122 123 bool IndexedDBInternalsUI::GetOriginData( 124 const base::ListValue* args, 125 base::FilePath* partition_path, 126 GURL* origin_url, 127 scoped_refptr<IndexedDBContextImpl>* context) { 128 base::FilePath::StringType path_string; 129 if (!args->GetString(0, &path_string)) 130 return false; 131 *partition_path = base::FilePath(path_string); 132 133 std::string url_string; 134 if (!args->GetString(1, &url_string)) 135 return false; 136 137 *origin_url = GURL(url_string); 138 139 return GetOriginContext(*partition_path, *origin_url, context); 140 } 141 142 bool IndexedDBInternalsUI::GetOriginContext( 143 const base::FilePath& path, 144 const GURL& origin_url, 145 scoped_refptr<IndexedDBContextImpl>* context) { 146 // search the origins to find the right context 147 BrowserContext* browser_context = 148 web_ui()->GetWebContents()->GetBrowserContext(); 149 150 StoragePartition* result_partition; 151 BrowserContext::StoragePartitionCallback cb = 152 base::Bind(&FindContext, path, &result_partition, context); 153 BrowserContext::ForEachStoragePartition(browser_context, cb); 154 155 if (!result_partition || !(context->get())) 156 return false; 157 158 return true; 159 } 160 161 void IndexedDBInternalsUI::DownloadOriginData(const base::ListValue* args) { 162 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 163 164 base::FilePath partition_path; 165 GURL origin_url; 166 scoped_refptr<IndexedDBContextImpl> context; 167 if (!GetOriginData(args, &partition_path, &origin_url, &context)) 168 return; 169 170 DCHECK(context.get()); 171 context->TaskRunner()->PostTask( 172 FROM_HERE, 173 base::Bind(&IndexedDBInternalsUI::DownloadOriginDataOnIndexedDBThread, 174 base::Unretained(this), 175 partition_path, 176 context, 177 origin_url)); 178 } 179 180 void IndexedDBInternalsUI::ForceCloseOrigin(const base::ListValue* args) { 181 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 182 183 base::FilePath partition_path; 184 GURL origin_url; 185 scoped_refptr<IndexedDBContextImpl> context; 186 if (!GetOriginData(args, &partition_path, &origin_url, &context)) 187 return; 188 189 context->TaskRunner()->PostTask( 190 FROM_HERE, 191 base::Bind(&IndexedDBInternalsUI::ForceCloseOriginOnIndexedDBThread, 192 base::Unretained(this), 193 partition_path, 194 context, 195 origin_url)); 196 } 197 198 void IndexedDBInternalsUI::DownloadOriginDataOnIndexedDBThread( 199 const base::FilePath& partition_path, 200 const scoped_refptr<IndexedDBContextImpl> context, 201 const GURL& origin_url) { 202 DCHECK(context->TaskRunner()->RunsTasksOnCurrentThread()); 203 204 // Make sure the database hasn't been deleted since the page was loaded. 205 if (!context->IsInOriginSet(origin_url)) 206 return; 207 208 context->ForceClose(origin_url, 209 IndexedDBContextImpl::FORCE_CLOSE_INTERNALS_PAGE); 210 size_t connection_count = context->GetConnectionCount(origin_url); 211 212 base::ScopedTempDir temp_dir; 213 if (!temp_dir.CreateUniqueTempDir()) 214 return; 215 216 // This will get cleaned up on the File thread after the download 217 // has completed. 218 base::FilePath temp_path = temp_dir.Take(); 219 220 std::string origin_id = storage::GetIdentifierFromOrigin(origin_url); 221 base::FilePath zip_path = 222 temp_path.AppendASCII(origin_id).AddExtension(FILE_PATH_LITERAL("zip")); 223 224 // This happens on the "webkit" thread (which is really just the IndexedDB 225 // thread) as a simple way to avoid another script reopening the origin 226 // while we are zipping. 227 zip::Zip(context->GetFilePath(origin_url), zip_path, true); 228 229 BrowserThread::PostTask(BrowserThread::UI, 230 FROM_HERE, 231 base::Bind(&IndexedDBInternalsUI::OnDownloadDataReady, 232 base::Unretained(this), 233 partition_path, 234 origin_url, 235 temp_path, 236 zip_path, 237 connection_count)); 238 } 239 240 void IndexedDBInternalsUI::ForceCloseOriginOnIndexedDBThread( 241 const base::FilePath& partition_path, 242 const scoped_refptr<IndexedDBContextImpl> context, 243 const GURL& origin_url) { 244 DCHECK(context->TaskRunner()->RunsTasksOnCurrentThread()); 245 246 // Make sure the database hasn't been deleted since the page was loaded. 247 if (!context->IsInOriginSet(origin_url)) 248 return; 249 250 context->ForceClose(origin_url, 251 IndexedDBContextImpl::FORCE_CLOSE_INTERNALS_PAGE); 252 size_t connection_count = context->GetConnectionCount(origin_url); 253 254 BrowserThread::PostTask(BrowserThread::UI, 255 FROM_HERE, 256 base::Bind(&IndexedDBInternalsUI::OnForcedClose, 257 base::Unretained(this), 258 partition_path, 259 origin_url, 260 connection_count)); 261 } 262 263 void IndexedDBInternalsUI::OnForcedClose(const base::FilePath& partition_path, 264 const GURL& origin_url, 265 size_t connection_count) { 266 web_ui()->CallJavascriptFunction( 267 "indexeddb.onForcedClose", 268 base::StringValue(partition_path.value()), 269 base::StringValue(origin_url.spec()), 270 base::FundamentalValue(static_cast<double>(connection_count))); 271 } 272 273 void IndexedDBInternalsUI::OnDownloadDataReady( 274 const base::FilePath& partition_path, 275 const GURL& origin_url, 276 const base::FilePath temp_path, 277 const base::FilePath zip_path, 278 size_t connection_count) { 279 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 280 const GURL url = GURL(FILE_PATH_LITERAL("file://") + zip_path.value()); 281 BrowserContext* browser_context = 282 web_ui()->GetWebContents()->GetBrowserContext(); 283 scoped_ptr<DownloadUrlParameters> dl_params( 284 DownloadUrlParameters::FromWebContents(web_ui()->GetWebContents(), url)); 285 DownloadManager* dlm = BrowserContext::GetDownloadManager(browser_context); 286 287 const GURL referrer(web_ui()->GetWebContents()->GetLastCommittedURL()); 288 dl_params->set_referrer( 289 content::Referrer(referrer, blink::WebReferrerPolicyDefault)); 290 291 // This is how to watch for the download to finish: first wait for it 292 // to start, then attach a DownloadItem::Observer to observe the 293 // state change to the finished state. 294 dl_params->set_callback(base::Bind(&IndexedDBInternalsUI::OnDownloadStarted, 295 base::Unretained(this), 296 partition_path, 297 origin_url, 298 temp_path, 299 connection_count)); 300 dlm->DownloadUrl(dl_params.Pass()); 301 } 302 303 // The entire purpose of this class is to delete the temp file after 304 // the download is complete. 305 class FileDeleter : public DownloadItem::Observer { 306 public: 307 explicit FileDeleter(const base::FilePath& temp_dir) : temp_dir_(temp_dir) {} 308 virtual ~FileDeleter(); 309 310 virtual void OnDownloadUpdated(DownloadItem* download) OVERRIDE; 311 virtual void OnDownloadOpened(DownloadItem* item) OVERRIDE {} 312 virtual void OnDownloadRemoved(DownloadItem* item) OVERRIDE {} 313 virtual void OnDownloadDestroyed(DownloadItem* item) OVERRIDE {} 314 315 private: 316 const base::FilePath temp_dir_; 317 318 DISALLOW_COPY_AND_ASSIGN(FileDeleter); 319 }; 320 321 void FileDeleter::OnDownloadUpdated(DownloadItem* item) { 322 switch (item->GetState()) { 323 case DownloadItem::IN_PROGRESS: 324 break; 325 case DownloadItem::COMPLETE: 326 case DownloadItem::CANCELLED: 327 case DownloadItem::INTERRUPTED: { 328 item->RemoveObserver(this); 329 BrowserThread::DeleteOnFileThread::Destruct(this); 330 break; 331 } 332 default: 333 NOTREACHED(); 334 } 335 } 336 337 FileDeleter::~FileDeleter() { 338 base::ScopedTempDir path; 339 bool will_delete ALLOW_UNUSED = path.Set(temp_dir_); 340 DCHECK(will_delete); 341 } 342 343 void IndexedDBInternalsUI::OnDownloadStarted( 344 const base::FilePath& partition_path, 345 const GURL& origin_url, 346 const base::FilePath& temp_path, 347 size_t connection_count, 348 DownloadItem* item, 349 DownloadInterruptReason interrupt_reason) { 350 351 if (interrupt_reason != DOWNLOAD_INTERRUPT_REASON_NONE) { 352 LOG(ERROR) << "Error downloading database dump: " 353 << DownloadInterruptReasonToString(interrupt_reason); 354 return; 355 } 356 357 item->AddObserver(new FileDeleter(temp_path)); 358 web_ui()->CallJavascriptFunction( 359 "indexeddb.onOriginDownloadReady", 360 base::StringValue(partition_path.value()), 361 base::StringValue(origin_url.spec()), 362 base::FundamentalValue(static_cast<double>(connection_count))); 363 } 364 365 } // namespace content 366