Commit 781fc47c authored by igsolla's avatar igsolla Committed by Commit bot

[aw] Ensure that WebView APIs do not crash if called after destruction.

The documentation for WebView#destroy() reads "This method should be called
after this WebView has been removed from the view system. No other methods
may be called on this WebView after destroy". However, some apps do not
respect that restriction so this change ensures that we fail gracefully
in that case and do not crash.

BUG=415666

Review URL: https://codereview.chromium.org/581423002

Cr-Commit-Position: refs/heads/master@{#296029}
parent 04b8324d
......@@ -60,7 +60,10 @@ public class AwContentViewClient extends ContentViewClient {
if (mAwContents.isFullScreen()) {
return;
}
viewGroup.addView(mAwContents.enterFullScreen());
View fullscreenView = mAwContents.enterFullScreen();
if (fullscreenView != null) {
viewGroup.addView(fullscreenView);
}
}
});
mAwContentsClient.onShowCustomView(viewGroup, cb);
......
......@@ -251,6 +251,10 @@ public class AwContents {
// when in this state.
private boolean mTemporarilyDetached;
// True when this AwContents has been destroyed.
// Do not use directly, call isDestroyed() instead.
private boolean mIsDestroyed = false;
private static final class DestroyRunnable implements Runnable {
private final long mNativeAwContents;
private DestroyRunnable(long nativeAwContents) {
......@@ -453,8 +457,7 @@ public class AwContents {
@Override
public void scrollNativeTo(int x, int y) {
if (mNativeAwContents == 0) return;
nativeScrollTo(mNativeAwContents, x, y);
if (!isDestroyed()) nativeScrollTo(mNativeAwContents, x, y);
}
@Override
......@@ -511,7 +514,7 @@ public class AwContents {
private class AwComponentCallbacks implements ComponentCallbacks2 {
@Override
public void onTrimMemory(final int level) {
if (mNativeAwContents == 0) return;
if (isDestroyed()) return;
boolean visibleRectEmpty = getGlobalVisibleRect().isEmpty();
final boolean visible = mIsViewVisible && mIsWindowVisible && !visibleRectEmpty;
nativeTrimMemory(mNativeAwContents, level, visible);
......@@ -591,6 +594,7 @@ public class AwContents {
@Override
public void onGestureZoomSupportChanged(
boolean supportsDoubleTapZoom, boolean supportsMultiTouchZoom) {
if (isDestroyed()) return;
mContentViewCore.updateDoubleTapSupport(supportsDoubleTapZoom);
mContentViewCore.updateMultiTouchZoomSupport(supportsMultiTouchZoom);
}
......@@ -636,10 +640,12 @@ public class AwContents {
/**
* Transitions this {@link AwContents} to fullscreen mode and returns the
* {@link View} where the contents will be drawn while in fullscreen.
* {@link View} where the contents will be drawn while in fullscreen, or null
* if this AwContents has already been destroyed.
*/
View enterFullScreen() {
assert !isFullScreen();
if (isDestroyed()) return null;
// Detach to tear down the GL functor if this is still associated with the old
// container view. It will be recreated during the next call to onDraw attached to
......@@ -666,7 +672,7 @@ public class AwContents {
* in the WebView.
*/
void exitFullScreen() {
if (!isFullScreen())
if (!isFullScreen() || isDestroyed())
// exitFullScreen() can be called without a prior call to enterFullScreen() if a
// "misbehave" app overrides onShowCustomView but does not add the custom view to
// the window. Exiting avoids a crash.
......@@ -747,7 +753,7 @@ public class AwContents {
*/
private void setNewAwContents(long newAwContentsPtr) {
if (mNativeAwContents != 0) {
destroy();
destroyNatives();
mContentViewCore = null;
mWebContents = null;
mNavigationController = null;
......@@ -864,9 +870,17 @@ public class AwContents {
}
/**
* Deletes the native counterpart of this object.
* Destroys this object and deletes its native counterpart.
*/
public void destroy() {
mIsDestroyed = true;
destroyNatives();
}
/**
* Deletes the native counterpart of this object.
*/
private void destroyNatives() {
if (mCleanupReference != null) {
assert mNativeAwContents != 0;
// If we are attached, we have to call native detach to clean up
......@@ -875,21 +889,37 @@ public class AwContents {
nativeOnDetachedFromWindow(mNativeAwContents);
}
// We explicitly do not null out the mContentViewCore reference here
// because ContentViewCore already has code to deal with the case
// methods are called on it after it's been destroyed, and other
// code relies on AwContents.mContentViewCore to be non-null.
mContentViewCore.destroy();
mContentViewCore = null;
mNativeAwContents = 0;
mWebContents = null;
mNavigationController = null;
mCleanupReference.cleanupNow();
mCleanupReference = null;
}
assert !mContentViewCore.isAlive();
assert mContentViewCore == null;
assert mWebContents == null;
assert mNavigationController == null;
assert mNativeAwContents == 0;
}
private boolean isDestroyed() {
if (mIsDestroyed) {
assert mContentViewCore == null;
assert mWebContents == null;
assert mNavigationController == null;
assert mNativeAwContents == 0;
} else {
assert mContentViewCore != null;
assert mWebContents != null;
assert mNavigationController != null;
assert mNativeAwContents != 0;
}
return mIsDestroyed;
}
@VisibleForTesting
public ContentViewCore getContentViewCore() {
return mContentViewCore;
......@@ -911,10 +941,7 @@ public class AwContents {
}
public AwPdfExporter getPdfExporter() {
// mNativeAwContents can be null, due to destroy().
if (mNativeAwContents == 0) {
return null;
}
if (isDestroyed()) return null;
if (mAwPdfExporter == null) {
mAwPdfExporter = new AwPdfExporter(mContainerView);
nativeCreatePdfExporter(mNativeAwContents, mAwPdfExporter);
......@@ -945,7 +972,7 @@ public class AwContents {
* to ensure backwards compatible behavior.
*/
public void disableJavascriptInterfacesInspection() {
mContentViewCore.setAllowJavascriptInterfacesInspection(false);
if (!isDestroyed()) mContentViewCore.setAllowJavascriptInterfacesInspection(false);
}
/**
......@@ -960,7 +987,7 @@ public class AwContents {
public long getAwDrawGLViewContext() {
// Only called during early construction, so client should not have had a chance to
// call destroy yet.
assert mNativeAwContents != 0;
assert !isDestroyed();
// Using the native pointer as the returned viewContext. This is matched by the
// reinterpret_cast back to BrowserViewRenderer pointer in the native DrawGLFunction.
......@@ -999,15 +1026,14 @@ public class AwContents {
}
public Picture capturePicture() {
if (mNativeAwContents == 0) return null;
if (isDestroyed()) return null;
return new AwPicture(nativeCapturePicture(mNativeAwContents,
mScrollOffsetManager.computeHorizontalScrollRange(),
mScrollOffsetManager.computeVerticalScrollRange()));
}
public void clearView() {
if (mNativeAwContents == 0) return;
nativeClearView(mNativeAwContents);
if (!isDestroyed()) nativeClearView(mNativeAwContents);
}
/**
......@@ -1016,7 +1042,7 @@ public class AwContents {
* @param invalidationOnly Flag to call back only on invalidation without providing a picture.
*/
public void enableOnNewPicture(boolean enabled, boolean invalidationOnly) {
if (mNativeAwContents == 0) return;
if (isDestroyed()) return;
if (invalidationOnly) {
mPictureListenerContentProvider = null;
} else if (enabled && mPictureListenerContentProvider == null) {
......@@ -1031,18 +1057,15 @@ public class AwContents {
}
public void findAllAsync(String searchString) {
if (mNativeAwContents == 0) return;
nativeFindAllAsync(mNativeAwContents, searchString);
if (!isDestroyed()) nativeFindAllAsync(mNativeAwContents, searchString);
}
public void findNext(boolean forward) {
if (mNativeAwContents == 0) return;
nativeFindNext(mNativeAwContents, forward);
if (!isDestroyed()) nativeFindNext(mNativeAwContents, forward);
}
public void clearMatches() {
if (mNativeAwContents == 0) return;
nativeClearMatches(mNativeAwContents);
if (!isDestroyed()) nativeClearMatches(mNativeAwContents);
}
/**
......@@ -1064,8 +1087,7 @@ public class AwContents {
ThreadUtils.runOnUiThread(new Runnable() {
@Override
public void run() {
if (mNativeAwContents == 0) return;
nativeAddVisitedLinks(mNativeAwContents, value);
if (!isDestroyed()) nativeAddVisitedLinks(mNativeAwContents, value);
}
});
}
......@@ -1081,6 +1103,8 @@ public class AwContents {
* @param params Parameters for this load.
*/
public void loadUrl(LoadUrlParams params) {
if (isDestroyed()) return;
if (params.getLoadUrlType() == LoadUrlParams.LOAD_TYPE_DATA &&
!params.isBaseUrlDataScheme()) {
// This allows data URLs with a non-data base URL access to file:///android_asset/ and
......@@ -1119,10 +1143,8 @@ public class AwContents {
}
}
if (mNativeAwContents != 0) {
nativeSetExtraHeadersForUrl(
mNativeAwContents, params.getUrl(), params.getExtraHttpRequestHeadersString());
}
params.setExtraHeaders(new HashMap<String, String>());
mNavigationController.loadUrl(params);
......@@ -1149,6 +1171,7 @@ public class AwContents {
* @return The URL of the current page or null if it's empty.
*/
public String getUrl() {
if (isDestroyed()) return null;
String url = mWebContents.getUrl();
if (url == null || url.trim().isEmpty()) return null;
return url;
......@@ -1160,7 +1183,7 @@ public class AwContents {
public void setBackgroundColor(int color) {
mBaseBackgroundColor = color;
if (mNativeAwContents != 0) nativeSetBackgroundColor(mNativeAwContents, color);
if (!isDestroyed()) nativeSetBackgroundColor(mNativeAwContents, color);
}
/**
......@@ -1174,7 +1197,7 @@ public class AwContents {
// Do not ask the ContentViewCore for the background color, as it will always
// report white prior to initial navigation or post destruction, whereas we want
// to use the client supplied base value in those cases.
if (mNativeAwContents == 0 || !mContentsClient.isCachedRendererBackgroundColorValid()) {
if (isDestroyed() || !mContentsClient.isCachedRendererBackgroundColorValid()) {
return mBaseBackgroundColor;
}
return mContentsClient.getCachedRendererBackgroundColor();
......@@ -1192,7 +1215,7 @@ public class AwContents {
* @see ContentViewCore#getContentSettings()
*/
public ContentSettings getContentSettings() {
return mContentViewCore.getContentSettings();
return isDestroyed() ? null : mContentViewCore.getContentSettings();
}
/**
......@@ -1340,77 +1363,77 @@ public class AwContents {
* @see android.webkit.WebView#stopLoading()
*/
public void stopLoading() {
mWebContents.stop();
if (!isDestroyed()) mWebContents.stop();
}
/**
* @see android.webkit.WebView#reload()
*/
public void reload() {
mNavigationController.reload(true);
if (!isDestroyed()) mNavigationController.reload(true);
}
/**
* @see android.webkit.WebView#canGoBack()
*/
public boolean canGoBack() {
return mNavigationController.canGoBack();
return isDestroyed() ? false : mNavigationController.canGoBack();
}
/**
* @see android.webkit.WebView#goBack()
*/
public void goBack() {
mNavigationController.goBack();
if (!isDestroyed()) mNavigationController.goBack();
}
/**
* @see android.webkit.WebView#canGoForward()
*/
public boolean canGoForward() {
return mNavigationController.canGoForward();
return isDestroyed() ? false : mNavigationController.canGoForward();
}
/**
* @see android.webkit.WebView#goForward()
*/
public void goForward() {
mNavigationController.goForward();
if (!isDestroyed()) mNavigationController.goForward();
}
/**
* @see android.webkit.WebView#canGoBackOrForward(int)
*/
public boolean canGoBackOrForward(int steps) {
return mNavigationController.canGoToOffset(steps);
return isDestroyed() ? false : mNavigationController.canGoToOffset(steps);
}
/**
* @see android.webkit.WebView#goBackOrForward(int)
*/
public void goBackOrForward(int steps) {
mNavigationController.goToOffset(steps);
if (!isDestroyed()) mNavigationController.goToOffset(steps);
}
/**
* @see android.webkit.WebView#pauseTimers()
*/
public void pauseTimers() {
ContentViewStatics.setWebKitSharedTimersSuspended(true);
if (!isDestroyed()) ContentViewStatics.setWebKitSharedTimersSuspended(true);
}
/**
* @see android.webkit.WebView#resumeTimers()
*/
public void resumeTimers() {
ContentViewStatics.setWebKitSharedTimersSuspended(false);
if (!isDestroyed()) ContentViewStatics.setWebKitSharedTimersSuspended(false);
}
/**
* @see android.webkit.WebView#onPause()
*/
public void onPause() {
if (mIsPaused || mNativeAwContents == 0) return;
if (mIsPaused || isDestroyed()) return;
mIsPaused = true;
nativeSetIsPaused(mNativeAwContents, mIsPaused);
}
......@@ -1419,7 +1442,7 @@ public class AwContents {
* @see android.webkit.WebView#onResume()
*/
public void onResume() {
if (!mIsPaused || mNativeAwContents == 0) return;
if (!mIsPaused || isDestroyed()) return;
mIsPaused = false;
nativeSetIsPaused(mNativeAwContents, mIsPaused);
}
......@@ -1459,13 +1482,11 @@ public class AwContents {
* @param includeDiskFiles if false, only the RAM cache is cleared
*/
public void clearCache(boolean includeDiskFiles) {
if (mNativeAwContents == 0) return;
nativeClearCache(mNativeAwContents, includeDiskFiles);
if (!isDestroyed()) nativeClearCache(mNativeAwContents, includeDiskFiles);
}
public void documentHasImages(Message message) {
if (mNativeAwContents == 0) return;
nativeDocumentHasImages(mNativeAwContents, message);
if (!isDestroyed()) nativeDocumentHasImages(mNativeAwContents, message);
}
public void saveWebArchive(
......@@ -1490,6 +1511,7 @@ public class AwContents {
}
public String getOriginalUrl() {
if (isDestroyed()) return null;
NavigationHistory history = mNavigationController.getNavigationHistory();
int currentIndex = history.getCurrentEntryIndex();
if (currentIndex >= 0 && currentIndex < history.getEntryCount()) {
......@@ -1502,21 +1524,21 @@ public class AwContents {
* @see ContentViewCore#getNavigationHistory()
*/
public NavigationHistory getNavigationHistory() {
return mNavigationController.getNavigationHistory();
return isDestroyed() ? null : mNavigationController.getNavigationHistory();
}
/**
* @see android.webkit.WebView#getTitle()
*/
public String getTitle() {
return mWebContents.getTitle();
return isDestroyed() ? null : mWebContents.getTitle();
}
/**
* @see android.webkit.WebView#clearHistory()
*/
public void clearHistory() {
mNavigationController.clearHistory();
if (!isDestroyed()) mNavigationController.clearHistory();
}
public String[] getHttpAuthUsernamePassword(String host, String realm) {
......@@ -1534,15 +1556,15 @@ public class AwContents {
* @see android.webkit.WebView#getCertificate()
*/
public SslCertificate getCertificate() {
if (mNativeAwContents == 0) return null;
return SslUtil.getCertificateFromDerBytes(nativeGetCertificate(mNativeAwContents));
return isDestroyed() ? null
: SslUtil.getCertificateFromDerBytes(nativeGetCertificate(mNativeAwContents));
}
/**
* @see android.webkit.WebView#clearSslPreferences()
*/
public void clearSslPreferences() {
mNavigationController.clearSslPreferences();
if (!isDestroyed()) mNavigationController.clearSslPreferences();
}
// TODO(sgurun) remove after this rolls in. To keep internal tree happy.
......@@ -1555,7 +1577,7 @@ public class AwContents {
* garbage allocation on repeated calls.
*/
public HitTestData getLastHitTestResult() {
if (mNativeAwContents == 0) return null;
if (isDestroyed()) return null;
nativeUpdateLastHitTestData(mNativeAwContents);
return mPossiblyStaleHitTestData;
}
......@@ -1564,7 +1586,7 @@ public class AwContents {
* @see android.webkit.WebView#requestFocusNodeHref()
*/
public void requestFocusNodeHref(Message msg) {
if (msg == null || mNativeAwContents == 0) return;
if (msg == null || isDestroyed()) return;
nativeUpdateLastHitTestData(mNativeAwContents);
Bundle data = msg.getData();
......@@ -1583,7 +1605,7 @@ public class AwContents {
* @see android.webkit.WebView#requestImageRef()
*/
public void requestImageRef(Message msg) {
if (msg == null || mNativeAwContents == 0) return;
if (msg == null || isDestroyed()) return;
nativeUpdateLastHitTestData(mNativeAwContents);
Bundle data = msg.getData();
......@@ -1678,6 +1700,7 @@ public class AwContents {
// This method uses the term 'zoom' for legacy reasons, but relates
// to what chrome calls the 'page scale factor'.
public boolean zoomBy(float delta) {
if (isDestroyed()) return false;
if (delta < 0.01f || delta > 100.0f) {
throw new IllegalStateException("zoom delta value outside [0.01, 100] range.");
}
......@@ -1688,14 +1711,14 @@ public class AwContents {
* @see android.webkit.WebView#invokeZoomPicker()
*/
public void invokeZoomPicker() {
mContentViewCore.invokeZoomPicker();
if (!isDestroyed()) mContentViewCore.invokeZoomPicker();
}
/**
* @see android.webkit.WebView#preauthorizePermission(Uri, long)
*/
public void preauthorizePermission(Uri origin, long resources) {
if (mNativeAwContents == 0) return;
if (isDestroyed()) return;
nativePreauthorizePermission(mNativeAwContents, origin.toString(), resources);
}
......@@ -1703,6 +1726,7 @@ public class AwContents {
* @see ContentViewCore.evaluateJavaScript(String, JavaScriptCallback)
*/
public void evaluateJavaScript(String script, final ValueCallback<String> callback) {
if (isDestroyed()) return;
JavaScriptCallback jsCallback = null;
if (callback != null) {
jsCallback = new JavaScriptCallback() {
......@@ -1718,7 +1742,7 @@ public class AwContents {
// TODO(boliu): Remove this once Android side no longer calls this.
public void evaluateJavaScriptEvenIfNotYetNavigated(String script) {
mWebContents.evaluateJavaScript(script, null);
if (!isDestroyed()) mWebContents.evaluateJavaScript(script, null);
}
//--------------------------------------------------------------------------------------------
......@@ -1743,7 +1767,7 @@ public class AwContents {
* @see android.view.View#onGenericMotionEvent()
*/
public boolean onGenericMotionEvent(MotionEvent event) {
return mContentViewCore.onGenericMotionEvent(event);
return isDestroyed() ? false : mContentViewCore.onGenericMotionEvent(event);
}
/**
......@@ -1822,14 +1846,12 @@ public class AwContents {
private void setViewVisibilityInternal(boolean visible) {
mIsViewVisible = visible;
if (mNativeAwContents == 0) return;
nativeSetViewVisibility(mNativeAwContents, mIsViewVisible);
if (!isDestroyed()) nativeSetViewVisibility(mNativeAwContents, mIsViewVisible);
}
private void setWindowVisibilityInternal(boolean visible) {
mIsWindowVisible = visible;
if (mNativeAwContents == 0) return;
nativeSetWindowVisibility(mNativeAwContents, mIsWindowVisible);
if (!isDestroyed()) nativeSetWindowVisibility(mNativeAwContents, mIsWindowVisible);
}
/**
......@@ -1842,7 +1864,7 @@ public class AwContents {
* @return False if saving state failed.
*/
public boolean saveState(Bundle outState) {
if (mNativeAwContents == 0 || outState == null) return false;
if (isDestroyed() || outState == null) return false;
byte[] state = nativeGetOpaqueState(mNativeAwContents);
if (state == null) return false;
......@@ -1857,7 +1879,7 @@ public class AwContents {
* @return False if restoring state failed.
*/
public boolean restoreState(Bundle inState) {
if (mNativeAwContents == 0 || inState == null) return false;
if (isDestroyed() || inState == null) return false;
byte[] state = inState.getByteArray(SAVE_RESTORE_STATE_KEY);
if (state == null) return false;
......@@ -1878,6 +1900,7 @@ public class AwContents {
*/
public void addPossiblyUnsafeJavascriptInterface(Object object, String name,
Class<? extends Annotation> requiredAnnotation) {
if (isDestroyed()) return;
mContentViewCore.addPossiblyUnsafeJavascriptInterface(object, name, requiredAnnotation);
}
......@@ -1885,7 +1908,7 @@ public class AwContents {
* @see android.webkit.WebView#removeJavascriptInterface(String)
*/
public void removeJavascriptInterface(String interfaceName) {
mContentViewCore.removeJavascriptInterface(interfaceName);
if (!isDestroyed()) mContentViewCore.removeJavascriptInterface(interfaceName);
}
/**
......@@ -1895,32 +1918,33 @@ public class AwContents {
* @return The AccessibilityNodeProvider, if available, or null otherwise.
*/
public AccessibilityNodeProvider getAccessibilityNodeProvider() {
return mContentViewCore.getAccessibilityNodeProvider();
return isDestroyed() ? null : mContentViewCore.getAccessibilityNodeProvider();
}
/**
* @see android.webkit.WebView#onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo)
*/
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
mContentViewCore.onInitializeAccessibilityNodeInfo(info);
if (!isDestroyed()) mContentViewCore.onInitializeAccessibilityNodeInfo(info);
}
/**
* @see android.webkit.WebView#onInitializeAccessibilityEvent(AccessibilityEvent)
*/
public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
mContentViewCore.onInitializeAccessibilityEvent(event);
if (!isDestroyed()) mContentViewCore.onInitializeAccessibilityEvent(event);
}
public boolean supportsAccessibilityAction(int action) {
return mContentViewCore.supportsAccessibilityAction(action);
return isDestroyed() ? false : mContentViewCore.supportsAccessibilityAction(action);
}
/**
* @see android.webkit.WebView#performAccessibilityAction(int, Bundle)
*/
public boolean performAccessibilityAction(int action, Bundle arguments) {
return mContentViewCore.performAccessibilityAction(action, arguments);
return isDestroyed() ? false
: mContentViewCore.performAccessibilityAction(action, arguments);
}
/**
......@@ -1933,8 +1957,7 @@ public class AwContents {
}
public void setNetworkAvailable(boolean networkUp) {
if (mNativeAwContents == 0) return;
nativeSetJsOnlineProperty(mNativeAwContents, networkUp);
if (!isDestroyed()) nativeSetJsOnlineProperty(mNativeAwContents, networkUp);
}
//--------------------------------------------------------------------------------------------
......@@ -1985,7 +2008,7 @@ public class AwContents {
mBrowserContext.getGeolocationPermissions().deny(origin);
}
}
if (mNativeAwContents == 0) return;
if (isDestroyed()) return;
nativeInvokeGeolocationCallback(mNativeAwContents, allow, origin);
}
});
......@@ -1994,7 +2017,7 @@ public class AwContents {
@CalledByNative
private void onGeolocationPermissionsShowPrompt(String origin) {
if (mNativeAwContents == 0) return;
if (isDestroyed()) return;
AwGeolocationPermissions permissions = mBrowserContext.getGeolocationPermissions();
// Reject if geoloaction is disabled, or the origin has a retained deny
if (!mSettings.getGeolocationEnabled()) {
......@@ -2157,7 +2180,7 @@ public class AwContents {
}
private void saveWebArchiveInternal(String path, final ValueCallback<String> callback) {
if (path == null || mNativeAwContents == 0) {
if (path == null || isDestroyed()) {
ThreadUtils.runOnUiThread(new Runnable() {
@Override
public void run() {
......@@ -2204,11 +2227,11 @@ public class AwContents {
}
public void extractSmartClipData(int x, int y, int width, int height) {
mContentViewCore.extractSmartClipData(x, y, width, height);
if (!isDestroyed()) mContentViewCore.extractSmartClipData(x, y, width, height);
}
public void setSmartClipDataListener(ContentViewCore.SmartClipDataListener listener) {
mContentViewCore.setSmartClipDataListener(listener);
if (!isDestroyed()) mContentViewCore.setSmartClipDataListener(listener);
}
// --------------------------------------------------------------------------------------------
......@@ -2224,7 +2247,7 @@ public class AwContents {
@Override
public void onDraw(Canvas canvas) {
if (mNativeAwContents == 0) {
if (isDestroyed()) {
canvas.drawColor(getEffectiveBackgroundColor());
return;
}
......@@ -2261,7 +2284,7 @@ public class AwContents {
@Override
public void requestFocus() {
if (mNativeAwContents == 0) return;
if (isDestroyed()) return;
if (!mContainerView.isInTouchMode() && mSettings.shouldFocusFirstNode()) {
nativeFocusFirstNode(mNativeAwContents);
}
......@@ -2282,16 +2305,17 @@ public class AwContents {
@Override
public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
return mContentViewCore.onCreateInputConnection(outAttrs);
return isDestroyed() ? null : mContentViewCore.onCreateInputConnection(outAttrs);
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
return mContentViewCore.onKeyUp(keyCode, event);
return isDestroyed() ? false : mContentViewCore.onKeyUp(keyCode, event);
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (isDestroyed()) return false;
if (isDpadEvent(event)) {
mSettings.setSpatialNavigationEnabled(true);
}
......@@ -2314,8 +2338,7 @@ public class AwContents {
@Override
public boolean onTouchEvent(MotionEvent event) {
if (mNativeAwContents == 0) return false;
if (isDestroyed()) return false;
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
mSettings.setSpatialNavigationEnabled(false);
}
......@@ -2349,22 +2372,22 @@ public class AwContents {
@Override
public boolean onHoverEvent(MotionEvent event) {
return mContentViewCore.onHoverEvent(event);
return isDestroyed() ? false : mContentViewCore.onHoverEvent(event);
}
@Override
public boolean onGenericMotionEvent(MotionEvent event) {
return mContentViewCore.onGenericMotionEvent(event);
return isDestroyed() ? false : mContentViewCore.onGenericMotionEvent(event);
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
mContentViewCore.onConfigurationChanged(newConfig);
if (!isDestroyed()) mContentViewCore.onConfigurationChanged(newConfig);
}
@Override
public void onAttachedToWindow() {
if (mNativeAwContents == 0) return;
if (isDestroyed()) return;
if (mIsAttachedToWindow) {
Log.w(TAG, "onAttachedToWindow called when already attached. Ignoring");
return;
......@@ -2383,15 +2406,14 @@ public class AwContents {
@Override
public void onDetachedFromWindow() {
if (isDestroyed()) return;
if (!mIsAttachedToWindow) {
Log.w(TAG, "onDetachedFromWindow called when already detached. Ignoring");
return;
}
mIsAttachedToWindow = false;
hideAutofillPopup();
if (mNativeAwContents != 0) {
nativeOnDetachedFromWindow(mNativeAwContents);
}
mContentViewCore.onDetachedFromWindow();
updateHardwareAcceleratedFeaturesToggle();
......@@ -2407,19 +2429,21 @@ public class AwContents {
@Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
if (isDestroyed()) return;
mWindowFocused = hasWindowFocus;
mContentViewCore.onWindowFocusChanged(hasWindowFocus);
}
@Override
public void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
if (isDestroyed()) return;
mContainerViewFocused = focused;
mContentViewCore.onFocusChanged(focused);
}
@Override
public void onSizeChanged(int w, int h, int ow, int oh) {
if (mNativeAwContents == 0) return;
if (isDestroyed()) return;
mScrollOffsetManager.setContainerViewSize(w, h);
// The AwLayoutSizer needs to go first so that if we're in
// fixedLayoutSize mode the update
......
......@@ -6,6 +6,7 @@ package org.chromium.android_webview.test;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
......@@ -13,16 +14,18 @@ import android.test.UiThreadTest;
import android.test.suitebuilder.annotation.LargeTest;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.Pair;
import android.view.KeyEvent;
import android.view.View;
import org.apache.http.Header;
import org.apache.http.HttpRequest;
import org.chromium.android_webview.AwContents;
import org.chromium.android_webview.AwSettings;
import org.chromium.android_webview.test.TestAwContentsClient.OnDownloadStartHelper;
import org.chromium.android_webview.test.util.CommonResources;
import org.chromium.base.test.util.Feature;
import org.chromium.content.browser.test.util.CallbackHelper;
import org.chromium.content_public.browser.LoadUrlParams;
import org.chromium.net.test.util.TestWebServer;
import java.io.InputStream;
......@@ -92,6 +95,50 @@ public class AwContentsTest extends AwTestBase {
}
}
@SmallTest
@Feature({"AndroidWebView"})
@UiThreadTest
public void testWebViewApisFailGracefullyAfterDestruction() throws Throwable {
AwContents awContents =
createAwTestContainerView(mContentsClient).getAwContents();
awContents.destroy();
assertNull(awContents.getWebContents());
assertNull(awContents.getContentViewCore());
assertNull(awContents.getNavigationController());
// The documentation for WebView#destroy() reads "This method should be called
// after this WebView has been removed from the view system. No other methods
// may be called on this WebView after destroy".
// However, some apps do not respect that restriction so we need to ensure that
// we fail gracefully and do not crash when APIs are invoked after destruction.
// Due to the large number of APIs we only test a representative selection here.
awContents.clearHistory();
awContents.loadUrl(new LoadUrlParams("http://www.google.com"));
awContents.findAllAsync("search");
assertNull(awContents.getUrl());
assertNull(awContents.getContentSettings());
assertFalse(awContents.canGoBack());
awContents.disableJavascriptInterfacesInspection();
awContents.invokeZoomPicker();
awContents.onResume();
awContents.stopLoading();
awContents.onWindowVisibilityChanged(View.VISIBLE);
awContents.requestFocus();
awContents.isMultiTouchZoomSupported();
awContents.setOverScrollMode(View.OVER_SCROLL_NEVER);
awContents.pauseTimers();
awContents.onContainerViewScrollChanged(200, 200, 100, 100);
awContents.computeScroll();
awContents.onMeasure(100, 100);
awContents.onDraw(new Canvas());
awContents.getMostRecentProgress();
assertEquals(0, awContents.computeHorizontalScrollOffset());
assertEquals(0, awContents.getContentWidthCss());
awContents.onKeyUp(KeyEvent.KEYCODE_BACK,
new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MENU));
}
@LargeTest
@Feature({"AndroidWebView"})
public void testCreateAndGcManyTimes() throws Throwable {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment