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