-
Notifications
You must be signed in to change notification settings - Fork 53
[CHORE][iOS] Fix Session Replay Text Extraction on RN 0.84 #1112
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
[CHORE][iOS] Fix Session Replay Text Extraction on RN 0.84 #1112
Conversation
|
|
||
| internal let uiManager: RCTUIManager | ||
| internal let fabricWrapper: RCTFabricWrapper | ||
| private let textExtractor: RCTTextExtractor |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't need it at the moment, but this could be easily extracted to be initialized by a new paramter passed to RCTTextViewRecorder.
| internal func tryToExtractTextFromSubViews( | ||
| subviews: [RCTShadowView]? | ||
| ) -> String? { | ||
| guard let subviews = subviews else { | ||
| return nil | ||
| } | ||
|
|
||
| return subviews.compactMap { subview in | ||
| if let sub = subview as? RCTRawTextShadowView { | ||
| return sub.text | ||
| } | ||
| if let sub = subview as? RCTVirtualTextShadowView { | ||
| // We recursively get all subviews for nested Text components | ||
| return tryToExtractTextFromSubViews(subviews: sub.reactSubviews()) | ||
| } | ||
| return nil | ||
| }.joined() | ||
| } | ||
|
|
||
| private func tryToExtractTextProperties(view: UIView) -> RCTTextPropertiesWrapper? { | ||
| guard let textView = view as? RCTTextView else { | ||
| return nil | ||
| } | ||
|
|
||
| var shadowView: RCTTextShadowView? = nil | ||
| let tag = textView.reactTag | ||
|
|
||
| let timeout: TimeInterval = 0.2 | ||
| let semaphore = DispatchSemaphore(value: 0) | ||
|
|
||
| // We need to access the shadow view from the UIManager queue, but we're currently on the main thread. | ||
| // Calling `.sync` from the main thread to the UIManager queue is unsafe, because the UIManager queue | ||
| // may already be executing a layout operation that in turn requires the main thread (e.g. measuring a native view). | ||
| // That would create a circular dependency and deadlock the app. | ||
| // To avoid this, we dispatch the work asynchronously to the UIManager queue and wait with a timeout. | ||
| // This ensures we block only if absolutely necessary, and can fail gracefully if the queue is busy. | ||
| RCTGetUIManagerQueue().async { | ||
| shadowView = self.uiManager.shadowView(forReactTag: tag) as? RCTTextShadowView | ||
| semaphore.signal() | ||
| } | ||
|
|
||
| let waitResult = semaphore.wait(timeout: .now() + timeout) | ||
| if waitResult == .timedOut { | ||
| return nil | ||
| } | ||
|
|
||
| guard let shadow = shadowView else { | ||
| return nil | ||
| } | ||
|
|
||
| let textProperties = RCTTextPropertiesWrapper() | ||
|
|
||
| // TODO: RUM-2173 check performance is ok | ||
| if let text = tryToExtractTextFromSubViews(subviews: shadow.reactSubviews()) { | ||
| textProperties.text = text | ||
| } | ||
|
|
||
| if let foregroundColor = shadow.textAttributes.foregroundColor { | ||
| textProperties.foregroundColor = foregroundColor | ||
| } | ||
|
|
||
| textProperties.alignment = shadow.textAttributes.alignment | ||
| textProperties.fontSize = shadow.textAttributes.fontSize | ||
| textProperties.contentRect = shadow.contentFrame | ||
|
|
||
| return textProperties | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not against extracting this logic outside of this class but I don't think we should remove the swift code, would this not work ?
// RCTHelper.h
#import <Foundation/Foundation.h>
@interface RCTHelper : NSObject
+ (BOOL)isNewArchEnabled;
@end
#import "RCTHelper.h"
@implementation RCTHelper
+ (BOOL)isNewArchEnabled {
#if RCT_NEW_ARCH_ENABLED
return YES;
#else
return NO;
#endif
}
@end
then in swfit:
let isNewArchEnabled = RCTHelper.isNewArchEnabled()
What does this PR do?
This PR solves a build issue with React Native 0.84 that affects Session Replay recording on iOS.
Our RCTTextViewRecorder relied on
RCTTextView,RCTShadowView,RCTRawTextShadowViewandRCTVirtualTextShadowViewbeing available both on new and old architecture. Since 0.84 removes them on new architecture we can no longer use them on the text property extraction code, as it will create an error at build time.This code now needs to be gatekept by the use of the RCT_NEW_ARCH_ENABLED env variable, and since that value is only available to obj-C files it means that we need to port over our
tryToExtractTextPropertiesto objC.That has been done by creating a new class,
RCTTextExtractor, which now encapsulates this logic and properly adapts both to new and old architectures. Tests have been adapted accordingly too.Motivation
We should support never versions of React Native seamlessly.
Additional Notes
Review checklist (to be filled by reviewers)