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