Skip to content

Commit 19bc12e

Browse files
[webview_flutter] Extract Android implementation into a separate package (flutter#4343)
* Setup webview_flutter_android package. Creates a new `webview_flutter_android` directory and adds the following meta-data files: - `AUTHORS`: copied from the `webview_flutter` package and added my name; - `CHANGELOG.md`: new file adding description for release 0.0.1; - `LICENSE`: copied from the `webview_flutter` package; - `README.md`: new file adding the standard platform implementation description; - `pubspec.yaml`: new file adding package meta-data for the `webview_flutter_android` package. * Direct copy of "android" folder. A one to one copy of the `webview_flutter/android` folder to `webview_flutter_android/` using the following command: ``` cp -R ./webview_flutter/android ./webview_flutter_android/ ``` * Direct copy of Android specific .dart files. Copied the Android specific .dart files over from the `./webview_flutter` package. Note that the `SurfaceAndroidWebView` class in the `./webview_flutter_android/lib/webview_surface_android.dart` file is copied directly (without modifactions) from the `./webview_flutter/lib/webview_flutter.dart` file. * Modify .dart code to work with platform_interface. Make sure the `AndroidWebView` and `SurfaceAndroidWebView` widgets extend the `WebViewPlatform` class from the `webview_flutter_platform_interface` package correctly by accepting an instance of the `JavascriptChannelRegistry` class. * Direct copy of the `webview_flutter/example` app. This commit makes a direct copy of the `webview_flutter/example` app to the `webview_flutter_android` package. After the copy the `example/ios` folder is removed as it doesn't serve a purpose in the Android specific package. Commands run where: ``` cp -R ./webview_flutter/example ./webview_flutter_android/ rm -rf ./webview_flutter_android/example/ios ``` * Update example to Android specific implementation. This commit updates the example App so it directly implements an Android specific implementation of the webview_flutter_platform_interface. * Update integration tests. Updated the existing integration tests (copied from webview_flutter package) so they work correctly with the implementation of the webview_flutter_android package. * Update webview_flutter_platform_interface dependency Updated the pubspec.yaml to depend on version 1.0.0 of the webview_flutter_platform_interface package instead of using a path reference (which is now possible since the platform interface package has now been published). Co-authored-by: BeMacized <[email protected]> * Use different bundle ID for Android example app. Make sure the `webview_flutter` and `webview_flutter_android` example apps use different application identifiers so that the CI doesn't run into problems. * Skip flaky integration_tests (issue 86757). * Exlude platform implementations from build all step. Make sure the webview_flutter_android and webview_flutter_wkwebview packages are excluded from the Build All plugins step as they will cause conflicts with the current implementation which is still part of the webview_flutter package. * Split helper classes from main example widget. Move the `WebView` and related `WebViewController` classes from the main.dart into a separate web_view.dart file. Co-authored-by: BeMacized <[email protected]>
1 parent e314c7a commit 19bc12e

File tree

57 files changed

+5128
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+5128
-0
lines changed
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# Below is a list of people and organizations that have contributed
2+
# to the Flutter project. Names should be added to the list like so:
3+
#
4+
# Name/Organization <email address>
5+
6+
Google Inc.
7+
The Chromium Authors
8+
German Saprykin <[email protected]>
9+
Benjamin Sauer <[email protected]>
10+
11+
Ali Bitek <[email protected]>
12+
Pol Batlló <[email protected]>
13+
Anatoly Pulyaevskiy
14+
Hayden Flinner <[email protected]>
15+
Stefano Rodriguez <[email protected]>
16+
Salvatore Giordano <[email protected]>
17+
Brian Armstrong <[email protected]>
18+
Paul DeMarco <[email protected]>
19+
Fabricio Nogueira <[email protected]>
20+
Simon Lightfoot <[email protected]>
21+
Ashton Thomas <[email protected]>
22+
Thomas Danner <[email protected]>
23+
Diego Velásquez <[email protected]>
24+
Hajime Nakamura <[email protected]>
25+
Tuyển Vũ Xuân <[email protected]>
26+
Miguel Ruivo <[email protected]>
27+
Sarthak Verma <[email protected]>
28+
Mike Diarmid <[email protected]>
29+
Invertase <[email protected]>
30+
Elliot Hesp <[email protected]>
31+
Vince Varga <[email protected]>
32+
Aawaz Gyawali <[email protected]>
33+
EUI Limited <[email protected]>
34+
Katarina Sheremet <[email protected]>
35+
Thomas Stockx <[email protected]>
36+
Sarbagya Dhaubanjar <[email protected]>
37+
Ozkan Eksi <[email protected]>
38+
Rishab Nayak <[email protected]>
39+
40+
Jonathan Younger <[email protected]>
41+
Jose Sanchez <[email protected]>
42+
Debkanchan Samadder <[email protected]>
43+
Audrius Karosevicius <[email protected]>
44+
Lukasz Piliszczuk <[email protected]>
45+
SoundReply Solutions GmbH <[email protected]>
46+
Rafal Wachol <[email protected]>
47+
Pau Picas <[email protected]>
48+
Christian Weder <[email protected]>
49+
Alexandru Tuca <[email protected]>
50+
Christian Weder <[email protected]>
51+
Rhodes Davis Jr. <[email protected]>
52+
Luigi Agosti <[email protected]>
53+
Quentin Le Guennec <[email protected]>
54+
Koushik Ravikumar <[email protected]>
55+
Nissim Dsilva <[email protected]>
56+
Giancarlo Rocha <[email protected]>
57+
Ryo Miyake <[email protected]>
58+
Théo Champion <[email protected]>
59+
Kazuki Yamaguchi <[email protected]>
60+
Eitan Schwartz <[email protected]>
61+
Chris Rutkowski <[email protected]>
62+
Juan Alvarez <[email protected]>
63+
Aleksandr Yurkovskiy <[email protected]>
64+
Anton Borries <[email protected]>
65+
66+
Rahul Raj <[email protected]>
67+
Maurits van Beusekom <[email protected]>
68+
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
## 2.0.13
2+
3+
* Extract Android implementation from `webview_flutter`.
4+
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
Copyright 2013 The Flutter Authors. All rights reserved.
2+
3+
Redistribution and use in source and binary forms, with or without modification,
4+
are permitted provided that the following conditions are met:
5+
6+
* Redistributions of source code must retain the above copyright
7+
notice, this list of conditions and the following disclaimer.
8+
* Redistributions in binary form must reproduce the above
9+
copyright notice, this list of conditions and the following
10+
disclaimer in the documentation and/or other materials provided
11+
with the distribution.
12+
* Neither the name of Google Inc. nor the names of its
13+
contributors may be used to endorse or promote products derived
14+
from this software without specific prior written permission.
15+
16+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
20+
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
23+
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26+
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# webview\_flutter\_android
2+
3+
The Android implementation of [`webview_flutter`][1].
4+
5+
## Usage
6+
7+
This package is [endorsed][2], which means you can simply use `webview_flutter`
8+
normally. This package will be automatically included in your app when you do.
9+
10+
[1]: https://pub.dev/packages/webview_flutter
11+
[2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin
12+
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
group 'io.flutter.plugins.webviewflutter'
2+
version '1.0-SNAPSHOT'
3+
4+
buildscript {
5+
repositories {
6+
google()
7+
mavenCentral()
8+
}
9+
10+
dependencies {
11+
classpath 'com.android.tools.build:gradle:3.3.0'
12+
}
13+
}
14+
15+
rootProject.allprojects {
16+
repositories {
17+
google()
18+
mavenCentral()
19+
}
20+
}
21+
22+
apply plugin: 'com.android.library'
23+
24+
android {
25+
compileSdkVersion 29
26+
27+
defaultConfig {
28+
minSdkVersion 19
29+
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
30+
}
31+
32+
lintOptions {
33+
disable 'InvalidPackage'
34+
disable 'GradleDependency'
35+
}
36+
37+
dependencies {
38+
implementation 'androidx.annotation:annotation:1.0.0'
39+
implementation 'androidx.webkit:webkit:1.0.0'
40+
testImplementation 'junit:junit:4.12'
41+
testImplementation 'org.mockito:mockito-inline:3.11.1'
42+
testImplementation 'androidx.test:core:1.3.0'
43+
}
44+
45+
46+
testOptions {
47+
unitTests.includeAndroidResources = true
48+
unitTests.returnDefaultValues = true
49+
unitTests.all {
50+
testLogging {
51+
events "passed", "skipped", "failed", "standardOut", "standardError"
52+
outputs.upToDateWhen {false}
53+
showStandardStreams = true
54+
}
55+
}
56+
}
57+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
rootProject.name = 'webview_flutter'
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
<manifest package="io.flutter.plugins.webviewflutter">
2+
</manifest>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
package io.flutter.plugins.webviewflutter;
6+
7+
import static android.hardware.display.DisplayManager.DisplayListener;
8+
9+
import android.annotation.TargetApi;
10+
import android.hardware.display.DisplayManager;
11+
import android.os.Build;
12+
import android.util.Log;
13+
import java.lang.reflect.Field;
14+
import java.util.ArrayList;
15+
16+
/**
17+
* Works around an Android WebView bug by filtering some DisplayListener invocations.
18+
*
19+
* <p>Older Android WebView versions had assumed that when {@link DisplayListener#onDisplayChanged}
20+
* is invoked, the display ID it is provided is of a valid display. However it turns out that when a
21+
* display is removed Android may call onDisplayChanged with the ID of the removed display, in this
22+
* case the Android WebView code tries to fetch and use the display with this ID and crashes with an
23+
* NPE.
24+
*
25+
* <p>This issue was fixed in the Android WebView code in
26+
* https://chromium-review.googlesource.com/517913 which is available starting WebView version
27+
* 58.0.3029.125 however older webviews in the wild still have this issue.
28+
*
29+
* <p>Since Flutter removes virtual displays whenever a platform view is resized the webview crash
30+
* is more likely to happen than other apps. And users were reporting this issue see:
31+
* https://github.com/flutter/flutter/issues/30420
32+
*
33+
* <p>This class works around the webview bug by unregistering the WebView's DisplayListener, and
34+
* instead registering its own DisplayListener which delegates the callbacks to the WebView's
35+
* listener unless it's a onDisplayChanged for an invalid display.
36+
*
37+
* <p>I did not find a clean way to get a handle of the WebView's DisplayListener so I'm using
38+
* reflection to fetch all registered listeners before and after initializing a webview. In the
39+
* first initialization of a webview within the process the difference between the lists is the
40+
* webview's display listener.
41+
*/
42+
@TargetApi(Build.VERSION_CODES.KITKAT)
43+
class DisplayListenerProxy {
44+
private static final String TAG = "DisplayListenerProxy";
45+
46+
private ArrayList<DisplayListener> listenersBeforeWebView;
47+
48+
/** Should be called prior to the webview's initialization. */
49+
void onPreWebViewInitialization(DisplayManager displayManager) {
50+
listenersBeforeWebView = yoinkDisplayListeners(displayManager);
51+
}
52+
53+
/** Should be called after the webview's initialization. */
54+
void onPostWebViewInitialization(final DisplayManager displayManager) {
55+
final ArrayList<DisplayListener> webViewListeners = yoinkDisplayListeners(displayManager);
56+
// We recorded the list of listeners prior to initializing webview, any new listeners we see
57+
// after initializing the webview are listeners added by the webview.
58+
webViewListeners.removeAll(listenersBeforeWebView);
59+
60+
if (webViewListeners.isEmpty()) {
61+
// The Android WebView registers a single display listener per process (even if there
62+
// are multiple WebView instances) so this list is expected to be non-empty only the
63+
// first time a webview is initialized.
64+
// Note that in an add2app scenario if the application had instantiated a non Flutter
65+
// WebView prior to instantiating the Flutter WebView we are not able to get a reference
66+
// to the WebView's display listener and can't work around the bug.
67+
//
68+
// This means that webview resizes in add2app Flutter apps with a non Flutter WebView
69+
// running on a system with a webview prior to 58.0.3029.125 may crash (the Android's
70+
// behavior seems to be racy so it doesn't always happen).
71+
return;
72+
}
73+
74+
for (DisplayListener webViewListener : webViewListeners) {
75+
// Note that while DisplayManager.unregisterDisplayListener throws when given an
76+
// unregistered listener, this isn't an issue as the WebView code never calls
77+
// unregisterDisplayListener.
78+
displayManager.unregisterDisplayListener(webViewListener);
79+
80+
// We never explicitly unregister this listener as the webview's listener is never
81+
// unregistered (it's released when the process is terminated).
82+
displayManager.registerDisplayListener(
83+
new DisplayListener() {
84+
@Override
85+
public void onDisplayAdded(int displayId) {
86+
for (DisplayListener webViewListener : webViewListeners) {
87+
webViewListener.onDisplayAdded(displayId);
88+
}
89+
}
90+
91+
@Override
92+
public void onDisplayRemoved(int displayId) {
93+
for (DisplayListener webViewListener : webViewListeners) {
94+
webViewListener.onDisplayRemoved(displayId);
95+
}
96+
}
97+
98+
@Override
99+
public void onDisplayChanged(int displayId) {
100+
if (displayManager.getDisplay(displayId) == null) {
101+
return;
102+
}
103+
for (DisplayListener webViewListener : webViewListeners) {
104+
webViewListener.onDisplayChanged(displayId);
105+
}
106+
}
107+
},
108+
null);
109+
}
110+
}
111+
112+
@SuppressWarnings({"unchecked", "PrivateApi"})
113+
private static ArrayList<DisplayListener> yoinkDisplayListeners(DisplayManager displayManager) {
114+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
115+
// We cannot use reflection on Android P, but it shouldn't matter as it shipped
116+
// with WebView 66.0.3359.158 and the WebView version the bug this code is working around was
117+
// fixed in 61.0.3116.0.
118+
return new ArrayList<>();
119+
}
120+
try {
121+
Field displayManagerGlobalField = DisplayManager.class.getDeclaredField("mGlobal");
122+
displayManagerGlobalField.setAccessible(true);
123+
Object displayManagerGlobal = displayManagerGlobalField.get(displayManager);
124+
Field displayListenersField =
125+
displayManagerGlobal.getClass().getDeclaredField("mDisplayListeners");
126+
displayListenersField.setAccessible(true);
127+
ArrayList<Object> delegates =
128+
(ArrayList<Object>) displayListenersField.get(displayManagerGlobal);
129+
130+
Field listenerField = null;
131+
ArrayList<DisplayManager.DisplayListener> listeners = new ArrayList<>();
132+
for (Object delegate : delegates) {
133+
if (listenerField == null) {
134+
listenerField = delegate.getClass().getField("mListener");
135+
listenerField.setAccessible(true);
136+
}
137+
DisplayManager.DisplayListener listener =
138+
(DisplayManager.DisplayListener) listenerField.get(delegate);
139+
listeners.add(listener);
140+
}
141+
return listeners;
142+
} catch (NoSuchFieldException | IllegalAccessException e) {
143+
Log.w(TAG, "Could not extract WebView's display listeners. " + e);
144+
return new ArrayList<>();
145+
}
146+
}
147+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
package io.flutter.plugins.webviewflutter;
6+
7+
import android.os.Build;
8+
import android.os.Build.VERSION_CODES;
9+
import android.webkit.CookieManager;
10+
import android.webkit.ValueCallback;
11+
import io.flutter.plugin.common.BinaryMessenger;
12+
import io.flutter.plugin.common.MethodCall;
13+
import io.flutter.plugin.common.MethodChannel;
14+
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
15+
import io.flutter.plugin.common.MethodChannel.Result;
16+
17+
class FlutterCookieManager implements MethodCallHandler {
18+
private final MethodChannel methodChannel;
19+
20+
FlutterCookieManager(BinaryMessenger messenger) {
21+
methodChannel = new MethodChannel(messenger, "plugins.flutter.io/cookie_manager");
22+
methodChannel.setMethodCallHandler(this);
23+
}
24+
25+
@Override
26+
public void onMethodCall(MethodCall methodCall, Result result) {
27+
switch (methodCall.method) {
28+
case "clearCookies":
29+
clearCookies(result);
30+
break;
31+
default:
32+
result.notImplemented();
33+
}
34+
}
35+
36+
void dispose() {
37+
methodChannel.setMethodCallHandler(null);
38+
}
39+
40+
private static void clearCookies(final Result result) {
41+
CookieManager cookieManager = CookieManager.getInstance();
42+
final boolean hasCookies = cookieManager.hasCookies();
43+
if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
44+
cookieManager.removeAllCookies(
45+
new ValueCallback<Boolean>() {
46+
@Override
47+
public void onReceiveValue(Boolean value) {
48+
result.success(hasCookies);
49+
}
50+
});
51+
} else {
52+
cookieManager.removeAllCookie();
53+
result.success(hasCookies);
54+
}
55+
}
56+
}

0 commit comments

Comments
 (0)