BookmarkManager: Fix 'new folder text field size changes on clicking it' issue.
[chromium-blink-merge.git] / chrome / browser / download / download_status_updater_mac.mm
blob2cdb9523ab840055ec560c3b7def6cbe99e8b5b0
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
3 // found in the LICENSE file.
5 #include "chrome/browser/download/download_status_updater.h"
7 #include "base/mac/foundation_util.h"
8 #include "base/mac/scoped_nsobject.h"
9 #include "base/mac/sdk_forward_declarations.h"
10 #include "base/strings/sys_string_conversions.h"
11 #include "base/supports_user_data.h"
12 #import "chrome/browser/ui/cocoa/dock_icon.h"
13 #include "content/public/browser/download_item.h"
14 #include "url/gurl.h"
16 namespace {
18 // These are not the keys themselves; they are the names for dynamic lookup via
19 // the ProgressString() function.
21 // Public keys, SPI in 10.8, API in 10.9:
22 NSString* const kNSProgressEstimatedTimeRemainingKeyName =
23     @"NSProgressEstimatedTimeRemainingKey";
24 NSString* const kNSProgressFileOperationKindDownloadingName =
25     @"NSProgressFileOperationKindDownloading";
26 NSString* const kNSProgressFileOperationKindKeyName =
27     @"NSProgressFileOperationKindKey";
28 NSString* const kNSProgressFileURLKeyName =
29     @"NSProgressFileURLKey";
30 NSString* const kNSProgressKindFileName =
31     @"NSProgressKindFile";
32 NSString* const kNSProgressThroughputKeyName =
33     @"NSProgressThroughputKey";
35 // Private keys, SPI in 10.8 and 10.9:
36 // TODO(avi): Are any of these actually needed for the NSProgress integration?
37 NSString* const kNSProgressFileDownloadingSourceURLKeyName =
38     @"NSProgressFileDownloadingSourceURLKey";
39 NSString* const kNSProgressFileLocationCanChangeKeyName =
40     @"NSProgressFileLocationCanChangeKey";
42 // Given an NSProgress string name (kNSProgress[...]Name above), looks up the
43 // real symbol of that name from Foundation and returns it.
44 NSString* ProgressString(NSString* string) {
45   static NSMutableDictionary* cache;
46   static CFBundleRef foundation;
47   if (!cache) {
48     cache = [[NSMutableDictionary alloc] init];
49     foundation = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.Foundation"));
50   }
52   NSString* result = [cache objectForKey:string];
53   if (!result) {
54     NSString** ref = static_cast<NSString**>(
55         CFBundleGetDataPointerForName(foundation,
56                                       base::mac::NSToCFCast(string)));
57     if (ref) {
58       result = *ref;
59       [cache setObject:result forKey:string];
60     }
61   }
63   if (!result && string == kNSProgressEstimatedTimeRemainingKeyName) {
64     // Perhaps this is 10.8; try the old name of this key.
65     NSString** ref = static_cast<NSString**>(
66         CFBundleGetDataPointerForName(foundation,
67                                       CFSTR("NSProgressEstimatedTimeKey")));
68     if (ref) {
69       result = *ref;
70       [cache setObject:result forKey:string];
71     }
72   }
74   if (!result) {
75     // Huh. At least return a local copy of the expected string.
76     result = string;
77     NSString* const kKeySuffix = @"Key";
78     if ([result hasSuffix:kKeySuffix])
79       result = [result substringToIndex:[result length] - [kKeySuffix length]];
80   }
82   return result;
85 bool NSProgressSupported() {
86   static bool supported;
87   static bool valid;
88   if (!valid) {
89     supported = NSClassFromString(@"NSProgress");
90     valid = true;
91   }
93   return supported;
96 const char kCrNSProgressUserDataKey[] = "CrNSProgressUserData";
98 class CrNSProgressUserData : public base::SupportsUserData::Data {
99  public:
100   CrNSProgressUserData(NSProgress* progress, const base::FilePath& target)
101       : target_(target) {
102     progress_.reset(progress);
103   }
104   ~CrNSProgressUserData() override { [progress_.get() unpublish]; }
106   NSProgress* progress() const { return progress_.get(); }
107   base::FilePath target() const { return target_; }
108   void setTarget(const base::FilePath& target) { target_ = target; }
110  private:
111   base::scoped_nsobject<NSProgress> progress_;
112   base::FilePath target_;
115 void UpdateAppIcon(int download_count,
116                    bool progress_known,
117                    float progress) {
118   DockIcon* dock_icon = [DockIcon sharedDockIcon];
119   [dock_icon setDownloads:download_count];
120   [dock_icon setIndeterminate:!progress_known];
121   [dock_icon setProgress:progress];
122   [dock_icon updateIcon];
125 void CreateNSProgress(content::DownloadItem* download) {
126   NSURL* source_url = [NSURL URLWithString:
127       base::SysUTF8ToNSString(download->GetURL().possibly_invalid_spec())];
128   base::FilePath destination_path = download->GetFullPath();
129   NSURL* destination_url = [NSURL fileURLWithPath:
130       base::mac::FilePathToNSString(destination_path)];
132   NSDictionary* user_info = @{
133     ProgressString(kNSProgressFileLocationCanChangeKeyName) : @true,
134     ProgressString(kNSProgressFileOperationKindKeyName) :
135         ProgressString(kNSProgressFileOperationKindDownloadingName),
136     ProgressString(kNSProgressFileURLKeyName) : destination_url
137   };
139   Class progress_class = NSClassFromString(@"NSProgress");
140   NSProgress* progress = [progress_class performSelector:@selector(alloc)];
141   progress = [progress performSelector:@selector(initWithParent:userInfo:)
142                             withObject:nil
143                             withObject:user_info];
144   progress.kind = ProgressString(kNSProgressKindFileName);
146   if (source_url) {
147     [progress setUserInfoObject:source_url forKey:
148         ProgressString(kNSProgressFileDownloadingSourceURLKeyName)];
149   }
151   progress.pausable = NO;
152   progress.cancellable = YES;
153   [progress setCancellationHandler:^{
154       dispatch_async(dispatch_get_main_queue(), ^{
155           download->Cancel(true);
156       });
157   }];
159   progress.totalUnitCount = download->GetTotalBytes();
160   progress.completedUnitCount = download->GetReceivedBytes();
162   [progress publish];
164   download->SetUserData(&kCrNSProgressUserDataKey,
165                         new CrNSProgressUserData(progress, destination_path));
168 void UpdateNSProgress(content::DownloadItem* download,
169                       CrNSProgressUserData* progress_data) {
170   NSProgress* progress = progress_data->progress();
171   progress.totalUnitCount = download->GetTotalBytes();
172   progress.completedUnitCount = download->GetReceivedBytes();
173   [progress setUserInfoObject:@(download->CurrentSpeed())
174                        forKey:ProgressString(kNSProgressThroughputKeyName)];
176   base::TimeDelta time_remaining;
177   NSNumber* time_remaining_ns = nil;
178   if (download->TimeRemaining(&time_remaining))
179     time_remaining_ns = @(time_remaining.InSeconds());
180   [progress setUserInfoObject:time_remaining_ns
181                forKey:ProgressString(kNSProgressEstimatedTimeRemainingKeyName)];
183   base::FilePath download_path = download->GetFullPath();
184   if (progress_data->target() != download_path) {
185     progress_data->setTarget(download_path);
186     NSURL* download_url = [NSURL fileURLWithPath:
187         base::mac::FilePathToNSString(download_path)];
188     [progress setUserInfoObject:download_url
189                          forKey:ProgressString(kNSProgressFileURLKeyName)];
190   }
193 void DestroyNSProgress(content::DownloadItem* download,
194                        CrNSProgressUserData* progress_data) {
195   download->RemoveUserData(&kCrNSProgressUserDataKey);
198 }  // namespace
200 void DownloadStatusUpdater::UpdateAppIconDownloadProgress(
201     content::DownloadItem* download) {
203   // Always update overall progress.
205   float progress = 0;
206   int download_count = 0;
207   bool progress_known = GetProgress(&progress, &download_count);
208   UpdateAppIcon(download_count, progress_known, progress);
210   // Update NSProgress-based indicators.
212   if (NSProgressSupported()) {
213     CrNSProgressUserData* progress_data = static_cast<CrNSProgressUserData*>(
214         download->GetUserData(&kCrNSProgressUserDataKey));
216     // Only show progress if the download is IN_PROGRESS and it hasn't been
217     // renamed to its final name. Setting the progress after the final rename
218     // results in the file being stuck in an in-progress state on the dock. See
219     // http://crbug.com/166683.
220     if (download->GetState() == content::DownloadItem::IN_PROGRESS &&
221         !download->GetFullPath().empty() &&
222         download->GetFullPath() != download->GetTargetFilePath()) {
223       if (!progress_data)
224         CreateNSProgress(download);
225       else
226         UpdateNSProgress(download, progress_data);
227     } else {
228       DestroyNSProgress(download, progress_data);
229     }
230   }
232   // Handle downloads that ended.
233   if (download->GetState() != content::DownloadItem::IN_PROGRESS &&
234       !download->GetTargetFilePath().empty()) {
235     NSString* download_path =
236         base::mac::FilePathToNSString(download->GetTargetFilePath());
237     if (download->GetState() == content::DownloadItem::COMPLETE) {
238       // Bounce the dock icon.
239       [[NSDistributedNotificationCenter defaultCenter]
240           postNotificationName:@"com.apple.DownloadFileFinished"
241                         object:download_path];
242     }
244     // Notify the Finder.
245     NSString* parent_path = [download_path stringByDeletingLastPathComponent];
246     FNNotifyByPath(
247         reinterpret_cast<const UInt8*>([parent_path fileSystemRepresentation]),
248         kFNDirectoryModifiedMessage,
249         kNilOptions);
250   }