Skip to content

Commit

Permalink
feat: Update view hierarchy attachment format to Json (#2491)
Browse files Browse the repository at this point in the history
Change view hierarchy format to conform to RFC 33

Closes GH-2467
  • Loading branch information
brustolin authored Dec 20, 2022
1 parent 17e2418 commit 1a18683
Show file tree
Hide file tree
Showing 30 changed files with 705 additions and 155 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ This version adds a dependency on Swift.
### Features

- Properly demangle Swift class name (#2162)
- Change view hierarchy attachment format to JSON (#2491)
- SwiftUI performance tracking (#2271)
- Enable [File I/O Tracking](https://docs.sentry.io/platforms/apple/performance/instrumentation/automatic-instrumentation/#file-io-tracking) by default (#2497)
- [User Interaction Tracing](https://docs.sentry.io/platforms/apple/performance/instrumentation/automatic-instrumentation/#user-interaction-tracing) is stable and enabled by default(#2503)
Expand Down
22 changes: 22 additions & 0 deletions Sentry.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -708,6 +708,7 @@
D867063D27C3BC2400048851 /* SentryCoreDataTrackingIntegration.h in Headers */ = {isa = PBXBuildFile; fileRef = D867063A27C3BC2400048851 /* SentryCoreDataTrackingIntegration.h */; };
D867063E27C3BC2400048851 /* SentryCoreDataSwizzling.h in Headers */ = {isa = PBXBuildFile; fileRef = D867063B27C3BC2400048851 /* SentryCoreDataSwizzling.h */; };
D867063F27C3BC2400048851 /* SentryCoreDataTracker.h in Headers */ = {isa = PBXBuildFile; fileRef = D867063C27C3BC2400048851 /* SentryCoreDataTracker.h */; };
D86B6835294348A400B8B1FC /* SentryAttachment+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = D86B6834294348A400B8B1FC /* SentryAttachment+Private.h */; };
D86F419827C8FEFA00490520 /* SentryCoreDataMiddleware+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D86F419727C8FEFA00490520 /* SentryCoreDataMiddleware+Extension.swift */; };
D8751FA5274743710032F4DE /* SentryNSURLSessionTaskSearchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8751FA4274743710032F4DE /* SentryNSURLSessionTaskSearchTests.swift */; };
D875ED0B276CC84700422FAC /* SentryNSDataTrackerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D875ED0A276CC84700422FAC /* SentryNSDataTrackerTests.swift */; };
Expand All @@ -733,6 +734,10 @@
D8BD2E6829361A0F00D96C6A /* PrivatesHeader.h in Headers */ = {isa = PBXBuildFile; fileRef = D8BD2E67293619F600D96C6A /* PrivatesHeader.h */; settings = {ATTRIBUTES = (Private, ); }; };
D8C67E9B28000E24007E326E /* SentryUIApplication.h in Headers */ = {isa = PBXBuildFile; fileRef = D8C67E9928000E23007E326E /* SentryUIApplication.h */; };
D8C67E9C28000E24007E326E /* SentryScreenshot.h in Headers */ = {isa = PBXBuildFile; fileRef = D8C67E9A28000E23007E326E /* SentryScreenshot.h */; };
D8CB74152947246600A5F964 /* SentryEnvelopeAttachmentHeader.h in Headers */ = {isa = PBXBuildFile; fileRef = D8CB74142947246600A5F964 /* SentryEnvelopeAttachmentHeader.h */; };
D8CB7417294724CC00A5F964 /* SentryEnvelopeAttachmentHeader.m in Sources */ = {isa = PBXBuildFile; fileRef = D8CB7416294724CC00A5F964 /* SentryEnvelopeAttachmentHeader.m */; };
D8CB74192947285A00A5F964 /* SentryEnvelopeItemHeader.h in Headers */ = {isa = PBXBuildFile; fileRef = D8CB74182947285A00A5F964 /* SentryEnvelopeItemHeader.h */; settings = {ATTRIBUTES = (Public, ); }; };
D8CB741B2947286500A5F964 /* SentryEnvelopeItemHeader.m in Sources */ = {isa = PBXBuildFile; fileRef = D8CB741A2947286500A5F964 /* SentryEnvelopeItemHeader.m */; };
D8CB742B294B1DD000A5F964 /* SentryUIApplicationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8CB742A294B1DD000A5F964 /* SentryUIApplicationTests.swift */; };
D8CB742E294B294B00A5F964 /* MockUIScene.m in Sources */ = {isa = PBXBuildFile; fileRef = D8CB742D294B294B00A5F964 /* MockUIScene.m */; };
D8CE69BC277E39C700C6EC5C /* SentryFileIOTrackingIntegrationObjCTests.m in Sources */ = {isa = PBXBuildFile; fileRef = D8CE69BB277E39C700C6EC5C /* SentryFileIOTrackingIntegrationObjCTests.m */; };
Expand Down Expand Up @@ -1546,6 +1551,8 @@
D867063A27C3BC2400048851 /* SentryCoreDataTrackingIntegration.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SentryCoreDataTrackingIntegration.h; path = include/SentryCoreDataTrackingIntegration.h; sourceTree = "<group>"; };
D867063B27C3BC2400048851 /* SentryCoreDataSwizzling.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SentryCoreDataSwizzling.h; path = include/SentryCoreDataSwizzling.h; sourceTree = "<group>"; };
D867063C27C3BC2400048851 /* SentryCoreDataTracker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SentryCoreDataTracker.h; path = include/SentryCoreDataTracker.h; sourceTree = "<group>"; };
D86B6820293F39E000B8B1FC /* TestSentryViewHierarchy.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TestSentryViewHierarchy.h; sourceTree = "<group>"; };
D86B6834294348A400B8B1FC /* SentryAttachment+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "SentryAttachment+Private.h"; path = "include/SentryAttachment+Private.h"; sourceTree = "<group>"; };
D86F419727C8FEFA00490520 /* SentryCoreDataMiddleware+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SentryCoreDataMiddleware+Extension.swift"; sourceTree = "<group>"; };
D8751FA4274743710032F4DE /* SentryNSURLSessionTaskSearchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryNSURLSessionTaskSearchTests.swift; sourceTree = "<group>"; };
D875ED0A276CC84700422FAC /* SentryNSDataTrackerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SentryNSDataTrackerTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1574,6 +1581,10 @@
D8CB742A294B1DD000A5F964 /* SentryUIApplicationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryUIApplicationTests.swift; sourceTree = "<group>"; };
D8CB742C294B294B00A5F964 /* MockUIScene.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MockUIScene.h; sourceTree = "<group>"; };
D8CB742D294B294B00A5F964 /* MockUIScene.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MockUIScene.m; sourceTree = "<group>"; };
D8CB74142947246600A5F964 /* SentryEnvelopeAttachmentHeader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryEnvelopeAttachmentHeader.h; path = include/SentryEnvelopeAttachmentHeader.h; sourceTree = "<group>"; };
D8CB7416294724CC00A5F964 /* SentryEnvelopeAttachmentHeader.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryEnvelopeAttachmentHeader.m; sourceTree = "<group>"; };
D8CB74182947285A00A5F964 /* SentryEnvelopeItemHeader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryEnvelopeItemHeader.h; path = Public/SentryEnvelopeItemHeader.h; sourceTree = "<group>"; };
D8CB741A2947286500A5F964 /* SentryEnvelopeItemHeader.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryEnvelopeItemHeader.m; sourceTree = "<group>"; };
D8CE69BB277E39C700C6EC5C /* SentryFileIOTrackingIntegrationObjCTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryFileIOTrackingIntegrationObjCTests.m; sourceTree = "<group>"; };
D8F6A2452885512100320515 /* SentryPredicateDescriptor.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryPredicateDescriptor.m; sourceTree = "<group>"; };
D8F6A24A2885515B00320515 /* SentryPredicateDescriptor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SentryPredicateDescriptor.h; path = include/SentryPredicateDescriptor.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1644,6 +1655,7 @@
children = (
0A9BF4EA28A127120068D266 /* SentryViewHierarchyIntegrationTests.swift */,
0A9BF4E628A123270068D266 /* TestSentryViewHierarchy.swift */,
D86B6820293F39E000B8B1FC /* TestSentryViewHierarchy.h */,
);
path = ViewHierarchy;
sourceTree = "<group>";
Expand Down Expand Up @@ -1694,6 +1706,10 @@
15E0A8E0240C41CE00F044E3 /* SentryEnvelope.h */,
7B5CAF7027F5953400ED0DB6 /* SentryEnvelope+Private.h */,
15E0A8E4240C457D00F044E3 /* SentryEnvelope.m */,
D8CB74182947285A00A5F964 /* SentryEnvelopeItemHeader.h */,
D8CB741A2947286500A5F964 /* SentryEnvelopeItemHeader.m */,
D8CB74142947246600A5F964 /* SentryEnvelopeAttachmentHeader.h */,
D8CB7416294724CC00A5F964 /* SentryEnvelopeAttachmentHeader.m */,
7BC852382458830A005A70F0 /* SentryEnvelopeItemType.h */,
6304360F1EC0600A00C4D3FA /* SentrySerializable.h */,
639FCF961EBC7B9700778193 /* SentryEvent.h */,
Expand Down Expand Up @@ -1732,6 +1748,7 @@
7BB654FA253DC14A00887E87 /* SentryUserFeedback.h */,
7BB65500253DC1B500887E87 /* SentryUserFeedback.m */,
7B4E375425822C4500059C93 /* SentryAttachment.h */,
D86B6834294348A400B8B1FC /* SentryAttachment+Private.h */,
7B4E375E258231FC00059C93 /* SentryAttachment.m */,
7BD4BD4227EB29BA0071F4FF /* SentryClientReport.h */,
7BD4BD4427EB29F50071F4FF /* SentryClientReport.m */,
Expand Down Expand Up @@ -3097,6 +3114,7 @@
03F84D2227DD414C008FE43F /* SentryStackFrame.hpp in Headers */,
8ECC673E25C23996000E2BF6 /* SentrySpanContext.h in Headers */,
8ECC674025C23996000E2BF6 /* SentryTransactionContext.h in Headers */,
D8CB74152947246600A5F964 /* SentryEnvelopeAttachmentHeader.h in Headers */,
63FE71BA20DA4C1100CDBAE8 /* SentryCrashInstallation+Private.h in Headers */,
63FE71AE20DA4C1100CDBAE8 /* SentryCrashInstallation.h in Headers */,
63FE70F120DA4C1000CDBAE8 /* SentryCrashMonitorType.h in Headers */,
Expand All @@ -3122,6 +3140,7 @@
D8C67E9B28000E24007E326E /* SentryUIApplication.h in Headers */,
7B6438AA26A70F24000D0F65 /* UIViewController+Sentry.h in Headers */,
639FCFAC1EBC811400778193 /* SentryUser.h in Headers */,
D8CB74192947285A00A5F964 /* SentryEnvelopeItemHeader.h in Headers */,
7D7F0A5F23DF3D2C00A4629C /* SentryGlobalEventProcessor.h in Headers */,
7B82D54524E2A05500EE670F /* SentryId.h in Headers */,
D867063D27C3BC2400048851 /* SentryCoreDataTrackingIntegration.h in Headers */,
Expand Down Expand Up @@ -3165,6 +3184,7 @@
7B18DE4028D9F748004845C6 /* SentryNSNotificationCenterWrapper.h in Headers */,
03F84D1E27DD414C008FE43F /* SentryBacktrace.hpp in Headers */,
63AA76991EB9C1C200D153DE /* SentryDefines.h in Headers */,
D86B6835294348A400B8B1FC /* SentryAttachment+Private.h in Headers */,
0A4EDEA928D3461B00FA67CB /* SentryPerformanceTracker+Private.h in Headers */,
7B2A70DB27D607CF008B0D15 /* SentryThreadWrapper.h in Headers */,
8EAE980B261E9F530073B6B3 /* SentryPerformanceTracker.h in Headers */,
Expand Down Expand Up @@ -3590,6 +3610,8 @@
7BE3C7692445C1A800A38442 /* SentryCurrentDate.m in Sources */,
7BCFA71627D0BB50008C662C /* SentryANRTracker.m in Sources */,
63EED6C02237923600E02400 /* SentryOptions.m in Sources */,
D8CB741B2947286500A5F964 /* SentryEnvelopeItemHeader.m in Sources */,
D8CB7417294724CC00A5F964 /* SentryEnvelopeAttachmentHeader.m in Sources */,
D84793262788737D00BE8E99 /* SentryByteCountFormatter.m in Sources */,
63AA769E1EB9C57A00D153DE /* SentryError.m in Sources */,
7B8713B026415B22006D6004 /* SentryAppStartTrackingIntegration.m in Sources */,
Expand Down
6 changes: 6 additions & 0 deletions Sources/Sentry/PrivateSentrySDKOnly.m
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#import "SentryMeta.h"
#import "SentrySDK+Private.h"
#import "SentrySerialization.h"
#import "SentryViewHierarchy.h"
#import <SentryDependencyContainer.h>
#import <SentryFramesTracker.h>
#import <SentryScreenshot.h>
Expand Down Expand Up @@ -126,6 +127,11 @@ + (SentryScreenFrames *)currentScreenFrames
return [SentryDependencyContainer.sharedInstance.screenshot takeScreenshots];
}

+ (NSData *)captureViewHierarchy
{
return [SentryDependencyContainer.sharedInstance.viewHierarchy fetchViewHierarchy];
}

#endif

@end
1 change: 1 addition & 0 deletions Sources/Sentry/Public/Sentry.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ FOUNDATION_EXPORT const unsigned char SentryVersionString[];
#import "SentryDebugMeta.h"
#import "SentryDefines.h"
#import "SentryDsn.h"
#import "SentryEnvelopeItemHeader.h"
#import "SentryError.h"
#import "SentryEvent.h"
#import "SentryException.h"
Expand Down
25 changes: 25 additions & 0 deletions Sources/Sentry/Public/SentryEnvelopeItemHeader.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#import "SentrySerializable.h"

NS_ASSUME_NONNULL_BEGIN

@interface SentryEnvelopeItemHeader : NSObject <SentrySerializable>
SENTRY_NO_INIT

- (instancetype)initWithType:(NSString *)type length:(NSUInteger)length NS_DESIGNATED_INITIALIZER;

- (instancetype)initWithType:(NSString *)type
length:(NSUInteger)length
filenname:(NSString *)filename
contentType:(NSString *)contentType;

/**
* The type of the envelope item.
*/
@property (nonatomic, readonly, copy) NSString *type;
@property (nonatomic, readonly) NSUInteger length;
@property (nonatomic, readonly, copy, nullable) NSString *filename;
@property (nonatomic, readonly, copy, nullable) NSString *contentType;

@end

NS_ASSUME_NONNULL_END
3 changes: 1 addition & 2 deletions Sources/Sentry/Public/SentrySerializable.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
#import <Foundation/Foundation.h>

#import "SentryDefines.h"
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

Expand Down
54 changes: 52 additions & 2 deletions Sources/Sentry/SentryAttachment.m
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#import "SentryAttachment.h"
#import "SentryAttachment+Private.h"
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN
Expand All @@ -7,18 +7,33 @@ @implementation SentryAttachment

- (instancetype)initWithData:(NSData *)data filename:(NSString *)filename
{
return [self initWithData:data filename:filename contentType:nil];
return [self initWithData:data
filename:filename
contentType:nil
attachmentType:kSentryAttachmentTypeEventAttachment];
}

- (instancetype)initWithData:(NSData *)data
filename:(NSString *)filename
contentType:(nullable NSString *)contentType
{
return [self initWithData:data
filename:filename
contentType:contentType
attachmentType:kSentryAttachmentTypeEventAttachment];
}

- (instancetype)initWithData:(NSData *)data
filename:(NSString *)filename
contentType:(nullable NSString *)contentType
attachmentType:(SentryAttachmentType)attachmentType
{

if (self = [super init]) {
_data = data;
_filename = filename;
_contentType = contentType;
_attachmentType = attachmentType;
}
return self;
}
Expand All @@ -36,15 +51,50 @@ - (instancetype)initWithPath:(NSString *)path filename:(NSString *)filename
- (instancetype)initWithPath:(NSString *)path
filename:(NSString *)filename
contentType:(nullable NSString *)contentType
{
return [self initWithPath:path
filename:filename
contentType:contentType
attachmentType:kSentryAttachmentTypeEventAttachment];
}

- (instancetype)initWithPath:(NSString *)path
filename:(NSString *)filename
contentType:(nullable NSString *)contentType
attachmentType:(SentryAttachmentType)attachmentType
{
if (self = [super init]) {
_path = path;
_filename = filename;
_contentType = contentType;
_attachmentType = attachmentType;
}
return self;
}

@end

NSString *const kSentryAttachmentTypeNameEventAttachment = @"event.attachment";
NSString *const kSentryAttachmentTypeNameViewHierarchy = @"event.view_hierarchy";

NSString *
nameForSentryAttachmentType(SentryAttachmentType attachmentType)
{
switch (attachmentType) {
case kSentryAttachmentTypeViewHierarchy:
return kSentryAttachmentTypeNameViewHierarchy;
default:
return kSentryAttachmentTypeNameEventAttachment;
}
}

SentryAttachmentType
typeForSentryAttachmentName(NSString *name)
{
if ([name isEqualToString:kSentryAttachmentTypeNameViewHierarchy]) {
return kSentryAttachmentTypeViewHierarchy;
}
return kSentryAttachmentTypeEventAttachment;
}

NS_ASSUME_NONNULL_END
4 changes: 2 additions & 2 deletions Sources/Sentry/SentryCrashReportSink.m
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
#import "SentryLog.h"
#import "SentrySDK+Private.h"
#import "SentrySDK.h"
#import "SentryScope.h"
#import "SentryScope+Private.h"
#import "SentryThread.h"

static const NSTimeInterval SENTRY_APP_START_CRASH_DURATION_THRESHOLD = 2.0;
Expand Down Expand Up @@ -94,7 +94,7 @@ - (void)handleConvertedEvent:(SentryEvent *)event

if (report[SENTRYCRASH_REPORT_ATTACHMENTS_ITEM]) {
for (NSString *ssPath in report[SENTRYCRASH_REPORT_ATTACHMENTS_ITEM]) {
[scope addAttachment:[[SentryAttachment alloc] initWithPath:ssPath]];
[scope addCrashReportAttachmentInPath:ssPath];
}
}

Expand Down
40 changes: 9 additions & 31 deletions Sources/Sentry/SentryEnvelope.m
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
#import "SentryEnvelope.h"
#import "SentryAttachment.h"
#import "SentryBreadcrumb.h"
#import "SentryClientReport.h"
#import "SentryEnvelope+Private.h"
#import "SentryEnvelopeAttachmentHeader.h"
#import "SentryEnvelopeItemHeader.h"
#import "SentryEnvelopeItemType.h"
#import "SentryEvent.h"
#import "SentryLog.h"
Expand Down Expand Up @@ -48,31 +50,6 @@ - (instancetype)initWithId:(nullable SentryId *)eventId

@end

@implementation SentryEnvelopeItemHeader

- (instancetype)initWithType:(NSString *)type length:(NSUInteger)length
{
if (self = [super init]) {
_type = type;
_length = length;
}
return self;
}

- (instancetype)initWithType:(NSString *)type
length:(NSUInteger)length
filenname:(NSString *)filename
contentType:(NSString *)contentType
{
if (self = [self initWithType:type length:length]) {
_filename = filename;
_contentType = contentType;
}
return self;
}

@end

@implementation SentryEnvelopeItem

- (instancetype)initWithHeader:(SentryEnvelopeItemHeader *)header data:(NSData *)data
Expand Down Expand Up @@ -210,16 +187,17 @@ - (_Nullable instancetype)initWithAttachment:(SentryAttachment *)attachment
data = [[NSFileManager defaultManager] contentsAtPath:attachment.path];
}

if (nil == data) {
if (data == nil) {
SENTRY_LOG_ERROR(@"Couldn't init Attachment.");
return nil;
}

SentryEnvelopeItemHeader *itemHeader =
[[SentryEnvelopeItemHeader alloc] initWithType:SentryEnvelopeItemTypeAttachment
length:data.length
filenname:attachment.filename
contentType:attachment.contentType];
[[SentryEnvelopeAttachmentHeader alloc] initWithType:SentryEnvelopeItemTypeAttachment
length:data.length
filename:attachment.filename
contentType:attachment.contentType
attachmentType:attachment.attachmentType];

return [self initWithHeader:itemHeader data:data];
}
Expand Down
34 changes: 34 additions & 0 deletions Sources/Sentry/SentryEnvelopeAttachmentHeader.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#import "SentryEnvelopeAttachmentHeader.h"
#import "SentryEnvelope+Private.h"

@implementation SentryEnvelopeAttachmentHeader

- (instancetype)initWithType:(NSString *)type length:(NSUInteger)length
{
if (self = [super initWithType:type length:length]) {
_attachmentType = kSentryAttachmentTypeEventAttachment;
}
return self;
}

- (instancetype)initWithType:(NSString *)type
length:(NSUInteger)length
filename:(NSString *)filename
contentType:(NSString *)contentType
attachmentType:(SentryAttachmentType)attachmentType
{

if (self = [self initWithType:type length:length filenname:filename contentType:contentType]) {
_attachmentType = attachmentType;
}
return self;
}

- (NSDictionary *)serialize
{
NSMutableDictionary *result = [[super serialize] mutableCopy];
[result setObject:nameForSentryAttachmentType(self.attachmentType) forKey:@"attachment_type"];
return result;
}

@end
Loading

0 comments on commit 1a18683

Please sign in to comment.