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