From a88fd788aa418e3f7ecf2b937517448f218a27de Mon Sep 17 00:00:00 2001 From: Marcin Kosiba Date: Tue, 7 Oct 2014 13:11:51 +0100 Subject: Wrap all of the WebView Contexts We're missing a few places in WebViewChromium and WebViewChromium where we should be wrapping the application context. Particularly we need to override getApplicationContext on ContextWrapper to return a wrapped version. Change-Id: I5ce6db1ebea6c5fc545b159a49115dad7c88a624 --- .../chromium/ResourcesContextWrapperFactory.java | 106 +++++++++++++++++++++ .../android/webview/chromium/WebViewChromium.java | 57 +++-------- .../chromium/WebViewChromiumFactoryProvider.java | 11 ++- .../chromium/WebViewContentsClientAdapter.java | 28 ++++-- 4 files changed, 143 insertions(+), 59 deletions(-) create mode 100644 chromium/java/com/android/webview/chromium/ResourcesContextWrapperFactory.java diff --git a/chromium/java/com/android/webview/chromium/ResourcesContextWrapperFactory.java b/chromium/java/com/android/webview/chromium/ResourcesContextWrapperFactory.java new file mode 100644 index 0000000..da4174f --- /dev/null +++ b/chromium/java/com/android/webview/chromium/ResourcesContextWrapperFactory.java @@ -0,0 +1,106 @@ +/* + * Copyright 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.webview.chromium; + +import android.content.ComponentCallbacks; +import android.content.Context; +import android.content.ContextWrapper; +import android.content.res.AssetManager; +import android.content.res.Resources; +import android.view.LayoutInflater; + +import java.util.WeakHashMap; + +/** + * This class allows us to wrap the application context so that the WebView implementation can + * correctly reference both org.chromium.* and application classes which is necessary to properly + * inflate UI. We keep a weak map from contexts to wrapped contexts to avoid constantly re-wrapping + * or doubly wrapping contexts. + */ +public class ResourcesContextWrapperFactory { + private static WeakHashMap sCtxToWrapper + = new WeakHashMap(); + private static final Object sLock = new Object(); + + private ResourcesContextWrapperFactory() { + } + + public static Context get(Context ctx) { + ContextWrapper wrappedCtx; + synchronized (sLock) { + wrappedCtx = sCtxToWrapper.get(ctx); + if (wrappedCtx == null) { + wrappedCtx = createWrapper(ctx); + sCtxToWrapper.put(ctx, wrappedCtx); + } + } + return wrappedCtx; + } + + private static ContextWrapper createWrapper(final Context ctx) { + return new ContextWrapper(ctx) { + private Context applicationContext; + + @Override + public ClassLoader getClassLoader() { + final ClassLoader appCl = getBaseContext().getClassLoader(); + final ClassLoader webViewCl = this.getClass().getClassLoader(); + return new ClassLoader() { + @Override + protected Class findClass(String name) throws ClassNotFoundException { + // First look in the WebViewProvider class loader. + try { + return webViewCl.loadClass(name); + } catch (ClassNotFoundException e) { + // Look in the app class loader; allowing it to throw ClassNotFoundException. + return appCl.loadClass(name); + } + } + }; + } + + @Override + public Object getSystemService(String name) { + if (Context.LAYOUT_INFLATER_SERVICE.equals(name)) { + LayoutInflater i = (LayoutInflater) getBaseContext().getSystemService(name); + return i.cloneInContext(this); + } else { + return getBaseContext().getSystemService(name); + } + } + + @Override + public Context getApplicationContext() { + if (applicationContext == null) + applicationContext = get(ctx.getApplicationContext()); + return applicationContext; + } + + @Override + public void registerComponentCallbacks(ComponentCallbacks callback) { + // We have to override registerComponentCallbacks and unregisterComponentCallbacks + // since they call getApplicationContext().[un]registerComponentCallbacks() + // which causes us to go into a loop. + ctx.registerComponentCallbacks(callback); + } + + @Override + public void unregisterComponentCallbacks(ComponentCallbacks callback) { + ctx.unregisterComponentCallbacks(callback); + } + }; + } +} diff --git a/chromium/java/com/android/webview/chromium/WebViewChromium.java b/chromium/java/com/android/webview/chromium/WebViewChromium.java index 6e857db..ca82bb7 100644 --- a/chromium/java/com/android/webview/chromium/WebViewChromium.java +++ b/chromium/java/com/android/webview/chromium/WebViewChromium.java @@ -17,10 +17,8 @@ package com.android.webview.chromium; import android.content.Context; -import android.content.ContextWrapper; import android.content.pm.PackageManager; import android.content.res.Configuration; -import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; @@ -39,7 +37,6 @@ import android.text.TextUtils; import android.util.Base64; import android.util.Log; import android.view.KeyEvent; -import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.MeasureSpec; @@ -138,6 +135,8 @@ class WebViewChromium implements WebViewProvider, WebView.PrivateAccess mWebViewPrivate; // The client adapter class. private WebViewContentsClientAdapter mContentsClientAdapter; + // The wrapped Context. + private Context mContext; // Variables for functionality provided by this adapter --------------------------------------- private ContentSettingsAdapter mWebSettings; @@ -164,7 +163,8 @@ class WebViewChromium implements WebViewProvider, mWebView = webView; mWebViewPrivate = webViewPrivate; mHitTestResult = new WebView.HitTestResult(); - mAppTargetSdkVersion = mWebView.getContext().getApplicationInfo().targetSdkVersion; + mContext = ResourcesContextWrapperFactory.get(mWebView.getContext()); + mAppTargetSdkVersion = mContext.getApplicationInfo().targetSdkVersion; mFactory = factory; mRunQueue = new WebViewChromiumRunQueue(); factory.getWebViewDelegate().addWebViewAssetPath(mWebView.getContext()); @@ -219,8 +219,8 @@ class WebViewChromium implements WebViewProvider, throw new IllegalArgumentException(msg); } else { Log.w(TAG, msg); - TextView warningLabel = new TextView(mWebView.getContext()); - warningLabel.setText(mWebView.getContext().getString( + TextView warningLabel = new TextView(mContext); + warningLabel.setText(mContext.getString( R.string.webviewchromium_private_browsing_warning)); mWebView.addView(warningLabel); } @@ -245,9 +245,9 @@ class WebViewChromium implements WebViewProvider, mAppTargetSdkVersion < Build.VERSION_CODES.KITKAT; mContentsClientAdapter = new WebViewContentsClientAdapter( - mWebView, mFactory.getWebViewDelegate()); + mWebView, mContext, mFactory.getWebViewDelegate()); mWebSettings = new ContentSettingsAdapter(new AwSettings( - mWebView.getContext(), isAccessFromFileURLsGrantedByDefault, + mContext, isAccessFromFileURLsGrantedByDefault, areLegacyQuirksEnabled)); if (mAppTargetSdkVersion < Build.VERSION_CODES.LOLLIPOP) { @@ -272,43 +272,8 @@ class WebViewChromium implements WebViewProvider, }); } - // Wrap Context so that we can use resources from the webview resource apk. - private static Context resourcesContextWrapper(final Context ctx) { - return new ContextWrapper(ctx) { - @Override - public ClassLoader getClassLoader() { - final ClassLoader appCl = getBaseContext().getClassLoader(); - final ClassLoader webViewCl = this.getClass().getClassLoader(); - return new ClassLoader() { - @Override - protected Class findClass(String name) throws ClassNotFoundException { - // First look in the WebViewProvider class loader. - try { - return webViewCl.loadClass(name); - } catch (ClassNotFoundException e) { - // Look in the app class loader; allowing it to throw ClassNotFoundException. - return appCl.loadClass(name); - } - } - }; - } - - @Override - public Object getSystemService(String name) { - if (name.equals(Context.LAYOUT_INFLATER_SERVICE)) { - LayoutInflater i = (LayoutInflater) getBaseContext().getSystemService(name); - return i.cloneInContext(this); - } else { - return getBaseContext().getSystemService(name); - } - } - - }; - } - private void initForReal() { - Context ctx = resourcesContextWrapper(mWebView.getContext()); - mAwContents = new AwContents(mFactory.getBrowserContext(), mWebView, ctx, + mAwContents = new AwContents(mFactory.getBrowserContext(), mWebView, mContext, new InternalAccessAdapter(), new WebViewNativeGLDelegate(), mContentsClientAdapter, mWebSettings.getAwSettings()); @@ -1261,7 +1226,7 @@ class WebViewChromium implements WebViewProvider, return false; } - FindActionModeCallback findAction = new FindActionModeCallback(mWebView.getContext()); + FindActionModeCallback findAction = new FindActionModeCallback(mContext); if (findAction == null) { return false; } @@ -1453,7 +1418,7 @@ class WebViewChromium implements WebViewProvider, // This was deprecated in 2009 and hidden in JB MR1, so just provide the minimum needed // to stop very out-dated applications from crashing. Log.w(TAG, "WebView doesn't support getZoomControls"); - return mAwContents.getSettings().supportZoom() ? new View(mWebView.getContext()) : null; + return mAwContents.getSettings().supportZoom() ? new View(mContext) : null; } @Override diff --git a/chromium/java/com/android/webview/chromium/WebViewChromiumFactoryProvider.java b/chromium/java/com/android/webview/chromium/WebViewChromiumFactoryProvider.java index dc48b6e..5111105 100644 --- a/chromium/java/com/android/webview/chromium/WebViewChromiumFactoryProvider.java +++ b/chromium/java/com/android/webview/chromium/WebViewChromiumFactoryProvider.java @@ -256,9 +256,10 @@ public class WebViewChromiumFactoryProvider implements WebViewFactoryProvider { "/system/framework/webview/paks"); // Make sure that ResourceProvider is initialized before starting the browser process. - setUpResources(mWebViewDelegate.getApplication()); + Context context = getWrappedCurrentApplicationContext(); + setUpResources(context); initPlatSupportLibrary(); - AwBrowserProcess.start(mWebViewDelegate.getApplication()); + AwBrowserProcess.start(context); if (isBuildDebuggable()) { setWebContentsDebuggingEnabled(true); @@ -295,6 +296,10 @@ public class WebViewChromiumFactoryProvider implements WebViewFactoryProvider { } } + private Context getWrappedCurrentApplicationContext() { + return ResourcesContextWrapperFactory.get(mWebViewDelegate.getApplication()); + } + AwBrowserContext getBrowserContext() { synchronized (mLock) { return getBrowserContextLocked(); @@ -426,7 +431,7 @@ public class WebViewChromiumFactoryProvider implements WebViewFactoryProvider { // will bring up just the parts it needs to make this work on a temporary // basis until Chromium is started for real. The temporary cookie manager // needs the application context to have been set. - ContentMain.initApplicationContext(mWebViewDelegate.getApplication()); + ContentMain.initApplicationContext(getWrappedCurrentApplicationContext()); } mCookieManager = new CookieManagerAdapter(new AwCookieManager()); } diff --git a/chromium/java/com/android/webview/chromium/WebViewContentsClientAdapter.java b/chromium/java/com/android/webview/chromium/WebViewContentsClientAdapter.java index f3351c5..a08c457 100644 --- a/chromium/java/com/android/webview/chromium/WebViewContentsClientAdapter.java +++ b/chromium/java/com/android/webview/chromium/WebViewContentsClientAdapter.java @@ -99,6 +99,8 @@ public class WebViewContentsClientAdapter extends AwContentsClient { private static final boolean TRACE = false; // The WebView instance that this adapter is serving. private final WebView mWebView; + // The Context to use. This is different from mWebView.getContext(), which should not be used. + private final Context mContext; // The WebViewClient instance that was passed to WebView.setWebViewClient(). private WebViewClient mWebViewClient; // The WebChromeClient instance that was passed to WebView.setContentViewClient(). @@ -123,11 +125,17 @@ public class WebViewContentsClientAdapter extends AwContentsClient { * * @param webView the {@link WebView} instance that this adapter is serving. */ - WebViewContentsClientAdapter(WebView webView, WebViewDelegate webViewDelegate) { + WebViewContentsClientAdapter(WebView webView, Context context, + WebViewDelegate webViewDelegate) { if (webView == null || webViewDelegate == null) { - throw new IllegalArgumentException("webView or delegate can't be null"); + throw new IllegalArgumentException("webView or delegate can't be null."); } + if (context == null) { + throw new IllegalArgumentException("context can't be null."); + } + + mContext = context; mWebView = webView; mWebViewDelegate = webViewDelegate; setWebViewClient(null); @@ -535,7 +543,7 @@ public class WebViewContentsClientAdapter extends AwContentsClient { // ErrorStrings is @hidden, so we can't do this in AwContents. // Normally the net/ layer will set a valid description, but for synthesized callbacks // (like in the case for intercepted requests) AwContents will pass in null. - description = mWebViewDelegate.getErrorString(mWebView.getContext(), errorCode); + description = mWebViewDelegate.getErrorString(mContext, errorCode); } TraceEvent.begin(); if (TRACE) Log.d(TAG, "onReceivedError=" + failingUrl); @@ -693,7 +701,7 @@ public class WebViewContentsClientAdapter extends AwContentsClient { if (TRACE) Log.d(TAG, "onJsAlert"); if (!mWebChromeClient.onJsAlert(mWebView, url, message, res)) { new JsDialogHelper(res, JsDialogHelper.ALERT, null, message, url) - .showDialog(mWebView.getContext()); + .showDialog(mContext); } } else { receiver.cancel(); @@ -710,7 +718,7 @@ public class WebViewContentsClientAdapter extends AwContentsClient { if (TRACE) Log.d(TAG, "onJsBeforeUnload"); if (!mWebChromeClient.onJsBeforeUnload(mWebView, url, message, res)) { new JsDialogHelper(res, JsDialogHelper.UNLOAD, null, message, url) - .showDialog(mWebView.getContext()); + .showDialog(mContext); } } else { receiver.cancel(); @@ -727,7 +735,7 @@ public class WebViewContentsClientAdapter extends AwContentsClient { if (TRACE) Log.d(TAG, "onJsConfirm"); if (!mWebChromeClient.onJsConfirm(mWebView, url, message, res)) { new JsDialogHelper(res, JsDialogHelper.CONFIRM, null, message, url) - .showDialog(mWebView.getContext()); + .showDialog(mContext); } } else { receiver.cancel(); @@ -745,7 +753,7 @@ public class WebViewContentsClientAdapter extends AwContentsClient { if (TRACE) Log.d(TAG, "onJsPrompt"); if (!mWebChromeClient.onJsPrompt(mWebView, url, message, defaultValue, res)) { new JsDialogHelper(res, JsDialogHelper.PROMPT, defaultValue, message, url) - .showDialog(mWebView.getContext()); + .showDialog(mContext); } } else { receiver.cancel(); @@ -891,7 +899,7 @@ public class WebViewContentsClientAdapter extends AwContentsClient { } TraceEvent.begin(); FileChooserParamsAdapter adapter = new FileChooserParamsAdapter( - fileChooserParams, mWebView.getContext()); + fileChooserParams, mContext); if (TRACE) Log.d(TAG, "showFileChooser"); ValueCallback callbackAdapter = new ValueCallback() { private boolean mCompleted; @@ -920,7 +928,7 @@ public class WebViewContentsClientAdapter extends AwContentsClient { // If the app did not handle it and we are running on Lollipop or newer, then // abort. - if (mWebView.getContext().getApplicationInfo().targetSdkVersion >= + if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP) { uploadFileCallback.onReceiveValue(null); return; @@ -1000,7 +1008,7 @@ public class WebViewContentsClientAdapter extends AwContentsClient { // The ic_media_video_poster icon is transparent so we need to draw it on a gray // background. Bitmap poster = BitmapFactory.decodeResource( - mWebView.getContext().getResources(), + mContext.getResources(), R.drawable.ic_media_video_poster); result = Bitmap.createBitmap(poster.getWidth(), poster.getHeight(), poster.getConfig()); result.eraseColor(Color.GRAY); -- cgit v1.2.3