Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,23 @@ internal class RCTTextViewRecorder: SessionReplayNodeRecorder {
var shadowView: RCTTextShadowView? = nil
let tag = textView.reactTag

RCTGetUIManagerQueue().sync {
shadowView = uiManager.shadowView(forReactTag: tag) as? RCTTextShadowView
let timeout: TimeInterval = 0.2
Comment thread
cdn34dd marked this conversation as resolved.
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 {
Expand Down
Loading