1 // Copyright 2012 The Chromium 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 org.chromium.android_webview; 6 7 import android.content.pm.ActivityInfo; 8 import android.graphics.Bitmap; 9 import android.graphics.Picture; 10 import android.net.http.SslError; 11 import android.os.Looper; 12 import android.os.Message; 13 import android.util.ArrayMap; 14 import android.view.KeyEvent; 15 import android.view.View; 16 import android.webkit.ConsoleMessage; 17 import android.webkit.GeolocationPermissions; 18 import android.webkit.ValueCallback; 19 import android.webkit.WebChromeClient; 20 21 import org.chromium.android_webview.permission.AwPermissionRequest; 22 import org.chromium.content.browser.ContentViewCore; 23 import org.chromium.content.browser.WebContentsObserverAndroid; 24 import org.chromium.net.NetError; 25 26 import java.security.Principal; 27 28 /** 29 * Base-class that an AwContents embedder derives from to receive callbacks. 30 * This extends ContentViewClient, as in many cases we want to pass-thru ContentViewCore 31 * callbacks right to our embedder, and this setup facilities that. 32 * For any other callbacks we need to make transformations of (e.g. adapt parameters 33 * or perform filtering) we can provide final overrides for methods here, and then introduce 34 * new abstract methods that the our own client must implement. 35 * i.e.: all methods in this class should either be final, or abstract. 36 */ 37 public abstract class AwContentsClient { 38 39 private final AwContentsClientCallbackHelper mCallbackHelper; 40 41 private AwWebContentsObserver mWebContentsObserver; 42 43 // Last background color reported from the renderer. Holds the sentinal value INVALID_COLOR 44 // if not valid. 45 private int mCachedRendererBackgroundColor = INVALID_COLOR; 46 47 private static final int INVALID_COLOR = 0; 48 AwContentsClient()49 public AwContentsClient() { 50 this(Looper.myLooper()); 51 } 52 53 // Alllow injection of the callback thread, for testing. AwContentsClient(Looper looper)54 public AwContentsClient(Looper looper) { 55 mCallbackHelper = new AwContentsClientCallbackHelper(looper, this); 56 } 57 58 class AwWebContentsObserver extends WebContentsObserverAndroid { AwWebContentsObserver(ContentViewCore contentViewCore)59 public AwWebContentsObserver(ContentViewCore contentViewCore) { 60 super(contentViewCore); 61 } 62 63 @Override didFinishLoad(long frameId, String validatedUrl, boolean isMainFrame)64 public void didFinishLoad(long frameId, String validatedUrl, boolean isMainFrame) { 65 String unreachableWebDataUrl = AwContentsStatics.getUnreachableWebDataUrl(); 66 boolean isErrorUrl = 67 unreachableWebDataUrl != null && unreachableWebDataUrl.equals(validatedUrl); 68 if (isMainFrame && !isErrorUrl) { 69 AwContentsClient.this.onPageFinished(validatedUrl); 70 } 71 } 72 73 @Override didFailLoad(boolean isProvisionalLoad, boolean isMainFrame, int errorCode, String description, String failingUrl)74 public void didFailLoad(boolean isProvisionalLoad, 75 boolean isMainFrame, int errorCode, String description, String failingUrl) { 76 if (isMainFrame) { 77 if (errorCode != NetError.ERR_ABORTED) { 78 // This error code is generated for the following reasons: 79 // - WebView.stopLoading is called, 80 // - the navigation is intercepted by the embedder via shouldOverrideNavigation. 81 // 82 // The Android WebView does not notify the embedder of these situations using 83 // this error code with the WebViewClient.onReceivedError callback. 84 AwContentsClient.this.onReceivedError( 85 ErrorCodeConversionHelper.convertErrorCode(errorCode), description, 86 failingUrl); 87 } 88 // Need to call onPageFinished after onReceivedError (if there is an error) for 89 // backwards compatibility with the classic webview. 90 AwContentsClient.this.onPageFinished(failingUrl); 91 } 92 } 93 94 @Override didNavigateMainFrame(String url, String baseUrl, boolean isNavigationToDifferentPage, boolean isFragmentNavigation)95 public void didNavigateMainFrame(String url, String baseUrl, 96 boolean isNavigationToDifferentPage, boolean isFragmentNavigation) { 97 // This is here to emulate the Classic WebView firing onPageFinished for main frame 98 // navigations where only the hash fragment changes. 99 if (isFragmentNavigation) { 100 AwContentsClient.this.onPageFinished(url); 101 } 102 } 103 104 @Override didNavigateAnyFrame(String url, String baseUrl, boolean isReload)105 public void didNavigateAnyFrame(String url, String baseUrl, boolean isReload) { 106 AwContentsClient.this.doUpdateVisitedHistory(url, isReload); 107 } 108 109 } 110 installWebContentsObserver(ContentViewCore contentViewCore)111 final void installWebContentsObserver(ContentViewCore contentViewCore) { 112 if (mWebContentsObserver != null) { 113 mWebContentsObserver.detachFromWebContents(); 114 } 115 mWebContentsObserver = new AwWebContentsObserver(contentViewCore); 116 } 117 getCallbackHelper()118 final AwContentsClientCallbackHelper getCallbackHelper() { 119 return mCallbackHelper; 120 } 121 getCachedRendererBackgroundColor()122 final int getCachedRendererBackgroundColor() { 123 assert isCachedRendererBackgroundColorValid(); 124 return mCachedRendererBackgroundColor; 125 } 126 isCachedRendererBackgroundColorValid()127 final boolean isCachedRendererBackgroundColorValid() { 128 return mCachedRendererBackgroundColor != INVALID_COLOR; 129 } 130 onBackgroundColorChanged(int color)131 final void onBackgroundColorChanged(int color) { 132 // Avoid storing the sentinal INVALID_COLOR (note that both 0 and 1 are both 133 // fully transparent so this transpose makes no visible difference). 134 mCachedRendererBackgroundColor = color == INVALID_COLOR ? 1 : color; 135 } 136 137 //-------------------------------------------------------------------------------------------- 138 // WebView specific methods that map directly to WebViewClient / WebChromeClient 139 //-------------------------------------------------------------------------------------------- 140 141 /** 142 * Parameters for the {@link AwContentsClient#showFileChooser} method. 143 */ 144 public static class FileChooserParams { 145 public int mode; 146 public String acceptTypes; 147 public String title; 148 public String defaultFilename; 149 public boolean capture; 150 } 151 152 /** 153 * Parameters for the {@link AwContentsClient#shouldInterceptRequest} method. 154 */ 155 public static class ShouldInterceptRequestParams { 156 // Url of the request. 157 public String url; 158 // Is this for the main frame or a child iframe? 159 public boolean isMainFrame; 160 // Was a gesture associated with the request? Don't trust can easily be spoofed. 161 public boolean hasUserGesture; 162 // Method used (GET/POST/OPTIONS) 163 public String method; 164 // Headers that would have been sent to server. 165 public ArrayMap<String, String> requestHeaders; 166 } 167 getVisitedHistory(ValueCallback<String[]> callback)168 public abstract void getVisitedHistory(ValueCallback<String[]> callback); 169 doUpdateVisitedHistory(String url, boolean isReload)170 public abstract void doUpdateVisitedHistory(String url, boolean isReload); 171 onProgressChanged(int progress)172 public abstract void onProgressChanged(int progress); 173 shouldInterceptRequest( ShouldInterceptRequestParams params)174 public abstract AwWebResourceResponse shouldInterceptRequest( 175 ShouldInterceptRequestParams params); 176 shouldOverrideKeyEvent(KeyEvent event)177 public abstract boolean shouldOverrideKeyEvent(KeyEvent event); 178 shouldOverrideUrlLoading(String url)179 public abstract boolean shouldOverrideUrlLoading(String url); 180 onLoadResource(String url)181 public abstract void onLoadResource(String url); 182 onUnhandledKeyEvent(KeyEvent event)183 public abstract void onUnhandledKeyEvent(KeyEvent event); 184 onConsoleMessage(ConsoleMessage consoleMessage)185 public abstract boolean onConsoleMessage(ConsoleMessage consoleMessage); 186 onReceivedHttpAuthRequest(AwHttpAuthHandler handler, String host, String realm)187 public abstract void onReceivedHttpAuthRequest(AwHttpAuthHandler handler, 188 String host, String realm); 189 onReceivedSslError(ValueCallback<Boolean> callback, SslError error)190 public abstract void onReceivedSslError(ValueCallback<Boolean> callback, SslError error); 191 192 // TODO(sgurun): Make abstract once this has rolled in downstream. onReceivedClientCertRequest( final AwContentsClientBridge.ClientCertificateRequestCallback callback, final String[] keyTypes, final Principal[] principals, final String host, final int port)193 public void onReceivedClientCertRequest( 194 final AwContentsClientBridge.ClientCertificateRequestCallback callback, 195 final String[] keyTypes, final Principal[] principals, final String host, 196 final int port) { } 197 onReceivedLoginRequest(String realm, String account, String args)198 public abstract void onReceivedLoginRequest(String realm, String account, String args); 199 onFormResubmission(Message dontResend, Message resend)200 public abstract void onFormResubmission(Message dontResend, Message resend); 201 onDownloadStart(String url, String userAgent, String contentDisposition, String mimeType, long contentLength)202 public abstract void onDownloadStart(String url, String userAgent, String contentDisposition, 203 String mimeType, long contentLength); 204 205 // TODO(joth): Make abstract once this has rolled in downstream. showFileChooser(ValueCallback<String[]> uploadFilePathsCallback, FileChooserParams fileChooserParams)206 public /*abstract*/ void showFileChooser(ValueCallback<String[]> uploadFilePathsCallback, 207 FileChooserParams fileChooserParams) { } 208 onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback)209 public abstract void onGeolocationPermissionsShowPrompt(String origin, 210 GeolocationPermissions.Callback callback); 211 onGeolocationPermissionsHidePrompt()212 public abstract void onGeolocationPermissionsHidePrompt(); 213 214 // TODO(michaelbai): Change the abstract once merged onPermissionRequest(AwPermissionRequest awPermissionRequest)215 public /*abstract*/ void onPermissionRequest(AwPermissionRequest awPermissionRequest) {} 216 217 // TODO(michaelbai): Change the abstract once merged onPermissionRequestCanceled( AwPermissionRequest awPermissionRequest)218 public /*abstract*/ void onPermissionRequestCanceled( 219 AwPermissionRequest awPermissionRequest) {} 220 onScaleChangedScaled(float oldScale, float newScale)221 public abstract void onScaleChangedScaled(float oldScale, float newScale); 222 handleJsAlert(String url, String message, JsResultReceiver receiver)223 protected abstract void handleJsAlert(String url, String message, JsResultReceiver receiver); 224 handleJsBeforeUnload(String url, String message, JsResultReceiver receiver)225 protected abstract void handleJsBeforeUnload(String url, String message, 226 JsResultReceiver receiver); 227 handleJsConfirm(String url, String message, JsResultReceiver receiver)228 protected abstract void handleJsConfirm(String url, String message, JsResultReceiver receiver); 229 handleJsPrompt(String url, String message, String defaultValue, JsPromptResultReceiver receiver)230 protected abstract void handleJsPrompt(String url, String message, String defaultValue, 231 JsPromptResultReceiver receiver); 232 onCreateWindow(boolean isDialog, boolean isUserGesture)233 protected abstract boolean onCreateWindow(boolean isDialog, boolean isUserGesture); 234 onCloseWindow()235 protected abstract void onCloseWindow(); 236 onReceivedTouchIconUrl(String url, boolean precomposed)237 public abstract void onReceivedTouchIconUrl(String url, boolean precomposed); 238 onReceivedIcon(Bitmap bitmap)239 public abstract void onReceivedIcon(Bitmap bitmap); 240 onReceivedTitle(String title)241 public abstract void onReceivedTitle(String title); 242 onRequestFocus()243 protected abstract void onRequestFocus(); 244 getVideoLoadingProgressView()245 protected abstract View getVideoLoadingProgressView(); 246 onPageStarted(String url)247 public abstract void onPageStarted(String url); 248 onPageFinished(String url)249 public abstract void onPageFinished(String url); 250 onReceivedError(int errorCode, String description, String failingUrl)251 public abstract void onReceivedError(int errorCode, String description, String failingUrl); 252 253 // TODO (michaelbai): Remove this method once the same method remove from 254 // WebViewContentsClientAdapter. onShowCustomView(View view, int requestedOrientation, WebChromeClient.CustomViewCallback callback)255 public void onShowCustomView(View view, 256 int requestedOrientation, WebChromeClient.CustomViewCallback callback) { 257 } 258 259 // TODO (michaelbai): This method should be abstract, having empty body here 260 // makes the merge to the Android easy. onShowCustomView(View view, WebChromeClient.CustomViewCallback callback)261 public void onShowCustomView(View view, WebChromeClient.CustomViewCallback callback) { 262 onShowCustomView(view, ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED, callback); 263 } 264 onHideCustomView()265 public abstract void onHideCustomView(); 266 getDefaultVideoPoster()267 public abstract Bitmap getDefaultVideoPoster(); 268 269 //-------------------------------------------------------------------------------------------- 270 // Other WebView-specific methods 271 //-------------------------------------------------------------------------------------------- 272 // onFindResultReceived(int activeMatchOrdinal, int numberOfMatches, boolean isDoneCounting)273 public abstract void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches, 274 boolean isDoneCounting); 275 276 /** 277 * Called whenever there is a new content picture available. 278 * @param picture New picture. 279 */ onNewPicture(Picture picture)280 public abstract void onNewPicture(Picture picture); 281 282 } 283