| // Copyright 2014 The Chromium Authors. All rights reserved. | 
 | // Use of this source code is governed by a BSD-style license that can be | 
 | // found in the LICENSE file. | 
 |  | 
 | #import "ios/chrome/browser/snapshots/web_controller_snapshot_helper.h" | 
 |  | 
 | #import "ios/chrome/browser/snapshots/snapshot_manager.h" | 
 | #import "ios/chrome/browser/tabs/tab.h" | 
 | #import "ios/chrome/browser/tabs/tab_headers_delegate.h" | 
 | #import "ios/chrome/browser/ui/uikit_ui_util.h" | 
 | #import "ios/web/web_state/ui/crw_web_controller.h" | 
 |  | 
 | #if !defined(__has_feature) || !__has_feature(objc_arc) | 
 | #error "This file requires ARC support." | 
 | #endif | 
 |  | 
 | @interface WebControllerSnapshotHelper () | 
 |  | 
 | // Takes a snapshot image for the WebController's current page. Returns an | 
 | // autoreleased image cropped and scaled appropriately. Returns a default image | 
 | // if a snapshot cannot be generated. | 
 | - (UIImage*)generateSnapshotOrDefaultForWebController: | 
 |                 (CRWWebController*)webController | 
 |                                          withOverlays:(NSArray*)overlays | 
 |                                      visibleFrameOnly:(BOOL)visibleFrameOnly; | 
 |  | 
 | // Returns the cached snapshot if there is one matching the given parameters. | 
 | // Returns nil otherwise or if there is no |_coalescingSnapshotContext|. | 
 | - (UIImage*)cachedSnapshotWithOverlays:(NSArray*)overlays | 
 |                       visibleFrameOnly:(BOOL)visibleFrameOnly; | 
 |  | 
 | // Caches |snapshot| for the given |overlays| and |visibleFrameOnly|. Does | 
 | // nothing if there is no |_coalescingSnapshotContext|. | 
 | - (void)setCachedSnapshot:(UIImage*)snapshot | 
 |              withOverlays:(NSArray*)overlays | 
 |          visibleFrameOnly:(BOOL)visibleFrameOnly; | 
 | @end | 
 |  | 
 | // Class that contains information used when caching snapshots of a web page. | 
 | @interface CoalescingSnapshotContext : NSObject | 
 |  | 
 | // Returns the cached snapshot if there is one matching the given parameters. | 
 | // Returns nil otherwise. | 
 | - (UIImage*)cachedSnapshotWithOverlays:(NSArray*)overlays | 
 |                       visibleFrameOnly:(BOOL)visibleFrameOnly; | 
 |  | 
 | // Caches |snapshot| for the given |overlays| and |visibleFrameOnly|. | 
 | - (void)setCachedSnapshot:(UIImage*)snapshot | 
 |              withOverlays:(NSArray*)overlays | 
 |          visibleFrameOnly:(BOOL)visibleFrameOnly; | 
 |  | 
 | @end | 
 |  | 
 | @implementation CoalescingSnapshotContext { | 
 |   UIImage* _cachedSnapshot; | 
 | } | 
 |  | 
 | // Returns whether a snapshot should be cached in a page loaded context. | 
 | // Note: Returns YES if |overlays| is nil or empty and if |visibleFrameOnly| is | 
 | // YES as this is the only case when the snapshot taken by the CRWWebController | 
 | // is reused. | 
 | - (BOOL)shouldCacheSnapshotWithOverlays:(NSArray*)overlays | 
 |                        visibleFrameOnly:(BOOL)visibleFrameOnly { | 
 |   return ![overlays count] && visibleFrameOnly; | 
 | } | 
 |  | 
 | - (UIImage*)cachedSnapshotWithOverlays:(NSArray*)overlays | 
 |                       visibleFrameOnly:(BOOL)visibleFrameOnly { | 
 |   if ([self shouldCacheSnapshotWithOverlays:overlays | 
 |                            visibleFrameOnly:visibleFrameOnly]) { | 
 |     return _cachedSnapshot; | 
 |   } | 
 |   return nil; | 
 | } | 
 |  | 
 | - (void)setCachedSnapshot:(UIImage*)snapshot | 
 |              withOverlays:(NSArray*)overlays | 
 |          visibleFrameOnly:(BOOL)visibleFrameOnly { | 
 |   if ([self shouldCacheSnapshotWithOverlays:overlays | 
 |                            visibleFrameOnly:visibleFrameOnly]) { | 
 |     DCHECK(!_cachedSnapshot); | 
 |     _cachedSnapshot = snapshot; | 
 |   } | 
 | } | 
 |  | 
 | @end | 
 |  | 
 | @implementation WebControllerSnapshotHelper { | 
 |   CoalescingSnapshotContext* _coalescingSnapshotContext; | 
 |   SnapshotManager* _snapshotManager; | 
 |   __weak CRWWebController* _webController; | 
 |   // Owns this WebControllerSnapshotHelper. | 
 |   __weak Tab* _tab; | 
 | } | 
 |  | 
 | - (instancetype)init { | 
 |   NOTREACHED(); | 
 |   return nil; | 
 | } | 
 |  | 
 | - (instancetype)initWithSnapshotManager:(SnapshotManager*)snapshotManager | 
 |                                     tab:(Tab*)tab { | 
 |   self = [super init]; | 
 |   if (self) { | 
 |     DCHECK(snapshotManager); | 
 |     DCHECK(tab); | 
 |     DCHECK(tab.tabId); | 
 |     DCHECK([tab webController]); | 
 |     _snapshotManager = snapshotManager; | 
 |     _webController = [tab webController]; | 
 |     _tab = tab; | 
 |   } | 
 |   return self; | 
 | } | 
 |  | 
 | - (void)setSnapshotCoalescingEnabled:(BOOL)snapshotCoalescingEnabled { | 
 |   if (snapshotCoalescingEnabled) { | 
 |     DCHECK(!_coalescingSnapshotContext); | 
 |     _coalescingSnapshotContext = [[CoalescingSnapshotContext alloc] init]; | 
 |   } else { | 
 |     DCHECK(_coalescingSnapshotContext); | 
 |     _coalescingSnapshotContext = nil; | 
 |   } | 
 | } | 
 |  | 
 | - (UIImage*)generateSnapshotOrDefaultForWebController: | 
 |                 (CRWWebController*)webController | 
 |                                          withOverlays:(NSArray*)overlays | 
 |                                      visibleFrameOnly:(BOOL)visibleFrameOnly { | 
 |   UIImage* result = [self generateSnapshotForWebController:webController | 
 |                                               withOverlays:overlays | 
 |                                           visibleFrameOnly:visibleFrameOnly]; | 
 |   return result ? result : [[self class] defaultSnapshotImage]; | 
 | } | 
 |  | 
 | - (void)retrieveSnapshotForWebController:(CRWWebController*)webController | 
 |                                sessionID:(NSString*)sessionID | 
 |                             withOverlays:(NSArray*)overlays | 
 |                                 callback:(void (^)(UIImage* image))callback { | 
 |   [_snapshotManager | 
 |       retrieveImageForSessionID:sessionID | 
 |                        callback:^(UIImage* image) { | 
 |                          if (image) { | 
 |                            callback(image); | 
 |                          } else { | 
 |                            callback([self | 
 |                                updateSnapshotForWebController:webController | 
 |                                                     sessionID:sessionID | 
 |                                                  withOverlays:overlays | 
 |                                              visibleFrameOnly:YES]); | 
 |                          } | 
 |                        }]; | 
 | } | 
 |  | 
 | - (void)retrieveGreySnapshotForWebController:(CRWWebController*)webController | 
 |                                    sessionID:(NSString*)sessionID | 
 |                                 withOverlays:(NSArray*)overlays | 
 |                                     callback: | 
 |                                         (void (^)(UIImage* image))callback { | 
 |   [_snapshotManager | 
 |       retrieveGreyImageForSessionID:sessionID | 
 |                            callback:^(UIImage* image) { | 
 |                              if (image) { | 
 |                                callback(image); | 
 |                              } else { | 
 |                                callback(GreyImage([self | 
 |                                    updateSnapshotForWebController:webController | 
 |                                                         sessionID:sessionID | 
 |                                                      withOverlays:overlays | 
 |                                                  visibleFrameOnly:YES])); | 
 |                              } | 
 |                            }]; | 
 | } | 
 |  | 
 | - (UIImage*)updateSnapshotForWebController:(CRWWebController*)webController | 
 |                                  sessionID:(NSString*)sessionID | 
 |                               withOverlays:(NSArray*)overlays | 
 |                           visibleFrameOnly:(BOOL)visibleFrameOnly { | 
 |   // TODO(crbug.com/661641): This is a temporary solution to make sure the | 
 |   // webController retained by us is the same being passed in in this method. | 
 |   // Remove this when all uses of the "CRWWebController" are dropped from the | 
 |   // methods names since it is already retained by us and not necessary to be | 
 |   // passed in. | 
 |   DCHECK_EQ([_tab webController], webController); | 
 |   UIImage* snapshot = | 
 |       [self generateSnapshotOrDefaultForWebController:webController | 
 |                                          withOverlays:overlays | 
 |                                      visibleFrameOnly:visibleFrameOnly]; | 
 |   // If the snapshot is the default one, return it without caching it. | 
 |   if (snapshot == [[self class] defaultSnapshotImage]) | 
 |     return snapshot; | 
 |  | 
 |   UIImage* snapshotToCache = nil; | 
 |   // TODO(crbug.com/370994): Remove all code that references a Tab's delegates | 
 |   // from this file. | 
 |   if (visibleFrameOnly || ![_tab fullScreenControllerDelegate]) { | 
 |     snapshotToCache = snapshot; | 
 |   } else { | 
 |     // Crops the bottom of the fullscreen snapshot. | 
 |     CGRect cropRect = | 
 |         CGRectMake(0, [[_tab tabHeadersDelegate] headerHeightForTab:_tab], | 
 |                    [snapshot size].width, [snapshot size].height); | 
 |     snapshotToCache = CropImage(snapshot, cropRect); | 
 |   } | 
 |   [_snapshotManager setImage:snapshotToCache withSessionID:sessionID]; | 
 |   return snapshot; | 
 | } | 
 |  | 
 | - (UIImage*)generateSnapshotForWebController:(CRWWebController*)webController | 
 |                                 withOverlays:(NSArray*)overlays | 
 |                             visibleFrameOnly:(BOOL)visibleFrameOnly { | 
 |   if (![webController canUseViewForGeneratingOverlayPlaceholderView]) | 
 |     return nil; | 
 |   CGRect visibleFrame = (visibleFrameOnly ? [_tab snapshotContentArea] | 
 |                                           : [webController.view bounds]); | 
 |   if (CGRectIsEmpty(visibleFrame)) | 
 |     return nil; | 
 |   UIImage* snapshot = [self cachedSnapshotWithOverlays:overlays | 
 |                                       visibleFrameOnly:visibleFrameOnly]; | 
 |   if (!snapshot) { | 
 |     [_tab willUpdateSnapshot]; | 
 |     snapshot = [_snapshotManager generateSnapshotForView:webController.view | 
 |                                                 withRect:visibleFrame | 
 |                                                 overlays:overlays]; | 
 |     [self setCachedSnapshot:snapshot | 
 |                withOverlays:overlays | 
 |            visibleFrameOnly:visibleFrameOnly]; | 
 |   } | 
 |   return snapshot; | 
 | } | 
 |  | 
 | // TODO(crbug.com/661642): This code is shared with SnapshotManager. Remove this | 
 | // and add it as part of WebDelegate delegate API such that a default image is | 
 | // returned immediately. | 
 | + (UIImage*)defaultSnapshotImage { | 
 |   static UIImage* defaultImage = nil; | 
 |  | 
 |   if (!defaultImage) { | 
 |     CGRect frame = CGRectMake(0, 0, 2, 2); | 
 |     UIGraphicsBeginImageContext(frame.size); | 
 |     [[UIColor whiteColor] setFill]; | 
 |     CGContextFillRect(UIGraphicsGetCurrentContext(), frame); | 
 |  | 
 |     UIImage* result = UIGraphicsGetImageFromCurrentImageContext(); | 
 |     UIGraphicsEndImageContext(); | 
 |  | 
 |     defaultImage = [result stretchableImageWithLeftCapWidth:1 topCapHeight:1]; | 
 |   } | 
 |   return defaultImage; | 
 | } | 
 |  | 
 | - (void)setCachedSnapshot:(UIImage*)snapshot | 
 |              withOverlays:(NSArray*)overlays | 
 |          visibleFrameOnly:(BOOL)visibleFrameOnly { | 
 |   return [_coalescingSnapshotContext setCachedSnapshot:snapshot | 
 |                                           withOverlays:overlays | 
 |                                       visibleFrameOnly:visibleFrameOnly]; | 
 | } | 
 |  | 
 | - (UIImage*)cachedSnapshotWithOverlays:(NSArray*)overlays | 
 |                       visibleFrameOnly:(BOOL)visibleFrameOnly { | 
 |   return | 
 |       [_coalescingSnapshotContext cachedSnapshotWithOverlays:overlays | 
 |                                             visibleFrameOnly:visibleFrameOnly]; | 
 | } | 
 |  | 
 | @end |