Skip to content

Commit 9391572

Browse files
[webview_flutter_wkwebview] Tear down ProxyAPIRegistrar in applicationWillTerminate (#11484)
InstanceManager contains the only strong reference to the finalizer delegate that makes the call to Dart when an object is deallocated. This sets the plugin to listen for `applicationWillTerminate` to tear down the `ProxyAPIRegistrar`. Potential fix for flutter/flutter#168535 . This crash is difficult to reproduce, so this is just a attempt to prevent it because most of the stacktraces in the issue include `[UIApplication _terminateWithStatus:]`. It's possible this could clean up the plugin before the crash can happen. ## Pre-Review Checklist **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [^1]: Regular contributors who have demonstrated familiarity with the repository guidelines only need to comment if the PR is not auto-exempted by repo tooling.
1 parent 7c8e13e commit 9391572

5 files changed

Lines changed: 125 additions & 4 deletions

File tree

packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 3.24.4
2+
3+
* Updates plugin to prevent message calls when application will terminate.
4+
15
## 3.24.3
26

37
* Adds support to get failing url from DNS errors on iOS 26+.
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
// Copyright 2013 The Flutter Authors
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import XCTest
6+
7+
@testable import webview_flutter_wkwebview
8+
9+
#if os(iOS)
10+
import Flutter
11+
import UIKit
12+
#endif
13+
14+
class WebViewFlutterPluginTests: XCTestCase {
15+
#if os(iOS)
16+
func testInstanceManagerIsDeallocatedInApplicationWillTerminate() {
17+
let plugin = WebViewFlutterPlugin(binaryMessenger: TestBinaryMessenger())
18+
plugin.proxyApiRegistrar!.setUp()
19+
20+
let view = UIView()
21+
_ = plugin.proxyApiRegistrar!.instanceManager.addHostCreatedInstance(view)
22+
23+
// Attaches an associated object to the InstanceManager to listen for when it is deallocated.
24+
var finalizer: TestFinalizer? = TestFinalizer()
25+
26+
let key = malloc(1)!
27+
defer {
28+
free(key)
29+
}
30+
objc_setAssociatedObject(
31+
plugin.proxyApiRegistrar!.instanceManager, key, finalizer, .OBJC_ASSOCIATION_RETAIN)
32+
let expectation = self.expectation(description: "Wait for InstanceManager to be deallocated.")
33+
TestFinalizer.onDeinit = {
34+
expectation.fulfill()
35+
}
36+
37+
// Ensure method is from `FlutterApplicationLifeCycleDelegate`.
38+
(plugin as FlutterApplicationLifeCycleDelegate).applicationWillTerminate!(
39+
UIApplication.shared)
40+
XCTAssertNil(plugin.proxyApiRegistrar)
41+
42+
finalizer = nil
43+
waitForExpectations(timeout: 5.0)
44+
}
45+
46+
func testInstanceManagerIsDeallocatedInSceneDidDisconnect() {
47+
let plugin = WebViewFlutterPlugin(binaryMessenger: TestBinaryMessenger())
48+
plugin.proxyApiRegistrar!.setUp()
49+
50+
let view = UIView()
51+
_ = plugin.proxyApiRegistrar!.instanceManager.addHostCreatedInstance(view)
52+
53+
// Attaches an associated object to the InstanceManager to listen for when it is deallocated.
54+
var finalizer: TestFinalizer? = TestFinalizer()
55+
56+
let key = malloc(1)!
57+
defer {
58+
free(key)
59+
}
60+
objc_setAssociatedObject(
61+
plugin.proxyApiRegistrar!.instanceManager, key, finalizer, .OBJC_ASSOCIATION_RETAIN)
62+
let expectation = self.expectation(description: "Wait for InstanceManager to be deallocated.")
63+
TestFinalizer.onDeinit = {
64+
expectation.fulfill()
65+
}
66+
67+
let scene = UIApplication.shared.connectedScenes.first!
68+
69+
// Ensure method is from `FlutterSceneLifeCycleDelegate`.
70+
(plugin as FlutterSceneLifeCycleDelegate).sceneDidDisconnect?(scene)
71+
XCTAssertNil(plugin.proxyApiRegistrar)
72+
73+
finalizer = nil
74+
waitForExpectations(timeout: 5.0)
75+
}
76+
#endif
77+
}
78+
79+
class TestFinalizer {
80+
static var onDeinit: (() -> Void)?
81+
82+
deinit {
83+
Self.onDeinit?()
84+
}
85+
}

packages/webview_flutter/webview_flutter_wkwebview/darwin/webview_flutter_wkwebview/Sources/webview_flutter_wkwebview/WebViewFlutterPlugin.swift

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,37 @@ public class WebViewFlutterPlugin: NSObject, FlutterPlugin {
2828
let plugin = WebViewFlutterPlugin(binaryMessenger: binaryMessenger)
2929

3030
let viewFactory = FlutterViewFactory(instanceManager: plugin.proxyApiRegistrar!.instanceManager)
31+
32+
#if os(iOS)
33+
registrar.addApplicationDelegate(plugin)
34+
registrar.addSceneDelegate(plugin)
35+
#endif
36+
3137
registrar.register(viewFactory, withId: "plugins.flutter.io/webview")
3238
registrar.publish(plugin)
3339
}
3440

3541
public func detachFromEngine(for registrar: FlutterPluginRegistrar) {
36-
proxyApiRegistrar!.ignoreCallsToDart = true
37-
proxyApiRegistrar!.tearDown()
42+
tearDownProxyAPIRegistrar()
43+
}
44+
45+
private func tearDownProxyAPIRegistrar() {
46+
proxyApiRegistrar?.ignoreCallsToDart = true
47+
proxyApiRegistrar?.tearDown()
48+
try? proxyApiRegistrar?.instanceManager.removeAllObjects()
3849
proxyApiRegistrar = nil
3950
}
4051
}
52+
53+
#if os(iOS)
54+
extension WebViewFlutterPlugin: FlutterApplicationLifeCycleDelegate, FlutterSceneLifeCycleDelegate
55+
{
56+
public func applicationWillTerminate(_ application: UIApplication) {
57+
tearDownProxyAPIRegistrar()
58+
}
59+
60+
public func sceneDidDisconnect(_ scene: UIScene) {
61+
tearDownProxyAPIRegistrar()
62+
}
63+
}
64+
#endif

packages/webview_flutter/webview_flutter_wkwebview/example/ios/Runner.xcodeproj/project.pbxproj

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
archiveVersion = 1;
44
classes = {
55
};
6-
objectVersion = 54;
6+
objectVersion = 60;
77
objects = {
88

99
/* Begin PBXBuildFile section */
@@ -42,6 +42,8 @@
4242
8F1488FE2D2DE27000191744 /* HTTPCookieProxyAPITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F1488C82D2DE27000191744 /* HTTPCookieProxyAPITests.swift */; };
4343
8F1488FF2D2DE27000191744 /* NavigationActionProxyAPITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F1488CB2D2DE27000191744 /* NavigationActionProxyAPITests.swift */; };
4444
8F1489012D2DE91C00191744 /* AuthenticationChallengeResponseProxyAPITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F1489002D2DE91C00191744 /* AuthenticationChallengeResponseProxyAPITests.swift */; };
45+
8F63D06B2F8812E400EC5076 /* WebViewFlutterPluginTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F63D06A2F8812E400EC5076 /* WebViewFlutterPluginTests.swift */; };
46+
8F63D06C2F8812E400EC5076 /* PlatformViewImplTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F63D0692F8812E400EC5076 /* PlatformViewImplTests.swift */; };
4547
8FEC64852DA2C6DC00C48569 /* GetTrustResultResponseProxyAPITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FEC64812DA2C6DC00C48569 /* GetTrustResultResponseProxyAPITests.swift */; };
4648
8FEC64862DA2C6DC00C48569 /* WebpagePreferencesProxyAPITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FEC64842DA2C6DC00C48569 /* WebpagePreferencesProxyAPITests.swift */; };
4749
8FEC64872DA2C6DC00C48569 /* SecCertificateProxyAPITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FEC64822DA2C6DC00C48569 /* SecCertificateProxyAPITests.swift */; };
@@ -128,6 +130,8 @@
128130
8F1488E02D2DE27000191744 /* WebViewConfigurationProxyAPITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = WebViewConfigurationProxyAPITests.swift; path = ../../darwin/Tests/WebViewConfigurationProxyAPITests.swift; sourceTree = SOURCE_ROOT; };
129131
8F1488E12D2DE27000191744 /* WebViewProxyAPITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = WebViewProxyAPITests.swift; path = ../../darwin/Tests/WebViewProxyAPITests.swift; sourceTree = SOURCE_ROOT; };
130132
8F1489002D2DE91C00191744 /* AuthenticationChallengeResponseProxyAPITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AuthenticationChallengeResponseProxyAPITests.swift; path = ../../darwin/Tests/AuthenticationChallengeResponseProxyAPITests.swift; sourceTree = SOURCE_ROOT; };
133+
8F63D0692F8812E400EC5076 /* PlatformViewImplTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = PlatformViewImplTests.swift; path = ../../darwin/Tests/PlatformViewImplTests.swift; sourceTree = SOURCE_ROOT; };
134+
8F63D06A2F8812E400EC5076 /* WebViewFlutterPluginTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = WebViewFlutterPluginTests.swift; path = ../../darwin/Tests/WebViewFlutterPluginTests.swift; sourceTree = SOURCE_ROOT; };
131135
8FEC64812DA2C6DC00C48569 /* GetTrustResultResponseProxyAPITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = GetTrustResultResponseProxyAPITests.swift; path = ../../darwin/Tests/GetTrustResultResponseProxyAPITests.swift; sourceTree = SOURCE_ROOT; };
132136
8FEC64822DA2C6DC00C48569 /* SecCertificateProxyAPITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SecCertificateProxyAPITests.swift; path = ../../darwin/Tests/SecCertificateProxyAPITests.swift; sourceTree = SOURCE_ROOT; };
133137
8FEC64832DA2C6DC00C48569 /* SecTrustProxyAPITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SecTrustProxyAPITests.swift; path = ../../darwin/Tests/SecTrustProxyAPITests.swift; sourceTree = SOURCE_ROOT; };
@@ -174,6 +178,8 @@
174178
68BDCAEA23C3F7CB00D9C032 /* RunnerTests */ = {
175179
isa = PBXGroup;
176180
children = (
181+
8F63D0692F8812E400EC5076 /* PlatformViewImplTests.swift */,
182+
8F63D06A2F8812E400EC5076 /* WebViewFlutterPluginTests.swift */,
177183
8F0E23512EEB5D6B002AB342 /* ColorProxyAPITests.swift */,
178184
8F0EDFD22E1F4967001938E6 /* ProxyAPIRegistrarTests.swift */,
179185
8FEC64812DA2C6DC00C48569 /* GetTrustResultResponseProxyAPITests.swift */,
@@ -498,6 +504,8 @@
498504
8F1488F42D2DE27000191744 /* URLCredentialProxyAPITests.swift in Sources */,
499505
8F1488F52D2DE27000191744 /* URLAuthenticationChallengeProxyAPITests.swift in Sources */,
500506
8F1488F62D2DE27000191744 /* NavigationDelegateProxyAPITests.swift in Sources */,
507+
8F63D06B2F8812E400EC5076 /* WebViewFlutterPluginTests.swift in Sources */,
508+
8F63D06C2F8812E400EC5076 /* PlatformViewImplTests.swift in Sources */,
501509
8F1488F72D2DE27000191744 /* UIDelegateProxyAPITests.swift in Sources */,
502510
8F1488F82D2DE27000191744 /* ScrollViewDelegateProxyAPITests.swift in Sources */,
503511
8F1488FA2D2DE27000191744 /* FWFWebViewFlutterWKWebViewExternalAPITests.swift in Sources */,

packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: webview_flutter_wkwebview
22
description: A Flutter plugin that provides a WebView widget based on Apple's WKWebView control.
33
repository: https://github.com/flutter/packages/tree/main/packages/webview_flutter/webview_flutter_wkwebview
44
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22
5-
version: 3.24.3
5+
version: 3.24.4
66

77
environment:
88
sdk: ^3.9.0

0 commit comments

Comments
 (0)