diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 85b121ac..66b34aad 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -436,6 +436,18 @@ E5C347B31ECB40AA00EC4BE4 /* ASTableNode+Beta.h in Headers */ = {isa = PBXBuildFile; fileRef = E5C347B21ECB40AA00EC4BE4 /* ASTableNode+Beta.h */; }; E5E281741E71C833006B67C2 /* ASCollectionLayoutState.h in Headers */ = {isa = PBXBuildFile; fileRef = E5E281731E71C833006B67C2 /* ASCollectionLayoutState.h */; settings = {ATTRIBUTES = (Public, ); }; }; E5E281761E71C845006B67C2 /* ASCollectionLayoutState.m in Sources */ = {isa = PBXBuildFile; fileRef = E5E281751E71C845006B67C2 /* ASCollectionLayoutState.m */; }; + E5EEAAB81EE7527D00FC07DA /* NSObject+TextureDebugger.h in Headers */ = {isa = PBXBuildFile; fileRef = E5EEAAAA1EE7527D00FC07DA /* NSObject+TextureDebugger.h */; }; + E5EEAAB91EE7527D00FC07DA /* NSObject+TextureDebugger.mm in Sources */ = {isa = PBXBuildFile; fileRef = E5EEAAAB1EE7527D00FC07DA /* NSObject+TextureDebugger.mm */; }; + E5EEAABA1EE7527D00FC07DA /* PDDOMTypes+UIKit.h in Headers */ = {isa = PBXBuildFile; fileRef = E5EEAAAC1EE7527D00FC07DA /* PDDOMTypes+UIKit.h */; }; + E5EEAABB1EE7527D00FC07DA /* PDDOMTypes+UIKit.m in Sources */ = {isa = PBXBuildFile; fileRef = E5EEAAAD1EE7527D00FC07DA /* PDDOMTypes+UIKit.m */; }; + E5EEAABC1EE7527D00FC07DA /* TDDebugger.h in Headers */ = {isa = PBXBuildFile; fileRef = E5EEAAAE1EE7527D00FC07DA /* TDDebugger.h */; }; + E5EEAABD1EE7527D00FC07DA /* TDDebugger.m in Sources */ = {isa = PBXBuildFile; fileRef = E5EEAAAF1EE7527D00FC07DA /* TDDebugger.m */; }; + E5EEAAC01EE7527D00FC07DA /* TDElementDomainController.h in Headers */ = {isa = PBXBuildFile; fileRef = E5EEAAB21EE7527D00FC07DA /* TDElementDomainController.h */; }; + E5EEAAC11EE7527D00FC07DA /* TDElementDomainController.m in Sources */ = {isa = PBXBuildFile; fileRef = E5EEAAB31EE7527D00FC07DA /* TDElementDomainController.m */; }; + E5EEAAC21EE7527D00FC07DA /* TDElementPropsDomainController.h in Headers */ = {isa = PBXBuildFile; fileRef = E5EEAAB41EE7527D00FC07DA /* TDElementPropsDomainController.h */; }; + E5EEAAC31EE7527D00FC07DA /* TDElementPropsDomainController.m in Sources */ = {isa = PBXBuildFile; fileRef = E5EEAAB51EE7527D00FC07DA /* TDElementPropsDomainController.m */; }; + E5EEAAC41EE7527D00FC07DA /* TDDOMContext.h in Headers */ = {isa = PBXBuildFile; fileRef = E5EEAAB61EE7527D00FC07DA /* TDDOMContext.h */; }; + E5EEAAC51EE7527D00FC07DA /* TDDOMContext.m in Sources */ = {isa = PBXBuildFile; fileRef = E5EEAAB71EE7527D00FC07DA /* TDDOMContext.m */; }; F711994E1D20C21100568860 /* ASDisplayNodeExtrasTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F711994D1D20C21100568860 /* ASDisplayNodeExtrasTests.m */; }; /* End PBXBuildFile section */ @@ -898,6 +910,18 @@ E5C347B21ECB40AA00EC4BE4 /* ASTableNode+Beta.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASTableNode+Beta.h"; sourceTree = ""; }; E5E281731E71C833006B67C2 /* ASCollectionLayoutState.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionLayoutState.h; sourceTree = ""; }; E5E281751E71C845006B67C2 /* ASCollectionLayoutState.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASCollectionLayoutState.m; sourceTree = ""; }; + E5EEAAAA1EE7527D00FC07DA /* NSObject+TextureDebugger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSObject+TextureDebugger.h"; path = "Debugger/NSObject+TextureDebugger.h"; sourceTree = ""; }; + E5EEAAAB1EE7527D00FC07DA /* NSObject+TextureDebugger.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "NSObject+TextureDebugger.mm"; path = "Debugger/NSObject+TextureDebugger.mm"; sourceTree = ""; }; + E5EEAAAC1EE7527D00FC07DA /* PDDOMTypes+UIKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "PDDOMTypes+UIKit.h"; path = "Debugger/PDDOMTypes+UIKit.h"; sourceTree = ""; }; + E5EEAAAD1EE7527D00FC07DA /* PDDOMTypes+UIKit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "PDDOMTypes+UIKit.m"; path = "Debugger/PDDOMTypes+UIKit.m"; sourceTree = ""; }; + E5EEAAAE1EE7527D00FC07DA /* TDDebugger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = TDDebugger.h; path = Debugger/TDDebugger.h; sourceTree = ""; }; + E5EEAAAF1EE7527D00FC07DA /* TDDebugger.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TDDebugger.m; path = Debugger/TDDebugger.m; sourceTree = ""; }; + E5EEAAB21EE7527D00FC07DA /* TDElementDomainController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = TDElementDomainController.h; path = Debugger/TDElementDomainController.h; sourceTree = ""; }; + E5EEAAB31EE7527D00FC07DA /* TDElementDomainController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TDElementDomainController.m; path = Debugger/TDElementDomainController.m; sourceTree = ""; }; + E5EEAAB41EE7527D00FC07DA /* TDElementPropsDomainController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = TDElementPropsDomainController.h; path = Debugger/TDElementPropsDomainController.h; sourceTree = ""; }; + E5EEAAB51EE7527D00FC07DA /* TDElementPropsDomainController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TDElementPropsDomainController.m; path = Debugger/TDElementPropsDomainController.m; sourceTree = ""; }; + E5EEAAB61EE7527D00FC07DA /* TDDOMContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = TDDOMContext.h; path = Debugger/TDDOMContext.h; sourceTree = ""; }; + E5EEAAB71EE7527D00FC07DA /* TDDOMContext.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TDDOMContext.m; path = Debugger/TDDOMContext.m; sourceTree = ""; }; EFA731F0396842FF8AB635EE /* libPods-AsyncDisplayKitTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-AsyncDisplayKitTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; F711994D1D20C21100568860 /* ASDisplayNodeExtrasTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASDisplayNodeExtrasTests.m; sourceTree = ""; }; FB07EABBCF28656C6297BC2D /* Pods-AsyncDisplayKitTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AsyncDisplayKitTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-AsyncDisplayKitTests/Pods-AsyncDisplayKitTests.debug.xcconfig"; sourceTree = ""; }; @@ -1086,6 +1110,7 @@ 058D0A01195D050800B7D73C /* Private */, AC6456051B0A333200CF11B8 /* Layout */, 257754661BED245B00737CA5 /* TextKit */, + E56FF9AD1EC9A75D008C34EC /* Debugger */, 690ED5911E36D118000627C0 /* tvOS */, 058D09B2195D04C000B7D73C /* Supporting Files */, ); @@ -1587,6 +1612,25 @@ path = Debug; sourceTree = ""; }; + E56FF9AD1EC9A75D008C34EC /* Debugger */ = { + isa = PBXGroup; + children = ( + E5EEAAAA1EE7527D00FC07DA /* NSObject+TextureDebugger.h */, + E5EEAAAB1EE7527D00FC07DA /* NSObject+TextureDebugger.mm */, + E5EEAAAC1EE7527D00FC07DA /* PDDOMTypes+UIKit.h */, + E5EEAAAD1EE7527D00FC07DA /* PDDOMTypes+UIKit.m */, + E5EEAAAE1EE7527D00FC07DA /* TDDebugger.h */, + E5EEAAAF1EE7527D00FC07DA /* TDDebugger.m */, + E5EEAAB21EE7527D00FC07DA /* TDElementDomainController.h */, + E5EEAAB31EE7527D00FC07DA /* TDElementDomainController.m */, + E5EEAAB41EE7527D00FC07DA /* TDElementPropsDomainController.h */, + E5EEAAB51EE7527D00FC07DA /* TDElementPropsDomainController.m */, + E5EEAAB61EE7527D00FC07DA /* TDDOMContext.h */, + E5EEAAB71EE7527D00FC07DA /* TDDOMContext.m */, + ); + name = Debugger; + sourceTree = ""; + }; E5B077EB1E6843AF00C24B5B /* Collection Layout */ = { isa = PBXGroup; children = ( @@ -1626,6 +1670,8 @@ E5E281741E71C833006B67C2 /* ASCollectionLayoutState.h in Headers */, E5B077FF1E69F4EB00C24B5B /* ASElementMap.h in Headers */, CCCCCCE31EC3EF060087FE10 /* NSParagraphStyle+ASText.h in Headers */, + E5EEAABC1EE7527D00FC07DA /* TDDebugger.h in Headers */, + E5EEAAC01EE7527D00FC07DA /* TDElementDomainController.h in Headers */, E58E9E441E941D74004CFC59 /* ASCollectionLayoutContext.h in Headers */, E58E9E421E941D74004CFC59 /* ASCollectionFlowLayoutDelegate.h in Headers */, 696F01EC1DD2AF450049FBD5 /* ASEventLog.h in Headers */, @@ -1640,6 +1686,7 @@ 69F10C871C84C35D0026140C /* ASRangeControllerUpdateRangeProtocol+Beta.h in Headers */, B350623C1B010EFD0018CF92 /* _ASAsyncTransaction.h in Headers */, 68355B411CB57A6C001D4E68 /* ASImageContainerProtocolCategories.h in Headers */, + E5EEAAB81EE7527D00FC07DA /* NSObject+TextureDebugger.h in Headers */, 7630FFA81C9E267E007A7C0E /* ASVideoNode.h in Headers */, B350623F1B010EFD0018CF92 /* _ASAsyncTransactionContainer.h in Headers */, B13CA1011C52004900E031AB /* ASCollectionNode+Beta.h in Headers */, @@ -1661,6 +1708,7 @@ A2763D7A1CBDD57D00A9ADBD /* ASPINRemoteImageDownloader.h in Headers */, 34EFC7611B701C9C00AD841F /* ASBackgroundLayoutSpec.h in Headers */, B35062591B010F070018CF92 /* ASBaseDefines.h in Headers */, + E5EEAAC41EE7527D00FC07DA /* TDDOMContext.h in Headers */, B35062131B010EFD0018CF92 /* ASBasicImageDownloader.h in Headers */, B35062151B010EFD0018CF92 /* ASBatchContext.h in Headers */, B35061F31B010EFD0018CF92 /* ASCellNode.h in Headers */, @@ -1716,6 +1764,7 @@ 254C6B771BF94DF4003EC431 /* ASTextKitAttributes.h in Headers */, 254C6B7D1BF94DF4003EC431 /* ASTextKitShadower.h in Headers */, 690ED58E1E36BCA6000627C0 /* ASLayoutElementStylePrivate.h in Headers */, + E5EEAAC21EE7527D00FC07DA /* TDElementPropsDomainController.h in Headers */, CC55A70D1E529FA200594372 /* UIResponder+AsyncDisplayKit.h in Headers */, 254C6B731BF94DF4003EC431 /* ASTextKitCoreTextAdditions.h in Headers */, 254C6B7A1BF94DF4003EC431 /* ASTextKitRenderer.h in Headers */, @@ -1781,6 +1830,7 @@ CC3B208A1C3F7A5400798563 /* ASWeakSet.h in Headers */, B35062041B010EFD0018CF92 /* ASMultiplexImageNode.h in Headers */, DECBD6E81BE56E1900CF4905 /* ASButtonNode.h in Headers */, + E5EEAABA1EE7527D00FC07DA /* PDDOMTypes+UIKit.h in Headers */, B35062241B010EFD0018CF92 /* ASMutableAttributedStringBuilder.h in Headers */, B13CA0F81C519EBA00E031AB /* ASCollectionViewLayoutFacilitatorProtocol.h in Headers */, B35062061B010EFD0018CF92 /* ASNetworkImageNode.h in Headers */, @@ -2091,6 +2141,7 @@ B30BF6541C59D889004FCD53 /* ASLayoutManager.m in Sources */, 690C35671E0567C600069B91 /* ASDimensionDeprecated.mm in Sources */, 92DD2FE71BF4D0850074C9DD /* ASMapNode.mm in Sources */, + E5EEAABD1EE7527D00FC07DA /* TDDebugger.m in Sources */, CCA282B91E9EA8E40037E8B7 /* AsyncDisplayKit+Tips.m in Sources */, 636EA1A51C7FF4EF00EE152F /* ASDefaultPlayButton.m in Sources */, B350623D1B010EFD0018CF92 /* _ASAsyncTransaction.mm in Sources */, @@ -2185,6 +2236,7 @@ 9019FBBE1ED8061D00C45F72 /* ASYogaLayoutSpec.mm in Sources */, B35062071B010EFD0018CF92 /* ASNetworkImageNode.mm in Sources */, 34EFC76D1B701CF100AD841F /* ASOverlayLayoutSpec.mm in Sources */, + E5EEAABB1EE7527D00FC07DA /* PDDOMTypes+UIKit.m in Sources */, 044285101BAA64EC00D16268 /* ASTwoDimensionalArrayUtils.m in Sources */, CCCCCCDE1EC3EF060087FE10 /* ASTextAttribute.m in Sources */, CCA282B51E9EA7310037E8B7 /* ASTipsController.m in Sources */, @@ -2193,6 +2245,7 @@ 68FC85E61CE29B9400EDD713 /* ASNavigationController.m in Sources */, CC4C2A791D88E3BF0039ACAB /* ASTraceEvent.m in Sources */, 34EFC76F1B701CF700AD841F /* ASRatioLayoutSpec.mm in Sources */, + E5EEAAC51EE7527D00FC07DA /* TDDOMContext.m in Sources */, 254C6B8B1BF94F8A003EC431 /* ASTextKitShadower.mm in Sources */, 254C6B851BF94F8A003EC431 /* ASTextKitAttributes.mm in Sources */, 90FC784F1E4BFE1B00383C5A /* ASDisplayNode+Yoga.mm in Sources */, @@ -2208,6 +2261,7 @@ 696F01EE1DD2AF450049FBD5 /* ASEventLog.mm in Sources */, 9C70F2051CDA4F06007D6C76 /* ASTraitCollection.m in Sources */, 83A7D95B1D44547700BF333E /* ASWeakMap.m in Sources */, + E5EEAAC31EE7527D00FC07DA /* TDElementPropsDomainController.m in Sources */, CC034A0A1E60BEB400626263 /* ASDisplayNode+Convenience.m in Sources */, E58E9E431E941D74004CFC59 /* ASCollectionFlowLayoutDelegate.m in Sources */, DE84918E1C8FFF9F003D89E9 /* ASRunLoopQueue.mm in Sources */, @@ -2226,6 +2280,7 @@ 509E68661B3AEDD7009B9150 /* CoreGraphics+ASConvenience.m in Sources */, 254C6B871BF94F8A003EC431 /* ASTextKitEntityAttribute.m in Sources */, 34566CB31BC1213700715E6B /* ASPhotosFrameworkImageRequest.m in Sources */, + E5EEAAC11EE7527D00FC07DA /* TDElementDomainController.m in Sources */, 254C6B831BF94F8A003EC431 /* ASTextKitCoreTextAdditions.m in Sources */, CCCCCCE21EC3EF060087FE10 /* ASTextUtilities.m in Sources */, CC55A70E1E529FA200594372 /* UIResponder+AsyncDisplayKit.m in Sources */, @@ -2235,6 +2290,7 @@ 044284FD1BAA365100D16268 /* UICollectionViewLayout+ASConvenience.m in Sources */, CC0F885B1E42807F00576FED /* ASCollectionViewFlowLayoutInspector.m in Sources */, 690ED5981E36D118000627C0 /* ASControlNode+tvOS.m in Sources */, + E5EEAAB91EE7527D00FC07DA /* NSObject+TextureDebugger.mm in Sources */, 254C6B8A1BF94F8A003EC431 /* ASTextKitRenderer+TextChecking.mm in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Source/Base/ASAvailability.h b/Source/Base/ASAvailability.h index 39355060..b05a53fb 100644 --- a/Source/Base/ASAvailability.h +++ b/Source/Base/ASAvailability.h @@ -50,6 +50,7 @@ #define AS_PIN_REMOTE_IMAGE __has_include() #define AS_IG_LIST_KIT __has_include() +#define AS_TEXTURE_DEBUGGER __has_include() /** * For IGListKit versions < 3.0, you have to use IGListCollectionView. diff --git a/Source/Debugger/NSObject+TextureDebugger.h b/Source/Debugger/NSObject+TextureDebugger.h new file mode 100644 index 00000000..8090dca9 --- /dev/null +++ b/Source/Debugger/NSObject+TextureDebugger.h @@ -0,0 +1,33 @@ +// +// NSObject+PDDOMNode.h +// Texture +// +// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// + +#import + +#if AS_TEXTURE_DEBUGGER + +#import + +NS_ASSUME_NONNULL_BEGIN + +@class PDDOMNode, TDDOMContext; + +@interface NSObject (TextureDebugger) + +- (PDDOMNode *)td_generateDOMNodeWithContext:(TDDOMContext *)context; +- (CGRect)td_frameInWindow; +- (NSArray *)td_children; + +@end + +NS_ASSUME_NONNULL_END + +#endif diff --git a/Source/Debugger/NSObject+TextureDebugger.mm b/Source/Debugger/NSObject+TextureDebugger.mm new file mode 100644 index 00000000..8c40f694 --- /dev/null +++ b/Source/Debugger/NSObject+TextureDebugger.mm @@ -0,0 +1,293 @@ +// +// NSObject+TextureDebugger.m +// Texture +// +// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// + +#import + +#if AS_TEXTURE_DEBUGGER + +#import +#import +#import +#import +#import + +#import + +#import + +// Constants defined in the DOM Level 2 Core: http://www.w3.org/TR/DOM-Level-2-Core/core.html#ID-1950641247 +static const int kPDDOMNodeTypeElement = 1; + +NS_ASSUME_NONNULL_BEGIN + +@interface NSObject (TDDOMNodeGenerating) + ++ (NSString *)td_nodeName; + +@end + +@implementation NSObject (TextureDebugger) + ++ (NSString *)td_nodeName +{ + return @"object"; +} + +- (PDDOMNode *)td_generateDOMNodeWithContext:(TDDOMContext *)context +{ + NSNumber *nodeId = [context idForObject:self]; + [context.idToFrameInWindow setRect:[self td_frameInWindow] forKey:nodeId]; + + PDDOMNode *node = [[PDDOMNode alloc] init]; + node.nodeType = @(kPDDOMNodeTypeElement); + node.nodeId = nodeId; + node.nodeName = [[self class] td_nodeName]; + node.attributes = @[ @"description", self.debugDescription ]; + + NSMutableArray *nodeChildren = [NSMutableArray array]; + for (id child in [self td_children]) { + [nodeChildren addObject:[child td_generateDOMNodeWithContext:context]]; + } + node.children = nodeChildren; + node.childNodeCount = @(nodeChildren.count); + + return node; +} + +- (CGRect)td_frameInWindow +{ + return CGRectNull; +} + +- (NSArray *)td_children +{ + return @[]; +} + +@end + +@implementation UIApplication (TextureDebugger) + ++ (NSString *)td_nodeName +{ + return @"application"; +} + +- (NSArray *)td_children +{ + return self.windows; +} + +@end + +@implementation CALayer (TextureDebugger) + ++ (NSString *)td_nodeName +{ + return @"layer"; +} + +- (PDDOMNode *)td_generateDOMNodeWithContext:(TDDOMContext *)context +{ + // For backing store of a display node (view/layer), let the node handle this job + ASDisplayNode *displayNode = ASLayerToDisplayNode(self); + if (displayNode) { + return [displayNode td_generateDOMNodeWithContext:context]; + } + + return [super td_generateDOMNodeWithContext:context]; +} + +- (CGRect)td_frameInWindow +{ + return [self convertRect:self.bounds toLayer:nil]; +} + +- (NSArray *)td_children +{ + return self.sublayers; +} + +@end + +@implementation UIView (TextureDebugger) + ++ (NSString *)td_nodeName +{ + return @"view"; +} + +- (PDDOMNode *)td_generateDOMNodeWithContext:(TDDOMContext *)context +{ + // For backing store of a display node (view/layer), let the node handle this job + ASDisplayNode *displayNode = ASViewToDisplayNode(self); + if (displayNode) { + return [displayNode td_generateDOMNodeWithContext:context]; + } + + return [super td_generateDOMNodeWithContext:context]; +} + +- (CGRect)td_frameInWindow +{ + return [self convertRect:self.bounds toView:nil]; +} + +- (NSArray *)td_children +{ + return self.subviews; +} + +@end + +@implementation UIWindow (TextureDebugger) + ++ (NSString *)td_nodeName +{ + return @"window"; +} + +@end + +@implementation ASLayoutSpec (TextureDebugger) + ++ (NSString *)td_nodeName +{ + return @"layout-spec"; +} + +@end + +@implementation ASDisplayNode (TextureDebugger) + ++ (NSString *)td_nodeName +{ + return @"display-node"; +} + +- (PDDOMNode *)td_generateDOMNodeWithContext:(TDDOMContext *)DOMCcontext +{ + PDDOMNode *rootNode = [super td_generateDOMNodeWithContext:DOMCcontext]; + if (rootNode.childNodeCount.intValue > 0) { + // If rootNode.children was populated, return right away. + return rootNode; + } + + /* + * The rest of this method does 2 things: + * - Generate the rest of the DOM tree: + * ASDisplayNode has a different way to generate DOM children. + * That is, from an unflattened layout, a DOM child is generated from the layout element of each sublayout in the layout tree. + * In addition, since non-display-node layout elements (e.g layout specs) don't (and shouldn't) store their calculated layout, + * they can't generate their own DOM children. So it's the responsibility of the root display node to fill out the gaps. + * - Calculate the frame in window of some layout elements in the layout tree: + * Non-display-node layout elements can't determine their own frame because they don't have a backing store. + * Thus, it's also the responsibility of the root display node to calculate and keep track of the frame of each child + * and assign to it if need to. + */ + struct Context { + PDDOMNode *node; + ASLayout *layout; + CGRect frameInWindow; + }; + + // Queue used to keep track of sublayouts while traversing this layout in BFS frashion. + std::queue queue; + queue.push({rootNode, self.unflattenedCalculatedLayout, self.td_frameInWindow}); + + while (!queue.empty()) { + Context context = queue.front(); + queue.pop(); + + ASLayout *layout = context.layout; + NSArray *sublayouts = layout.sublayouts; + PDDOMNode *node = context.node; + NSMutableArray *children = [NSMutableArray arrayWithCapacity:sublayouts.count]; + CGRect frameInWindow = context.frameInWindow; + + for (ASLayout *sublayout in sublayouts) { + NSObject *sublayoutElement = sublayout.layoutElement; + PDDOMNode *subnode = [sublayoutElement td_generateDOMNodeWithContext:DOMCcontext]; + [children addObject:subnode]; + + // Non-display-node (sub)elements can't generate their own DOM children and frame in window + // We calculate the frame and assign to those now + // We add them to the queue to generate their DOM children later + if ([sublayout.layoutElement isKindOfClass:[ASDisplayNode class]] == NO) { + CGRect sublayoutElementFrameInWindow = CGRectNull; + if (! CGRectIsNull(frameInWindow)) { + sublayoutElementFrameInWindow = CGRectMake(frameInWindow.origin.x + sublayout.position.x, + frameInWindow.origin.y + sublayout.position.y, + sublayout.size.width, + sublayout.size.height); + } + [DOMCcontext.idToFrameInWindow setRect:sublayoutElementFrameInWindow forKey:subnode.nodeId]; + + queue.push({subnode, sublayout, sublayoutElementFrameInWindow}); + } + } + + node.children = children; + node.childNodeCount = @(children.count); + } + + return rootNode; +} + +- (CGRect)td_frameInWindow +{ + if (self.isNodeLoaded == NO || self.isInHierarchy == NO) { + return CGRectNull; + } + + if (self.layerBacked) { + return self.layer.td_frameInWindow; + } else { + return self.view.td_frameInWindow; + } +} + +@end + +@implementation ASCollectionNode (TextureDebugger) + ++ (NSString *)td_nodeName +{ + return @"collection-node"; +} + +- (NSArray *)td_children +{ + // Only show visible nodes for now. This requires user to refresh the browser to update the DOM. + return self.visibleNodes; +} + +@end + +@implementation ASTableNode (TextureDebugger) + ++ (NSString *)td_nodeName +{ + return @"table-node"; +} + +- (NSArray *)td_children +{ + // Only show visible nodes for now. This requires user to refresh the browser to update the DOM. + return self.visibleNodes; +} + +@end + +NS_ASSUME_NONNULL_END + +#endif diff --git a/Source/Debugger/PDDOMTypes+UIKit.h b/Source/Debugger/PDDOMTypes+UIKit.h new file mode 100644 index 00000000..70b38d3a --- /dev/null +++ b/Source/Debugger/PDDOMTypes+UIKit.h @@ -0,0 +1,38 @@ +// +// PDDOMTypes+UIKit.h +// Texture +// +// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// + +#import + +#if AS_TEXTURE_DEBUGGER + +#import +#import + +@interface NSDictionary (PDDOMRGBA) + +-(UIColor *)UIColor; + +@end + +@interface NSDictionary (PDDOMHighlightConfig) + +-(UIColor *)contentUIColor; + +-(UIColor *)paddingUIColor; + +-(UIColor *)borderUIColor; + +-(UIColor *)marginUIColor; + +@end + +#endif diff --git a/Source/Debugger/PDDOMTypes+UIKit.m b/Source/Debugger/PDDOMTypes+UIKit.m new file mode 100644 index 00000000..7554a48e --- /dev/null +++ b/Source/Debugger/PDDOMTypes+UIKit.m @@ -0,0 +1,55 @@ +// +// PDDOMTypes+UIKit.m +// Texture +// +// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// + +#import + +#if AS_TEXTURE_DEBUGGER + +#import "PDDOMTypes+UIKit.h" + +@implementation NSDictionary (PDDOMRGBA) + +- (UIColor *)UIColor +{ + return [UIColor colorWithRed:[[self valueForKey:@"r"] floatValue] / 255.0 + green:[[self valueForKey:@"g"] floatValue] / 255.0 + blue:[[self valueForKey:@"b"] floatValue] / 255.0 + alpha:[[self valueForKey:@"a"] floatValue]]; +} + +@end + +@implementation NSDictionary (PDDOMHighlightConfig) + +- (UIColor *)contentUIColor +{ + return ((NSDictionary *)[self valueForKey:@"contentColor"]).UIColor; +} + +- (UIColor *)paddingUIColor +{ + return ((NSDictionary *)[self valueForKey:@"paddingColor"]).UIColor; +} + +- (UIColor *)borderUIColor +{ + return ((NSDictionary *)[self valueForKey:@"borderColor"]).UIColor; +} + +- (UIColor *)marginUIColor +{ + return ((NSDictionary *)[self valueForKey:@"marginColor"]).UIColor; +} + +@end + +#endif diff --git a/Source/Debugger/TDDOMContext.h b/Source/Debugger/TDDOMContext.h new file mode 100644 index 00000000..63afa6e2 --- /dev/null +++ b/Source/Debugger/TDDOMContext.h @@ -0,0 +1,32 @@ +// +// TDDOMContext.h +// Texture +// +// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface TDDOMContext : NSObject + +/** + * Id to object map for fast lookup. Populated on-the-fly whenever an object id is requested. + */ +@property (nonatomic, strong) NSMapTable *idToObjectMap; +/** + * Map of id to frame in window of the represented object, for fast lookup. Populated on-the-fly. + */ +@property (nonatomic, strong) NSMapTable *idToFrameInWindow; + +- (NSNumber *)idForObject:(NSObject *)object; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/Debugger/TDDOMContext.m b/Source/Debugger/TDDOMContext.m new file mode 100644 index 00000000..a202d2f0 --- /dev/null +++ b/Source/Debugger/TDDOMContext.m @@ -0,0 +1,38 @@ +// +// TDDOMContext.m +// Texture +// +// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// + +#import + +#import + +@implementation TDDOMContext { + NSUInteger _counter; +} + +- (instancetype)init +{ + self = [super init]; + if (self) { + _idToObjectMap = [NSMapTable strongToStrongObjectsMapTable]; + _idToFrameInWindow = [ASRectTable rectTableForStrongObjectPointers]; + } + return self; +} + +- (NSNumber *)idForObject:(NSObject *)object +{ + NSNumber *result = @(_counter++); + [_idToObjectMap setObject:object forKey:result]; + return result; +} + +@end diff --git a/Source/Debugger/TDDebugger.h b/Source/Debugger/TDDebugger.h new file mode 100644 index 00000000..fe51c7e5 --- /dev/null +++ b/Source/Debugger/TDDebugger.h @@ -0,0 +1,27 @@ +// +// TDDebugger.h +// Texture +// +// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// + +#import + +#if AS_TEXTURE_DEBUGGER + +#import + +@interface TDDebugger : PDDebugger + ++ (TDDebugger *)defaultInstance; + +- (void)enableLayoutElementDebuggingWithApplication:(UIApplication *)application; + +@end + +#endif // AS_TEXTURE_DEBUGGER diff --git a/Source/Debugger/TDDebugger.m b/Source/Debugger/TDDebugger.m new file mode 100644 index 00000000..f5878a6a --- /dev/null +++ b/Source/Debugger/TDDebugger.m @@ -0,0 +1,42 @@ +// +// TDDebugger.m +// Texture +// +// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// + +#import + +#if AS_TEXTURE_DEBUGGER + +#import +#import + +@implementation TDDebugger + ++ (TDDebugger *)defaultInstance +{ + static dispatch_once_t onceToken; + static TDDebugger *defaultInstance = nil; + dispatch_once(&onceToken, ^{ + defaultInstance = [[[self class] alloc] init]; + }); + + return defaultInstance; +} + +- (void)enableLayoutElementDebuggingWithApplication:(UIApplication *)application +{ + TDElementDomainController *elementController = [TDElementDomainController defaultInstance]; + [self addController:elementController]; + [elementController startMonitoringWithApplication:application]; +} + +@end + +#endif // AS_TEXTURE_DEBUGGER diff --git a/Source/Debugger/TDElementDomainController.h b/Source/Debugger/TDElementDomainController.h new file mode 100644 index 00000000..f4f93b66 --- /dev/null +++ b/Source/Debugger/TDElementDomainController.h @@ -0,0 +1,34 @@ +// +// TDElementDomainController.h +// Texture +// +// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// + +#import + +#if AS_TEXTURE_DEBUGGER + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface TDElementDomainController : PDDomainController + +@property (nonatomic, strong) PDDOMDomain *domain; + ++ (TDElementDomainController *)defaultInstance; + +- (void)startMonitoringWithApplication:(UIApplication *)application; + +@end + +NS_ASSUME_NONNULL_END + +#endif diff --git a/Source/Debugger/TDElementDomainController.m b/Source/Debugger/TDElementDomainController.m new file mode 100644 index 00000000..85a5a5df --- /dev/null +++ b/Source/Debugger/TDElementDomainController.m @@ -0,0 +1,149 @@ +// +// TDElementDomainController.m +// Texture +// +// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// + +#import + +#if AS_TEXTURE_DEBUGGER + +#import "TDElementDomainController.h" + +#import +#import + +#import +#import +#import +#import +#import + +#import + +// Constants defined in the DOM Level 2 Core: http://www.w3.org/TR/DOM-Level-2-Core/core.html#ID-1950641247 +static const int kPDDOMNodeTypeDocument = 9; + +@interface TDElementDomainController () + +@property (nonatomic, strong) TDDOMContext *context; + +@property (nonatomic, strong) UIView *highlightOverlay; + +@property (nonatomic, weak) UIApplication *application; + +@end + +#pragma mark - Implementation + +@implementation TDElementDomainController + +@dynamic domain; + ++ (TDElementDomainController *)defaultInstance; +{ + static TDElementDomainController *defaultInstance = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + defaultInstance = [[TDElementDomainController alloc] init]; + }); + return defaultInstance; +} + ++ (Class)domainClass; +{ + return [PDDOMDomain class]; +} + +- (id)init; +{ + if (self = [super init]) { + self.highlightOverlay = [[UIView alloc] initWithFrame:CGRectZero]; + self.highlightOverlay.layer.borderWidth = 1.0; + } + return self; +} + +- (void)startMonitoringWithApplication:(UIApplication *)application +{ + self.application = application; + [ASDisplayNode setShouldStoreUnflattenedLayouts:YES]; + [ASLayout setShouldRetainSublayoutLayoutElements:YES]; +} + +#pragma mark - PDDOMCommandDelegate + +- (void)domain:(PDDOMDomain *)domain getDocumentWithCallback:(void (^)(PDDOMNode *root, id error))callback; +{ + // Generate the DOM tree + _context = [[TDDOMContext alloc] init]; + PDDOMNode *documentNode = [TDElementDomainController documentNodeWithApplication:self.application context:_context]; + + callback(documentNode, nil); +} + +- (void)domain:(PDDOMDomain *)domain highlightNodeWithNodeId:(NSNumber *)nodeId highlightConfig:(PDDOMHighlightConfig *)highlightConfig callback:(void (^)(id))callback; +{ + NSObject *object = [_context.idToObjectMap objectForKey:nodeId]; + if (object != nil) { + [self configureHighlightOverlayWithConfig:highlightConfig]; + + CGRect frameInWindow = [_context.idToFrameInWindow rectForKey:nodeId]; + [self revealHighlightOverlayAtRect:CGRectIsNull(frameInWindow) ? CGRectZero : frameInWindow]; + } + + callback(nil); +} + +- (void)domain:(PDDOMDomain *)domain hideHighlightWithCallback:(void (^)(id))callback; +{ + [self.highlightOverlay removeFromSuperview]; + callback(nil); +} + +- (void)domain:(PDDOMDomain *)domain requestNodeWithObjectId:(NSString *)objectId callback:(void (^)(NSNumber *, id))callback; +{ + callback(@([objectId intValue]), nil); +} + +#pragma mark - Highlight Overlay + +- (void)configureHighlightOverlayWithConfig:(PDDOMHighlightConfig *)highlightConfig; +{ + self.highlightOverlay.backgroundColor = ((NSDictionary *)highlightConfig).contentUIColor; + self.highlightOverlay.layer.borderColor = ((NSDictionary *)highlightConfig).borderUIColor.CGColor; +} + +- (void)revealHighlightOverlayAtRect:(CGRect)rect +{ + UIApplication *application = self.application; + if (!application) { + return; + } + + self.highlightOverlay.frame = rect; + [application.keyWindow addSubview:self.highlightOverlay]; +} + +#pragma mark - Node Generation + ++ (PDDOMNode *)documentNodeWithApplication:(UIApplication *)application context:(TDDOMContext *)context +{ + PDDOMNode *rootNode = [[PDDOMNode alloc] init]; + rootNode.nodeId = [context idForObject:[NSObject new]]; + rootNode.nodeType = @(kPDDOMNodeTypeDocument); + rootNode.nodeName = @"#document"; + rootNode.children = application ? @[ [application td_generateDOMNodeWithContext:context] ] : nil; + rootNode.childNodeCount = @(rootNode.children.count); + return rootNode; +} + +@end + +#endif // AS_TEXTURE_DEBUGGER diff --git a/Texture.podspec b/Texture.podspec index 89ee0f4b..0114a825 100644 --- a/Texture.podspec +++ b/Texture.podspec @@ -55,6 +55,18 @@ Pod::Spec.new do |spec| igl.dependency 'Texture/Core' end + spec.subspec 'Debugger' do |db| + db.dependency 'PonyDebugger' + db.dependency 'Texture/Core' + db.public_header_files = [ + 'Source/Debugger/*.h', + ] + + db.source_files = [ + 'Source/Debugger/**/*.{h,m,mm}', + ] + end + spec.subspec 'Yoga' do |yoga| yoga.xcconfig = { 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) YOGA=1' } yoga.dependency 'Yoga', '1.5.0' diff --git a/examples/ASDKgram/Podfile b/examples/ASDKgram/Podfile index 8c63ccf5..e8afeb1c 100644 --- a/examples/ASDKgram/Podfile +++ b/examples/ASDKgram/Podfile @@ -1,6 +1,8 @@ source 'https://github.com/CocoaPods/Specs.git' platform :ios, '8.0' target 'Sample' do - pod 'Texture/IGListKit', :path => '../..' - pod 'Texture/PINRemoteImage', :path => '../..' + pod 'PonyDebugger', :git => 'git@github.com:nguyenhuy/PonyDebugger.git', :branch => 'master' + pod 'Texture/IGListKit', :path => '../..' + pod 'Texture/PINRemoteImage', :path => '../..' + pod 'Texture/Debugger', :path => '../..' end diff --git a/examples/ASDKgram/Sample/AppDelegate.m b/examples/ASDKgram/Sample/AppDelegate.m index ce3c4a33..0b77311a 100644 --- a/examples/ASDKgram/Sample/AppDelegate.m +++ b/examples/ASDKgram/Sample/AppDelegate.m @@ -23,6 +23,7 @@ #import "PhotoFeedListKitViewController.h" #import "WindowWithStatusBarUnderlay.h" #import "Utilities.h" +#import @interface AppDelegate () @end @@ -76,6 +77,10 @@ [application setStatusBarHidden:YES withAnimation:UIStatusBarAnimationNone]; [application setStatusBarHidden:NO withAnimation:UIStatusBarAnimationNone]; + TDDebugger *debugger = [TDDebugger defaultInstance]; + [debugger enableLayoutElementDebuggingWithApplication:application];; + [debugger connectToURL:[NSURL URLWithString:@"ws://localhost:9000/device"]]; + return YES; }