OLD | NEW |
(Empty) | |
| 1 // Copyright 2015 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/ui/tab_switcher/tab_switcher_cache.h" |
| 6 |
| 7 #include <unordered_map> |
| 8 |
| 9 #import "base/ios/weak_nsobject.h" |
| 10 #include "base/logging.h" |
| 11 #include "base/mac/scoped_nsobject.h" |
| 12 #include "base/strings/stringprintf.h" |
| 13 #include "base/synchronization/lock.h" |
| 14 #import "ios/chrome/browser/tabs/tab.h" |
| 15 #import "ios/chrome/browser/tabs/tab_model.h" |
| 16 #import "ios/chrome/browser/ui/uikit_ui_util.h" |
| 17 #include "ios/chrome/common/ios_app_bundle_id_prefix.h" |
| 18 #include "ios/web/public/navigation_item.h" |
| 19 |
| 20 namespace { |
| 21 // The maximum amount of pixels the cache should hold. |
| 22 NSUInteger kCacheMaxPixelCount = 2048 * 1536 * 4; |
| 23 // Two floats that are different from less than |kMaxFloatDelta| are considered |
| 24 // equals. |
| 25 const CGFloat kMaxFloatDelta = 0.01; |
| 26 } // namespace |
| 27 |
| 28 @interface TabSwitcherCache () |
| 29 // Clears the cache. Called when a low memory warning was received. |
| 30 - (void)lowMemoryWarningReceived; |
| 31 // Returns a autoreleased resized image of |image|. |
| 32 + (UIImage*)resizedImage:(UIImage*)image toSize:(CGSize)size; |
| 33 |
| 34 @end |
| 35 |
| 36 @implementation TabSwitcherCache { |
| 37 base::scoped_nsobject<NSCache> _cache; |
| 38 dispatch_queue_t _cacheQueue; |
| 39 // The tab models. |
| 40 TabModel* _mainTabModel; // weak |
| 41 TabModel* _otrTabModel; // weak |
| 42 |
| 43 // Lock protecting the pending requests map. |
| 44 base::Lock _lock; |
| 45 std::unordered_map<NSUInteger, PendingSnapshotRequest> _pendingRequests; |
| 46 } |
| 47 |
| 48 @synthesize mainTabModel = _mainTabModel; |
| 49 |
| 50 - (instancetype)init { |
| 51 self = [super init]; |
| 52 if (self) { |
| 53 _cache.reset([[NSCache alloc] init]); |
| 54 [_cache setTotalCostLimit:kCacheMaxPixelCount]; |
| 55 NSNotificationCenter* nc = [NSNotificationCenter defaultCenter]; |
| 56 [nc addObserver:self |
| 57 selector:@selector(lowMemoryWarningReceived) |
| 58 name:UIApplicationDidReceiveMemoryWarningNotification |
| 59 object:nil]; |
| 60 std::string queueName = |
| 61 base::StringPrintf("%s.chrome.ios.TabSwitcherCacheQueue", |
| 62 BUILDFLAG(IOS_APP_BUNDLE_ID_PREFIX)); |
| 63 _cacheQueue = |
| 64 dispatch_queue_create(queueName.c_str(), DISPATCH_QUEUE_SERIAL); |
| 65 } |
| 66 return self; |
| 67 } |
| 68 |
| 69 - (void)dealloc { |
| 70 NSNotificationCenter* nc = [NSNotificationCenter defaultCenter]; |
| 71 [nc removeObserver:self |
| 72 name:UIApplicationDidReceiveMemoryWarningNotification |
| 73 object:nil]; |
| 74 dispatch_release(_cacheQueue); |
| 75 [_mainTabModel removeObserver:self]; |
| 76 [_otrTabModel removeObserver:self]; |
| 77 [super dealloc]; |
| 78 } |
| 79 |
| 80 - (PendingSnapshotRequest)requestSnapshotForTab:(Tab*)tab |
| 81 withSize:(CGSize)size |
| 82 completionBlock: |
| 83 (SnapshotCompletionBlock)completionBlock { |
| 84 DCHECK([NSThread isMainThread]); |
| 85 DCHECK(tab); |
| 86 DCHECK(completionBlock); |
| 87 DCHECK(!CGSizeEqualToSize(size, CGSizeZero)); |
| 88 PendingSnapshotRequest currentRequest; |
| 89 UIImage* snapshot = [_cache objectForKey:[self keyForTab:tab]]; |
| 90 if (snapshot) { |
| 91 CGFloat tabContentAreaRatio = tab.snapshotContentArea.size.width / |
| 92 tab.snapshotContentArea.size.height; |
| 93 CGFloat cachedSnapshotRatio = |
| 94 [snapshot size].width / [snapshot size].height; |
| 95 // Check that the cached snapshot's ratio matches the content area ratio. |
| 96 if (std::abs(tabContentAreaRatio - cachedSnapshotRatio) < kMaxFloatDelta && |
| 97 [snapshot size].width >= size.width) { |
| 98 // Cache hit. |
| 99 completionBlock(snapshot); |
| 100 return currentRequest; |
| 101 } |
| 102 } |
| 103 |
| 104 // Cache miss. |
| 105 currentRequest = [self recordPendingRequestForTab:tab]; |
| 106 NSString* key = [self keyForTab:tab]; |
| 107 [tab retrieveSnapshot:^(UIImage* snapshot) { |
| 108 PendingSnapshotRequest requestForSession = [self pendingRequestForTab:tab]; |
| 109 // Cancel this request if another one has replaced it for this sessionId. |
| 110 if (currentRequest.requestId != requestForSession.requestId) |
| 111 return; |
| 112 dispatch_async(_cacheQueue, ^{ |
| 113 DCHECK(![NSThread isMainThread]); |
| 114 UIImage* resizedSnapshot = |
| 115 [TabSwitcherCache resizedImage:snapshot toSize:size]; |
| 116 if ([self storeImage:resizedSnapshot forKey:key request:currentRequest]) { |
| 117 dispatch_async(dispatch_get_main_queue(), ^{ |
| 118 // Cancel this request if another one has replaced it for this |
| 119 // sessionId. |
| 120 PendingSnapshotRequest requestForSession = |
| 121 [self pendingRequestForTab:tab]; |
| 122 if (currentRequest.requestId != requestForSession.requestId) |
| 123 return; |
| 124 completionBlock(resizedSnapshot); |
| 125 [self removePendingSnapshotRequest:currentRequest]; |
| 126 }); |
| 127 } |
| 128 }); |
| 129 }]; |
| 130 return currentRequest; |
| 131 } |
| 132 |
| 133 - (void)updateSnapshotForTab:(Tab*)tab |
| 134 withImage:(UIImage*)image |
| 135 size:(CGSize)size { |
| 136 DCHECK([NSThread isMainThread]); |
| 137 DCHECK(tab); |
| 138 DCHECK(image); |
| 139 PendingSnapshotRequest currentRequest = [self recordPendingRequestForTab:tab]; |
| 140 NSString* key = [self keyForTab:tab]; |
| 141 |
| 142 dispatch_async(_cacheQueue, ^{ |
| 143 DCHECK(![NSThread isMainThread]); |
| 144 UIImage* resizedSnapshot = |
| 145 [TabSwitcherCache resizedImage:image toSize:size]; |
| 146 [self storeImage:resizedSnapshot forKey:key request:currentRequest]; |
| 147 [self removePendingSnapshotRequest:currentRequest]; |
| 148 }); |
| 149 } |
| 150 |
| 151 - (void)cancelPendingSnapshotRequest:(PendingSnapshotRequest)pendingRequest { |
| 152 [self removePendingSnapshotRequest:pendingRequest]; |
| 153 } |
| 154 |
| 155 #pragma mark - Private |
| 156 |
| 157 - (NSString*)keyForTab:(Tab*)tab { |
| 158 DCHECK([NSThread isMainThread]); |
| 159 return [tab currentSessionID]; |
| 160 } |
| 161 |
| 162 - (PendingSnapshotRequest)recordPendingRequestForTab:(Tab*)tab { |
| 163 PendingSnapshotRequest pendingRequest; |
| 164 pendingRequest.requestId = [[NSDate date] timeIntervalSince1970]; |
| 165 pendingRequest.sessionId = [[self keyForTab:tab] hash]; |
| 166 base::AutoLock guard(_lock); |
| 167 _pendingRequests[pendingRequest.sessionId] = pendingRequest; |
| 168 return pendingRequest; |
| 169 } |
| 170 |
| 171 - (PendingSnapshotRequest)pendingRequestForTab:(Tab*)tab { |
| 172 DCHECK([NSThread isMainThread]); |
| 173 PendingSnapshotRequest pendingRequest; |
| 174 if (![tab webStateImpl]) |
| 175 return pendingRequest; |
| 176 NSUInteger sessionId = [[self keyForTab:tab] hash]; |
| 177 base::AutoLock guard(_lock); |
| 178 auto it = _pendingRequests.find(sessionId); |
| 179 if (it != _pendingRequests.end()) |
| 180 pendingRequest = it->second; |
| 181 return pendingRequest; |
| 182 } |
| 183 |
| 184 - (void)removePendingSnapshotRequest:(PendingSnapshotRequest)pendingRequest { |
| 185 base::AutoLock guard(_lock); |
| 186 auto itRequest = _pendingRequests.find(pendingRequest.sessionId); |
| 187 if (itRequest != _pendingRequests.end() && |
| 188 pendingRequest.requestId == itRequest->second.requestId) { |
| 189 _pendingRequests.erase(itRequest); |
| 190 } |
| 191 } |
| 192 |
| 193 - (void)removePendingSnapshotRequestForTab:(Tab*)tab { |
| 194 base::AutoLock guard(_lock); |
| 195 auto itRequest = _pendingRequests.find([[self keyForTab:tab] hash]); |
| 196 if (itRequest != _pendingRequests.end()) |
| 197 _pendingRequests.erase(itRequest); |
| 198 } |
| 199 |
| 200 - (BOOL)storeImage:(UIImage*)image |
| 201 forKey:(NSString*)key |
| 202 request:(PendingSnapshotRequest)request { |
| 203 DCHECK(request.requestId != 0); |
| 204 if (!image) |
| 205 return NO; |
| 206 |
| 207 { |
| 208 base::AutoLock guard(_lock); |
| 209 auto it = _pendingRequests.find(request.sessionId); |
| 210 if (it == _pendingRequests.end()) |
| 211 return NO; |
| 212 |
| 213 // Only write the image in cache if the request is still valid. |
| 214 if (request.requestId != it->second.requestId) |
| 215 return NO; |
| 216 } |
| 217 |
| 218 const CGFloat screenScale = [[UIScreen mainScreen] scale]; |
| 219 const NSUInteger cost = |
| 220 image.size.width * screenScale * image.size.height * screenScale; |
| 221 [_cache setObject:image forKey:key cost:cost]; |
| 222 return YES; |
| 223 } |
| 224 |
| 225 + (UIImage*)resizedImage:(UIImage*)image toSize:(CGSize)size { |
| 226 DCHECK(image.scale == 1); |
| 227 CGFloat screenScale = [[UIScreen mainScreen] scale]; |
| 228 CGSize pixelSize = size; |
| 229 pixelSize.width *= screenScale; |
| 230 pixelSize.height *= screenScale; |
| 231 UIImage* resizedSnapshot = |
| 232 ResizeImage(image, pixelSize, ProjectionMode::kAspectFillNoClipping, YES); |
| 233 // Creates a new image with the correct |scale| attribute. |
| 234 return [[[UIImage alloc] initWithCGImage:resizedSnapshot.CGImage |
| 235 scale:screenScale |
| 236 orientation:UIImageOrientationUp] autorelease]; |
| 237 } |
| 238 |
| 239 - (void)lowMemoryWarningReceived { |
| 240 [_cache removeAllObjects]; |
| 241 } |
| 242 |
| 243 - (void)setMainTabModel:(TabModel*)mainTabModel |
| 244 otrTabModel:(TabModel*)otrTabModel { |
| 245 if (mainTabModel != _mainTabModel) |
| 246 [self replaceOldTabModel:&_mainTabModel withTabModel:mainTabModel]; |
| 247 if (otrTabModel != _otrTabModel) |
| 248 [self replaceOldTabModel:&_otrTabModel withTabModel:otrTabModel]; |
| 249 } |
| 250 |
| 251 - (void)replaceOldTabModel:(TabModel**)oldTabModel |
| 252 withTabModel:(TabModel*)newTabModel { |
| 253 [*oldTabModel removeObserver:self]; |
| 254 *oldTabModel = newTabModel; |
| 255 [newTabModel addObserver:self]; |
| 256 } |
| 257 |
| 258 #pragma mark - TabModelObserver |
| 259 |
| 260 - (void)tabModel:(TabModel*)model |
| 261 didRemoveTab:(Tab*)tab |
| 262 atIndex:(NSUInteger)index { |
| 263 [self removePendingSnapshotRequestForTab:tab]; |
| 264 [_cache removeObjectForKey:[self keyForTab:tab]]; |
| 265 } |
| 266 |
| 267 - (void)tabModel:(TabModel*)model |
| 268 didChangeTabSnapshot:(Tab*)tab |
| 269 withImage:image { |
| 270 [self removePendingSnapshotRequestForTab:tab]; |
| 271 [_cache removeObjectForKey:[self keyForTab:tab]]; |
| 272 } |
| 273 |
| 274 @end |
OLD | NEW |