Skip to content

Commit 219728c

Browse files
committed
Fixes #2878 - Retain cycles on the ElementCall webView and correctly tear down the call on dismissal.
1 parent 137c7dc commit 219728c

File tree

10 files changed

+76
-21
lines changed

10 files changed

+76
-21
lines changed

ElementX.xcodeproj/project.pbxproj

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,7 @@
296296
4799A852132F1744E2825994 /* CreateRoomViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 340179A0FC1AD4AEDA7FC134 /* CreateRoomViewModelProtocol.swift */; };
297297
47FF70C051A991FB65CDBCF3 /* RoomScreenInteractionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0135A608FFAD86E6674EE730 /* RoomScreenInteractionHandler.swift */; };
298298
4807E8F51DB54F56B25E1C7E /* AppLockSetupSettingsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D8C38663020DF2EB2D13F5E /* AppLockSetupSettingsScreenViewModel.swift */; };
299+
48416BBEB8DDF3E4DED0EDB6 /* ElementCallServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FC8B21E86B137BE4E91F82A /* ElementCallServiceProtocol.swift */; };
299300
484202C5D50983442D24D061 /* AttributedString.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52BD6ED18E2EB61E28C340AD /* AttributedString.swift */; };
300301
489BB6A733D3DA0FE7062650 /* IdentityConfirmationScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C71B9802433F1B4252291BB /* IdentityConfirmationScreenViewModelProtocol.swift */; };
301302
491D62ACD19E6F134B1766AF /* RoomNotificationSettingsUserDefinedScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3203C6566DC17B7AECC1B7FD /* RoomNotificationSettingsUserDefinedScreen.swift */; };
@@ -361,6 +362,7 @@
361362
565868808A1DA565707394ED /* CurrentValuePublisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 127C8472672A5BA09EF1ACF8 /* CurrentValuePublisher.swift */; };
362363
56F0A22972A3BB519DA2261C /* HomeScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24F5530B2212862FA4BEFF2D /* HomeScreenViewModelProtocol.swift */; };
363364
5710AAB27D5D866292C1FE06 /* SessionVerificationScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF848B41DAF1066F3054D4A1 /* SessionVerificationScreenModels.swift */; };
365+
5732395A4F71F51F9C754C5A /* ElementCallService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33AE897D86784CCA5E4E9227 /* ElementCallService.swift */; };
364366
5780E444F405AA1304E1C23E /* DeveloperOptionsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38E521D6C2BF8DF0DFB35146 /* DeveloperOptionsScreen.swift */; };
365367
57E115A8C33E599DE564F8C3 /* TimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDEB27575FEBCF414D4DEE31 /* TimelineView.swift */; };
366368
588411C8FD72B2A2DFE5F7DE /* XCUIElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = E992D7B8BE54B2AB454613AF /* XCUIElement.swift */; };
@@ -441,6 +443,7 @@
441443
6AECC84BE14A13440120FED8 /* NSESettingsProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FB4F169D653296023ED65E6 /* NSESettingsProtocol.swift */; };
442444
6B05AA5D9BBCD6D8D63B80EB /* TimelineItemAccessibilityModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74C6F3DAD167F972702C8893 /* TimelineItemAccessibilityModifier.swift */; };
443445
6B31508C6334C617360C2EAB /* RoomMemberDetailsViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC589E641AE46EFB2962534D /* RoomMemberDetailsViewModelTests.swift */; };
446+
6B67AC7AA41136FC9804C136 /* ElementCallServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FC8B21E86B137BE4E91F82A /* ElementCallServiceProtocol.swift */; };
444447
6BAD956B909A6E29F6CC6E7C /* ButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CC23C63849452BC86EA2852 /* ButtonStyle.swift */; };
445448
6BB6944443C421C722ED1E7D /* portrait_test_video.mp4 in Resources */ = {isa = PBXBuildFile; fileRef = F2D513D2477B57F90E98EEC0 /* portrait_test_video.mp4 */; };
446449
6C34237AFB808E38FC8776B9 /* RoomStateEventStringBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D55702474F279D910D2D162 /* RoomStateEventStringBuilder.swift */; };
@@ -1331,6 +1334,7 @@
13311334
32C5DAA1773F57653BF1C4F9 /* SoftLogoutViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoftLogoutViewModelTests.swift; sourceTree = "<group>"; };
13321335
330AF4D121C3396F7A14B21D /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = id; path = id.lproj/SAS.strings; sourceTree = "<group>"; };
13331336
3368395F06AA180138E185B6 /* PollFormScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollFormScreenUITests.swift; sourceTree = "<group>"; };
1337+
33AE897D86784CCA5E4E9227 /* ElementCallService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementCallService.swift; sourceTree = "<group>"; };
13341338
33E49C5C6F802B4D94CA78D1 /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ro; path = ro.lproj/Localizable.strings; sourceTree = "<group>"; };
13351339
340179A0FC1AD4AEDA7FC134 /* CreateRoomViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateRoomViewModelProtocol.swift; sourceTree = "<group>"; };
13361340
342BEBC3C5FC3F9943C41C4C /* TemplateScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateScreenViewModelProtocol.swift; sourceTree = "<group>"; };
@@ -1558,6 +1562,7 @@
15581562
6F3DFE5B444F131648066F05 /* StateStoreViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StateStoreViewModel.swift; sourceTree = "<group>"; };
15591563
6F6E6EDC4BBF962B2ED595A4 /* MessageForwardingScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageForwardingScreenViewModelTests.swift; sourceTree = "<group>"; };
15601564
6FC5015B9634698BDB8701AF /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = it; path = it.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
1565+
6FC8B21E86B137BE4E91F82A /* ElementCallServiceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementCallServiceProtocol.swift; sourceTree = "<group>"; };
15611566
7023EB4F3B7C7D1FBA68638B /* TimelineItemDebugView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemDebugView.swift; sourceTree = "<group>"; };
15621567
7061BE2C0BF427C38AEDEF5E /* SecureBackupRecoveryKeyScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupRecoveryKeyScreenViewModel.swift; sourceTree = "<group>"; };
15631568
70C86696AC9521F8ED88FBEB /* MediaUploadPreviewScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaUploadPreviewScreen.swift; sourceTree = "<group>"; };
@@ -4021,6 +4026,8 @@
40214026
92E99C57D7F92ED16F73282C /* ElementCall */ = {
40224027
isa = PBXGroup;
40234028
children = (
4029+
33AE897D86784CCA5E4E9227 /* ElementCallService.swift */,
4030+
6FC8B21E86B137BE4E91F82A /* ElementCallServiceProtocol.swift */,
40244031
309AD8BAE6437C31BA7157BF /* ElementCallWidgetDriver.swift */,
40254032
A6C11AD9813045E44F950410 /* ElementCallWidgetDriverProtocol.swift */,
40264033
);
@@ -5623,6 +5630,7 @@
56235630
B5618E3C948584E5C1F67033 /* DTHTMLElement+AttributedStringBuilder.swift in Sources */,
56245631
DFCA89C4EC2A5332ED6B441F /* DataProtectionManager.swift in Sources */,
56255632
24A75F72EEB7561B82D726FD /* Date.swift in Sources */,
5633+
6B67AC7AA41136FC9804C136 /* ElementCallServiceProtocol.swift in Sources */,
56265634
CFEC53440C572CEEABC4A6A0 /* ElementXAttributeScope.swift in Sources */,
56275635
A33784831AD880A670CAA9F9 /* FileManager.swift in Sources */,
56285636
59F940FCBE6BC343AECEF75E /* ImageCache.swift in Sources */,
@@ -5972,6 +5980,8 @@
59725980
2955F4C160CFD7794D819C64 /* EffectsScene.swift in Sources */,
59735981
AE1160076F663BF14E0E893A /* EffectsView.swift in Sources */,
59745982
FE4593FC2A02AAF92E089565 /* ElementAnimations.swift in Sources */,
5983+
5732395A4F71F51F9C754C5A /* ElementCallService.swift in Sources */,
5984+
48416BBEB8DDF3E4DED0EDB6 /* ElementCallServiceProtocol.swift in Sources */,
59755985
07CC13C5729C24255348CBBD /* ElementCallWidgetDriver.swift in Sources */,
59765986
370AF5BFCD4384DD455479B6 /* ElementCallWidgetDriverProtocol.swift in Sources */,
59775987
7C1A7B594B2F8143F0DD0005 /* ElementXAttributeScope.swift in Sources */,
@@ -7313,7 +7323,7 @@
73137323
repositoryURL = "https://github.com/element-hq/matrix-rust-components-swift";
73147324
requirement = {
73157325
kind = exactVersion;
7316-
version = 1.0.2;
7326+
version = 1.0.3;
73177327
};
73187328
};
73197329
701C7BEF8F70F7A83E852DCC /* XCRemoteSwiftPackageReference "GZIP" */ = {

ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ElementX/Sources/Mocks/Generated/GeneratedMocks.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4313,6 +4313,11 @@ class ElementCallServiceMock: ElementCallServiceProtocol {
43134313
}
43144314
}
43154315
class ElementCallWidgetDriverMock: ElementCallWidgetDriverProtocol {
4316+
var widgetID: String {
4317+
get { return underlyingWidgetID }
4318+
set(value) { underlyingWidgetID = value }
4319+
}
4320+
var underlyingWidgetID: String!
43164321
var messagePublisher: PassthroughSubject<String, Never> {
43174322
get { return underlyingMessagePublisher }
43184323
set(value) { underlyingMessagePublisher = value }

ElementX/Sources/Screens/CallScreen/CallScreenCoordinator.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@ final class CallScreenCoordinator: CoordinatorProtocol {
5555
}
5656
.store(in: &cancellables)
5757
}
58+
59+
func stop() {
60+
viewModel.stop()
61+
}
5862

5963
func toPresentable() -> AnyView {
6064
AnyView(CallScreen(context: viewModel.context))

ElementX/Sources/Screens/CallScreen/CallScreenViewModel.swift

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,6 @@ class CallScreenViewModel: CallScreenViewModelType, CallScreenViewModelProtocol
3131
actionsSubject.eraseToAnyPublisher()
3232
}
3333

34-
deinit {
35-
elementCallService.tearDownCallSession()
36-
}
37-
3834
/// Designated initialiser
3935
/// - Parameters:
4036
/// - elementCallService: service responsible for setting up CallKit
@@ -120,7 +116,28 @@ class CallScreenViewModel: CallScreenViewModelType, CallScreenViewModelProtocol
120116
}
121117
}
122118

119+
func stop() {
120+
Task {
121+
await hangUp()
122+
}
123+
124+
elementCallService.tearDownCallSession()
125+
}
126+
123127
// MARK: - Private
128+
129+
private func hangUp() async {
130+
let hangUpMessage = """
131+
"api":"toWidget",
132+
"widgetId":"\(widgetDriver.widgetID)",
133+
"requestId":"widgetapi-\(UUID())",
134+
"action":"im.vector.hangup",
135+
"data":{}
136+
"""
137+
138+
let result = await widgetDriver.sendMessage(hangUpMessage)
139+
MXLog.error("Result yo: \(result)")
140+
}
124141

125142
private static let eventHandlerName = "elementx"
126143

ElementX/Sources/Screens/CallScreen/CallScreenViewModelProtocol.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,6 @@ import Combine
2020
protocol CallScreenViewModelProtocol {
2121
var actions: AnyPublisher<CallScreenViewModelAction, Never> { get }
2222
var context: CallScreenViewModelType.Context { get }
23+
24+
func stop()
2325
}

ElementX/Sources/Screens/CallScreen/View/CallScreen.swift

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,8 @@ private struct WebView: UIViewRepresentable {
5353
}
5454

5555
@MainActor
56-
class Coordinator: NSObject, WKScriptMessageHandler, WKUIDelegate, WKNavigationDelegate {
57-
private let viewModelContext: CallScreenViewModel.Context
56+
class Coordinator: NSObject, WKUIDelegate, WKNavigationDelegate {
57+
private weak var viewModelContext: CallScreenViewModel.Context?
5858

5959
private(set) var webView: WKWebView!
6060

@@ -73,7 +73,7 @@ private struct WebView: UIViewRepresentable {
7373
let configuration = WKWebViewConfiguration()
7474

7575
let userContentController = WKUserContentController()
76-
userContentController.add(self, name: viewModelContext.viewState.messageHandler)
76+
userContentController.add(WKScriptMessageHandlerWrapper(self), name: viewModelContext.viewState.messageHandler)
7777

7878
configuration.userContentController = userContentController
7979
configuration.allowsInlineMediaPlayback = true
@@ -98,8 +98,8 @@ private struct WebView: UIViewRepresentable {
9898
// After testing different scenarios it seems that when using async/await version of these
9999
// methods wkwebView expects JavaScript to return with a value (something other than Void),
100100
// if there is no value returning from the JavaScript that you evaluate you will have a crash.
101-
try await withCheckedThrowingContinuation { continuaton in
102-
webView.evaluateJavaScript(script) { result, error in
101+
try await withCheckedThrowingContinuation { [weak self] continuaton in
102+
self?.webView.evaluateJavaScript(script) { result, error in
103103
if let error {
104104
continuaton.resume(throwing: error)
105105
} else {
@@ -109,12 +109,10 @@ private struct WebView: UIViewRepresentable {
109109
}
110110
}
111111

112-
// MARK: - WKScriptMessageHandler
113-
114112
nonisolated func userContentController(_ userContentController: WKUserContentController,
115113
didReceive message: WKScriptMessage) {
116-
Task { @MainActor in
117-
viewModelContext.javaScriptMessageHandler?(message.body)
114+
Task { @MainActor [weak self] in
115+
self?.viewModelContext?.javaScriptMessageHandler?(message.body)
118116
}
119117
}
120118

@@ -148,10 +146,26 @@ private struct WebView: UIViewRepresentable {
148146

149147
nonisolated func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
150148
Task { @MainActor in
151-
viewModelContext.send(viewAction: .urlChanged(webView.url))
149+
viewModelContext?.send(viewAction: .urlChanged(webView.url))
152150
}
153151
}
154152
}
153+
154+
/// Avoids retain loops between the configuration and webView coordinator
155+
private class WKScriptMessageHandlerWrapper: NSObject, WKScriptMessageHandler {
156+
private weak var coordinator: Coordinator?
157+
158+
init(_ coordinator: Coordinator) {
159+
self.coordinator = coordinator
160+
}
161+
162+
// MARK: - WKScriptMessageHandler
163+
164+
nonisolated func userContentController(_ userContentController: WKUserContentController,
165+
didReceive message: WKScriptMessage) {
166+
coordinator?.userContentController(userContentController, didReceive: message)
167+
}
168+
}
155169
}
156170

157171
// MARK: - Previews

ElementX/Sources/Services/ElementCall/ElementCallWidgetDriver.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ class ElementCallWidgetDriver: WidgetCapabilitiesProvider, ElementCallWidgetDriv
4141
private let room: RoomProtocol
4242
private var widgetDriver: WidgetDriverAndHandle?
4343

44+
let widgetID = UUID().uuidString
4445
let messagePublisher = PassthroughSubject<String, Never>()
4546

4647
private let actionsSubject: PassthroughSubject<ElementCallWidgetDriverAction, Never> = .init()
@@ -60,7 +61,7 @@ class ElementCallWidgetDriver: WidgetCapabilitiesProvider, ElementCallWidgetDriv
6061
let useEncryption = (try? room.isEncrypted()) ?? false
6162

6263
guard let widgetSettings = try? newVirtualElementCallWidget(props: .init(elementCallUrl: baseURL.absoluteString,
63-
widgetId: UUID().uuidString,
64+
widgetId: widgetID,
6465
parentUrl: nil,
6566
hideHeader: nil,
6667
preload: nil,

ElementX/Sources/Services/ElementCall/ElementCallWidgetDriverProtocol.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ enum ElementCallWidgetDriverAction {
3232

3333
// sourcery: AutoMockable
3434
protocol ElementCallWidgetDriverProtocol {
35+
var widgetID: String { get }
36+
3537
var messagePublisher: PassthroughSubject<String, Never> { get }
3638
var actions: AnyPublisher<ElementCallWidgetDriverAction, Never> { get }
3739

project.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ packages:
4949
# Element/Matrix dependencies
5050
MatrixRustSDK:
5151
url: https://github.com/element-hq/matrix-rust-components-swift
52-
exactVersion: 1.0.2
52+
exactVersion: 1.0.3
5353
# path: ../matrix-rust-sdk
5454
Compound:
5555
url: https://github.com/element-hq/compound-ios

0 commit comments

Comments
 (0)