save_package.cc revision 201ade2fbba22bfb27ae029f4d23fca6ded109a0
1// Copyright (c) 2010 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 "chrome/browser/download/save_package.h" 6 7#include <algorithm> 8 9#include "app/l10n_util.h" 10#include "base/file_path.h" 11#include "base/file_util.h" 12#include "base/i18n/file_util_icu.h" 13#include "base/logging.h" 14#include "base/message_loop.h" 15#include "base/stl_util-inl.h" 16#include "base/string_piece.h" 17#include "base/string_split.h" 18#include "base/utf_string_conversions.h" 19#include "base/task.h" 20#include "base/thread.h" 21#include "chrome/browser/browser_process.h" 22#include "chrome/browser/browser_thread.h" 23#include "chrome/browser/download/download_item.h" 24#include "chrome/browser/download/download_item_model.h" 25#include "chrome/browser/download/download_manager.h" 26#include "chrome/browser/download/download_shelf.h" 27#include "chrome/browser/download/download_util.h" 28#include "chrome/browser/download/save_file.h" 29#include "chrome/browser/download/save_file_manager.h" 30#include "chrome/browser/download/save_item.h" 31#include "chrome/browser/net/url_fixer_upper.h" 32#include "chrome/browser/platform_util.h" 33#include "chrome/browser/prefs/pref_member.h" 34#include "chrome/browser/prefs/pref_service.h" 35#include "chrome/browser/profile.h" 36#include "chrome/browser/renderer_host/render_process_host.h" 37#include "chrome/browser/renderer_host/render_view_host.h" 38#include "chrome/browser/renderer_host/render_view_host_delegate.h" 39#include "chrome/browser/renderer_host/resource_dispatcher_host.h" 40#include "chrome/browser/tab_contents/tab_contents.h" 41#include "chrome/browser/tab_contents/tab_util.h" 42#include "chrome/common/chrome_paths.h" 43#include "chrome/common/net/url_request_context_getter.h" 44#include "chrome/common/notification_service.h" 45#include "chrome/common/notification_type.h" 46#include "chrome/common/pref_names.h" 47#include "chrome/common/url_constants.h" 48#include "grit/generated_resources.h" 49#include "net/base/io_buffer.h" 50#include "net/base/mime_util.h" 51#include "net/base/net_util.h" 52#include "net/url_request/url_request_context.h" 53#include "third_party/WebKit/WebKit/chromium/public/WebPageSerializerClient.h" 54 55using base::Time; 56using WebKit::WebPageSerializerClient; 57 58namespace { 59 60// A counter for uniquely identifying each save package. 61int g_save_package_id = 0; 62 63// Default name which will be used when we can not get proper name from 64// resource URL. 65const FilePath::CharType kDefaultSaveName[] = 66 FILE_PATH_LITERAL("saved_resource"); 67 68const FilePath::CharType kDefaultHtmlExtension[] = 69#if defined(OS_WIN) 70 FILE_PATH_LITERAL("htm"); 71#else 72 FILE_PATH_LITERAL("html"); 73#endif 74 75// Maximum number of file ordinal number. I think it's big enough for resolving 76// name-conflict files which has same base file name. 77const int32 kMaxFileOrdinalNumber = 9999; 78 79// Maximum length for file path. Since Windows have MAX_PATH limitation for 80// file path, we need to make sure length of file path of every saved file 81// is less than MAX_PATH 82#if defined(OS_WIN) 83const uint32 kMaxFilePathLength = MAX_PATH - 1; 84#elif defined(OS_POSIX) 85const uint32 kMaxFilePathLength = PATH_MAX - 1; 86#endif 87 88// Maximum length for file ordinal number part. Since we only support the 89// maximum 9999 for ordinal number, which means maximum file ordinal number part 90// should be "(9998)", so the value is 6. 91const uint32 kMaxFileOrdinalNumberPartLength = 6; 92 93// If false, we don't prompt the user as to where to save the file. This 94// exists only for testing. 95bool g_should_prompt_for_filename = true; 96 97// Strip current ordinal number, if any. Should only be used on pure 98// file names, i.e. those stripped of their extensions. 99// TODO(estade): improve this to not choke on alternate encodings. 100FilePath::StringType StripOrdinalNumber( 101 const FilePath::StringType& pure_file_name) { 102 FilePath::StringType::size_type r_paren_index = 103 pure_file_name.rfind(FILE_PATH_LITERAL(')')); 104 FilePath::StringType::size_type l_paren_index = 105 pure_file_name.rfind(FILE_PATH_LITERAL('(')); 106 if (l_paren_index >= r_paren_index) 107 return pure_file_name; 108 109 for (FilePath::StringType::size_type i = l_paren_index + 1; 110 i != r_paren_index; ++i) { 111 if (!IsAsciiDigit(pure_file_name[i])) 112 return pure_file_name; 113 } 114 115 return pure_file_name.substr(0, l_paren_index); 116} 117 118// Check whether we can save page as complete-HTML for the contents which 119// have specified a MIME type. Now only contents which have the MIME type 120// "text/html" can be saved as complete-HTML. 121bool CanSaveAsComplete(const std::string& contents_mime_type) { 122 return contents_mime_type == "text/html" || 123 contents_mime_type == "application/xhtml+xml"; 124} 125 126// File name is considered being consist of pure file name, dot and file 127// extension name. File name might has no dot and file extension, or has 128// multiple dot inside file name. The dot, which separates the pure file 129// name and file extension name, is last dot in the whole file name. 130// This function is for making sure the length of specified file path is not 131// great than the specified maximum length of file path and getting safe pure 132// file name part if the input pure file name is too long. 133// The parameter |dir_path| specifies directory part of the specified 134// file path. The parameter |file_name_ext| specifies file extension 135// name part of the specified file path (including start dot). The parameter 136// |max_file_path_len| specifies maximum length of the specified file path. 137// The parameter |pure_file_name| input pure file name part of the specified 138// file path. If the length of specified file path is great than 139// |max_file_path_len|, the |pure_file_name| will output new pure file name 140// part for making sure the length of specified file path is less than 141// specified maximum length of file path. Return false if the function can 142// not get a safe pure file name, otherwise it returns true. 143bool GetSafePureFileName(const FilePath& dir_path, 144 const FilePath::StringType& file_name_ext, 145 uint32 max_file_path_len, 146 FilePath::StringType* pure_file_name) { 147 DCHECK(!pure_file_name->empty()); 148 int available_length = static_cast<int>( 149 max_file_path_len - dir_path.value().length() - file_name_ext.length()); 150 // Need an extra space for the separator. 151 if (!file_util::EndsWithSeparator(dir_path)) 152 --available_length; 153 154 // Plenty of room. 155 if (static_cast<int>(pure_file_name->length()) <= available_length) 156 return true; 157 158 // Limited room. Truncate |pure_file_name| to fit. 159 if (available_length > 0) { 160 *pure_file_name = 161 pure_file_name->substr(0, available_length); 162 return true; 163 } 164 165 // Not enough room to even use a shortened |pure_file_name|. 166 pure_file_name->clear(); 167 return false; 168} 169 170} // namespace 171 172SavePackage::SavePackage(TabContents* tab_contents, 173 SavePackageType save_type, 174 const FilePath& file_full_path, 175 const FilePath& directory_full_path) 176 : file_manager_(NULL), 177 tab_contents_(tab_contents), 178 download_(NULL), 179 page_url_(GetUrlToBeSaved()), 180 saved_main_file_path_(file_full_path), 181 saved_main_directory_path_(directory_full_path), 182 title_(tab_contents->GetTitle()), 183 finished_(false), 184 user_canceled_(false), 185 disk_error_occurred_(false), 186 save_type_(save_type), 187 all_save_items_count_(0), 188 wait_state_(INITIALIZE), 189 tab_id_(tab_contents->GetRenderProcessHost()->id()), 190 unique_id_(g_save_package_id++), 191 ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)) { 192 DCHECK(page_url_.is_valid()); 193 DCHECK(save_type_ == SAVE_AS_ONLY_HTML || 194 save_type_ == SAVE_AS_COMPLETE_HTML); 195 DCHECK(!saved_main_file_path_.empty() && 196 saved_main_file_path_.value().length() <= kMaxFilePathLength); 197 DCHECK(!saved_main_directory_path_.empty() && 198 saved_main_directory_path_.value().length() < kMaxFilePathLength); 199 InternalInit(); 200} 201 202SavePackage::SavePackage(TabContents* tab_contents) 203 : file_manager_(NULL), 204 tab_contents_(tab_contents), 205 download_(NULL), 206 page_url_(GetUrlToBeSaved()), 207 title_(tab_contents->GetTitle()), 208 finished_(false), 209 user_canceled_(false), 210 disk_error_occurred_(false), 211 save_type_(SAVE_TYPE_UNKNOWN), 212 all_save_items_count_(0), 213 wait_state_(INITIALIZE), 214 tab_id_(tab_contents->GetRenderProcessHost()->id()), 215 unique_id_(g_save_package_id++), 216 ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)) { 217 DCHECK(page_url_.is_valid()); 218 InternalInit(); 219} 220 221// This is for testing use. Set |finished_| as true because we don't want 222// method Cancel to be be called in destructor in test mode. 223// We also don't call InternalInit(). 224SavePackage::SavePackage(TabContents* tab_contents, 225 const FilePath& file_full_path, 226 const FilePath& directory_full_path) 227 : file_manager_(NULL), 228 tab_contents_(tab_contents), 229 download_(NULL), 230 saved_main_file_path_(file_full_path), 231 saved_main_directory_path_(directory_full_path), 232 finished_(true), 233 user_canceled_(false), 234 disk_error_occurred_(false), 235 save_type_(SAVE_TYPE_UNKNOWN), 236 all_save_items_count_(0), 237 wait_state_(INITIALIZE), 238 tab_id_(0), 239 unique_id_(g_save_package_id++), 240 ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)) { 241} 242 243SavePackage::~SavePackage() { 244 // Stop receiving saving job's updates 245 if (!finished_ && !canceled()) { 246 // Unexpected quit. 247 Cancel(true); 248 } 249 250 DCHECK(all_save_items_count_ == (waiting_item_queue_.size() + 251 completed_count() + 252 in_process_count())); 253 // Free all SaveItems. 254 while (!waiting_item_queue_.empty()) { 255 // We still have some items which are waiting for start to save. 256 SaveItem* save_item = waiting_item_queue_.front(); 257 waiting_item_queue_.pop(); 258 delete save_item; 259 } 260 261 STLDeleteValues(&saved_success_items_); 262 STLDeleteValues(&in_progress_items_); 263 STLDeleteValues(&saved_failed_items_); 264 265 // The DownloadItem is owned by DownloadManager. 266 download_ = NULL; 267 268 file_manager_ = NULL; 269 270 // If there's an outstanding save dialog, make sure it doesn't call us back 271 // now that we're gone. 272 if (select_file_dialog_.get()) 273 select_file_dialog_->ListenerDestroyed(); 274} 275 276// Retrieves the URL to be saved from tab_contents_ variable. 277GURL SavePackage::GetUrlToBeSaved() { 278 // Instead of using tab_contents_.GetURL here, we use url() 279 // (which is the "real" url of the page) 280 // from the NavigationEntry because it reflects its' origin 281 // rather than the displayed one (returned by GetURL) which may be 282 // different (like having "view-source:" on the front). 283 NavigationEntry* active_entry = 284 tab_contents_->controller().GetActiveEntry(); 285 return active_entry->url(); 286} 287 288// Cancel all in progress request, might be called by user or internal error. 289void SavePackage::Cancel(bool user_action) { 290 if (!canceled()) { 291 if (user_action) 292 user_canceled_ = true; 293 else 294 disk_error_occurred_ = true; 295 Stop(); 296 } 297} 298 299// Init() can be called directly, or indirectly via GetSaveInfo(). In both 300// cases, we need file_manager_ to be initialized, so we do this first. 301void SavePackage::InternalInit() { 302 ResourceDispatcherHost* rdh = g_browser_process->resource_dispatcher_host(); 303 if (!rdh) { 304 NOTREACHED(); 305 return; 306 } 307 308 file_manager_ = rdh->save_file_manager(); 309 if (!file_manager_) { 310 NOTREACHED(); 311 return; 312 } 313} 314 315// Initialize the SavePackage. 316bool SavePackage::Init() { 317 // Set proper running state. 318 if (wait_state_ != INITIALIZE) 319 return false; 320 321 wait_state_ = START_PROCESS; 322 323 // Initialize the request context and resource dispatcher. 324 Profile* profile = tab_contents_->profile(); 325 if (!profile) { 326 NOTREACHED(); 327 return false; 328 } 329 330 request_context_getter_ = profile->GetRequestContext(); 331 332 // Create the fake DownloadItem and display the view. 333 DownloadManager* download_manager = 334 tab_contents_->profile()->GetDownloadManager(); 335 download_ = new DownloadItem(download_manager, 336 saved_main_file_path_, 337 page_url_, 338 profile->IsOffTheRecord()); 339 340 // Transfer the ownership to the download manager. We need the DownloadItem 341 // to be alive as long as the Profile is alive. 342 download_manager->SavePageAsDownloadStarted(download_); 343 344 tab_contents_->OnStartDownload(download_); 345 346 // Check save type and process the save page job. 347 if (save_type_ == SAVE_AS_COMPLETE_HTML) { 348 // Get directory 349 DCHECK(!saved_main_directory_path_.empty()); 350 GetAllSavableResourceLinksForCurrentPage(); 351 } else { 352 wait_state_ = NET_FILES; 353 SaveFileCreateInfo::SaveFileSource save_source = page_url_.SchemeIsFile() ? 354 SaveFileCreateInfo::SAVE_FILE_FROM_FILE : 355 SaveFileCreateInfo::SAVE_FILE_FROM_NET; 356 SaveItem* save_item = new SaveItem(page_url_, 357 GURL(), 358 this, 359 save_source); 360 // Add this item to waiting list. 361 waiting_item_queue_.push(save_item); 362 all_save_items_count_ = 1; 363 download_->set_total_bytes(1); 364 365 DoSavingProcess(); 366 } 367 368 return true; 369} 370 371// Generate name for saving resource. 372bool SavePackage::GenerateFileName(const std::string& disposition, 373 const GURL& url, 374 bool need_html_ext, 375 FilePath::StringType* generated_name) { 376 // TODO(jungshik): Figure out the referrer charset when having one 377 // makes sense and pass it to GetSuggestedFilename. 378 FilePath file_path = net::GetSuggestedFilename(url, disposition, "", 379 FilePath(kDefaultSaveName)); 380 381 DCHECK(!file_path.empty()); 382 FilePath::StringType pure_file_name = 383 file_path.RemoveExtension().BaseName().value(); 384 FilePath::StringType file_name_ext = file_path.Extension(); 385 386 // If it is HTML resource, use ".htm{l,}" as its extension. 387 if (need_html_ext) { 388 file_name_ext = FILE_PATH_LITERAL("."); 389 file_name_ext.append(kDefaultHtmlExtension); 390 } 391 392 // Get safe pure file name. 393 if (!GetSafePureFileName(saved_main_directory_path_, file_name_ext, 394 kMaxFilePathLength, &pure_file_name)) 395 return false; 396 397 FilePath::StringType file_name = pure_file_name + file_name_ext; 398 399 // Check whether we already have same name. 400 if (file_name_set_.find(file_name) == file_name_set_.end()) { 401 file_name_set_.insert(file_name); 402 } else { 403 // Found same name, increase the ordinal number for the file name. 404 FilePath::StringType base_file_name = StripOrdinalNumber(pure_file_name); 405 406 // We need to make sure the length of base file name plus maximum ordinal 407 // number path will be less than or equal to kMaxFilePathLength. 408 if (!GetSafePureFileName(saved_main_directory_path_, file_name_ext, 409 kMaxFilePathLength - kMaxFileOrdinalNumberPartLength, &base_file_name)) 410 return false; 411 412 // Prepare the new ordinal number. 413 uint32 ordinal_number; 414 FileNameCountMap::iterator it = file_name_count_map_.find(base_file_name); 415 if (it == file_name_count_map_.end()) { 416 // First base-name-conflict resolving, use 1 as initial ordinal number. 417 file_name_count_map_[base_file_name] = 1; 418 ordinal_number = 1; 419 } else { 420 // We have met same base-name conflict, use latest ordinal number. 421 ordinal_number = it->second; 422 } 423 424 if (ordinal_number > (kMaxFileOrdinalNumber - 1)) { 425 // Use a random file from temporary file. 426 FilePath temp_file; 427 file_util::CreateTemporaryFile(&temp_file); 428 file_name = temp_file.RemoveExtension().BaseName().value(); 429 // Get safe pure file name. 430 if (!GetSafePureFileName(saved_main_directory_path_, 431 FilePath::StringType(), 432 kMaxFilePathLength, &file_name)) 433 return false; 434 } else { 435 for (int i = ordinal_number; i < kMaxFileOrdinalNumber; ++i) { 436 FilePath::StringType new_name = base_file_name + 437 StringPrintf(FILE_PATH_LITERAL("(%d)"), i) + file_name_ext; 438 if (file_name_set_.find(new_name) == file_name_set_.end()) { 439 // Resolved name conflict. 440 file_name = new_name; 441 file_name_count_map_[base_file_name] = ++i; 442 break; 443 } 444 } 445 } 446 447 file_name_set_.insert(file_name); 448 } 449 450 DCHECK(!file_name.empty()); 451 generated_name->assign(file_name); 452 453 return true; 454} 455 456// We have received a message from SaveFileManager about a new saving job. We 457// create a SaveItem and store it in our in_progress list. 458void SavePackage::StartSave(const SaveFileCreateInfo* info) { 459 DCHECK(info && !info->url.is_empty()); 460 461 SaveUrlItemMap::iterator it = in_progress_items_.find(info->url.spec()); 462 if (it == in_progress_items_.end()) { 463 // If not found, we must have cancel action. 464 DCHECK(canceled()); 465 return; 466 } 467 SaveItem* save_item = it->second; 468 469 DCHECK(!saved_main_file_path_.empty()); 470 471 save_item->SetSaveId(info->save_id); 472 save_item->SetTotalBytes(info->total_bytes); 473 474 // Determine the proper path for a saving job, by choosing either the default 475 // save directory, or prompting the user. 476 DCHECK(!save_item->has_final_name()); 477 if (info->url != page_url_) { 478 FilePath::StringType generated_name; 479 // For HTML resource file, make sure it will have .htm as extension name, 480 // otherwise, when you open the saved page in Chrome again, download 481 // file manager will treat it as downloadable resource, and download it 482 // instead of opening it as HTML. 483 bool need_html_ext = 484 info->save_source == SaveFileCreateInfo::SAVE_FILE_FROM_DOM; 485 if (!GenerateFileName(info->content_disposition, 486 GURL(info->url), 487 need_html_ext, 488 &generated_name)) { 489 // We can not generate file name for this SaveItem, so we cancel the 490 // saving page job if the save source is from serialized DOM data. 491 // Otherwise, it means this SaveItem is sub-resource type, we treat it 492 // as an error happened on saving. We can ignore this type error for 493 // sub-resource links which will be resolved as absolute links instead 494 // of local links in final saved contents. 495 if (info->save_source == SaveFileCreateInfo::SAVE_FILE_FROM_DOM) 496 Cancel(true); 497 else 498 SaveFinished(save_item->save_id(), 0, false); 499 return; 500 } 501 502 // When saving page as only-HTML, we only have a SaveItem whose url 503 // must be page_url_. 504 DCHECK(save_type_ == SAVE_AS_COMPLETE_HTML); 505 DCHECK(!saved_main_directory_path_.empty()); 506 507 // Now we get final name retrieved from GenerateFileName, we will use it 508 // rename the SaveItem. 509 FilePath final_name = saved_main_directory_path_.Append(generated_name); 510 save_item->Rename(final_name); 511 } else { 512 // It is the main HTML file, use the name chosen by the user. 513 save_item->Rename(saved_main_file_path_); 514 } 515 516 // If the save source is from file system, inform SaveFileManager to copy 517 // corresponding file to the file path which this SaveItem specifies. 518 if (info->save_source == SaveFileCreateInfo::SAVE_FILE_FROM_FILE) { 519 BrowserThread::PostTask( 520 BrowserThread::FILE, FROM_HERE, 521 NewRunnableMethod(file_manager_, 522 &SaveFileManager::SaveLocalFile, 523 save_item->url(), 524 save_item->save_id(), 525 tab_id())); 526 return; 527 } 528 529 // Check whether we begin to require serialized HTML data. 530 if (save_type_ == SAVE_AS_COMPLETE_HTML && wait_state_ == HTML_DATA) { 531 // Inform backend to serialize the all frames' DOM and send serialized 532 // HTML data back. 533 GetSerializedHtmlDataForCurrentPageWithLocalLinks(); 534 } 535} 536 537// Look up SaveItem by save id from in progress map. 538SaveItem* SavePackage::LookupItemInProcessBySaveId(int32 save_id) { 539 if (in_process_count()) { 540 for (SaveUrlItemMap::iterator it = in_progress_items_.begin(); 541 it != in_progress_items_.end(); ++it) { 542 SaveItem* save_item = it->second; 543 DCHECK(save_item->state() == SaveItem::IN_PROGRESS); 544 if (save_item->save_id() == save_id) 545 return save_item; 546 } 547 } 548 return NULL; 549} 550 551// Remove SaveItem from in progress map and put it to saved map. 552void SavePackage::PutInProgressItemToSavedMap(SaveItem* save_item) { 553 SaveUrlItemMap::iterator it = in_progress_items_.find( 554 save_item->url().spec()); 555 DCHECK(it != in_progress_items_.end()); 556 DCHECK(save_item == it->second); 557 in_progress_items_.erase(it); 558 559 if (save_item->success()) { 560 // Add it to saved_success_items_. 561 DCHECK(saved_success_items_.find(save_item->save_id()) == 562 saved_success_items_.end()); 563 saved_success_items_[save_item->save_id()] = save_item; 564 } else { 565 // Add it to saved_failed_items_. 566 DCHECK(saved_failed_items_.find(save_item->url().spec()) == 567 saved_failed_items_.end()); 568 saved_failed_items_[save_item->url().spec()] = save_item; 569 } 570} 571 572// Called for updating saving state. 573bool SavePackage::UpdateSaveProgress(int32 save_id, 574 int64 size, 575 bool write_success) { 576 // Because we might have canceled this saving job before, 577 // so we might not find corresponding SaveItem. 578 SaveItem* save_item = LookupItemInProcessBySaveId(save_id); 579 if (!save_item) 580 return false; 581 582 save_item->Update(size); 583 584 // If we got disk error, cancel whole save page job. 585 if (!write_success) { 586 // Cancel job with reason of disk error. 587 Cancel(false); 588 } 589 return true; 590} 591 592// Stop all page saving jobs that are in progress and instruct the file thread 593// to delete all saved files. 594void SavePackage::Stop() { 595 // If we haven't moved out of the initial state, there's nothing to cancel and 596 // there won't be valid pointers for file_manager_ or download_. 597 if (wait_state_ == INITIALIZE) 598 return; 599 600 // When stopping, if it still has some items in in_progress, cancel them. 601 DCHECK(canceled()); 602 if (in_process_count()) { 603 SaveUrlItemMap::iterator it = in_progress_items_.begin(); 604 for (; it != in_progress_items_.end(); ++it) { 605 SaveItem* save_item = it->second; 606 DCHECK(save_item->state() == SaveItem::IN_PROGRESS); 607 save_item->Cancel(); 608 } 609 // Remove all in progress item to saved map. For failed items, they will 610 // be put into saved_failed_items_, for successful item, they will be put 611 // into saved_success_items_. 612 while (in_process_count()) 613 PutInProgressItemToSavedMap(in_progress_items_.begin()->second); 614 } 615 616 // This vector contains the save ids of the save files which SaveFileManager 617 // needs to remove from its save_file_map_. 618 SaveIDList save_ids; 619 for (SavedItemMap::iterator it = saved_success_items_.begin(); 620 it != saved_success_items_.end(); ++it) 621 save_ids.push_back(it->first); 622 for (SaveUrlItemMap::iterator it = saved_failed_items_.begin(); 623 it != saved_failed_items_.end(); ++it) 624 save_ids.push_back(it->second->save_id()); 625 626 BrowserThread::PostTask( 627 BrowserThread::FILE, FROM_HERE, 628 NewRunnableMethod(file_manager_, 629 &SaveFileManager::RemoveSavedFileFromFileMap, 630 save_ids)); 631 632 finished_ = true; 633 wait_state_ = FAILED; 634 635 // Inform the DownloadItem we have canceled whole save page job. 636 download_->Cancel(false); 637} 638 639void SavePackage::CheckFinish() { 640 if (in_process_count() || finished_) 641 return; 642 643 FilePath dir = (save_type_ == SAVE_AS_COMPLETE_HTML && 644 saved_success_items_.size() > 1) ? 645 saved_main_directory_path_ : FilePath(); 646 647 // This vector contains the final names of all the successfully saved files 648 // along with their save ids. It will be passed to SaveFileManager to do the 649 // renaming job. 650 FinalNameList final_names; 651 for (SavedItemMap::iterator it = saved_success_items_.begin(); 652 it != saved_success_items_.end(); ++it) 653 final_names.push_back(std::make_pair(it->first, 654 it->second->full_path())); 655 656 BrowserThread::PostTask( 657 BrowserThread::FILE, FROM_HERE, 658 NewRunnableMethod(file_manager_, 659 &SaveFileManager::RenameAllFiles, 660 final_names, 661 dir, 662 tab_contents_->GetRenderProcessHost()->id(), 663 tab_contents_->render_view_host()->routing_id(), 664 id())); 665} 666 667// Successfully finished all items of this SavePackage. 668void SavePackage::Finish() { 669 // User may cancel the job when we're moving files to the final directory. 670 if (canceled()) 671 return; 672 673 wait_state_ = SUCCESSFUL; 674 finished_ = true; 675 676 // This vector contains the save ids of the save files which SaveFileManager 677 // needs to remove from its save_file_map_. 678 SaveIDList save_ids; 679 for (SaveUrlItemMap::iterator it = saved_failed_items_.begin(); 680 it != saved_failed_items_.end(); ++it) 681 save_ids.push_back(it->second->save_id()); 682 683 BrowserThread::PostTask( 684 BrowserThread::FILE, FROM_HERE, 685 NewRunnableMethod(file_manager_, 686 &SaveFileManager::RemoveSavedFileFromFileMap, 687 save_ids)); 688 689 download_->OnAllDataSaved(all_save_items_count_); 690 // Notify download observers that we are complete (the call 691 // to OnAllDataSaved() set the state to complete but did not notify). 692 download_->UpdateObservers(); 693 694 NotificationService::current()->Notify( 695 NotificationType::SAVE_PACKAGE_SUCCESSFULLY_FINISHED, 696 Source<SavePackage>(this), 697 Details<GURL>(&page_url_)); 698} 699 700// Called for updating end state. 701void SavePackage::SaveFinished(int32 save_id, int64 size, bool is_success) { 702 // Because we might have canceled this saving job before, 703 // so we might not find corresponding SaveItem. Just ignore it. 704 SaveItem* save_item = LookupItemInProcessBySaveId(save_id); 705 if (!save_item) 706 return; 707 708 // Let SaveItem set end state. 709 save_item->Finish(size, is_success); 710 // Remove the associated save id and SavePackage. 711 file_manager_->RemoveSaveFile(save_id, save_item->url(), this); 712 713 PutInProgressItemToSavedMap(save_item); 714 715 // Inform the DownloadItem to update UI. 716 // We use the received bytes as number of saved files. 717 download_->Update(completed_count()); 718 719 if (save_item->save_source() == SaveFileCreateInfo::SAVE_FILE_FROM_DOM && 720 save_item->url() == page_url_ && !save_item->received_bytes()) { 721 // If size of main HTML page is 0, treat it as disk error. 722 Cancel(false); 723 return; 724 } 725 726 if (canceled()) { 727 DCHECK(finished_); 728 return; 729 } 730 731 // Continue processing the save page job. 732 DoSavingProcess(); 733 734 // Check whether we can successfully finish whole job. 735 CheckFinish(); 736} 737 738// Sometimes, the net io will only call SaveFileManager::SaveFinished with 739// save id -1 when it encounters error. Since in this case, save id will be 740// -1, so we can only use URL to find which SaveItem is associated with 741// this error. 742// Saving an item failed. If it's a sub-resource, ignore it. If the error comes 743// from serializing HTML data, then cancel saving page. 744void SavePackage::SaveFailed(const GURL& save_url) { 745 SaveUrlItemMap::iterator it = in_progress_items_.find(save_url.spec()); 746 if (it == in_progress_items_.end()) { 747 NOTREACHED(); // Should not exist! 748 return; 749 } 750 SaveItem* save_item = it->second; 751 752 save_item->Finish(0, false); 753 754 PutInProgressItemToSavedMap(save_item); 755 756 // Inform the DownloadItem to update UI. 757 // We use the received bytes as number of saved files. 758 download_->Update(completed_count()); 759 760 if (save_type_ == SAVE_AS_ONLY_HTML || 761 save_item->save_source() == SaveFileCreateInfo::SAVE_FILE_FROM_DOM) { 762 // We got error when saving page. Treat it as disk error. 763 Cancel(true); 764 } 765 766 if (canceled()) { 767 DCHECK(finished_); 768 return; 769 } 770 771 // Continue processing the save page job. 772 DoSavingProcess(); 773 774 CheckFinish(); 775} 776 777void SavePackage::SaveCanceled(SaveItem* save_item) { 778 // Call the RemoveSaveFile in UI thread. 779 file_manager_->RemoveSaveFile(save_item->save_id(), 780 save_item->url(), 781 this); 782 if (save_item->save_id() != -1) 783 BrowserThread::PostTask( 784 BrowserThread::FILE, FROM_HERE, 785 NewRunnableMethod(file_manager_, 786 &SaveFileManager::CancelSave, 787 save_item->save_id())); 788} 789 790// Initiate a saving job of a specific URL. We send the request to 791// SaveFileManager, which will dispatch it to different approach according to 792// the save source. Parameter process_all_remaining_items indicates whether 793// we need to save all remaining items. 794void SavePackage::SaveNextFile(bool process_all_remaining_items) { 795 DCHECK(tab_contents_); 796 DCHECK(waiting_item_queue_.size()); 797 798 do { 799 // Pop SaveItem from waiting list. 800 SaveItem* save_item = waiting_item_queue_.front(); 801 waiting_item_queue_.pop(); 802 803 // Add the item to in_progress_items_. 804 SaveUrlItemMap::iterator it = in_progress_items_.find( 805 save_item->url().spec()); 806 DCHECK(it == in_progress_items_.end()); 807 in_progress_items_[save_item->url().spec()] = save_item; 808 save_item->Start(); 809 file_manager_->SaveURL(save_item->url(), 810 save_item->referrer(), 811 tab_contents_->GetRenderProcessHost()->id(), 812 tab_contents_->render_view_host()->routing_id(), 813 save_item->save_source(), 814 save_item->full_path(), 815 request_context_getter_.get(), 816 this); 817 } while (process_all_remaining_items && waiting_item_queue_.size()); 818} 819 820 821// Open download page in windows explorer on file thread, to avoid blocking the 822// user interface. 823void SavePackage::ShowDownloadInShell() { 824 DCHECK(file_manager_); 825 DCHECK(finished_ && !canceled() && !saved_main_file_path_.empty()); 826 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 827#if defined(OS_MACOSX) 828 // Mac OS X requires opening downloads on the UI thread. 829 platform_util::ShowItemInFolder(saved_main_file_path_); 830#else 831 BrowserThread::PostTask( 832 BrowserThread::FILE, FROM_HERE, 833 NewRunnableMethod(file_manager_, 834 &SaveFileManager::OnShowSavedFileInShell, 835 saved_main_file_path_)); 836#endif 837} 838 839// Calculate the percentage of whole save page job. 840int SavePackage::PercentComplete() { 841 if (!all_save_items_count_) 842 return 0; 843 else if (!in_process_count()) 844 return 100; 845 else 846 return completed_count() / all_save_items_count_; 847} 848 849// Continue processing the save page job after one SaveItem has been 850// finished. 851void SavePackage::DoSavingProcess() { 852 if (save_type_ == SAVE_AS_COMPLETE_HTML) { 853 // We guarantee that images and JavaScripts must be downloaded first. 854 // So when finishing all those sub-resources, we will know which 855 // sub-resource's link can be replaced with local file path, which 856 // sub-resource's link need to be replaced with absolute URL which 857 // point to its internet address because it got error when saving its data. 858 SaveItem* save_item = NULL; 859 // Start a new SaveItem job if we still have job in waiting queue. 860 if (waiting_item_queue_.size()) { 861 DCHECK(wait_state_ == NET_FILES); 862 save_item = waiting_item_queue_.front(); 863 if (save_item->save_source() != SaveFileCreateInfo::SAVE_FILE_FROM_DOM) { 864 SaveNextFile(false); 865 } else if (!in_process_count()) { 866 // If there is no in-process SaveItem, it means all sub-resources 867 // have been processed. Now we need to start serializing HTML DOM 868 // for the current page to get the generated HTML data. 869 wait_state_ = HTML_DATA; 870 // All non-HTML resources have been finished, start all remaining 871 // HTML files. 872 SaveNextFile(true); 873 } 874 } else if (in_process_count()) { 875 // Continue asking for HTML data. 876 DCHECK(wait_state_ == HTML_DATA); 877 } 878 } else { 879 // Save as HTML only. 880 DCHECK(wait_state_ == NET_FILES); 881 DCHECK(save_type_ == SAVE_AS_ONLY_HTML); 882 if (waiting_item_queue_.size()) { 883 DCHECK(all_save_items_count_ == waiting_item_queue_.size()); 884 SaveNextFile(false); 885 } 886 } 887} 888 889// After finishing all SaveItems which need to get data from net. 890// We collect all URLs which have local storage and send the 891// map:(originalURL:currentLocalPath) to render process (backend). 892// Then render process will serialize DOM and send data to us. 893void SavePackage::GetSerializedHtmlDataForCurrentPageWithLocalLinks() { 894 if (wait_state_ != HTML_DATA) 895 return; 896 std::vector<GURL> saved_links; 897 std::vector<FilePath> saved_file_paths; 898 int successful_started_items_count = 0; 899 900 // Collect all saved items which have local storage. 901 // First collect the status of all the resource files and check whether they 902 // have created local files although they have not been completely saved. 903 // If yes, the file can be saved. Otherwise, there is a disk error, so we 904 // need to cancel the page saving job. 905 for (SaveUrlItemMap::iterator it = in_progress_items_.begin(); 906 it != in_progress_items_.end(); ++it) { 907 DCHECK(it->second->save_source() == 908 SaveFileCreateInfo::SAVE_FILE_FROM_DOM); 909 if (it->second->has_final_name()) 910 successful_started_items_count++; 911 saved_links.push_back(it->second->url()); 912 saved_file_paths.push_back(it->second->file_name()); 913 } 914 915 // If not all file of HTML resource have been started, then wait. 916 if (successful_started_items_count != in_process_count()) 917 return; 918 919 // Collect all saved success items. 920 for (SavedItemMap::iterator it = saved_success_items_.begin(); 921 it != saved_success_items_.end(); ++it) { 922 DCHECK(it->second->has_final_name()); 923 saved_links.push_back(it->second->url()); 924 saved_file_paths.push_back(it->second->file_name()); 925 } 926 927 // Get the relative directory name. 928 FilePath relative_dir_name = saved_main_directory_path_.BaseName(); 929 930 tab_contents_->render_view_host()-> 931 GetSerializedHtmlDataForCurrentPageWithLocalLinks( 932 saved_links, saved_file_paths, relative_dir_name); 933} 934 935// Process the serialized HTML content data of a specified web page 936// retrieved from render process. 937void SavePackage::OnReceivedSerializedHtmlData(const GURL& frame_url, 938 const std::string& data, 939 int32 status) { 940 WebPageSerializerClient::PageSerializationStatus flag = 941 static_cast<WebPageSerializerClient::PageSerializationStatus>(status); 942 // Check current state. 943 if (wait_state_ != HTML_DATA) 944 return; 945 946 int id = tab_id(); 947 // If the all frames are finished saving, we need to close the 948 // remaining SaveItems. 949 if (flag == WebPageSerializerClient::AllFramesAreFinished) { 950 for (SaveUrlItemMap::iterator it = in_progress_items_.begin(); 951 it != in_progress_items_.end(); ++it) { 952 BrowserThread::PostTask( 953 BrowserThread::FILE, FROM_HERE, 954 NewRunnableMethod(file_manager_, 955 &SaveFileManager::SaveFinished, 956 it->second->save_id(), 957 it->second->url(), 958 id, 959 true)); 960 } 961 return; 962 } 963 964 SaveUrlItemMap::iterator it = in_progress_items_.find(frame_url.spec()); 965 if (it == in_progress_items_.end()) 966 return; 967 SaveItem* save_item = it->second; 968 DCHECK(save_item->save_source() == SaveFileCreateInfo::SAVE_FILE_FROM_DOM); 969 970 if (!data.empty()) { 971 // Prepare buffer for saving HTML data. 972 scoped_refptr<net::IOBuffer> new_data(new net::IOBuffer(data.size())); 973 memcpy(new_data->data(), data.data(), data.size()); 974 975 // Call write file functionality in file thread. 976 BrowserThread::PostTask( 977 BrowserThread::FILE, FROM_HERE, 978 NewRunnableMethod(file_manager_, 979 &SaveFileManager::UpdateSaveProgress, 980 save_item->save_id(), 981 new_data, 982 static_cast<int>(data.size()))); 983 } 984 985 // Current frame is completed saving, call finish in file thread. 986 if (flag == WebPageSerializerClient::CurrentFrameIsFinished) { 987 BrowserThread::PostTask( 988 BrowserThread::FILE, FROM_HERE, 989 NewRunnableMethod(file_manager_, 990 &SaveFileManager::SaveFinished, 991 save_item->save_id(), 992 save_item->url(), 993 id, 994 true)); 995 } 996} 997 998// Ask for all savable resource links from backend, include main frame and 999// sub-frame. 1000void SavePackage::GetAllSavableResourceLinksForCurrentPage() { 1001 if (wait_state_ != START_PROCESS) 1002 return; 1003 1004 wait_state_ = RESOURCES_LIST; 1005 tab_contents_->render_view_host()-> 1006 GetAllSavableResourceLinksForCurrentPage(page_url_); 1007} 1008 1009// Give backend the lists which contain all resource links that have local 1010// storage, after which, render process will serialize DOM for generating 1011// HTML data. 1012void SavePackage::OnReceivedSavableResourceLinksForCurrentPage( 1013 const std::vector<GURL>& resources_list, 1014 const std::vector<GURL>& referrers_list, 1015 const std::vector<GURL>& frames_list) { 1016 if (wait_state_ != RESOURCES_LIST) 1017 return; 1018 1019 DCHECK(resources_list.size() == referrers_list.size()); 1020 all_save_items_count_ = static_cast<int>(resources_list.size()) + 1021 static_cast<int>(frames_list.size()); 1022 1023 // We use total bytes as the total number of files we want to save. 1024 download_->set_total_bytes(all_save_items_count_); 1025 1026 if (all_save_items_count_) { 1027 // Put all sub-resources to wait list. 1028 for (int i = 0; i < static_cast<int>(resources_list.size()); ++i) { 1029 const GURL& u = resources_list[i]; 1030 DCHECK(u.is_valid()); 1031 SaveFileCreateInfo::SaveFileSource save_source = u.SchemeIsFile() ? 1032 SaveFileCreateInfo::SAVE_FILE_FROM_FILE : 1033 SaveFileCreateInfo::SAVE_FILE_FROM_NET; 1034 SaveItem* save_item = new SaveItem(u, referrers_list[i], 1035 this, save_source); 1036 waiting_item_queue_.push(save_item); 1037 } 1038 // Put all HTML resources to wait list. 1039 for (int i = 0; i < static_cast<int>(frames_list.size()); ++i) { 1040 const GURL& u = frames_list[i]; 1041 DCHECK(u.is_valid()); 1042 SaveItem* save_item = new SaveItem(u, GURL(), 1043 this, SaveFileCreateInfo::SAVE_FILE_FROM_DOM); 1044 waiting_item_queue_.push(save_item); 1045 } 1046 wait_state_ = NET_FILES; 1047 DoSavingProcess(); 1048 } else { 1049 // No resource files need to be saved, treat it as user cancel. 1050 Cancel(true); 1051 } 1052} 1053 1054void SavePackage::SetShouldPromptUser(bool should_prompt) { 1055 g_should_prompt_for_filename = should_prompt; 1056} 1057 1058FilePath SavePackage::GetSuggestedNameForSaveAs( 1059 bool can_save_as_complete, 1060 const std::string& contents_mime_type) { 1061 FilePath name_with_proper_ext = 1062 FilePath::FromWStringHack(UTF16ToWideHack(title_)); 1063 1064 // If the page's title matches its URL, use the URL. Try to use the last path 1065 // component or if there is none, the domain as the file name. 1066 // Normally we want to base the filename on the page title, or if it doesn't 1067 // exist, on the URL. It's not easy to tell if the page has no title, because 1068 // if the page has no title, TabContents::GetTitle() will return the page's 1069 // URL (adjusted for display purposes). Therefore, we convert the "title" 1070 // back to a URL, and if it matches the original page URL, we know the page 1071 // had no title (or had a title equal to its URL, which is fine to treat 1072 // similarly). 1073 GURL fixed_up_title_url = 1074 URLFixerUpper::FixupURL(UTF16ToUTF8(title_), std::string()); 1075 1076 if (page_url_ == fixed_up_title_url) { 1077 std::string url_path; 1078 std::vector<std::string> url_parts; 1079 base::SplitString(page_url_.path(), '/', &url_parts); 1080 if (!url_parts.empty()) { 1081 for (int i = static_cast<int>(url_parts.size()) - 1; i >= 0; --i) { 1082 url_path = url_parts[i]; 1083 if (!url_path.empty()) 1084 break; 1085 } 1086 } 1087 if (url_path.empty()) 1088 url_path = page_url_.host(); 1089 name_with_proper_ext = FilePath::FromWStringHack(UTF8ToWide(url_path)); 1090 } 1091 1092 // Ask user for getting final saving name. 1093 name_with_proper_ext = EnsureMimeExtension(name_with_proper_ext, 1094 contents_mime_type); 1095 // Adjust extension for complete types. 1096 if (can_save_as_complete) 1097 name_with_proper_ext = EnsureHtmlExtension(name_with_proper_ext); 1098 1099 FilePath::StringType file_name = name_with_proper_ext.value(); 1100 file_util::ReplaceIllegalCharactersInPath(&file_name, ' '); 1101 return FilePath(file_name); 1102} 1103 1104FilePath SavePackage::EnsureHtmlExtension(const FilePath& name) { 1105 // If the file name doesn't have an extension suitable for HTML files, 1106 // append one. 1107 FilePath::StringType ext = name.Extension(); 1108 if (!ext.empty()) 1109 ext.erase(ext.begin()); // Erase preceding '.'. 1110 std::string mime_type; 1111 if (!net::GetMimeTypeFromExtension(ext, &mime_type) || 1112 !CanSaveAsComplete(mime_type)) { 1113 return FilePath(name.value() + FILE_PATH_LITERAL(".") + 1114 kDefaultHtmlExtension); 1115 } 1116 return name; 1117} 1118 1119FilePath SavePackage::EnsureMimeExtension(const FilePath& name, 1120 const std::string& contents_mime_type) { 1121 // Start extension at 1 to skip over period if non-empty. 1122 FilePath::StringType ext = name.Extension().length() ? 1123 name.Extension().substr(1) : name.Extension(); 1124 FilePath::StringType suggested_extension = 1125 ExtensionForMimeType(contents_mime_type); 1126 std::string mime_type; 1127 if (!suggested_extension.empty() && 1128 (!net::GetMimeTypeFromExtension(ext, &mime_type) || 1129 !IsSavableContents(mime_type))) { 1130 // Extension is absent or needs to be updated. 1131 return FilePath(name.value() + FILE_PATH_LITERAL(".") + 1132 suggested_extension); 1133 } 1134 return name; 1135} 1136 1137const FilePath::CharType* SavePackage::ExtensionForMimeType( 1138 const std::string& contents_mime_type) { 1139 static const struct { 1140 const FilePath::CharType *mime_type; 1141 const FilePath::CharType *suggested_extension; 1142 } extensions[] = { 1143 { FILE_PATH_LITERAL("text/html"), kDefaultHtmlExtension }, 1144 { FILE_PATH_LITERAL("text/xml"), FILE_PATH_LITERAL("xml") }, 1145 { FILE_PATH_LITERAL("application/xhtml+xml"), FILE_PATH_LITERAL("xhtml") }, 1146 { FILE_PATH_LITERAL("text/plain"), FILE_PATH_LITERAL("txt") }, 1147 { FILE_PATH_LITERAL("text/css"), FILE_PATH_LITERAL("css") }, 1148 }; 1149#if defined(OS_POSIX) 1150 FilePath::StringType mime_type(contents_mime_type); 1151#elif defined(OS_WIN) 1152 FilePath::StringType mime_type(UTF8ToWide(contents_mime_type)); 1153#endif // OS_WIN 1154 for (uint32 i = 0; i < ARRAYSIZE_UNSAFE(extensions); ++i) { 1155 if (mime_type == extensions[i].mime_type) 1156 return extensions[i].suggested_extension; 1157 } 1158 return FILE_PATH_LITERAL(""); 1159} 1160 1161 1162 1163// static. 1164// Check whether the preference has the preferred directory for saving file. If 1165// not, initialize it with default directory. 1166FilePath SavePackage::GetSaveDirPreference(PrefService* prefs) { 1167 DCHECK(prefs); 1168 1169 if (!prefs->FindPreference(prefs::kSaveFileDefaultDirectory)) { 1170 DCHECK(prefs->FindPreference(prefs::kDownloadDefaultDirectory)); 1171 FilePath default_save_path = prefs->GetFilePath( 1172 prefs::kDownloadDefaultDirectory); 1173 prefs->RegisterFilePathPref(prefs::kSaveFileDefaultDirectory, 1174 default_save_path); 1175 } 1176 1177 // Get the directory from preference. 1178 FilePath save_file_path = prefs->GetFilePath( 1179 prefs::kSaveFileDefaultDirectory); 1180 DCHECK(!save_file_path.empty()); 1181 1182 return save_file_path; 1183} 1184 1185void SavePackage::GetSaveInfo() { 1186 // Can't use tab_contents_ in the file thread, so get the data that we need 1187 // before calling to it. 1188 PrefService* prefs = tab_contents_->profile()->GetPrefs(); 1189 FilePath website_save_dir = GetSaveDirPreference(prefs); 1190 FilePath download_save_dir = prefs->GetFilePath( 1191 prefs::kDownloadDefaultDirectory); 1192 std::string mime_type = tab_contents_->contents_mime_type(); 1193 1194 BrowserThread::PostTask( 1195 BrowserThread::FILE, FROM_HERE, 1196 NewRunnableMethod(this, &SavePackage::CreateDirectoryOnFileThread, 1197 website_save_dir, download_save_dir, mime_type)); 1198} 1199 1200void SavePackage::CreateDirectoryOnFileThread( 1201 const FilePath& website_save_dir, 1202 const FilePath& download_save_dir, 1203 const std::string& mime_type) { 1204 FilePath save_dir; 1205 // If the default html/websites save folder doesn't exist... 1206 if (!file_util::DirectoryExists(website_save_dir)) { 1207 // If the default download dir doesn't exist, create it. 1208 if (!file_util::DirectoryExists(download_save_dir)) 1209 file_util::CreateDirectory(download_save_dir); 1210 save_dir = download_save_dir; 1211 } else { 1212 // If it does exist, use the default save dir param. 1213 save_dir = website_save_dir; 1214 } 1215 1216 bool can_save_as_complete = CanSaveAsComplete(mime_type); 1217 FilePath suggested_filename = GetSuggestedNameForSaveAs(can_save_as_complete, 1218 mime_type); 1219 FilePath::StringType pure_file_name = 1220 suggested_filename.RemoveExtension().BaseName().value(); 1221 FilePath::StringType file_name_ext = suggested_filename.Extension(); 1222 1223 // Need to make sure the suggested file name is not too long. 1224 uint32 max_path = kMaxFilePathLength; 1225#if defined(OS_POSIX) 1226 // On POSIX, the length of |pure_file_name| + |file_name_ext| is further 1227 // restricted by NAME_MAX. The maximum allowed path looks like: 1228 // '/path/to/save_dir' + '/' + NAME_MAX. 1229 max_path = std::min(max_path, 1230 static_cast<uint32>(save_dir.value().length()) + 1231 NAME_MAX + 1); 1232#endif 1233 if (GetSafePureFileName(save_dir, file_name_ext, max_path, &pure_file_name)) { 1234 save_dir = save_dir.Append(pure_file_name + file_name_ext); 1235 } else { 1236 // Cannot create a shorter filename. This will cause the save as operation 1237 // to fail unless the user pick a shorter name. Continuing even though it 1238 // will fail because returning means no save as popup for the user, which 1239 // is even more confusing. This case should be rare though. 1240 save_dir = save_dir.Append(suggested_filename); 1241 } 1242 1243 BrowserThread::PostTask( 1244 BrowserThread::UI, FROM_HERE, 1245 NewRunnableMethod(this, &SavePackage::ContinueGetSaveInfo, save_dir, 1246 can_save_as_complete)); 1247} 1248 1249void SavePackage::ContinueGetSaveInfo(const FilePath& suggested_path, 1250 bool can_save_as_complete) { 1251 // Use "Web Page, Complete" option as default choice of saving page. 1252 int file_type_index = 2; 1253 SelectFileDialog::FileTypeInfo file_type_info; 1254 FilePath::StringType default_extension; 1255 1256 // If the contents can not be saved as complete-HTML, do not show the 1257 // file filters. 1258 if (can_save_as_complete) { 1259 bool add_extra_extension = false; 1260 FilePath::StringType extra_extension; 1261 if (!suggested_path.Extension().empty() && 1262 suggested_path.Extension().compare(FILE_PATH_LITERAL("htm")) && 1263 suggested_path.Extension().compare(FILE_PATH_LITERAL("html"))) { 1264 add_extra_extension = true; 1265 extra_extension = suggested_path.Extension().substr(1); 1266 } 1267 file_type_info.extensions.resize(2); 1268 file_type_info.extensions[0].push_back(FILE_PATH_LITERAL("htm")); 1269 file_type_info.extensions[0].push_back(FILE_PATH_LITERAL("html")); 1270 if (add_extra_extension) 1271 file_type_info.extensions[0].push_back(extra_extension); 1272 file_type_info.extension_description_overrides.push_back( 1273 l10n_util::GetStringUTF16(IDS_SAVE_PAGE_DESC_HTML_ONLY)); 1274 file_type_info.extensions[1].push_back(FILE_PATH_LITERAL("htm")); 1275 file_type_info.extensions[1].push_back(FILE_PATH_LITERAL("html")); 1276 if (add_extra_extension) 1277 file_type_info.extensions[1].push_back(extra_extension); 1278 file_type_info.extension_description_overrides.push_back( 1279 l10n_util::GetStringUTF16(IDS_SAVE_PAGE_DESC_COMPLETE)); 1280 file_type_info.include_all_files = false; 1281 default_extension = kDefaultHtmlExtension; 1282 } else { 1283 file_type_info.extensions.resize(1); 1284 file_type_info.extensions[0].push_back(suggested_path.Extension()); 1285 if (!file_type_info.extensions[0][0].empty()) 1286 file_type_info.extensions[0][0].erase(0, 1); // drop the . 1287 file_type_info.include_all_files = true; 1288 file_type_index = 1; 1289 } 1290 1291 if (g_should_prompt_for_filename) { 1292 if (!select_file_dialog_.get()) 1293 select_file_dialog_ = SelectFileDialog::Create(this); 1294 select_file_dialog_->SelectFile(SelectFileDialog::SELECT_SAVEAS_FILE, 1295 string16(), 1296 suggested_path, 1297 &file_type_info, 1298 file_type_index, 1299 default_extension, 1300 platform_util::GetTopLevel( 1301 tab_contents_->GetNativeView()), 1302 NULL); 1303 } else { 1304 // Just use 'suggested_path' instead of opening the dialog prompt. 1305 ContinueSave(suggested_path, file_type_index); 1306 } 1307} 1308 1309// Called after the save file dialog box returns. 1310void SavePackage::ContinueSave(const FilePath& final_name, 1311 int index) { 1312 // Ensure the filename is safe. 1313 saved_main_file_path_ = final_name; 1314 download_util::GenerateSafeFileName(tab_contents_->contents_mime_type(), 1315 &saved_main_file_path_); 1316 1317 // The option index is not zero-based. 1318 DCHECK(index > 0 && index < 3); 1319 saved_main_directory_path_ = saved_main_file_path_.DirName(); 1320 1321 PrefService* prefs = tab_contents_->profile()->GetPrefs(); 1322 StringPrefMember save_file_path; 1323 save_file_path.Init(prefs::kSaveFileDefaultDirectory, prefs, NULL); 1324#if defined(OS_POSIX) 1325 std::string path_string = saved_main_directory_path_.value(); 1326#elif defined(OS_WIN) 1327 std::string path_string = WideToUTF8(saved_main_directory_path_.value()); 1328#endif 1329 // If user change the default saving directory, we will remember it just 1330 // like IE and FireFox. 1331 if (!tab_contents_->profile()->IsOffTheRecord() && 1332 save_file_path.GetValue() != path_string) { 1333 save_file_path.SetValue(path_string); 1334 } 1335 1336 save_type_ = (index == 1) ? SavePackage::SAVE_AS_ONLY_HTML : 1337 SavePackage::SAVE_AS_COMPLETE_HTML; 1338 1339 if (save_type_ == SavePackage::SAVE_AS_COMPLETE_HTML) { 1340 // Make new directory for saving complete file. 1341 saved_main_directory_path_ = saved_main_directory_path_.Append( 1342 saved_main_file_path_.RemoveExtension().BaseName().value() + 1343 FILE_PATH_LITERAL("_files")); 1344 } 1345 1346 Init(); 1347} 1348 1349// Static 1350bool SavePackage::IsSavableURL(const GURL& url) { 1351 for (int i = 0; chrome::kSavableSchemes[i] != NULL; ++i) { 1352 if (url.SchemeIs(chrome::kSavableSchemes[i])) { 1353 return true; 1354 } 1355 } 1356 return false; 1357} 1358 1359// Static 1360bool SavePackage::IsSavableContents(const std::string& contents_mime_type) { 1361 // WebKit creates Document object when MIME type is application/xhtml+xml, 1362 // so we also support this MIME type. 1363 return contents_mime_type == "text/html" || 1364 contents_mime_type == "text/xml" || 1365 contents_mime_type == "application/xhtml+xml" || 1366 contents_mime_type == "text/plain" || 1367 contents_mime_type == "text/css" || 1368 net::IsSupportedJavascriptMimeType(contents_mime_type.c_str()); 1369} 1370 1371// SelectFileDialog::Listener interface. 1372void SavePackage::FileSelected(const FilePath& path, 1373 int index, void* params) { 1374 ContinueSave(path, index); 1375} 1376 1377void SavePackage::FileSelectionCanceled(void* params) { 1378} 1379