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<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<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 size_t connection_count = context->GetConnectionCount(origin_url); 206 207 base::ScopedTempDir temp_dir; 208 if (!temp_dir.CreateUniqueTempDir()) 209 return; 210 211 // This will get cleaned up on the File thread after the download 212 // has completed. 213 base::FilePath temp_path = temp_dir.Take(); 214 215 std::string origin_id = webkit_database::GetIdentifierFromOrigin(origin_url); 216 base::FilePath zip_path = 217 temp_path.AppendASCII(origin_id).AddExtension(FILE_PATH_LITERAL("zip")); 218 219 // This happens on the "webkit" thread (which is really just the IndexedDB 220 // thread) as a simple way to avoid another script reopening the origin 221 // while we are zipping. 222 zip::Zip(context->GetFilePath(origin_url), zip_path, true); 223 224 BrowserThread::PostTask(BrowserThread::UI, 225 FROM_HERE, 226 base::Bind(&IndexedDBInternalsUI::OnDownloadDataReady, 227 base::Unretained(this), 228 partition_path, 229 origin_url, 230 temp_path, 231 zip_path, 232 connection_count)); 233 } 234 235 void IndexedDBInternalsUI::ForceCloseOriginOnIndexedDBThread( 236 const base::FilePath& partition_path, 237 const scoped_refptr<IndexedDBContextImpl> context, 238 const GURL& origin_url) { 239 DCHECK(context->TaskRunner()->RunsTasksOnCurrentThread()); 240 241 // Make sure the database hasn't been deleted since the page was loaded. 242 if (!context->IsInOriginSet(origin_url)) 243 return; 244 245 context->ForceClose(origin_url); 246 size_t connection_count = context->GetConnectionCount(origin_url); 247 248 BrowserThread::PostTask(BrowserThread::UI, 249 FROM_HERE, 250 base::Bind(&IndexedDBInternalsUI::OnForcedClose, 251 base::Unretained(this), 252 partition_path, 253 origin_url, 254 connection_count)); 255 } 256 257 void IndexedDBInternalsUI::OnForcedClose(const base::FilePath& partition_path, 258 const GURL& origin_url, 259 size_t connection_count) { 260 261 scoped_refptr<IndexedDBContextImpl> context; 262 263 web_ui()->CallJavascriptFunction( 264 "indexeddb.onForcedClose", 265 base::StringValue(partition_path.value()), 266 base::StringValue(origin_url.spec()), 267 base::FundamentalValue(double(connection_count))); 268 } 269 270 void IndexedDBInternalsUI::OnDownloadDataReady( 271 const base::FilePath& partition_path, 272 const GURL& origin_url, 273 const base::FilePath temp_path, 274 const base::FilePath zip_path, 275 size_t connection_count) { 276 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 277 const GURL url = GURL(FILE_PATH_LITERAL("file://") + zip_path.value()); 278 BrowserContext* browser_context = 279 web_ui()->GetWebContents()->GetBrowserContext(); 280 scoped_ptr<DownloadUrlParameters> dl_params( 281 DownloadUrlParameters::FromWebContents(web_ui()->GetWebContents(), url)); 282 DownloadManager* dlm = BrowserContext::GetDownloadManager(browser_context); 283 284 const GURL referrer(web_ui()->GetWebContents()->GetURL()); 285 dl_params->set_referrer( 286 content::Referrer(referrer, WebKit::WebReferrerPolicyDefault)); 287 288 // This is how to watch for the download to finish: first wait for it 289 // to start, then attach a DownloadItem::Observer to observe the 290 // state change to the finished state. 291 dl_params->set_callback(base::Bind(&IndexedDBInternalsUI::OnDownloadStarted, 292 base::Unretained(this), 293 partition_path, 294 origin_url, 295 temp_path, 296 connection_count)); 297 dlm->DownloadUrl(dl_params.Pass()); 298 } 299 300 // The entire purpose of this class is to delete the temp file after 301 // the download is complete. 302 class FileDeleter : public DownloadItem::Observer { 303 public: 304 explicit FileDeleter(const base::FilePath& temp_dir) : temp_dir_(temp_dir) {} 305 virtual ~FileDeleter(); 306 307 virtual void OnDownloadUpdated(DownloadItem* download) OVERRIDE; 308 virtual void OnDownloadOpened(DownloadItem* item) OVERRIDE {} 309 virtual void OnDownloadRemoved(DownloadItem* item) OVERRIDE {} 310 virtual void OnDownloadDestroyed(DownloadItem* item) OVERRIDE {} 311 312 private: 313 const base::FilePath temp_dir_; 314 }; 315 316 void FileDeleter::OnDownloadUpdated(DownloadItem* item) { 317 switch (item->GetState()) { 318 case DownloadItem::IN_PROGRESS: 319 break; 320 case DownloadItem::COMPLETE: 321 case DownloadItem::CANCELLED: 322 case DownloadItem::INTERRUPTED: { 323 item->RemoveObserver(this); 324 BrowserThread::DeleteOnFileThread::Destruct(this); 325 break; 326 } 327 default: 328 NOTREACHED(); 329 } 330 } 331 332 FileDeleter::~FileDeleter() { 333 base::ScopedTempDir path; 334 bool will_delete ALLOW_UNUSED = path.Set(temp_dir_); 335 DCHECK(will_delete); 336 } 337 338 void IndexedDBInternalsUI::OnDownloadStarted( 339 const base::FilePath& partition_path, 340 const GURL& origin_url, 341 const base::FilePath& temp_path, 342 size_t connection_count, 343 DownloadItem* item, 344 net::Error error) { 345 346 if (error != net::OK) { 347 LOG(ERROR) 348 << "Error downloading database dump: " << net::ErrorToString(error); 349 return; 350 } 351 352 item->AddObserver(new FileDeleter(temp_path)); 353 web_ui()->CallJavascriptFunction( 354 "indexeddb.onOriginDownloadReady", 355 base::StringValue(partition_path.value()), 356 base::StringValue(origin_url.spec()), 357 base::FundamentalValue(double(connection_count))); 358 } 359 360 } // namespace content 361