Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(31)

Side by Side Diff: chrome/browser/win/jumplist.cc

Issue 2570693002: Fix base::CopyDirectory() and JumpListIcons(Old) folders' operation logic (Closed)
Patch Set: Created 4 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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
9 #include "base/bind.h" 7 #include "base/bind.h"
10 #include "base/bind_helpers.h" 8 #include "base/bind_helpers.h"
11 #include "base/command_line.h" 9 #include "base/command_line.h"
12 #include "base/files/file_enumerator.h"
13 #include "base/files/file_util.h" 10 #include "base/files/file_util.h"
14 #include "base/macros.h" 11 #include "base/macros.h"
15 #include "base/metrics/histogram_macros.h" 12 #include "base/metrics/histogram_macros.h"
16 #include "base/path_service.h" 13 #include "base/path_service.h"
17 #include "base/strings/string_util.h" 14 #include "base/strings/string_util.h"
18 #include "base/strings/utf_string_conversions.h" 15 #include "base/strings/utf_string_conversions.h"
19 #include "base/threading/thread.h" 16 #include "base/threading/thread.h"
20 #include "base/threading/thread_restrictions.h"
21 #include "base/trace_event/trace_event.h" 17 #include "base/trace_event/trace_event.h"
22 #include "chrome/browser/chrome_notification_types.h" 18 #include "chrome/browser/chrome_notification_types.h"
23 #include "chrome/browser/favicon/favicon_service_factory.h" 19 #include "chrome/browser/favicon/favicon_service_factory.h"
24 #include "chrome/browser/history/top_sites_factory.h" 20 #include "chrome/browser/history/top_sites_factory.h"
25 #include "chrome/browser/metrics/jumplist_metrics_win.h" 21 #include "chrome/browser/metrics/jumplist_metrics_win.h"
26 #include "chrome/browser/profiles/profile.h" 22 #include "chrome/browser/profiles/profile.h"
27 #include "chrome/browser/sessions/tab_restore_service_factory.h" 23 #include "chrome/browser/sessions/tab_restore_service_factory.h"
28 #include "chrome/browser/shell_integration_win.h" 24 #include "chrome/browser/shell_integration_win.h"
29 #include "chrome/common/chrome_constants.h" 25 #include "chrome/common/chrome_constants.h"
30 #include "chrome/common/chrome_switches.h" 26 #include "chrome/common/chrome_switches.h"
(...skipping 21 matching lines...) Expand all
52 #include "url/gurl.h" 48 #include "url/gurl.h"
53 49
54 using content::BrowserThread; 50 using content::BrowserThread;
55 using JumpListData = JumpList::JumpListData; 51 using JumpListData = JumpList::JumpListData;
56 52
57 namespace { 53 namespace {
58 54
59 // Delay jumplist updates to allow collapsing of redundant update requests. 55 // Delay jumplist updates to allow collapsing of redundant update requests.
60 const int kDelayForJumplistUpdateInMS = 3500; 56 const int kDelayForJumplistUpdateInMS = 3500;
61 57
62 // JumpList folder's move operation status.
63 enum MoveOperationResult {
64 SUCCESS = 0,
65 MOVE_FILE_EX_FAILED = 1 << 0,
66 COPY_DIR_FAILED = 1 << 1,
67 DELETE_FILE_RECURSIVE_FAILED = 1 << 2,
68 DELETE_SOURCE_FOLDER_FAILED = 1 << 3,
69 DELETE_TARGET_FOLDER_FAILED = 1 << 4,
70 DELETE_DIR_READ_ONLY = 1 << 5,
71 END = 1 << 6
72 };
73
74 // Append the common switches to each shell link. 58 // Append the common switches to each shell link.
75 void AppendCommonSwitches(ShellLinkItem* shell_link) { 59 void AppendCommonSwitches(ShellLinkItem* shell_link) {
76 const char* kSwitchNames[] = { switches::kUserDataDir }; 60 const char* kSwitchNames[] = { switches::kUserDataDir };
77 const base::CommandLine& command_line = 61 const base::CommandLine& command_line =
78 *base::CommandLine::ForCurrentProcess(); 62 *base::CommandLine::ForCurrentProcess();
79 shell_link->GetCommandLine()->CopySwitchesFrom(command_line, 63 shell_link->GetCommandLine()->CopySwitchesFrom(command_line,
80 kSwitchNames, 64 kSwitchNames,
81 arraysize(kSwitchNames)); 65 arraysize(kSwitchNames));
82 } 66 }
83 67
(...skipping 137 matching lines...) Expand 10 before | Expand all | Expand 10 after
221 if (!UpdateTaskCategory(&jumplist_updater, incognito_availability)) 205 if (!UpdateTaskCategory(&jumplist_updater, incognito_availability))
222 return false; 206 return false;
223 207
224 // Commit this transaction and send the updated JumpList to Windows. 208 // Commit this transaction and send the updated JumpList to Windows.
225 if (!jumplist_updater.CommitUpdate()) 209 if (!jumplist_updater.CommitUpdate())
226 return false; 210 return false;
227 211
228 return true; 212 return true;
229 } 213 }
230 214
231 // A wrapper function for recording UMA histogram.
232 void RecordFolderMoveResult(int move_operation_status) {
233 UMA_HISTOGRAM_ENUMERATION("WinJumplist.DetailedFolderMoveResults",
234 move_operation_status, MoveOperationResult::END);
235 }
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
429 // This function is a temporary fork of Move() and MoveUnsafe() in
430 // base/files/file_util.h, where UMA functions are added to gather user data.
431 // As //base is highly mature, we tend not to add this temporary function
432 // in it. This change will be reverted after user data gathered.
433 bool MoveDebugWithUMA(const base::FilePath& from_path,
434 const base::FilePath& to_path,
435 int folder_delete_fail) {
436 if (from_path.ReferencesParent() || to_path.ReferencesParent())
437 return false;
438
439 base::ThreadRestrictions::AssertIOAllowed();
440
441 // This variable records the status of move operations.
442 int move_operation_status = MoveOperationResult::SUCCESS;
443 if (folder_delete_fail)
444 move_operation_status |= MoveOperationResult::DELETE_TARGET_FOLDER_FAILED;
445
446 // NOTE: I suspect we could support longer paths, but that would involve
447 // analyzing all our usage of files.
448 if (from_path.value().length() >= MAX_PATH ||
449 to_path.value().length() >= MAX_PATH) {
450 return false;
451 }
452 if (MoveFileEx(from_path.value().c_str(), to_path.value().c_str(),
453 MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING) != 0) {
454 RecordFolderMoveResult(move_operation_status);
455 return true;
456 }
457
458 move_operation_status |= MoveOperationResult::MOVE_FILE_EX_FAILED;
459
460 // Keep the last error value from MoveFileEx around in case the below
461 // fails.
462 bool ret = false;
463 DWORD last_error = ::GetLastError();
464
465 if (base::DirectoryExists(from_path)) {
466 // MoveFileEx fails if moving directory across volumes. We will simulate
467 // the move by using Copy and Delete. Ideally we could check whether
468 // from_path and to_path are indeed in different volumes.
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 }
478 }
479
480 if (!ret) {
481 // Leave a clue about what went wrong so that it can be (at least) picked
482 // up by a PLOG entry.
483 ::SetLastError(last_error);
484 }
485
486 RecordFolderMoveResult(move_operation_status);
487
488 return ret;
489 }
490
491 // Updates the jumplist, once all the data has been fetched. 215 // Updates the jumplist, once all the data has been fetched.
492 void RunUpdateOnFileThread( 216 void RunUpdateOnFileThread(
493 IncognitoModePrefs::Availability incognito_availability, 217 IncognitoModePrefs::Availability incognito_availability,
494 const std::wstring& app_id, 218 const std::wstring& app_id,
495 const base::FilePath& icon_dir, 219 const base::FilePath& icon_dir,
496 base::RefCountedData<JumpListData>* ref_counted_data) { 220 base::RefCountedData<JumpListData>* ref_counted_data) {
497 JumpListData* data = &ref_counted_data->data; 221 JumpListData* data = &ref_counted_data->data;
498 ShellLinkItemList local_most_visited_pages; 222 ShellLinkItemList local_most_visited_pages;
499 ShellLinkItemList local_recently_closed_pages; 223 ShellLinkItemList local_recently_closed_pages;
500 224
501 { 225 {
502 base::AutoLock auto_lock(data->list_lock_); 226 base::AutoLock auto_lock(data->list_lock_);
503 // Make sure we are not out of date: if icon_urls_ is not empty, then 227 // Make sure we are not out of date: if icon_urls_ is not empty, then
504 // another notification has been received since we processed this one 228 // another notification has been received since we processed this one
505 if (!data->icon_urls_.empty()) 229 if (!data->icon_urls_.empty())
506 return; 230 return;
507 231
508 // Make local copies of lists so we can release the lock. 232 // Make local copies of lists so we can release the lock.
509 local_most_visited_pages = data->most_visited_pages_; 233 local_most_visited_pages = data->most_visited_pages_;
510 local_recently_closed_pages = data->recently_closed_pages_; 234 local_recently_closed_pages = data->recently_closed_pages_;
511 } 235 }
512 236
513 // Delete the directory which contains old icon files, rename the current 237 // Delete the directory which contains old icon files, rename the current
514 // icon directory, and create a new directory which contains new JumpList 238 // icon directory, and create a new directory which contains new JumpList
515 // icon files. 239 // icon files.
516 base::FilePath icon_dir_old(icon_dir.value() + L"Old"); 240 base::FilePath icon_dir_old(icon_dir.value() + L"Old");
517 241
518 enum FolderOperationResult { 242 enum FolderOperationResult {
519 SUCCESS = 0, 243 SUCCESS = 0,
520 DELETE_FAILED = 1 << 0, 244 DELETE_DEST_FAILED = 1 << 0,
521 MOVE_FAILED = 1 << 1, 245 MOVE_FAILED = 1 << 1,
522 CREATE_DIR_FAILED = 1 << 2, 246 DELETE_SRC_FAILED = 1 << 2,
247 CREATE_SRC_FAILED = 1 << 3,
523 // This value is beyond the sum of all bit fields above and 248 // This value is beyond the sum of all bit fields above and
524 // should remain last (shifted by one more than the last value) 249 // should remain last (shifted by one more than the last value)
525 END = 1 << 3 250 END = 1 << 4
526 }; 251 };
527 252
528 // This variable records the status of three folder operations. 253 // This variable records the status of three folder operations.
529 int folder_operation_status = FolderOperationResult::SUCCESS; 254 int folder_operation_status = FolderOperationResult::SUCCESS;
530 255
531 if (base::PathExists(icon_dir_old) && !base::DeleteFile(icon_dir_old, true)) 256 if (base::PathExists(icon_dir_old) && !base::DeleteFile(icon_dir_old, true))
532 folder_operation_status |= FolderOperationResult::DELETE_FAILED; 257 folder_operation_status |= FolderOperationResult::DELETE_DEST_FAILED;
533 if (!MoveDebugWithUMA(icon_dir, icon_dir_old, folder_operation_status)) 258 if (!base::Move(icon_dir, icon_dir_old)) {
534 folder_operation_status |= FolderOperationResult::MOVE_FAILED; 259 folder_operation_status |= FolderOperationResult::MOVE_FAILED;
260 // If Move() fails, we force to delete |icon_dir| to avoide file
261 // accumulation
262 // in this directory, which can eventually lead the folder to be huge.
263 if (!base::DeleteFile(icon_dir, true))
264 folder_operation_status |= FolderOperationResult::DELETE_SRC_FAILED;
265 }
535 if (!base::CreateDirectory(icon_dir)) 266 if (!base::CreateDirectory(icon_dir))
536 folder_operation_status |= FolderOperationResult::CREATE_DIR_FAILED; 267 folder_operation_status |= FolderOperationResult::CREATE_SRC_FAILED;
537 268
538 UMA_HISTOGRAM_ENUMERATION("WinJumplist.FolderResults", 269 UMA_HISTOGRAM_ENUMERATION("WinJumplist.DetailedFolderResults",
539 folder_operation_status, 270 folder_operation_status,
540 FolderOperationResult::END); 271 FolderOperationResult::END);
541 272
542 // Create temporary icon files for shortcuts in the "Most Visited" category. 273 // Create temporary icon files for shortcuts in the "Most Visited" category.
543 CreateIconFiles(icon_dir, local_most_visited_pages); 274 CreateIconFiles(icon_dir, local_most_visited_pages);
544 275
545 // Create temporary icon files for shortcuts in the "Recently Closed" 276 // Create temporary icon files for shortcuts in the "Recently Closed"
546 // category. 277 // category.
547 CreateIconFiles(icon_dir, local_recently_closed_pages); 278 CreateIconFiles(icon_dir, local_recently_closed_pages);
548 279
(...skipping 324 matching lines...) Expand 10 before | Expand all | Expand 10 after
873 void JumpList::TopSitesLoaded(history::TopSites* top_sites) { 604 void JumpList::TopSitesLoaded(history::TopSites* top_sites) {
874 } 605 }
875 606
876 void JumpList::TopSitesChanged(history::TopSites* top_sites, 607 void JumpList::TopSitesChanged(history::TopSites* top_sites,
877 ChangeReason change_reason) { 608 ChangeReason change_reason) {
878 top_sites->GetMostVisitedURLs( 609 top_sites->GetMostVisitedURLs(
879 base::Bind(&JumpList::OnMostVisitedURLsAvailable, 610 base::Bind(&JumpList::OnMostVisitedURLsAvailable,
880 weak_ptr_factory_.GetWeakPtr()), 611 weak_ptr_factory_.GetWeakPtr()),
881 false); 612 false);
882 } 613 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698