OLD | NEW |
(Empty) | |
| 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. |
| 4 |
| 5 #import "ios/chrome/browser/snapshots/snapshot_cache.h" |
| 6 |
| 7 #import <UIKit/UIKit.h> |
| 8 |
| 9 #include "base/critical_closure.h" |
| 10 #include "base/mac/bind_objc_block.h" |
| 11 #include "base/files/file_enumerator.h" |
| 12 #include "base/files/file_path.h" |
| 13 #include "base/files/file_util.h" |
| 14 #include "base/location.h" |
| 15 #include "base/logging.h" |
| 16 #include "base/mac/bind_objc_block.h" |
| 17 #include "base/mac/scoped_cftyperef.h" |
| 18 #include "base/strings/sys_string_conversions.h" |
| 19 #include "base/task_runner_util.h" |
| 20 #include "base/threading/thread_restrictions.h" |
| 21 #include "ios/chrome/browser/ui/ui_util.h" |
| 22 #import "ios/chrome/browser/ui/uikit_ui_util.h" |
| 23 #include "ios/public/consumer/base/util.h" |
| 24 #include "ios/web/public/web_thread.h" |
| 25 |
| 26 @interface SnapshotCache () |
| 27 + (base::FilePath)imagePathForSessionID:(NSString*)sessionID; |
| 28 + (base::FilePath)greyImagePathForSessionID:(NSString*)sessionID; |
| 29 // Returns the directory where the thumbnails are saved. |
| 30 + (base::FilePath)cacheDirectory; |
| 31 // Returns the directory where the thumbnails were stored in M28 and earlier. |
| 32 - (base::FilePath)oldCacheDirectory; |
| 33 // Remove all UIImages from |imageDictionary_|. |
| 34 - (void)handleEnterBackground; |
| 35 // Remove all but adjacent UIImages from |imageDictionary_|. |
| 36 - (void)handleLowMemory; |
| 37 // Restore adjacent UIImages to |imageDictionary_|. |
| 38 - (void)handleBecomeActive; |
| 39 // Clear most recent caller information. |
| 40 - (void)clearGreySessionInfo; |
| 41 // Load uncached snapshot image and convert image to grey. |
| 42 - (void)loadGreyImageAsync:(NSString*)sessionID; |
| 43 // Save grey image to |greyImageDictionary_| and call into most recent |
| 44 // |mostRecentGreyBlock_| if |mostRecentGreySessionId_| matches |sessionID|. |
| 45 - (void)saveGreyImage:(UIImage*)greyImage forKey:(NSString*)sessionID; |
| 46 @end |
| 47 |
| 48 namespace { |
| 49 static NSArray* const kSnapshotCacheDirectory = @[ @"Chromium", @"Snapshots" ]; |
| 50 |
| 51 const NSUInteger kCacheInitialCapacity = 100; |
| 52 const NSUInteger kGreyInitialCapacity = 8; |
| 53 const CGFloat kJPEGImageQuality = 1.0; // Highest quality. No compression. |
| 54 // Sequence token to make sure creation/deletion of snapshots don't overlap. |
| 55 const char kSequenceToken[] = "SnapshotCacheSequenceToken"; |
| 56 |
| 57 // The paths of the images saved to disk, given a cache directory. |
| 58 base::FilePath FilePathForSessionID(NSString* sessionID, |
| 59 const base::FilePath& directory) { |
| 60 base::FilePath path = directory.Append(base::SysNSStringToUTF8(sessionID)) |
| 61 .ReplaceExtension(".jpg"); |
| 62 if ([SnapshotCache snapshotScaleForDevice] == 2.0) { |
| 63 path = path.InsertBeforeExtension("@2x"); |
| 64 } else if ([SnapshotCache snapshotScaleForDevice] == 3.0) { |
| 65 path = path.InsertBeforeExtension("@3x"); |
| 66 } |
| 67 return path; |
| 68 } |
| 69 |
| 70 base::FilePath GreyFilePathForSessionID(NSString* sessionID, |
| 71 const base::FilePath& directory) { |
| 72 base::FilePath path = directory.Append(base::SysNSStringToUTF8(sessionID) + |
| 73 "Grey").ReplaceExtension(".jpg"); |
| 74 if ([SnapshotCache snapshotScaleForDevice] == 2.0) { |
| 75 path = path.InsertBeforeExtension("@2x"); |
| 76 } else if ([SnapshotCache snapshotScaleForDevice] == 3.0) { |
| 77 path = path.InsertBeforeExtension("@3x"); |
| 78 } |
| 79 return path; |
| 80 } |
| 81 |
| 82 UIImage* ReadImageFromDisk(const base::FilePath& filePath) { |
| 83 base::ThreadRestrictions::AssertIOAllowed(); |
| 84 // TODO(justincohen): Consider changing this back to -imageWithContentsOfFile |
| 85 // instead of -imageWithData, if the crashing rdar://15747161 is ever fixed. |
| 86 // Tracked in crbug.com/295891. |
| 87 NSString* path = base::SysUTF8ToNSString(filePath.value()); |
| 88 return [UIImage imageWithData:[NSData dataWithContentsOfFile:path] |
| 89 scale:[SnapshotCache snapshotScaleForDevice]]; |
| 90 } |
| 91 |
| 92 void WriteImageToDisk(const base::scoped_nsobject<UIImage>& image, |
| 93 const base::FilePath& filePath) { |
| 94 base::ThreadRestrictions::AssertIOAllowed(); |
| 95 if (!image) |
| 96 return; |
| 97 NSString* path = base::SysUTF8ToNSString(filePath.value()); |
| 98 [UIImageJPEGRepresentation(image, kJPEGImageQuality) writeToFile:path |
| 99 atomically:YES]; |
| 100 // Encrypt the snapshot file (mostly for Incognito, but can't hurt to |
| 101 // always do it). |
| 102 NSDictionary* attributeDict = |
| 103 [NSDictionary dictionaryWithObject:NSFileProtectionComplete |
| 104 forKey:NSFileProtectionKey]; |
| 105 NSError* error = nil; |
| 106 BOOL success = [[NSFileManager defaultManager] setAttributes:attributeDict |
| 107 ofItemAtPath:path |
| 108 error:&error]; |
| 109 if (!success) { |
| 110 DLOG(ERROR) << "Error encrypting thumbnail file" |
| 111 << base::SysNSStringToUTF8([error description]); |
| 112 } |
| 113 } |
| 114 |
| 115 void ConvertAndSaveGreyImage( |
| 116 const base::FilePath& colorPath, |
| 117 const base::FilePath& greyPath, |
| 118 const base::scoped_nsobject<UIImage>& cachedImage) { |
| 119 base::ThreadRestrictions::AssertIOAllowed(); |
| 120 base::scoped_nsobject<UIImage> colorImage = cachedImage; |
| 121 if (!colorImage) |
| 122 colorImage.reset([ReadImageFromDisk(colorPath) retain]); |
| 123 if (!colorImage) |
| 124 return; |
| 125 base::scoped_nsobject<UIImage> greyImage([GreyImage(colorImage) retain]); |
| 126 WriteImageToDisk(greyImage, greyPath); |
| 127 } |
| 128 |
| 129 } // anonymous namespace |
| 130 |
| 131 @implementation SnapshotCache |
| 132 |
| 133 @synthesize pinnedIDs = pinnedIDs_; |
| 134 |
| 135 + (SnapshotCache*)sharedInstance { |
| 136 static SnapshotCache* instance = [[SnapshotCache alloc] init]; |
| 137 return instance; |
| 138 } |
| 139 |
| 140 - (id)init { |
| 141 if ((self = [super init])) { |
| 142 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI); |
| 143 propertyReleaser_SnapshotCache_.Init(self, [SnapshotCache class]); |
| 144 |
| 145 // TODO(andybons): In the case where the cache grows, it is expensive. |
| 146 // Make sure this doesn't suck when there are more than ten tabs. |
| 147 imageDictionary_.reset( |
| 148 [[NSMutableDictionary alloc] initWithCapacity:kCacheInitialCapacity]); |
| 149 [[NSNotificationCenter defaultCenter] |
| 150 addObserver:self |
| 151 selector:@selector(handleLowMemory) |
| 152 name:UIApplicationDidReceiveMemoryWarningNotification |
| 153 object:nil]; |
| 154 [[NSNotificationCenter defaultCenter] |
| 155 addObserver:self |
| 156 selector:@selector(handleEnterBackground) |
| 157 name:UIApplicationDidEnterBackgroundNotification |
| 158 object:nil]; |
| 159 [[NSNotificationCenter defaultCenter] |
| 160 addObserver:self |
| 161 selector:@selector(handleBecomeActive) |
| 162 name:UIApplicationDidBecomeActiveNotification |
| 163 object:nil]; |
| 164 } |
| 165 return self; |
| 166 } |
| 167 |
| 168 + (CGFloat)snapshotScaleForDevice { |
| 169 // On handset, the color snapshot is used for the stack view, so the scale of |
| 170 // the snapshot images should match the scale of the device. |
| 171 // On tablet, the color snapshot is only used to generate the grey snapshot, |
| 172 // which does not have to be high quality, so use scale of 1.0 on all tablets. |
| 173 if (IsIPadIdiom()) { |
| 174 return 1.0; |
| 175 } |
| 176 // Cap snapshot resolution to 2x to reduce the amount of memory they use. |
| 177 return MIN([UIScreen mainScreen].scale, 2.0); |
| 178 } |
| 179 |
| 180 - (void)retrieveImageForSessionID:(NSString*)sessionID |
| 181 callback:(void (^)(UIImage*))callback { |
| 182 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI); |
| 183 DCHECK(sessionID); |
| 184 UIImage* img = [imageDictionary_ objectForKey:sessionID]; |
| 185 if (img) { |
| 186 if (callback) |
| 187 callback(img); |
| 188 return; |
| 189 } |
| 190 |
| 191 base::PostTaskAndReplyWithResult( |
| 192 web::WebThread::GetMessageLoopProxyForThread( |
| 193 web::WebThread::FILE_USER_BLOCKING).get(), |
| 194 FROM_HERE, |
| 195 base::BindBlock(^base::scoped_nsobject<UIImage>() { |
| 196 // Retrieve the image on a high priority thread. |
| 197 return base::scoped_nsobject<UIImage>([ReadImageFromDisk( |
| 198 [SnapshotCache imagePathForSessionID:sessionID]) retain]); |
| 199 }), |
| 200 base::BindBlock(^(base::scoped_nsobject<UIImage> image) { |
| 201 if (image) |
| 202 [imageDictionary_ setObject:image forKey:sessionID]; |
| 203 if (callback) |
| 204 callback(image); |
| 205 })); |
| 206 } |
| 207 |
| 208 - (void)setImage:(UIImage*)img withSessionID:(NSString*)sessionID { |
| 209 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI); |
| 210 if (!img || !sessionID) |
| 211 return; |
| 212 |
| 213 // Color snapshots are not used on tablets, so don't keep them in memory. |
| 214 if (!IsIPadIdiom()) { |
| 215 [imageDictionary_ setObject:img forKey:sessionID]; |
| 216 } |
| 217 // Save the image to disk. |
| 218 web::WebThread::PostBlockingPoolSequencedTask( |
| 219 kSequenceToken, FROM_HERE, |
| 220 base::BindBlock(^{ |
| 221 base::scoped_nsobject<UIImage> image([img retain]); |
| 222 WriteImageToDisk(image, |
| 223 [SnapshotCache imagePathForSessionID:sessionID]); |
| 224 })); |
| 225 } |
| 226 |
| 227 - (void)removeImageWithSessionID:(NSString*)sessionID { |
| 228 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI); |
| 229 [imageDictionary_ removeObjectForKey:sessionID]; |
| 230 web::WebThread::PostBlockingPoolSequencedTask( |
| 231 kSequenceToken, FROM_HERE, |
| 232 base::BindBlock(^{ |
| 233 base::FilePath imagePath = |
| 234 [SnapshotCache imagePathForSessionID:sessionID]; |
| 235 base::DeleteFile(imagePath, false); |
| 236 base::DeleteFile([SnapshotCache greyImagePathForSessionID:sessionID], |
| 237 false); |
| 238 })); |
| 239 } |
| 240 |
| 241 - (base::FilePath)oldCacheDirectory { |
| 242 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI); |
| 243 NSArray* paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, |
| 244 NSUserDomainMask, YES); |
| 245 NSString* path = [paths objectAtIndex:0]; |
| 246 NSArray* path_components = |
| 247 [NSArray arrayWithObjects:path, kSnapshotCacheDirectory[1], nil]; |
| 248 return base::FilePath( |
| 249 base::SysNSStringToUTF8([NSString pathWithComponents:path_components])); |
| 250 } |
| 251 |
| 252 + (base::FilePath)cacheDirectory { |
| 253 NSArray* paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, |
| 254 NSUserDomainMask, YES); |
| 255 NSString* path = [paths objectAtIndex:0]; |
| 256 NSArray* path_components = |
| 257 [NSArray arrayWithObjects:path, kSnapshotCacheDirectory[0], |
| 258 kSnapshotCacheDirectory[1], nil]; |
| 259 return base::FilePath( |
| 260 base::SysNSStringToUTF8([NSString pathWithComponents:path_components])); |
| 261 } |
| 262 |
| 263 + (base::FilePath)imagePathForSessionID:(NSString*)sessionID { |
| 264 base::ThreadRestrictions::AssertIOAllowed(); |
| 265 |
| 266 base::FilePath path([SnapshotCache cacheDirectory]); |
| 267 |
| 268 BOOL exists = base::PathExists(path); |
| 269 DCHECK(base::DirectoryExists(path) || !exists); |
| 270 if (!exists) { |
| 271 bool result = base::CreateDirectory(path); |
| 272 DCHECK(result); |
| 273 } |
| 274 return FilePathForSessionID(sessionID, path); |
| 275 } |
| 276 |
| 277 + (base::FilePath)greyImagePathForSessionID:(NSString*)sessionID { |
| 278 base::ThreadRestrictions::AssertIOAllowed(); |
| 279 |
| 280 base::FilePath path([self cacheDirectory]); |
| 281 |
| 282 BOOL exists = base::PathExists(path); |
| 283 DCHECK(base::DirectoryExists(path) || !exists); |
| 284 if (!exists) { |
| 285 bool result = base::CreateDirectory(path); |
| 286 DCHECK(result); |
| 287 } |
| 288 return GreyFilePathForSessionID(sessionID, path); |
| 289 } |
| 290 |
| 291 - (void)purgeCacheOlderThan:(const base::Time&)date |
| 292 keeping:(NSSet*)liveSessionIds { |
| 293 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI); |
| 294 // Copying the date, as the block must copy the value, not the reference. |
| 295 const base::Time dateCopy = date; |
| 296 web::WebThread::PostBlockingPoolSequencedTask( |
| 297 kSequenceToken, FROM_HERE, |
| 298 base::BindBlock(^{ |
| 299 std::set<base::FilePath> filesToKeep; |
| 300 for (NSString* sessionID : liveSessionIds) { |
| 301 base::FilePath curImagePath = |
| 302 [SnapshotCache imagePathForSessionID:sessionID]; |
| 303 filesToKeep.insert(curImagePath); |
| 304 filesToKeep.insert( |
| 305 [SnapshotCache greyImagePathForSessionID:sessionID]); |
| 306 } |
| 307 base::FileEnumerator enumerator([SnapshotCache cacheDirectory], false, |
| 308 base::FileEnumerator::FILES); |
| 309 base::FilePath cur_file; |
| 310 while (!(cur_file = enumerator.Next()).value().empty()) { |
| 311 if (cur_file.Extension() != ".jpg") |
| 312 continue; |
| 313 if (filesToKeep.find(cur_file) != filesToKeep.end()) { |
| 314 continue; |
| 315 } |
| 316 base::FileEnumerator::FileInfo fileInfo = enumerator.GetInfo(); |
| 317 if (fileInfo.GetLastModifiedTime() > dateCopy) { |
| 318 continue; |
| 319 } |
| 320 base::DeleteFile(cur_file, false); |
| 321 } |
| 322 })); |
| 323 } |
| 324 |
| 325 - (void)willBeSavedGreyWhenBackgrounding:(NSString*)sessionID { |
| 326 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI); |
| 327 if (!sessionID) |
| 328 return; |
| 329 backgroundingImageSessionId_.reset([sessionID copy]); |
| 330 backgroundingColorImage_.reset( |
| 331 [[imageDictionary_ objectForKey:sessionID] retain]); |
| 332 } |
| 333 |
| 334 - (void)handleLowMemory { |
| 335 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI); |
| 336 NSMutableDictionary* dictionary = |
| 337 [[NSMutableDictionary alloc] initWithCapacity:2]; |
| 338 for (NSString* sessionID in pinnedIDs_) { |
| 339 UIImage* image = [imageDictionary_ objectForKey:sessionID]; |
| 340 if (image) |
| 341 [dictionary setObject:image forKey:sessionID]; |
| 342 } |
| 343 imageDictionary_.reset(dictionary); |
| 344 } |
| 345 |
| 346 - (void)handleEnterBackground { |
| 347 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI); |
| 348 [imageDictionary_ removeAllObjects]; |
| 349 } |
| 350 |
| 351 - (void)handleBecomeActive { |
| 352 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI); |
| 353 for (NSString* sessionID in pinnedIDs_) |
| 354 [self retrieveImageForSessionID:sessionID callback:nil]; |
| 355 } |
| 356 |
| 357 - (void)saveGreyImage:(UIImage*)greyImage forKey:(NSString*)sessionID { |
| 358 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI); |
| 359 if (greyImage) |
| 360 [greyImageDictionary_ setObject:greyImage forKey:sessionID]; |
| 361 if ([sessionID isEqualToString:mostRecentGreySessionId_]) { |
| 362 mostRecentGreyBlock_.get()(greyImage); |
| 363 [self clearGreySessionInfo]; |
| 364 } |
| 365 } |
| 366 |
| 367 - (void)loadGreyImageAsync:(NSString*)sessionID { |
| 368 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI); |
| 369 // Don't call -retrieveImageForSessionID here because it caches the colored |
| 370 // image, which we don't need for the grey image cache. But if the image is |
| 371 // already in the cache, use it. |
| 372 UIImage* img = [imageDictionary_ objectForKey:sessionID]; |
| 373 base::PostTaskAndReplyWithResult( |
| 374 web::WebThread::GetMessageLoopProxyForThread( |
| 375 web::WebThread::FILE_USER_BLOCKING).get(), |
| 376 FROM_HERE, |
| 377 base::BindBlock(^base::scoped_nsobject<UIImage>() { |
| 378 base::scoped_nsobject<UIImage> result([img retain]); |
| 379 // If the image is not in the cache, load it from disk. |
| 380 if (!result) |
| 381 result.reset([ReadImageFromDisk( |
| 382 [SnapshotCache imagePathForSessionID:sessionID]) retain]); |
| 383 if (result) |
| 384 result.reset([GreyImage(result) retain]); |
| 385 return result; |
| 386 }), |
| 387 base::BindBlock(^(base::scoped_nsobject<UIImage> greyImage) { |
| 388 [self saveGreyImage:greyImage forKey:sessionID]; |
| 389 })); |
| 390 } |
| 391 |
| 392 - (void)createGreyCache:(NSArray*)sessionIDs { |
| 393 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI); |
| 394 greyImageDictionary_.reset( |
| 395 [[NSMutableDictionary alloc] initWithCapacity:kGreyInitialCapacity]); |
| 396 for (NSString* sessionID in sessionIDs) |
| 397 [self loadGreyImageAsync:sessionID]; |
| 398 } |
| 399 |
| 400 - (void)removeGreyCache { |
| 401 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI); |
| 402 greyImageDictionary_.reset(); |
| 403 [self clearGreySessionInfo]; |
| 404 } |
| 405 |
| 406 - (void)clearGreySessionInfo { |
| 407 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI); |
| 408 mostRecentGreySessionId_.reset(); |
| 409 mostRecentGreyBlock_.reset(); |
| 410 } |
| 411 |
| 412 - (void)greyImageForSessionID:(NSString*)sessionID |
| 413 callback:(void (^)(UIImage*))callback { |
| 414 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI); |
| 415 DCHECK(greyImageDictionary_); |
| 416 UIImage* image = [greyImageDictionary_ objectForKey:sessionID]; |
| 417 if (image) { |
| 418 callback(image); |
| 419 [self clearGreySessionInfo]; |
| 420 } else { |
| 421 mostRecentGreySessionId_.reset([sessionID copy]); |
| 422 mostRecentGreyBlock_.reset([callback copy]); |
| 423 } |
| 424 } |
| 425 |
| 426 - (void)retrieveGreyImageForSessionID:(NSString*)sessionID |
| 427 callback:(void (^)(UIImage*))callback { |
| 428 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI); |
| 429 if (greyImageDictionary_) { |
| 430 UIImage* image = [greyImageDictionary_ objectForKey:sessionID]; |
| 431 if (image) { |
| 432 callback(image); |
| 433 return; |
| 434 } |
| 435 } |
| 436 |
| 437 base::PostTaskAndReplyWithResult( |
| 438 web::WebThread::GetMessageLoopProxyForThread( |
| 439 web::WebThread::FILE_USER_BLOCKING).get(), |
| 440 FROM_HERE, |
| 441 base::BindBlock(^base::scoped_nsobject<UIImage>() { |
| 442 // Retrieve the image on a high priority thread. |
| 443 // Loading the file into NSData is more reliable. |
| 444 // -imageWithContentsOfFile would ocassionally claim the image was not a |
| 445 // valid jpg. |
| 446 // "ImageIO: <ERROR> JPEGNot a JPEG file: starts with 0xff 0xd9" |
| 447 // See |
| 448 // http://stackoverflow.com/questions/5081297/ios-uiimagejpegrepresentat
ion-error-not-a-jpeg-file-starts-with-0xff-0xd9 |
| 449 NSData* imageData = [NSData |
| 450 dataWithContentsOfFile:base::SysUTF8ToNSString( |
| 451 [SnapshotCache greyImagePathForSessionID:sessionID].value())]; |
| 452 if (!imageData) |
| 453 return base::scoped_nsobject<UIImage>(); |
| 454 DCHECK(callback); |
| 455 return base::scoped_nsobject<UIImage>( |
| 456 [[UIImage imageWithData:imageData] retain]); |
| 457 }), |
| 458 base::BindBlock(^(base::scoped_nsobject<UIImage> image) { |
| 459 if (!image) { |
| 460 [self retrieveImageForSessionID:sessionID |
| 461 callback:^(UIImage* img) { |
| 462 if (callback && img) |
| 463 callback(GreyImage(img)); |
| 464 }]; |
| 465 } else if (callback) { |
| 466 callback(image); |
| 467 } |
| 468 })); |
| 469 } |
| 470 |
| 471 - (void)saveGreyInBackgroundForSessionID:(NSString*)sessionID { |
| 472 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI); |
| 473 if (!sessionID) |
| 474 return; |
| 475 |
| 476 base::FilePath greyImagePath = |
| 477 GreyFilePathForSessionID(sessionID, [SnapshotCache cacheDirectory]); |
| 478 base::FilePath colorImagePath = |
| 479 FilePathForSessionID(sessionID, [SnapshotCache cacheDirectory]); |
| 480 |
| 481 // The color image may still be in memory. Verify the sessionID matches. |
| 482 if (backgroundingColorImage_) { |
| 483 if (![backgroundingImageSessionId_ isEqualToString:sessionID]) { |
| 484 backgroundingColorImage_.reset(); |
| 485 backgroundingImageSessionId_.reset(); |
| 486 } |
| 487 } |
| 488 |
| 489 web::WebThread::PostBlockingPoolTask( |
| 490 FROM_HERE, base::Bind(&ConvertAndSaveGreyImage, colorImagePath, |
| 491 greyImagePath, backgroundingColorImage_)); |
| 492 } |
| 493 |
| 494 @end |
OLD | NEW |