Files
Texture/Source/Details/_ASDisplayLayer.mm
ricky 2d7bf71e7c [_ASDisplayLayer] Add protection around setting a layer’s position and transform (#2116)
There is built in protection around setting invalid bounds for `_ASDisplayLayer`. Let’s extend this to also include protecting against setting an invalid position and an invalid transform.
2024-09-04 08:44:57 -07:00

221 lines
6.4 KiB
Plaintext

//
// _ASDisplayLayer.mm
// Texture
//
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
//
#import <AsyncDisplayKit/_ASDisplayLayer.h>
#import <AsyncDisplayKit/_ASAsyncTransactionContainer.h>
#import <AsyncDisplayKit/ASAssert.h>
#import <AsyncDisplayKit/ASDisplayNode.h>
#import <AsyncDisplayKit/ASDisplayNodeInternal.h>
#import <AsyncDisplayKit/ASInternalHelpers.h>
@implementation _ASDisplayLayer
{
BOOL _attemptedDisplayWhileZeroSized;
}
@dynamic displaysAsynchronously;
#pragma mark - Properties
- (void)setDisplaySuspended:(BOOL)displaySuspended
{
ASDisplayNodeAssertMainThread();
if (_displaySuspended != displaySuspended) {
_displaySuspended = displaySuspended;
if (!displaySuspended) {
// If resuming display, trigger a display now.
[self setNeedsDisplay];
} else {
// If suspending display, cancel any current async display so that we don't have contents set on us when it's finished.
[self cancelAsyncDisplay];
}
}
}
- (void)setPosition:(CGPoint)position
{
BOOL valid = ASDisplayNodeAssertNonFatal(ASIsCGPositionValidForLayout(position), @"Caught attempt to set invalid position %@ on %@.", NSStringFromCGPoint(position), self);
if (!valid) {
return;
}
[super setPosition:position];
}
- (void)setTransform:(CATransform3D)transform
{
BOOL valid = ASDisplayNodeAssertNonFatal(ASIsTransformValidForLayout(transform), @"Caught attempt to set invalid transform on %@.", self);
if (!valid) {
return;
}
[super setTransform:transform];
}
- (void)setBounds:(CGRect)bounds
{
BOOL valid = ASDisplayNodeAssertNonFatal(ASIsCGRectValidForLayout(bounds), @"Caught attempt to set invalid bounds %@ on %@.", NSStringFromCGRect(bounds), self);
if (!valid) {
return;
}
if ([self.delegate respondsToSelector:@selector(layer:didChangeBoundsWithOldValue:newValue:)]) {
CGRect oldBounds = self.bounds;
[super setBounds:bounds];
self.asyncdisplaykit_node.threadSafeBounds = bounds;
[(id<ASCALayerExtendedDelegate>)self.delegate layer:self didChangeBoundsWithOldValue:oldBounds newValue:bounds];
} else {
[super setBounds:bounds];
self.asyncdisplaykit_node.threadSafeBounds = bounds;
}
if (_attemptedDisplayWhileZeroSized && CGRectIsEmpty(bounds) == NO && self.needsDisplayOnBoundsChange == NO) {
_attemptedDisplayWhileZeroSized = NO;
[self setNeedsDisplay];
}
}
#if DEBUG // These override is strictly to help detect application-level threading errors. Avoid method overhead in release.
- (void)setContents:(id)contents
{
ASDisplayNodeAssertMainThread();
[super setContents:contents];
}
- (void)setNeedsLayout
{
ASDisplayNodeAssertMainThread();
as_log_verbose(ASNodeLog(), "%s on %@", sel_getName(_cmd), self);
[super setNeedsLayout];
}
#endif
- (void)layoutSublayers
{
ASDisplayNodeAssertMainThread();
[super layoutSublayers];
[self.asyncdisplaykit_node __layout];
}
- (void)setNeedsDisplay
{
ASDisplayNodeAssertMainThread();
// FIXME: Reconsider whether we should cancel a display in progress.
// We should definitely cancel a display that is scheduled, but unstarted display.
[self cancelAsyncDisplay];
// Short circuit if display is suspended. When resumed, we will setNeedsDisplay at that time.
if (!_displaySuspended) {
[super setNeedsDisplay];
}
}
#pragma mark -
+ (id<CAAction>)defaultActionForKey:(NSString *)event
{
// We never want to run one of CA's root default actions. So if we return nil from actionForLayer:forKey:, and let CA
// dig into the actions dictionary, and it doesn't find it there, it will check here and we need to stop the search.
return (id)kCFNull;
}
+ (dispatch_queue_t)displayQueue
{
static dispatch_queue_t displayQueue = NULL;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
displayQueue = dispatch_queue_create("org.AsyncDisplayKit.ASDisplayLayer.displayQueue", DISPATCH_QUEUE_CONCURRENT);
// we use the highpri queue to prioritize UI rendering over other async operations
dispatch_set_target_queue(displayQueue, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0));
});
return displayQueue;
}
+ (id)defaultValueForKey:(NSString *)key
{
if ([key isEqualToString:@"displaysAsynchronously"]) {
return @YES;
} else if ([key isEqualToString:@"opaque"]) {
return @YES;
} else if ([key isEqualToString:@"contentsScale"]) {
return @(ASScreenScale());
} else {
return [super defaultValueForKey:key];
}
}
#pragma mark - Display
- (void)displayImmediately
{
// This method is a low-level bypass that avoids touching CA, including any reset of the
// needsDisplay flag, until the .contents property is set with the result.
// It is designed to be able to block the thread of any caller and fully execute the display.
ASDisplayNodeAssertMainThread();
[self display:NO];
}
- (void)_hackResetNeedsDisplay
{
ASDisplayNodeAssertMainThread();
// Don't listen to our subclasses crazy ideas about setContents by going through super
super.contents = super.contents;
}
- (void)display
{
ASDisplayNodeAssertMainThread();
[self _hackResetNeedsDisplay];
if (self.displaySuspended) {
return;
}
[self display:self.displaysAsynchronously];
}
- (void)display:(BOOL)asynchronously
{
if (CGRectIsEmpty(self.bounds)) {
_attemptedDisplayWhileZeroSized = YES;
}
[self.asyncDelegate displayAsyncLayer:self asynchronously:asynchronously];
}
- (void)cancelAsyncDisplay
{
ASDisplayNodeAssertMainThread();
[self.asyncDelegate cancelDisplayAsyncLayer:self];
}
// e.g. <MYTextNodeLayer: 0xFFFFFF; node = <MYTextNode: 0xFFFFFFE; name = "Username node for user 179">>
- (NSString *)description
{
NSMutableString *description = [[super description] mutableCopy];
ASDisplayNode *node = self.asyncdisplaykit_node;
if (node != nil) {
NSString *classString = [NSString stringWithFormat:@"%s-", object_getClassName(node)];
[description replaceOccurrencesOfString:@"_ASDisplay" withString:classString options:kNilOptions range:NSMakeRange(0, description.length)];
NSUInteger insertionIndex = [description rangeOfString:@">"].location;
if (insertionIndex != NSNotFound) {
NSString *nodeString = [NSString stringWithFormat:@"; node = %@", node];
[description insertString:nodeString atIndex:insertionIndex];
}
}
return description;
}
@end