diff --git a/ConsentViewController/Classes/Views/iOS/SPWebMessageViewController.swift b/ConsentViewController/Classes/Views/iOS/SPWebMessageViewController.swift index 7cf6f9b8c..3fb261577 100644 --- a/ConsentViewController/Classes/Views/iOS/SPWebMessageViewController.swift +++ b/ConsentViewController/Classes/Views/iOS/SPWebMessageViewController.swift @@ -178,6 +178,7 @@ import WebKit } var isFirstLayerMessage = true + private var hasCalledLoaded = false lazy var timeoutWorkItem: DispatchWorkItem = { DispatchWorkItem { [weak self] in @@ -288,14 +289,16 @@ import WebKit func onMessageReady() { timeoutWorkItem.cancel() + guard !hasCalledLoaded else { return } + hasCalledLoaded = true webview?.evaluateJavaScript("window.spLegislation=\"\(self.campaignType.rawValue)\"") messageUIDelegate?.loaded(self) } func onPmReady() { timeoutWorkItem.cancel() - if isFirstLayerMessage { - messageUIDelegate?.loaded(self) - } + guard isFirstLayerMessage, !hasCalledLoaded else { return } + hasCalledLoaded = true + messageUIDelegate?.loaded(self) } } diff --git a/Example/ConsentViewController_ExampleTests/GenericWebMessageViewControllerSpec.swift b/Example/ConsentViewController_ExampleTests/GenericWebMessageViewControllerSpec.swift index f63dda709..960ca853c 100644 --- a/Example/ConsentViewController_ExampleTests/GenericWebMessageViewControllerSpec.swift +++ b/Example/ConsentViewController_ExampleTests/GenericWebMessageViewControllerSpec.swift @@ -33,6 +33,28 @@ func renderingAppMock(messageReadyDelayInSeconds: Int) -> String { """ } +// Simulates a rendering app that fires sp.showMessage twice in sequence for the same message, +// reproducing the crash: "Application tried to present a view controller already being presented." +class DuplicatingRenderingAppMock: WKWebView { + override func load(_ request: URLRequest) -> WKNavigation? { + loadHTMLString(""" + +
+ +
+ + + """, baseURL: URL(string: "https://example.com")!) + } +} + class FaultyRenderingAppMock: WKWebView { override func load(_ request: URLRequest) -> WKNavigation? { loadHTMLString( @@ -100,6 +122,13 @@ class GenericWebMessageViewControllerSpec: QuickSpec { expect(delegate.onErrorWasCalled).to(beFalse()) } + it("calls loaded exactly once even if the rendering app fires sp.showMessage twice") { + loadMessage(with: DuplicatingRenderingAppMock.self, delegate: delegate) + // Wait until at least one loaded call arrives, then verify no duplicate was delivered. + expect(delegate.loadedCallCount).toEventually(beGreaterThanOrEqualTo(1), timeout: .seconds(15)) + expect(delegate.loadedCallCount).to(equal(1)) + } + it("calls onError if .loaded() is not called on the delegate before the timeout") { loadMessage(with: FaultyRenderingAppMock.self, delegate: delegate, timeout: 2.0) expect(delegate.onErrorWasCalled).toEventually(beTrue(), timeout: .seconds(10)) diff --git a/Example/ConsentViewController_ExampleTests/MessageUIDelegateMock.swift b/Example/ConsentViewController_ExampleTests/MessageUIDelegateMock.swift index 37702cd53..fb2a3b528 100644 --- a/Example/ConsentViewController_ExampleTests/MessageUIDelegateMock.swift +++ b/Example/ConsentViewController_ExampleTests/MessageUIDelegateMock.swift @@ -11,12 +11,14 @@ import Foundation class MessageUIDelegateSpy: SPMessageUIDelegate { var loadedWasCalled = false + var loadedCallCount = 0 var onErrorWasCalled = false var actionCalledWith: SPAction? var onLoaded: ((UIViewController?) -> Void)? func loaded(_ controller: UIViewController) { loadedWasCalled = true + loadedCallCount += 1 onLoaded?(controller) }