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