Skip to content

Commit 62949f4

Browse files
[webview_flutter_wkwebview] Updates plugin for iOS 26 (#11415)
1. Prevents test `WKFrameInfo` from deallocating during tests to prevent crash 2. Retrieves failing url from DNS failures from `NSURLErrorFailingURLErrorKey`. Fixes flutter/flutter#182846 ## 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 1aa892c commit 62949f4

9 files changed

Lines changed: 101 additions & 24 deletions

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.3
2+
3+
* Adds support to get failing url from DNS errors on iOS 26+.
4+
15
## 3.24.2
26

37
* Fixes dartdoc comments that accidentally used HTML.

packages/webview_flutter/webview_flutter_wkwebview/darwin/Tests/FrameInfoProxyAPITests.swift

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,27 +13,27 @@ class FrameInfoProxyAPITests: XCTestCase {
1313
let registrar = TestProxyApiRegistrar()
1414
let api = registrar.apiDelegate.pigeonApiWKFrameInfo(registrar)
1515

16-
let instance: TestFrameInfo? = TestFrameInfo()
17-
let value = try? api.pigeonDelegate.isMainFrame(pigeonApi: api, pigeonInstance: instance!)
16+
let instance = TestFrameInfo.instance
17+
let value = try? api.pigeonDelegate.isMainFrame(pigeonApi: api, pigeonInstance: instance)
1818

19-
XCTAssertEqual(value, instance!.isMainFrame)
19+
XCTAssertEqual(value, instance.isMainFrame)
2020
}
2121

2222
@MainActor func testRequest() {
2323
let registrar = TestProxyApiRegistrar()
2424
let api = registrar.apiDelegate.pigeonApiWKFrameInfo(registrar)
2525

26-
let instance: TestFrameInfo? = TestFrameInfo()
27-
let value = try? api.pigeonDelegate.request(pigeonApi: api, pigeonInstance: instance!)
26+
let instance = TestFrameInfo.instance
27+
let value = try? api.pigeonDelegate.request(pigeonApi: api, pigeonInstance: instance)
2828

29-
XCTAssertEqual(value?.value, instance!.request)
29+
XCTAssertEqual(value?.value, instance.request)
3030
}
3131

3232
@MainActor func testNilRequest() {
3333
let registrar = TestProxyApiRegistrar()
3434
let api = registrar.apiDelegate.pigeonApiWKFrameInfo(registrar)
3535

36-
let instance = TestFrameInfoWithNilRequest()
36+
let instance = TestFrameInfoWithNilRequest.instance
3737
let value = try? api.pigeonDelegate.request(pigeonApi: api, pigeonInstance: instance)
3838
// On macOS 15.5+, `WKFrameInfo.request` returns with an empty URLRequest.
3939
// Previously it would return nil so accept either.
@@ -46,6 +46,14 @@ class FrameInfoProxyAPITests: XCTestCase {
4646
}
4747

4848
class TestFrameInfo: WKFrameInfo {
49+
// Global test instance of `WKFrameInfo`. Using a static instance prevents a crash when
50+
// a `WKFrameInfo` is deallocated during a test on iOS 26+.
51+
static let instance = TestFrameInfo()
52+
53+
private override init() {
54+
55+
}
56+
4957
override var isMainFrame: Bool {
5058
return true
5159
}
@@ -56,4 +64,11 @@ class TestFrameInfo: WKFrameInfo {
5664
}
5765

5866
class TestFrameInfoWithNilRequest: WKFrameInfo {
67+
// Global test instance of `WKFrameInfo` with a nil URLRequest. Using a static instance prevents a
68+
// crash when a `WKFrameInfo` is deallocated during a test on iOS 26+.
69+
static let instance = TestFrameInfoWithNilRequest()
70+
71+
private override init() {
72+
73+
}
5974
}

packages/webview_flutter/webview_flutter_wkwebview/darwin/Tests/NavigationActionProxyAPITests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ class NavigationActionProxyAPITests: XCTestCase {
4040
}
4141

4242
class TestNavigationAction: WKNavigationAction {
43-
let internalTargetFrame = TestFrameInfo()
43+
let internalTargetFrame = TestFrameInfo.instance
4444

4545
override var request: URLRequest {
4646
return URLRequest(url: URL(string: "http://google.com")!)

packages/webview_flutter/webview_flutter_wkwebview/darwin/Tests/NavigationDelegateProxyAPITests.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -245,8 +245,7 @@ class TestWebView: WKWebView {
245245
}
246246

247247
class TestURLAuthenticationChallengeSender: NSObject, URLAuthenticationChallengeSender,
248-
@unchecked
249-
Sendable
248+
@unchecked Sendable
250249
{
251250
func use(_ credential: URLCredential, for challenge: URLAuthenticationChallenge) {
252251

packages/webview_flutter/webview_flutter_wkwebview/darwin/Tests/UIDelegateProxyAPITests.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ class UIDelegateProxyAPITests: XCTestCase {
3939
let instance = UIDelegateImpl(api: api, registrar: registrar)
4040
let webView = WKWebView(frame: .zero)
4141
let origin = SecurityOriginProxyAPITests.testSecurityOrigin
42-
let frame = TestFrameInfo()
42+
let frame = TestFrameInfo.instance
4343
let type: WKMediaCaptureType = .camera
4444

4545
var resultDecision: WKPermissionDecision?
@@ -64,7 +64,7 @@ class UIDelegateProxyAPITests: XCTestCase {
6464
let instance = UIDelegateImpl(api: api, registrar: registrar)
6565
let webView = WKWebView(frame: .zero)
6666
let message = "myString"
67-
let frame = TestFrameInfo()
67+
let frame = TestFrameInfo.instance
6868

6969
instance.webView(webView, runJavaScriptAlertPanelWithMessage: message, initiatedByFrame: frame)
7070
{
@@ -79,7 +79,7 @@ class UIDelegateProxyAPITests: XCTestCase {
7979
let instance = UIDelegateImpl(api: api, registrar: registrar)
8080
let webView = WKWebView(frame: .zero)
8181
let message = "myString"
82-
let frame = TestFrameInfo()
82+
let frame = TestFrameInfo.instance
8383

8484
var confirmedResult: Bool?
8585
let callbackExpectation = expectation(description: "Wait for callback.")
@@ -103,7 +103,7 @@ class UIDelegateProxyAPITests: XCTestCase {
103103
let webView = WKWebView(frame: .zero)
104104
let prompt = "myString"
105105
let defaultText = "myString3"
106-
let frame = TestFrameInfo()
106+
let frame = TestFrameInfo.instance
107107

108108
var inputResult: String?
109109
let callbackExpectation = expectation(description: "Wait for callback.")

packages/webview_flutter/webview_flutter_wkwebview/lib/src/common/webkit_constants.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,11 @@ class NSErrorUserInfoKey {
5151
/// See https://developer.apple.com/documentation/foundation/nsurlerrorfailingurlstringerrorkey?language=objc.
5252
static const String NSURLErrorFailingURLStringError =
5353
'NSErrorFailingURLStringKey';
54+
55+
/// The URL which caused a load to fail.
56+
///
57+
/// See https://developer.apple.com/documentation/foundation/nsurlerrorfailingurlerrorkey.
58+
static const String NSURLErrorFailingURLErrorKey = 'NSErrorFailingURLKey';
5459
}
5560

5661
/// The authentication method used by the receiver.

packages/webview_flutter/webview_flutter_wkwebview/lib/src/webkit_webview_controller.dart

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1254,17 +1254,22 @@ class WebKitNavigationDelegate extends PlatformNavigationDelegate {
12541254
);
12551255
}
12561256
},
1257-
didFailProvisionalNavigation: (_, __, NSError error) {
1257+
didFailProvisionalNavigation: (_, __, NSError error) async {
1258+
var url =
1259+
error.userInfo[NSErrorUserInfoKey.NSURLErrorFailingURLStringError]
1260+
as String?;
1261+
1262+
// On iOS 26+, the error is stored with `NSURLErrorFailingURLErrorKey`.
1263+
if (url == null) {
1264+
final nativeURL =
1265+
error.userInfo[NSErrorUserInfoKey.NSURLErrorFailingURLErrorKey]
1266+
as URL?;
1267+
url = await nativeURL?.getAbsoluteString();
1268+
}
1269+
12581270
if (weakThis.target?._onWebResourceError != null) {
12591271
weakThis.target!._onWebResourceError!(
1260-
WebKitWebResourceError._(
1261-
error,
1262-
isForMainFrame: true,
1263-
url:
1264-
error.userInfo[NSErrorUserInfoKey
1265-
.NSURLErrorFailingURLStringError]
1266-
as String?,
1267-
),
1272+
WebKitWebResourceError._(error, isForMainFrame: true, url: url),
12681273
);
12691274
}
12701275
},

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.2
5+
version: 3.24.3
66

77
environment:
88
sdk: ^3.9.0

packages/webview_flutter/webview_flutter_wkwebview/test/webkit_navigation_delegate_test.dart

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,55 @@ void main() {
287287
expect(callbackError.isForMainFrame, true);
288288
});
289289

290+
test(
291+
'onWebResourceError can receive DNS errors from didFailProvisionalNavigation',
292+
() async {
293+
PigeonOverrides.wKNavigationDelegate_new =
294+
CapturingNavigationDelegate.new;
295+
final webKitDelegate = WebKitNavigationDelegate(
296+
const WebKitNavigationDelegateCreationParams(),
297+
);
298+
299+
late final WebKitWebResourceError callbackError;
300+
void onWebResourceError(WebResourceError error) {
301+
callbackError = error as WebKitWebResourceError;
302+
}
303+
304+
await webKitDelegate.setOnWebResourceError(onWebResourceError);
305+
306+
CapturingNavigationDelegate
307+
.lastCreatedDelegate
308+
.didFailProvisionalNavigation!(
309+
WKNavigationDelegate.pigeon_detached(
310+
decidePolicyForNavigationAction: (_, __, ___) async {
311+
return NavigationActionPolicy.cancel;
312+
},
313+
decidePolicyForNavigationResponse: (_, __, ___) async {
314+
return NavigationResponsePolicy.cancel;
315+
},
316+
didReceiveAuthenticationChallenge: (_, __, ___) async {
317+
return AuthenticationChallengeResponse.pigeon_detached(
318+
disposition:
319+
UrlSessionAuthChallengeDisposition.performDefaultHandling,
320+
);
321+
},
322+
),
323+
WKWebView.pigeon_detached(),
324+
NSError.pigeon_detached(
325+
code: WKErrorCode.webViewInvalidated,
326+
domain: 'domain',
327+
userInfo: const <String, Object?>{
328+
NSErrorUserInfoKey.NSURLErrorFailingURLStringError:
329+
'www.flutter.dev',
330+
NSErrorUserInfoKey.NSLocalizedDescription: 'my desc',
331+
},
332+
),
333+
);
334+
335+
expect(callbackError.url, 'www.flutter.dev');
336+
},
337+
);
338+
290339
test(
291340
'onWebResourceError from webViewWebContentProcessDidTerminate',
292341
() async {

0 commit comments

Comments
 (0)