This repository was archived by the owner on Feb 22, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 9.7k
[webview_flutter] Filter onChanged events for invalid displays #1964
Merged
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
142 changes: 142 additions & 0 deletions
142
...flutter/android/src/main/java/io/flutter/plugins/webviewflutter/DisplayListenerProxy.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,142 @@ | ||
| package io.flutter.plugins.webviewflutter; | ||
|
|
||
| import static android.hardware.display.DisplayManager.DisplayListener; | ||
|
|
||
| import android.annotation.TargetApi; | ||
| import android.hardware.display.DisplayManager; | ||
| import android.os.Build; | ||
| import android.util.Log; | ||
| import java.lang.reflect.Field; | ||
| import java.util.ArrayList; | ||
|
|
||
| /** | ||
| * Works around an Android WebView bug by filtering some DisplayListener invocations. | ||
| * | ||
| * <p>Older Android WebView versions had assumed that when {@link DisplayListener#onDisplayChanged} | ||
| * is invoked, the display ID it is provided is of a valid display. However it turns out that when a | ||
| * display is removed Android may call onDisplayChanged with the ID of the removed display, in this | ||
| * case the Android WebView code tries to fetch and use the display with this ID and crashes with an | ||
| * NPE. | ||
| * | ||
| * <p>This issue was fixed in the Android WebView code in | ||
| * https://chromium-review.googlesource.com/517913 which is available starting WebView version | ||
| * 58.0.3029.125 however older webviews in the wild still have this issue. | ||
| * | ||
| * <p>Since Flutter removes virtual displays whenever a platform view is resized the webview crash | ||
| * is more likely to happen than other apps. And users were reporting this issue see: | ||
| * https://github.com/flutter/flutter/issues/30420 | ||
| * | ||
| * <p>This class works around the webview bug by unregistering the WebView's DisplayListener, and | ||
| * instead registering its own DisplayListener which delegates the callbacks to the WebView's | ||
| * listener unless it's a onDisplayChanged for an invalid display. | ||
| * | ||
| * <p>I did not find a clean way to get a handle of the WebView's DisplayListener so I'm using | ||
| * reflection to fetch all registered listeners before and after initializing a webview. In the | ||
| * first initialization of a webview within the process the difference between the lists is the | ||
| * webview's display listener. | ||
| */ | ||
| @TargetApi(Build.VERSION_CODES.KITKAT) | ||
| class DisplayListenerProxy { | ||
| private static final String TAG = "DisplayListenerProxy"; | ||
|
|
||
| private ArrayList<DisplayListener> listenersBeforeWebView; | ||
|
|
||
| /** Should be called prior to the webview's initialization. */ | ||
| void onPreWebViewInitialization(DisplayManager displayManager) { | ||
| listenersBeforeWebView = yoinkDisplayListeners(displayManager); | ||
| } | ||
|
|
||
| /** Should be called after the webview's initialization. */ | ||
| void onPostWebViewInitialization(final DisplayManager displayManager) { | ||
| final ArrayList<DisplayListener> webViewListeners = yoinkDisplayListeners(displayManager); | ||
| // We recorded the list of listeners prior to initializing webview, any new listeners we see | ||
| // after initializing the webview are listeners added by the webview. | ||
| webViewListeners.removeAll(listenersBeforeWebView); | ||
|
|
||
| if (webViewListeners.isEmpty()) { | ||
| // The Android WebView registers a single display listener per process (even if there | ||
| // are multiple WebView instances) so this list is expected to be non-empty only the | ||
| // first time a webview is initialized. | ||
| // Note that in an add2app scenario if the application had instantiated a non Flutter | ||
| // WebView prior to instantiating the Flutter WebView we are not able to get a reference | ||
| // to the WebView's display listener and can't work around the bug. | ||
| // | ||
| // This means that webview resizes in add2app Flutter apps with a non Flutter WebView | ||
| // running on a system with a webview prior to 58.0.3029.125 may crash (the Android's | ||
| // behavior seems to be racy so it doesn't always happen). | ||
| return; | ||
| } | ||
|
|
||
| for (DisplayListener webViewListener : webViewListeners) { | ||
| // Note that while DisplayManager.unregisterDisplayListener throws when given an | ||
| // unregistered listener, this isn't an issue as the WebView code never calls | ||
| // unregisterDisplayListener. | ||
| displayManager.unregisterDisplayListener(webViewListener); | ||
|
|
||
| // We never explicitly unregister this listener as the webview's listener is never | ||
| // unregistered (it's released when the process is terminated). | ||
| displayManager.registerDisplayListener( | ||
| new DisplayListener() { | ||
| @Override | ||
| public void onDisplayAdded(int displayId) { | ||
| for (DisplayListener webViewListener : webViewListeners) { | ||
| webViewListener.onDisplayAdded(displayId); | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| public void onDisplayRemoved(int displayId) { | ||
| for (DisplayListener webViewListener : webViewListeners) { | ||
| webViewListener.onDisplayRemoved(displayId); | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| public void onDisplayChanged(int displayId) { | ||
| if (displayManager.getDisplay(displayId) == null) { | ||
| return; | ||
| } | ||
| for (DisplayListener webViewListener : webViewListeners) { | ||
| webViewListener.onDisplayChanged(displayId); | ||
| } | ||
| } | ||
| }, | ||
| null); | ||
| } | ||
| } | ||
|
|
||
| @SuppressWarnings({"unchecked", "PrivateApi"}) | ||
| private static ArrayList<DisplayListener> yoinkDisplayListeners(DisplayManager displayManager) { | ||
| if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { | ||
| // We cannot use reflection on Android O, but it shouldn't matter as it shipped | ||
| // with a WebView version that has the bug this code is working around fixed. | ||
| return new ArrayList<>(); | ||
| } | ||
| try { | ||
| Field displayManagerGlobalField = DisplayManager.class.getDeclaredField("mGlobal"); | ||
| displayManagerGlobalField.setAccessible(true); | ||
| Object displayManagerGlobal = displayManagerGlobalField.get(displayManager); | ||
| Field displayListenersField = | ||
| displayManagerGlobal.getClass().getDeclaredField("mDisplayListeners"); | ||
| displayListenersField.setAccessible(true); | ||
| ArrayList<Object> delegates = | ||
| (ArrayList<Object>) displayListenersField.get(displayManagerGlobal); | ||
|
|
||
| Field listenerField = null; | ||
| ArrayList<DisplayManager.DisplayListener> listeners = new ArrayList<>(); | ||
| for (Object delegate : delegates) { | ||
| if (listenerField == null) { | ||
| listenerField = delegate.getClass().getField("mListener"); | ||
| listenerField.setAccessible(true); | ||
| } | ||
| DisplayManager.DisplayListener listener = | ||
| (DisplayManager.DisplayListener) listenerField.get(delegate); | ||
| listeners.add(listener); | ||
| } | ||
| return listeners; | ||
| } catch (NoSuchFieldException | IllegalAccessException e) { | ||
| Log.w(TAG, "Could not extract WebView's display listeners. " + e); | ||
| return new ArrayList<>(); | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -38,6 +38,7 @@ | |
| /* End PBXCopyFilesBuildPhase section */ | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: The changes in this file look like they could be reverted. |
||
|
|
||
| /* Begin PBXFileReference section */ | ||
| 127772EEA7782174BE0D74B5 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; }; | ||
| 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; }; | ||
| 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; }; | ||
| 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; }; | ||
|
|
@@ -55,6 +56,7 @@ | |
| 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; }; | ||
| 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; }; | ||
| 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; | ||
| C475C484BD510DD9CB2E403C /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; }; | ||
| /* End PBXFileReference section */ | ||
|
|
||
| /* Begin PBXFrameworksBuildPhase section */ | ||
|
|
@@ -138,6 +140,8 @@ | |
| C6FFB52F5C2B8A41A7E39DE2 /* Pods */ = { | ||
| isa = PBXGroup; | ||
| children = ( | ||
| 127772EEA7782174BE0D74B5 /* Pods-Runner.debug.xcconfig */, | ||
| C475C484BD510DD9CB2E403C /* Pods-Runner.release.xcconfig */, | ||
| ); | ||
| name = Pods; | ||
| sourceTree = "<group>"; | ||
|
|
@@ -249,7 +253,7 @@ | |
| files = ( | ||
| ); | ||
| inputPaths = ( | ||
| "${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", | ||
| "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", | ||
| "${PODS_ROOT}/../.symlinks/flutter/ios/Flutter.framework", | ||
| ); | ||
| name = "[CP] Embed Pods Frameworks"; | ||
|
|
@@ -258,7 +262,7 @@ | |
| ); | ||
| runOnlyForDeploymentPostprocessing = 0; | ||
| shellPath = /bin/sh; | ||
| shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; | ||
| shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; | ||
| showEnvVarsInLog = 0; | ||
| }; | ||
| B71376B4FB8384EF9D5F3F84 /* [CP] Check Pods Manifest.lock */ = { | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,6 @@ | ||
| name: webview_flutter | ||
| description: A Flutter plugin that provides a WebView widget on Android and iOS. | ||
| version: 0.3.11 | ||
| version: 0.3.11+1 | ||
| author: Flutter Team <[email protected]> | ||
| homepage: https://github.com/flutter/plugins/tree/master/packages/webview_flutter | ||
|
|
||
|
|
||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
😄
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Learned from the best 😄