blob: 4e904f14e1667d74368d860fa5bf66a1745e0969 [file] [log] [blame]
// 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