Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
e80044e
use static frame info
bparrishMines Apr 1, 2026
34f0b0b
Merge branch 'main' of github.com:flutter/packages into wkwebview_frame
bparrishMines Apr 1, 2026
874f300
some comments
bparrishMines Apr 1, 2026
00a46bc
update url for 26
bparrishMines Apr 9, 2026
0472d7e
version bump
bparrishMines Apr 9, 2026
717ec41
Merge branch 'main' of github.com:flutter/packages into wkwebview_frame
bparrishMines Apr 9, 2026
67ec5e1
formatting
bparrishMines Apr 9, 2026
7aa696c
Merge branch 'wkwebview_frame' into wkwebview_terminate
bparrishMines Apr 9, 2026
923129e
tear down registar
bparrishMines Apr 9, 2026
1b49005
improve test
bparrishMines Apr 10, 2026
5459a1d
formatting
bparrishMines Apr 10, 2026
d94196c
Merge branch 'main' of github.com:flutter/packages into wkwebview_ter…
bparrishMines Apr 10, 2026
da6430b
version bump
bparrishMines Apr 10, 2026
9660f7a
use source root
bparrishMines Apr 10, 2026
1487005
fix for macos
bparrishMines Apr 10, 2026
01b03e9
Merge branch 'main' of github.com:flutter/packages into wkwebview_ter…
bparrishMines Apr 10, 2026
dd0f3c9
only add on ios
bparrishMines Apr 10, 2026
b148f11
remove all objects and free memory
bparrishMines Apr 14, 2026
c2383ed
Merge branch 'main' of github.com:flutter/packages into wkwebview_ter…
bparrishMines Apr 14, 2026
2224d91
impl for scene disconnect
bparrishMines Apr 21, 2026
676b183
formatting
bparrishMines Apr 21, 2026
7d93dd4
Merge branch 'main' of github.com:flutter/packages into wkwebview_ter…
bparrishMines Apr 21, 2026
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
@@ -1,3 +1,7 @@
## 3.24.4

* Updates plugin to prevent message calls when application will terminate.

## 3.24.3

* Adds support to get failing url from DNS errors on iOS 26+.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// Copyright 2013 The Flutter Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import XCTest

@testable import webview_flutter_wkwebview

#if os(iOS)
import Flutter
import UIKit
#endif

class WebViewFlutterPluginTests: XCTestCase {
#if os(iOS)
func testInstanceManagerIsDeallocatedInApplicationWillTerminate() {
let plugin = WebViewFlutterPlugin(binaryMessenger: TestBinaryMessenger())
plugin.proxyApiRegistrar!.setUp()

let view = UIView()
_ = plugin.proxyApiRegistrar!.instanceManager.addHostCreatedInstance(view)

// Attaches an associated object to the InstanceManager to listen for when it is deallocated.
var finalizer: TestFinalizer? = TestFinalizer()

let key = malloc(1)!
defer {
free(key)
}
objc_setAssociatedObject(
plugin.proxyApiRegistrar!.instanceManager, key, finalizer, .OBJC_ASSOCIATION_RETAIN)
let expectation = self.expectation(description: "Wait for InstanceManager to be deallocated.")
TestFinalizer.onDeinit = {
expectation.fulfill()
}

// Ensure method is from `FlutterApplicationLifeCycleDelegate`.
(plugin as FlutterApplicationLifeCycleDelegate).applicationWillTerminate!(
UIApplication.shared)
XCTAssertNil(plugin.proxyApiRegistrar)

finalizer = nil
waitForExpectations(timeout: 5.0)
}

func testInstanceManagerIsDeallocatedInSceneDidDisconnect() {
let plugin = WebViewFlutterPlugin(binaryMessenger: TestBinaryMessenger())
plugin.proxyApiRegistrar!.setUp()

let view = UIView()
_ = plugin.proxyApiRegistrar!.instanceManager.addHostCreatedInstance(view)

// Attaches an associated object to the InstanceManager to listen for when it is deallocated.
var finalizer: TestFinalizer? = TestFinalizer()

let key = malloc(1)!
defer {
free(key)
}
objc_setAssociatedObject(
plugin.proxyApiRegistrar!.instanceManager, key, finalizer, .OBJC_ASSOCIATION_RETAIN)
let expectation = self.expectation(description: "Wait for InstanceManager to be deallocated.")
TestFinalizer.onDeinit = {
expectation.fulfill()
}

let scene = UIApplication.shared.connectedScenes.first!

// Ensure method is from `FlutterSceneLifeCycleDelegate`.
(plugin as FlutterSceneLifeCycleDelegate).sceneDidDisconnect?(scene)
XCTAssertNil(plugin.proxyApiRegistrar)

finalizer = nil
waitForExpectations(timeout: 5.0)
}
#endif
}

class TestFinalizer {
static var onDeinit: (() -> Void)?

deinit {
Self.onDeinit?()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,37 @@ public class WebViewFlutterPlugin: NSObject, FlutterPlugin {
let plugin = WebViewFlutterPlugin(binaryMessenger: binaryMessenger)

let viewFactory = FlutterViewFactory(instanceManager: plugin.proxyApiRegistrar!.instanceManager)

#if os(iOS)
registrar.addApplicationDelegate(plugin)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why application delegate rather than the newer scene delegate that replaces it?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Scene delegate doesn't completely replace application delegate. Only the callbacks related to UI State. applicationWillTerminate is still called for the application delegate. I manually tested that this is called when the application is removed in the app switcher.

I also added the sceneDidDisconnect callback to tear down the registrar. This should also be an indicator that the engine is being torn down and callbacks would cause an error.

registrar.addSceneDelegate(plugin)
#endif

registrar.register(viewFactory, withId: "plugins.flutter.io/webview")
registrar.publish(plugin)
}

public func detachFromEngine(for registrar: FlutterPluginRegistrar) {
proxyApiRegistrar!.ignoreCallsToDart = true
proxyApiRegistrar!.tearDown()
tearDownProxyAPIRegistrar()
}

private func tearDownProxyAPIRegistrar() {
proxyApiRegistrar?.ignoreCallsToDart = true
proxyApiRegistrar?.tearDown()
try? proxyApiRegistrar?.instanceManager.removeAllObjects()
proxyApiRegistrar = nil
}
Comment on lines +45 to 50
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The proxyApiRegistrar is an optional that is set to nil at the end of this method. Since tearDownProxyAPIRegistrar is now called from both detachFromEngine and applicationWillTerminate, it is possible for it to be called twice during application shutdown (for example, if the engine is detached and then the application terminates). The force-unwrapping of proxyApiRegistrar will cause a crash on the second call. It should be handled safely using a guard or optional chaining.

  private func tearDownProxyAPIRegistrar() {
    guard let registrar = proxyApiRegistrar else {
      return
    }
    registrar.ignoreCallsToDart = true
    registrar.tearDown()
    proxyApiRegistrar = nil
  }

}

#if os(iOS)
extension WebViewFlutterPlugin: FlutterApplicationLifeCycleDelegate, FlutterSceneLifeCycleDelegate
{
public func applicationWillTerminate(_ application: UIApplication) {
tearDownProxyAPIRegistrar()
}

public func sceneDidDisconnect(_ scene: UIScene) {
tearDownProxyAPIRegistrar()
}
}
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 54;
objectVersion = 60;
objects = {

/* Begin PBXBuildFile section */
Expand Down Expand Up @@ -42,6 +42,8 @@
8F1488FE2D2DE27000191744 /* HTTPCookieProxyAPITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F1488C82D2DE27000191744 /* HTTPCookieProxyAPITests.swift */; };
8F1488FF2D2DE27000191744 /* NavigationActionProxyAPITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F1488CB2D2DE27000191744 /* NavigationActionProxyAPITests.swift */; };
8F1489012D2DE91C00191744 /* AuthenticationChallengeResponseProxyAPITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F1489002D2DE91C00191744 /* AuthenticationChallengeResponseProxyAPITests.swift */; };
8F63D06B2F8812E400EC5076 /* WebViewFlutterPluginTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F63D06A2F8812E400EC5076 /* WebViewFlutterPluginTests.swift */; };
8F63D06C2F8812E400EC5076 /* PlatformViewImplTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F63D0692F8812E400EC5076 /* PlatformViewImplTests.swift */; };
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The project file references PlatformViewImplTests.swift (also seen on lines 133, 181, and 508), but this file is not included in the pull request. This will cause a build failure in the example app's test target as the compiler will be unable to find the file.

8FEC64852DA2C6DC00C48569 /* GetTrustResultResponseProxyAPITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FEC64812DA2C6DC00C48569 /* GetTrustResultResponseProxyAPITests.swift */; };
8FEC64862DA2C6DC00C48569 /* WebpagePreferencesProxyAPITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FEC64842DA2C6DC00C48569 /* WebpagePreferencesProxyAPITests.swift */; };
8FEC64872DA2C6DC00C48569 /* SecCertificateProxyAPITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FEC64822DA2C6DC00C48569 /* SecCertificateProxyAPITests.swift */; };
Expand Down Expand Up @@ -128,6 +130,8 @@
8F1488E02D2DE27000191744 /* WebViewConfigurationProxyAPITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = WebViewConfigurationProxyAPITests.swift; path = ../../darwin/Tests/WebViewConfigurationProxyAPITests.swift; sourceTree = SOURCE_ROOT; };
8F1488E12D2DE27000191744 /* WebViewProxyAPITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = WebViewProxyAPITests.swift; path = ../../darwin/Tests/WebViewProxyAPITests.swift; sourceTree = SOURCE_ROOT; };
8F1489002D2DE91C00191744 /* AuthenticationChallengeResponseProxyAPITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AuthenticationChallengeResponseProxyAPITests.swift; path = ../../darwin/Tests/AuthenticationChallengeResponseProxyAPITests.swift; sourceTree = SOURCE_ROOT; };
8F63D0692F8812E400EC5076 /* PlatformViewImplTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = PlatformViewImplTests.swift; path = ../../darwin/Tests/PlatformViewImplTests.swift; sourceTree = SOURCE_ROOT; };
8F63D06A2F8812E400EC5076 /* WebViewFlutterPluginTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = WebViewFlutterPluginTests.swift; path = ../../darwin/Tests/WebViewFlutterPluginTests.swift; sourceTree = SOURCE_ROOT; };
8FEC64812DA2C6DC00C48569 /* GetTrustResultResponseProxyAPITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = GetTrustResultResponseProxyAPITests.swift; path = ../../darwin/Tests/GetTrustResultResponseProxyAPITests.swift; sourceTree = SOURCE_ROOT; };
8FEC64822DA2C6DC00C48569 /* SecCertificateProxyAPITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SecCertificateProxyAPITests.swift; path = ../../darwin/Tests/SecCertificateProxyAPITests.swift; sourceTree = SOURCE_ROOT; };
8FEC64832DA2C6DC00C48569 /* SecTrustProxyAPITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SecTrustProxyAPITests.swift; path = ../../darwin/Tests/SecTrustProxyAPITests.swift; sourceTree = SOURCE_ROOT; };
Expand Down Expand Up @@ -174,6 +178,8 @@
68BDCAEA23C3F7CB00D9C032 /* RunnerTests */ = {
isa = PBXGroup;
children = (
8F63D0692F8812E400EC5076 /* PlatformViewImplTests.swift */,
8F63D06A2F8812E400EC5076 /* WebViewFlutterPluginTests.swift */,
8F0E23512EEB5D6B002AB342 /* ColorProxyAPITests.swift */,
8F0EDFD22E1F4967001938E6 /* ProxyAPIRegistrarTests.swift */,
8FEC64812DA2C6DC00C48569 /* GetTrustResultResponseProxyAPITests.swift */,
Expand Down Expand Up @@ -498,6 +504,8 @@
8F1488F42D2DE27000191744 /* URLCredentialProxyAPITests.swift in Sources */,
8F1488F52D2DE27000191744 /* URLAuthenticationChallengeProxyAPITests.swift in Sources */,
8F1488F62D2DE27000191744 /* NavigationDelegateProxyAPITests.swift in Sources */,
8F63D06B2F8812E400EC5076 /* WebViewFlutterPluginTests.swift in Sources */,
8F63D06C2F8812E400EC5076 /* PlatformViewImplTests.swift in Sources */,
8F1488F72D2DE27000191744 /* UIDelegateProxyAPITests.swift in Sources */,
8F1488F82D2DE27000191744 /* ScrollViewDelegateProxyAPITests.swift in Sources */,
8F1488FA2D2DE27000191744 /* FWFWebViewFlutterWKWebViewExternalAPITests.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: webview_flutter_wkwebview
description: A Flutter plugin that provides a WebView widget based on Apple's WKWebView control.
repository: https://github.com/flutter/packages/tree/main/packages/webview_flutter/webview_flutter_wkwebview
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22
version: 3.24.3
version: 3.24.4

environment:
sdk: ^3.9.0
Expand Down
Loading