Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2012 The Chromium Authors. All rights reserved. | 1 // Copyright 2012 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "chrome/browser/win/jumplist.h" | 5 #include "chrome/browser/win/jumplist.h" |
| 6 | 6 |
| 7 #include <windows.h> | |
| 7 #include "base/bind.h" | 8 #include "base/bind.h" |
|
gab
2016/12/09 23:17:28
Empty line between system headers and Chrome heade
chengx
2016/12/10 00:59:05
Done.
| |
| 8 #include "base/bind_helpers.h" | 9 #include "base/bind_helpers.h" |
| 9 #include "base/command_line.h" | 10 #include "base/command_line.h" |
| 10 #include "base/files/file_util.h" | 11 #include "base/files/file_util.h" |
| 11 #include "base/macros.h" | 12 #include "base/macros.h" |
| 12 #include "base/metrics/histogram_macros.h" | 13 #include "base/metrics/histogram_macros.h" |
| 13 #include "base/path_service.h" | 14 #include "base/path_service.h" |
| 14 #include "base/strings/string_util.h" | 15 #include "base/strings/string_util.h" |
| 15 #include "base/strings/utf_string_conversions.h" | 16 #include "base/strings/utf_string_conversions.h" |
| 16 #include "base/threading/thread.h" | 17 #include "base/threading/thread.h" |
| 17 #include "base/threading/thread_restrictions.h" | 18 #include "base/threading/thread_restrictions.h" |
| (...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 52 using JumpListData = JumpList::JumpListData; | 53 using JumpListData = JumpList::JumpListData; |
| 53 | 54 |
| 54 namespace { | 55 namespace { |
| 55 | 56 |
| 56 // Delay jumplist updates to allow collapsing of redundant update requests. | 57 // Delay jumplist updates to allow collapsing of redundant update requests. |
| 57 const int kDelayForJumplistUpdateInMS = 3500; | 58 const int kDelayForJumplistUpdateInMS = 3500; |
| 58 | 59 |
| 59 // JumpList folder's move operation status. | 60 // JumpList folder's move operation status. |
| 60 enum MoveOperationResult { | 61 enum MoveOperationResult { |
| 61 SUCCESS = 0, | 62 SUCCESS = 0, |
| 62 MAX_PATH_CHECK_FAILED = 1 << 0, | 63 MOVE_FILE_EX_FAILED = 1 << 0, |
| 63 MOVE_FILE_EX_FAILED = 1 << 1, | 64 COPY_DIR_FAILED = 1 << 1, |
| 64 COPY_AND_DELETE_DIR_FAILED = 1 << 2, | 65 DELETE_FILE_RECURSIVE_FAILED = 1 << 2, |
| 65 DELETE_FAILED = 1 << 3, | 66 DELETE_SOURCE_FOLDER_FAILED = 1 << 3, |
| 66 END = 1 << 4 | 67 DELETE_TARGET_FOLDER_FAILED = 1 << 4, |
| 68 DELETE_DIR_READ_ONLY = 1 << 5, | |
| 69 END = 1 << 6 | |
| 67 }; | 70 }; |
| 68 | 71 |
| 69 // Append the common switches to each shell link. | 72 // Append the common switches to each shell link. |
| 70 void AppendCommonSwitches(ShellLinkItem* shell_link) { | 73 void AppendCommonSwitches(ShellLinkItem* shell_link) { |
| 71 const char* kSwitchNames[] = { switches::kUserDataDir }; | 74 const char* kSwitchNames[] = { switches::kUserDataDir }; |
| 72 const base::CommandLine& command_line = | 75 const base::CommandLine& command_line = |
| 73 *base::CommandLine::ForCurrentProcess(); | 76 *base::CommandLine::ForCurrentProcess(); |
| 74 shell_link->GetCommandLine()->CopySwitchesFrom(command_line, | 77 shell_link->GetCommandLine()->CopySwitchesFrom(command_line, |
| 75 kSwitchNames, | 78 kSwitchNames, |
| 76 arraysize(kSwitchNames)); | 79 arraysize(kSwitchNames)); |
| (...skipping 141 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 218 | 221 |
| 219 // Commit this transaction and send the updated JumpList to Windows. | 222 // Commit this transaction and send the updated JumpList to Windows. |
| 220 if (!jumplist_updater.CommitUpdate()) | 223 if (!jumplist_updater.CommitUpdate()) |
| 221 return false; | 224 return false; |
| 222 | 225 |
| 223 return true; | 226 return true; |
| 224 } | 227 } |
| 225 | 228 |
| 226 // A wrapper function for recording UMA histogram. | 229 // A wrapper function for recording UMA histogram. |
| 227 void RecordFolderMoveResult(int move_operation_status) { | 230 void RecordFolderMoveResult(int move_operation_status) { |
| 228 UMA_HISTOGRAM_ENUMERATION("WinJumplist.FolderMoveResults", | 231 UMA_HISTOGRAM_ENUMERATION("WinJumplist.DetailedFolderMoveResults", |
| 229 move_operation_status, MoveOperationResult::END); | 232 move_operation_status, MoveOperationResult::END); |
| 230 } | 233 } |
| 231 | 234 |
| 235 // This function is an exact copy of //base for UMA debugging purpose of | |
| 236 // DeleteFile() below. | |
| 237 // Deletes all files and directories in a path. | |
| 238 // Returns false on the first failure it encounters. | |
| 239 bool DeleteFileRecursive(const base::FilePath& path, | |
| 240 const base::FilePath::StringType& pattern, | |
| 241 bool recursive) { | |
| 242 base::FileEnumerator traversal( | |
| 243 path, false, | |
| 244 base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES, pattern); | |
| 245 for (base::FilePath current = traversal.Next(); !current.empty(); | |
| 246 current = traversal.Next()) { | |
| 247 // Try to clear the read-only bit if we find it. | |
| 248 base::FileEnumerator::FileInfo info = traversal.GetInfo(); | |
| 249 if ((info.find_data().dwFileAttributes & FILE_ATTRIBUTE_READONLY) && | |
| 250 (recursive || !info.IsDirectory())) { | |
| 251 ::SetFileAttributes( | |
| 252 current.value().c_str(), | |
| 253 info.find_data().dwFileAttributes & ~FILE_ATTRIBUTE_READONLY); | |
| 254 } | |
| 255 | |
| 256 if (info.IsDirectory()) { | |
| 257 if (recursive && (!DeleteFileRecursive(current, pattern, true) || | |
| 258 !::RemoveDirectory(current.value().c_str()))) | |
| 259 return false; | |
| 260 } else if (!::DeleteFile(current.value().c_str())) { | |
| 261 return false; | |
| 262 } | |
| 263 } | |
| 264 return true; | |
| 265 } | |
| 266 | |
| 267 // This function is a copy from //base for UMA debugging purpose. | |
| 268 // Deletes all files and directories in a path. | |
| 269 // Returns false on the first failure it encounters. | |
| 270 bool DeleteFile(const base::FilePath& path, | |
| 271 bool recursive, | |
| 272 int* move_operation_status) { | |
| 273 base::ThreadRestrictions::AssertIOAllowed(); | |
| 274 | |
| 275 if (path.empty()) | |
| 276 return true; | |
| 277 | |
| 278 if (path.value().length() >= MAX_PATH) | |
| 279 return false; | |
| 280 | |
| 281 // Handle any path with wildcards. | |
| 282 if (path.BaseName().value().find_first_of(L"*?") != | |
| 283 base::FilePath::StringType::npos) { | |
| 284 bool ret = | |
| 285 DeleteFileRecursive(path.DirName(), path.BaseName().value(), recursive); | |
| 286 if (!ret) { | |
| 287 *move_operation_status |= | |
| 288 MoveOperationResult::DELETE_FILE_RECURSIVE_FAILED; | |
| 289 *move_operation_status |= | |
| 290 MoveOperationResult::DELETE_SOURCE_FOLDER_FAILED; | |
| 291 } | |
| 292 return ret; | |
| 293 } | |
| 294 DWORD attr = ::GetFileAttributes(path.value().c_str()); | |
| 295 // We're done if we can't find the path. | |
| 296 if (attr == INVALID_FILE_ATTRIBUTES) | |
| 297 return true; | |
| 298 // We may need to clear the read-only bit. | |
| 299 if ((attr & FILE_ATTRIBUTE_READONLY) && | |
| 300 !::SetFileAttributes(path.value().c_str(), | |
| 301 attr & ~FILE_ATTRIBUTE_READONLY)) { | |
| 302 *move_operation_status |= MoveOperationResult::DELETE_DIR_READ_ONLY; | |
| 303 *move_operation_status |= MoveOperationResult::DELETE_FILE_RECURSIVE_FAILED; | |
| 304 *move_operation_status |= MoveOperationResult::DELETE_SOURCE_FOLDER_FAILED; | |
| 305 return false; | |
| 306 } | |
| 307 // Directories are handled differently if they're recursive. | |
| 308 if (!(attr & FILE_ATTRIBUTE_DIRECTORY)) | |
| 309 return !!::DeleteFile(path.value().c_str()); | |
| 310 // Handle a simple, single file delete. | |
| 311 if (!recursive || DeleteFileRecursive(path, L"*", true)) { | |
| 312 bool ret = !!::RemoveDirectory(path.value().c_str()); | |
| 313 if (!ret) | |
| 314 *move_operation_status |= | |
| 315 MoveOperationResult::DELETE_SOURCE_FOLDER_FAILED; | |
| 316 return ret; | |
| 317 } | |
| 318 | |
| 319 *move_operation_status |= MoveOperationResult::DELETE_FILE_RECURSIVE_FAILED; | |
| 320 *move_operation_status |= MoveOperationResult::DELETE_SOURCE_FOLDER_FAILED; | |
| 321 return false; | |
| 322 } | |
| 323 | |
| 324 // This function is mostly copied from //base. | |
| 325 // It has some issue when the dest dir string contains the src dir string. | |
| 326 // Temporary fix by replacing the old logic with IsParent() call. | |
| 327 bool CopyDirectoryTemp(const base::FilePath& from_path, | |
| 328 const base::FilePath& to_path, | |
| 329 bool recursive) { | |
| 330 // NOTE(maruel): Previous version of this function used to call | |
| 331 // SHFileOperation(). This used to copy the file attributes and extended | |
| 332 // attributes, OLE structured storage, NTFS file system alternate data | |
| 333 // streams, SECURITY_DESCRIPTOR. In practice, this is not what we want, we | |
| 334 // want the containing directory to propagate its SECURITY_DESCRIPTOR. | |
| 335 base::ThreadRestrictions::AssertIOAllowed(); | |
| 336 | |
| 337 // NOTE: I suspect we could support longer paths, but that would involve | |
| 338 // analyzing all our usage of files. | |
| 339 if (from_path.value().length() >= MAX_PATH || | |
| 340 to_path.value().length() >= MAX_PATH) { | |
| 341 return false; | |
| 342 } | |
| 343 | |
| 344 // This function does not properly handle destinations within the source. | |
| 345 base::FilePath real_to_path = to_path; | |
| 346 if (base::PathExists(real_to_path)) { | |
| 347 real_to_path = base::MakeAbsoluteFilePath(real_to_path); | |
| 348 if (real_to_path.empty()) | |
| 349 return false; | |
| 350 } else { | |
| 351 real_to_path = base::MakeAbsoluteFilePath(real_to_path.DirName()); | |
| 352 if (real_to_path.empty()) | |
| 353 return false; | |
| 354 } | |
| 355 base::FilePath real_from_path = base::MakeAbsoluteFilePath(from_path); | |
| 356 if (real_from_path.empty()) | |
| 357 return false; | |
| 358 | |
| 359 // Originally this CopyDirectory function returns false when to_path string | |
| 360 // contains from_path string. While this can be that the to_path is within | |
| 361 // the from_path, e.g., parent-child relationship in terms of folder, it | |
|
gab
2016/12/09 23:17:28
|to_path| , |from_path| (i.e. vars in ||)
| |
| 362 // also can be the two paths are in the same folder, just that one name | |
| 363 // contains | |
| 364 // the other, e.g. C:\\JumpListIcon and C:\\JumpListIconOld. | |
| 365 // We make this function not return false in the latter situation by calling | |
| 366 // IsParent() function. | |
| 367 if (real_to_path.value().size() >= real_from_path.value().size() && | |
| 368 real_from_path.IsParent(real_to_path)) { | |
| 369 return false; | |
| 370 } | |
| 371 | |
| 372 int traverse_type = base::FileEnumerator::FILES; | |
| 373 if (recursive) | |
| 374 traverse_type |= base::FileEnumerator::DIRECTORIES; | |
| 375 base::FileEnumerator traversal(from_path, recursive, traverse_type); | |
| 376 | |
| 377 if (!base::PathExists(from_path)) { | |
| 378 DLOG(ERROR) << "CopyDirectory() couldn't stat source directory: " | |
| 379 << from_path.value().c_str(); | |
| 380 return false; | |
| 381 } | |
| 382 // TODO(maruel): This is not necessary anymore. | |
| 383 DCHECK(recursive || base::DirectoryExists(from_path)); | |
| 384 | |
| 385 base::FilePath current = from_path; | |
| 386 bool from_is_dir = base::DirectoryExists(from_path); | |
| 387 bool success = true; | |
| 388 base::FilePath from_path_base = from_path; | |
| 389 if (recursive && base::DirectoryExists(to_path)) { | |
| 390 // If the destination already exists and is a directory, then the | |
| 391 // top level of source needs to be copied. | |
| 392 from_path_base = from_path.DirName(); | |
| 393 } | |
| 394 | |
| 395 while (success && !current.empty()) { | |
| 396 // current is the source path, including from_path, so append | |
| 397 // the suffix after from_path to to_path to create the target_path. | |
| 398 base::FilePath target_path(to_path); | |
| 399 if (from_path_base != current) { | |
| 400 if (!from_path_base.AppendRelativePath(current, &target_path)) { | |
| 401 success = false; | |
| 402 break; | |
| 403 } | |
| 404 } | |
| 405 | |
| 406 if (from_is_dir) { | |
| 407 if (!base::DirectoryExists(target_path) && | |
| 408 !::CreateDirectory(target_path.value().c_str(), NULL)) { | |
| 409 DLOG(ERROR) << "CopyDirectory() couldn't create directory: " | |
| 410 << target_path.value().c_str(); | |
| 411 success = false; | |
| 412 } | |
| 413 } else if (!base::CopyFile(current, target_path)) { | |
| 414 DLOG(ERROR) << "CopyDirectory() couldn't create file: " | |
| 415 << target_path.value().c_str(); | |
| 416 success = false; | |
| 417 } | |
| 418 | |
| 419 current = traversal.Next(); | |
| 420 if (!current.empty()) | |
| 421 from_is_dir = traversal.GetInfo().IsDirectory(); | |
| 422 } | |
| 423 | |
| 424 return success; | |
| 425 } | |
| 426 | |
| 232 // This function is a temporary fork of Move() and MoveUnsafe() in | 427 // This function is a temporary fork of Move() and MoveUnsafe() in |
| 233 // base/files/file_util.h, where UMA functions are added to gather user data. | 428 // base/files/file_util.h, where UMA functions are added to gather user data. |
| 234 // As //base is highly mature, we tend not to add this temporary function | 429 // As //base is highly mature, we tend not to add this temporary function |
| 235 // in it. This change will be reverted after user data gathered. | 430 // in it. This change will be reverted after user data gathered. |
| 236 bool MoveDebugWithUMA(const base::FilePath& from_path, | 431 bool MoveDebugWithUMA(const base::FilePath& from_path, |
| 237 const base::FilePath& to_path, | 432 const base::FilePath& to_path, |
| 238 int folder_delete_fail) { | 433 int folder_delete_fail) { |
| 239 if (from_path.ReferencesParent() || to_path.ReferencesParent()) | 434 if (from_path.ReferencesParent() || to_path.ReferencesParent()) |
| 240 return false; | 435 return false; |
| 241 | 436 |
| 242 base::ThreadRestrictions::AssertIOAllowed(); | 437 base::ThreadRestrictions::AssertIOAllowed(); |
| 243 | 438 |
| 244 // This variable records the status of move operations. | 439 // This variable records the status of move operations. |
| 245 int move_operation_status = MoveOperationResult::SUCCESS; | 440 int move_operation_status = MoveOperationResult::SUCCESS; |
| 246 if (folder_delete_fail) | 441 if (folder_delete_fail) |
| 247 move_operation_status |= MoveOperationResult::DELETE_FAILED; | 442 move_operation_status |= MoveOperationResult::DELETE_TARGET_FOLDER_FAILED; |
| 248 | 443 |
| 249 // NOTE: I suspect we could support longer paths, but that would involve | 444 // NOTE: I suspect we could support longer paths, but that would involve |
| 250 // analyzing all our usage of files. | 445 // analyzing all our usage of files. |
| 251 if (from_path.value().length() >= MAX_PATH || | 446 if (from_path.value().length() >= MAX_PATH || |
| 252 to_path.value().length() >= MAX_PATH) { | 447 to_path.value().length() >= MAX_PATH) { |
| 253 move_operation_status |= MoveOperationResult::MAX_PATH_CHECK_FAILED; | |
| 254 RecordFolderMoveResult(move_operation_status); | |
| 255 return false; | 448 return false; |
| 256 } | 449 } |
| 257 if (MoveFileEx(from_path.value().c_str(), to_path.value().c_str(), | 450 if (MoveFileEx(from_path.value().c_str(), to_path.value().c_str(), |
| 258 MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING) != 0) { | 451 MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING) != 0) { |
| 259 RecordFolderMoveResult(move_operation_status); | 452 RecordFolderMoveResult(move_operation_status); |
| 260 return true; | 453 return true; |
| 261 } | 454 } |
| 262 | 455 |
| 263 move_operation_status |= MoveOperationResult::MOVE_FILE_EX_FAILED; | 456 move_operation_status |= MoveOperationResult::MOVE_FILE_EX_FAILED; |
| 264 | 457 |
| 265 // Keep the last error value from MoveFileEx around in case the below | 458 // Keep the last error value from MoveFileEx around in case the below |
| 266 // fails. | 459 // fails. |
| 267 bool ret = false; | 460 bool ret = false; |
| 268 DWORD last_error = ::GetLastError(); | 461 DWORD last_error = ::GetLastError(); |
| 269 | 462 |
| 270 if (base::DirectoryExists(from_path)) { | 463 if (base::DirectoryExists(from_path)) { |
| 271 // MoveFileEx fails if moving directory across volumes. We will simulate | 464 // MoveFileEx fails if moving directory across volumes. We will simulate |
| 272 // the move by using Copy and Delete. Ideally we could check whether | 465 // the move by using Copy and Delete. Ideally we could check whether |
| 273 // from_path and to_path are indeed in different volumes. | 466 // from_path and to_path are indeed in different volumes. |
| 274 ret = base::internal::CopyAndDeleteDirectory(from_path, to_path); | 467 if (CopyDirectoryTemp(from_path, to_path, true)) { |
| 468 if (DeleteFile(from_path, true, &move_operation_status)) | |
| 469 ret = true; | |
| 470 } else { | |
| 471 move_operation_status |= MoveOperationResult::COPY_DIR_FAILED; | |
| 472 move_operation_status |= | |
| 473 MoveOperationResult::DELETE_FILE_RECURSIVE_FAILED; | |
| 474 move_operation_status |= MoveOperationResult::DELETE_SOURCE_FOLDER_FAILED; | |
| 475 } | |
| 275 } | 476 } |
| 276 | 477 |
| 277 if (!ret) { | 478 if (!ret) { |
| 278 // Leave a clue about what went wrong so that it can be (at least) picked | 479 // Leave a clue about what went wrong so that it can be (at least) picked |
| 279 // up by a PLOG entry. | 480 // up by a PLOG entry. |
| 280 ::SetLastError(last_error); | 481 ::SetLastError(last_error); |
| 281 move_operation_status |= MoveOperationResult::COPY_AND_DELETE_DIR_FAILED; | |
| 282 } | 482 } |
| 283 | 483 |
| 284 RecordFolderMoveResult(move_operation_status); | 484 RecordFolderMoveResult(move_operation_status); |
| 285 | 485 |
| 286 return ret; | 486 return ret; |
| 287 } | 487 } |
| 288 | 488 |
| 289 // Updates the jumplist, once all the data has been fetched. | 489 // Updates the jumplist, once all the data has been fetched. |
| 290 void RunUpdateOnFileThread( | 490 void RunUpdateOnFileThread( |
| 291 IncognitoModePrefs::Availability incognito_availability, | 491 IncognitoModePrefs::Availability incognito_availability, |
| (...skipping 379 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 671 void JumpList::TopSitesLoaded(history::TopSites* top_sites) { | 871 void JumpList::TopSitesLoaded(history::TopSites* top_sites) { |
| 672 } | 872 } |
| 673 | 873 |
| 674 void JumpList::TopSitesChanged(history::TopSites* top_sites, | 874 void JumpList::TopSitesChanged(history::TopSites* top_sites, |
| 675 ChangeReason change_reason) { | 875 ChangeReason change_reason) { |
| 676 top_sites->GetMostVisitedURLs( | 876 top_sites->GetMostVisitedURLs( |
| 677 base::Bind(&JumpList::OnMostVisitedURLsAvailable, | 877 base::Bind(&JumpList::OnMostVisitedURLsAvailable, |
| 678 weak_ptr_factory_.GetWeakPtr()), | 878 weak_ptr_factory_.GetWeakPtr()), |
| 679 false); | 879 false); |
| 680 } | 880 } |
| OLD | NEW |