Log an error message if WebView methods are called on the wrong thread.
All public WebView methods should be called on the UI thread. This
change adds a check to all public non-inherited methods, other than
those that have always been hidden.
Bug: 4373919
Change-Id: I573fc704faa58287a7b14a971958fa5417855d70
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index b5d0492..f99d895 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -986,6 +986,7 @@
protected WebView(Context context, AttributeSet attrs, int defStyle,
Map<String, Object> javaScriptInterfaces, boolean privateBrowsing) {
super(context, attrs, defStyle);
+ checkThread();
// Used by the chrome stack to find application paths
JniUtil.setContext(context);
@@ -1138,7 +1139,7 @@
PackageInfo pInfo = pm.getPackageInfo(name,
PackageManager.GET_ACTIVITIES | PackageManager.GET_SERVICES);
installedPackages.add(name);
- } catch(PackageManager.NameNotFoundException e) {
+ } catch (PackageManager.NameNotFoundException e) {
// package not found
}
}
@@ -1309,6 +1310,7 @@
* @param overlay TRUE if horizontal scrollbar should have overlay style.
*/
public void setHorizontalScrollbarOverlay(boolean overlay) {
+ checkThread();
mOverlayHorizontalScrollbar = overlay;
}
@@ -1317,6 +1319,7 @@
* @param overlay TRUE if vertical scrollbar should have overlay style.
*/
public void setVerticalScrollbarOverlay(boolean overlay) {
+ checkThread();
mOverlayVerticalScrollbar = overlay;
}
@@ -1325,6 +1328,7 @@
* @return TRUE if horizontal scrollbar has overlay style.
*/
public boolean overlayHorizontalScrollbar() {
+ checkThread();
return mOverlayHorizontalScrollbar;
}
@@ -1333,6 +1337,7 @@
* @return TRUE if vertical scrollbar has overlay style.
*/
public boolean overlayVerticalScrollbar() {
+ checkThread();
return mOverlayVerticalScrollbar;
}
@@ -1364,6 +1369,7 @@
* @deprecated This method is now obsolete.
*/
public int getVisibleTitleHeight() {
+ checkThread();
// need to restrict mScrollY due to over scroll
return Math.max(getTitleHeight() - Math.max(0, mScrollY), 0);
}
@@ -1390,6 +1396,7 @@
* there is no certificate (the site is not secure).
*/
public SslCertificate getCertificate() {
+ checkThread();
return mCertificate;
}
@@ -1397,6 +1404,7 @@
* Sets the SSL certificate for the main top-level page.
*/
public void setCertificate(SslCertificate certificate) {
+ checkThread();
if (DebugFlags.WEB_VIEW) {
Log.v(LOGTAG, "setCertificate=" + certificate);
}
@@ -1416,6 +1424,7 @@
* @param password The password for the given host.
*/
public void savePassword(String host, String username, String password) {
+ checkThread();
mDatabase.setUsernamePassword(host, username, password);
}
@@ -1430,6 +1439,7 @@
*/
public void setHttpAuthUsernamePassword(String host, String realm,
String username, String password) {
+ checkThread();
mDatabase.setHttpAuthUsernamePassword(host, realm, username, password);
}
@@ -1443,6 +1453,7 @@
* String[1] is password. Return null if it can't find anything.
*/
public String[] getHttpAuthUsernamePassword(String host, String realm) {
+ checkThread();
return mDatabase.getHttpAuthUsernamePassword(host, realm);
}
@@ -1475,6 +1486,11 @@
* methods may be called on a WebView after destroy.
*/
public void destroy() {
+ checkThread();
+ destroyImpl();
+ }
+
+ private void destroyImpl() {
clearHelpers();
if (mListBoxDialog != null) {
mListBoxDialog.dismiss();
@@ -1511,6 +1527,7 @@
* Enables platform notifications of data state and proxy changes.
*/
public static void enablePlatformNotifications() {
+ checkThread();
Network.enablePlatformNotifications();
}
@@ -1519,6 +1536,7 @@
* from the Activity's onPause() or onStop().
*/
public static void disablePlatformNotifications() {
+ checkThread();
Network.disablePlatformNotifications();
}
@@ -1530,6 +1548,7 @@
* @hide pending API solidification
*/
public void setJsFlags(String flags) {
+ checkThread();
mWebViewCore.sendMessage(EventHub.SET_JS_FLAGS, flags);
}
@@ -1540,6 +1559,7 @@
* @param networkUp boolean indicating if network is available
*/
public void setNetworkAvailable(boolean networkUp) {
+ checkThread();
mWebViewCore.sendMessage(EventHub.SET_NETWORK_STATE,
networkUp ? 1 : 0, 0);
}
@@ -1549,6 +1569,7 @@
* {@hide}
*/
public void setNetworkType(String type, String subtype) {
+ checkThread();
Map<String, String> map = new HashMap<String, String>();
map.put("type", type);
map.put("subtype", subtype);
@@ -1568,6 +1589,7 @@
* @see #restorePicture
*/
public WebBackForwardList saveState(Bundle outState) {
+ checkThread();
if (outState == null) {
return null;
}
@@ -1622,6 +1644,7 @@
* @return True if the picture was successfully saved.
*/
public boolean savePicture(Bundle b, final File dest) {
+ checkThread();
if (dest == null || b == null) {
return false;
}
@@ -1684,6 +1707,7 @@
* @return True if the picture was successfully restored.
*/
public boolean restorePicture(Bundle b, File src) {
+ checkThread();
if (src == null || b == null) {
return false;
}
@@ -1736,6 +1760,7 @@
* @see #restorePicture
*/
public WebBackForwardList restoreState(Bundle inState) {
+ checkThread();
WebBackForwardList returnList = null;
if (inState == null) {
return returnList;
@@ -1795,6 +1820,11 @@
* will be replaced by the intrinsic value of the WebView.
*/
public void loadUrl(String url, Map<String, String> extraHeaders) {
+ checkThread();
+ loadUrlImpl(url, extraHeaders);
+ }
+
+ private void loadUrlImpl(String url, Map<String, String> extraHeaders) {
switchOutDrawHistory();
WebViewCore.GetUrlData arg = new WebViewCore.GetUrlData();
arg.mUrl = url;
@@ -1808,10 +1838,15 @@
* @param url The url of the resource to load.
*/
public void loadUrl(String url) {
+ checkThread();
+ loadUrlImpl(url);
+ }
+
+ private void loadUrlImpl(String url) {
if (url == null) {
return;
}
- loadUrl(url, null);
+ loadUrlImpl(url, null);
}
/**
@@ -1823,6 +1858,7 @@
* @param postData The data will be passed to "POST" request.
*/
public void postUrl(String url, byte[] postData) {
+ checkThread();
if (URLUtil.isNetworkUrl(url)) {
switchOutDrawHistory();
WebViewCore.PostUrlData arg = new WebViewCore.PostUrlData();
@@ -1831,7 +1867,7 @@
mWebViewCore.sendMessage(EventHub.POST_URL, arg);
clearHelpers();
} else {
- loadUrl(url);
+ loadUrlImpl(url);
}
}
@@ -1846,7 +1882,12 @@
* @param encoding The encoding of the data. i.e. utf-8, base64
*/
public void loadData(String data, String mimeType, String encoding) {
- loadUrl("data:" + mimeType + ";" + encoding + "," + data);
+ checkThread();
+ loadDataImpl(data, mimeType, encoding);
+ }
+
+ private void loadDataImpl(String data, String mimeType, String encoding) {
+ loadUrlImpl("data:" + mimeType + ";" + encoding + "," + data);
}
/**
@@ -1872,9 +1913,10 @@
*/
public void loadDataWithBaseURL(String baseUrl, String data,
String mimeType, String encoding, String historyUrl) {
+ checkThread();
if (baseUrl != null && baseUrl.toLowerCase().startsWith("data:")) {
- loadData(data, mimeType, encoding);
+ loadDataImpl(data, mimeType, encoding);
return;
}
switchOutDrawHistory();
@@ -1894,7 +1936,8 @@
* @param filename The filename where the archive should be placed.
*/
public void saveWebArchive(String filename) {
- saveWebArchive(filename, false, null);
+ checkThread();
+ saveWebArchiveImpl(filename, false, null);
}
/* package */ static class SaveWebArchiveMessage {
@@ -1923,6 +1966,12 @@
* file failed.
*/
public void saveWebArchive(String basename, boolean autoname, ValueCallback<String> callback) {
+ checkThread();
+ saveWebArchiveImpl(basename, autoname, callback);
+ }
+
+ private void saveWebArchiveImpl(String basename, boolean autoname,
+ ValueCallback<String> callback) {
mWebViewCore.sendMessage(EventHub.SAVE_WEBARCHIVE,
new SaveWebArchiveMessage(basename, autoname, callback));
}
@@ -1931,6 +1980,7 @@
* Stop the current load.
*/
public void stopLoading() {
+ checkThread();
// TODO: should we clear all the messages in the queue before sending
// STOP_LOADING?
switchOutDrawHistory();
@@ -1941,6 +1991,7 @@
* Reload the current url.
*/
public void reload() {
+ checkThread();
clearHelpers();
switchOutDrawHistory();
mWebViewCore.sendMessage(EventHub.RELOAD);
@@ -1951,6 +2002,7 @@
* @return True iff this WebView has a back history item.
*/
public boolean canGoBack() {
+ checkThread();
WebBackForwardList l = mCallbackProxy.getBackForwardList();
synchronized (l) {
if (l.getClearPending()) {
@@ -1965,7 +2017,8 @@
* Go back in the history of this WebView.
*/
public void goBack() {
- goBackOrForward(-1);
+ checkThread();
+ goBackOrForwardImpl(-1);
}
/**
@@ -1973,6 +2026,7 @@
* @return True iff this Webview has a forward history item.
*/
public boolean canGoForward() {
+ checkThread();
WebBackForwardList l = mCallbackProxy.getBackForwardList();
synchronized (l) {
if (l.getClearPending()) {
@@ -1987,7 +2041,8 @@
* Go forward in the history of this WebView.
*/
public void goForward() {
- goBackOrForward(1);
+ checkThread();
+ goBackOrForwardImpl(1);
}
/**
@@ -1997,6 +2052,7 @@
* history.
*/
public boolean canGoBackOrForward(int steps) {
+ checkThread();
WebBackForwardList l = mCallbackProxy.getBackForwardList();
synchronized (l) {
if (l.getClearPending()) {
@@ -2016,6 +2072,11 @@
* forward list.
*/
public void goBackOrForward(int steps) {
+ checkThread();
+ goBackOrForwardImpl(steps);
+ }
+
+ private void goBackOrForwardImpl(int steps) {
goBackOrForward(steps, false);
}
@@ -2031,6 +2092,7 @@
* Returns true if private browsing is enabled in this WebView.
*/
public boolean isPrivateBrowsingEnabled() {
+ checkThread();
return getSettings().isPrivateBrowsingEnabled();
}
@@ -2053,6 +2115,7 @@
* @return true if the page was scrolled
*/
public boolean pageUp(boolean top) {
+ checkThread();
if (mNativeClass == 0) {
return false;
}
@@ -2079,6 +2142,7 @@
* @return true if the page was scrolled
*/
public boolean pageDown(boolean bottom) {
+ checkThread();
if (mNativeClass == 0) {
return false;
}
@@ -2103,6 +2167,7 @@
* and onMeasure() will return 0 if MeasureSpec is not MeasureSpec.EXACTLY
*/
public void clearView() {
+ checkThread();
mContentWidth = 0;
mContentHeight = 0;
setBaseLayer(0, null, false, false);
@@ -2119,6 +2184,7 @@
* bounds of the view.
*/
public Picture capturePicture() {
+ checkThread();
if (mNativeClass == 0) return null;
Picture result = new Picture();
nativeCopyBaseContentToPicture(result);
@@ -2149,6 +2215,7 @@
* @return The current scale.
*/
public float getScale() {
+ checkThread();
return mZoomManager.getScale();
}
@@ -2161,6 +2228,7 @@
* @param scaleInPercent The initial scale in percent.
*/
public void setInitialScale(int scaleInPercent) {
+ checkThread();
mZoomManager.setInitialScaleInPercent(scaleInPercent);
}
@@ -2170,6 +2238,7 @@
* level of this WebView.
*/
public void invokeZoomPicker() {
+ checkThread();
if (!getSettings().supportZoom()) {
Log.w(LOGTAG, "This WebView doesn't support zoom.");
return;
@@ -2197,6 +2266,7 @@
* HitTestResult type is set to UNKNOWN_TYPE.
*/
public HitTestResult getHitTestResult() {
+ checkThread();
return hitTestResult(mInitialHitTestResult);
}
@@ -2278,6 +2348,7 @@
* - "src" returns the image's src attribute.
*/
public void requestFocusNodeHref(Message hrefMsg) {
+ checkThread();
if (hrefMsg == null) {
return;
}
@@ -2306,6 +2377,7 @@
* as the data member with "url" as key. The result can be null.
*/
public void requestImageRef(Message msg) {
+ checkThread();
if (0 == mNativeClass) return; // client isn't initialized
int contentX = viewToContentX(mLastTouchX + mScrollX);
int contentY = viewToContentY(mLastTouchY + mScrollY);
@@ -2802,6 +2874,7 @@
* @return The url for the current page.
*/
public String getUrl() {
+ checkThread();
WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem();
return h != null ? h.getUrl() : null;
}
@@ -2815,6 +2888,7 @@
* @return The url that was originally requested for the current page.
*/
public String getOriginalUrl() {
+ checkThread();
WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem();
return h != null ? h.getOriginalUrl() : null;
}
@@ -2825,6 +2899,7 @@
* @return The title for the current page.
*/
public String getTitle() {
+ checkThread();
WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem();
return h != null ? h.getTitle() : null;
}
@@ -2835,6 +2910,7 @@
* @return The favicon for the current page.
*/
public Bitmap getFavicon() {
+ checkThread();
WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem();
return h != null ? h.getFavicon() : null;
}
@@ -2855,6 +2931,7 @@
* @return The progress for the current page between 0 and 100.
*/
public int getProgress() {
+ checkThread();
return mCallbackProxy.getProgress();
}
@@ -2862,6 +2939,7 @@
* @return the height of the HTML content.
*/
public int getContentHeight() {
+ checkThread();
return mContentHeight;
}
@@ -2879,6 +2957,7 @@
* useful if the application has been paused.
*/
public void pauseTimers() {
+ checkThread();
mWebViewCore.sendMessage(EventHub.PAUSE_TIMERS);
}
@@ -2887,6 +2966,7 @@
* This will resume dispatching all timers.
*/
public void resumeTimers() {
+ checkThread();
mWebViewCore.sendMessage(EventHub.RESUME_TIMERS);
}
@@ -2899,6 +2979,7 @@
* Note that this differs from pauseTimers(), which affects all WebViews.
*/
public void onPause() {
+ checkThread();
if (!mIsPaused) {
mIsPaused = true;
mWebViewCore.sendMessage(EventHub.ON_PAUSE);
@@ -2914,6 +2995,7 @@
* Call this to resume a WebView after a previous call to onPause().
*/
public void onResume() {
+ checkThread();
if (mIsPaused) {
mIsPaused = false;
mWebViewCore.sendMessage(EventHub.ON_RESUME);
@@ -2934,6 +3016,7 @@
* free any available memory.
*/
public void freeMemory() {
+ checkThread();
mWebViewCore.sendMessage(EventHub.FREE_MEMORY);
}
@@ -2944,6 +3027,7 @@
* @param includeDiskFiles If false, only the RAM cache is cleared.
*/
public void clearCache(boolean includeDiskFiles) {
+ checkThread();
// Note: this really needs to be a static method as it clears cache for all
// WebView. But we need mWebViewCore to send message to WebCore thread, so
// we can't make this static.
@@ -2956,6 +3040,7 @@
* currently focused textfield if there is one.
*/
public void clearFormData() {
+ checkThread();
if (inEditingMode()) {
AutoCompleteAdapter adapter = null;
mWebTextView.setAdapterCustom(adapter);
@@ -2966,6 +3051,7 @@
* Tell the WebView to clear its internal back/forward list.
*/
public void clearHistory() {
+ checkThread();
mCallbackProxy.getBackForwardList().setClearPending();
mWebViewCore.sendMessage(EventHub.CLEAR_HISTORY);
}
@@ -2975,6 +3061,7 @@
* certificate errors.
*/
public void clearSslPreferences() {
+ checkThread();
mWebViewCore.sendMessage(EventHub.CLEAR_SSL_PREF_TABLE);
}
@@ -2987,6 +3074,7 @@
* updated to reflect any new state.
*/
public WebBackForwardList copyBackForwardList() {
+ checkThread();
return mCallbackProxy.getBackForwardList().clone();
}
@@ -2998,6 +3086,7 @@
* @param forward Direction to search.
*/
public void findNext(boolean forward) {
+ checkThread();
if (0 == mNativeClass) return; // client isn't initialized
nativeFindNext(forward);
}
@@ -3009,6 +3098,7 @@
* that were found.
*/
public int findAll(String find) {
+ checkThread();
if (0 == mNativeClass) return 0; // client isn't initialized
int result = find != null ? nativeFindAll(find.toLowerCase(),
find.toUpperCase(), find.equalsIgnoreCase(mLastFind)) : 0;
@@ -3028,6 +3118,7 @@
* @return boolean True if the find dialog is shown, false otherwise.
*/
public boolean showFindDialog(String text, boolean showIme) {
+ checkThread();
FindActionModeCallback callback = new FindActionModeCallback(mContext);
if (getParent() == null || startActionMode(callback) == null) {
// Could not start the action mode, so end Find on page
@@ -3104,6 +3195,7 @@
* @return the address, or if no address is found, return null.
*/
public static String findAddress(String addr) {
+ checkThread();
return findAddress(addr, false);
}
@@ -3137,6 +3229,7 @@
* Clear the highlighting surrounding text matches created by findAll.
*/
public void clearMatches() {
+ checkThread();
if (mNativeClass == 0)
return;
nativeSetFindIsEmpty();
@@ -3166,6 +3259,7 @@
* @param response The message that will be dispatched with the result.
*/
public void documentHasImages(Message response) {
+ checkThread();
if (response == null) {
return;
}
@@ -3561,6 +3655,7 @@
* @param client An implementation of WebViewClient.
*/
public void setWebViewClient(WebViewClient client) {
+ checkThread();
mCallbackProxy.setWebViewClient(client);
}
@@ -3581,6 +3676,7 @@
* @param listener An implementation of DownloadListener.
*/
public void setDownloadListener(DownloadListener listener) {
+ checkThread();
mCallbackProxy.setDownloadListener(listener);
}
@@ -3591,6 +3687,7 @@
* @param client An implementation of WebChromeClient.
*/
public void setWebChromeClient(WebChromeClient client) {
+ checkThread();
mCallbackProxy.setWebChromeClient(client);
}
@@ -3629,6 +3726,7 @@
* @param listener An implementation of WebView.PictureListener.
*/
public void setPictureListener(PictureListener listener) {
+ checkThread();
mPictureListener = listener;
}
@@ -3670,6 +3768,7 @@
* JavaScript.
*/
public void addJavascriptInterface(Object obj, String interfaceName) {
+ checkThread();
if (obj == null) {
return;
}
@@ -3684,6 +3783,7 @@
* @param interfaceName The name of the interface to remove.
*/
public void removeJavascriptInterface(String interfaceName) {
+ checkThread();
if (mWebViewCore != null) {
WebViewCore.JSInterfaceData arg = new WebViewCore.JSInterfaceData();
arg.mInterfaceName = interfaceName;
@@ -3698,6 +3798,7 @@
* settings.
*/
public WebSettings getSettings() {
+ checkThread();
return (mWebViewCore != null) ? mWebViewCore.getSettings() : null;
}
@@ -3710,6 +3811,7 @@
*/
@Deprecated
public static synchronized PluginList getPluginList() {
+ checkThread();
return new PluginList();
}
@@ -3718,7 +3820,9 @@
* @deprecated This was used for Gears, which has been deprecated.
*/
@Deprecated
- public void refreshPlugins(boolean reloadOpenPages) { }
+ public void refreshPlugins(boolean reloadOpenPages) {
+ checkThread();
+ }
//-------------------------------------------------------------------------
// Override View methods
@@ -3727,7 +3831,7 @@
@Override
protected void finalize() throws Throwable {
try {
- destroy();
+ destroyImpl();
} finally {
super.finalize();
}
@@ -4988,6 +5092,7 @@
* Do not rely on this functionality; it will be deprecated in the future.
*/
public void emulateShiftHeld() {
+ checkThread();
setUpSelect(false, 0, 0);
}
@@ -6257,6 +6362,7 @@
private boolean mMapTrackballToArrowKeys = true;
public void setMapTrackballToArrowKeys(boolean setMap) {
+ checkThread();
mMapTrackballToArrowKeys = setMap;
}
@@ -6549,6 +6655,7 @@
}
public void flingScroll(int vx, int vy) {
+ checkThread();
mScroller.fling(mScrollX, mScrollY, vx, vy, 0, computeMaxScrollX(), 0,
computeMaxScrollY(), mOverflingDistance, mOverflingDistance);
invalidate();
@@ -6684,6 +6791,7 @@
*/
@Deprecated
public View getZoomControls() {
+ checkThread();
if (!getSettings().supportZoom()) {
Log.w(LOGTAG, "This WebView doesn't support zoom.");
return null;
@@ -6703,6 +6811,7 @@
* @return TRUE if the WebView can be zoomed in.
*/
public boolean canZoomIn() {
+ checkThread();
return mZoomManager.canZoomIn();
}
@@ -6710,6 +6819,7 @@
* @return TRUE if the WebView can be zoomed out.
*/
public boolean canZoomOut() {
+ checkThread();
return mZoomManager.canZoomOut();
}
@@ -6718,6 +6828,7 @@
* @return TRUE if zoom in succeeds. FALSE if no zoom changes.
*/
public boolean zoomIn() {
+ checkThread();
return mZoomManager.zoomIn();
}
@@ -6726,6 +6837,7 @@
* @return TRUE if zoom out succeeds. FALSE if no zoom changes.
*/
public boolean zoomOut() {
+ checkThread();
return mZoomManager.zoomOut();
}
@@ -8682,6 +8794,7 @@
}
public void debugDump() {
+ checkThread();
nativeDebugDump();
mWebViewCore.sendMessage(EventHub.DUMP_NAVTREE);
}
@@ -8743,6 +8856,19 @@
return mViewManager;
}
+ private static void checkThread() {
+ if (!"main".equals(Thread.currentThread().getName())) {
+ try {
+ throw new RuntimeException("A WebView method was called on thread '" +
+ Thread.currentThread().getName() + "'. " +
+ "All WebView methods must be called on the UI thread. " +
+ "Future versions of WebView may not support use on other threads.");
+ } catch (RuntimeException e) {
+ Log.e(LOGTAG, Log.getStackTraceString(e));
+ }
+ }
+ }
+
private native int nativeCacheHitFramePointer();
private native boolean nativeCacheHitIsPlugin();
private native Rect nativeCacheHitNodeBounds();