diff options
author | mnaganov@chromium.org <mnaganov@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-09-27 18:36:26 +0000 |
---|---|---|
committer | mnaganov@chromium.org <mnaganov@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-09-27 18:36:26 +0000 |
commit | e7bee399b0d064cdd281bc567a2736852079e87a (patch) | |
tree | e9c1b83b4d70a1d8bc52021db2d96a91f33eb8e7 /android_webview | |
parent | 0d2cb888776e6a8e0dd4939390a2b9c220ff40fa (diff) | |
download | chromium_src-e7bee399b0d064cdd281bc567a2736852079e87a.zip chromium_src-e7bee399b0d064cdd281bc567a2736852079e87a.tar.gz chromium_src-e7bee399b0d064cdd281bc567a2736852079e87a.tar.bz2 |
Move AndroidProtocolAdaptor into android_webview
Also move Android-specific URL constants into android_webview
R=benm@chromium.org,joth@chromium.org
Review URL: https://chromiumcodereview.appspot.com/10985044
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@159078 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'android_webview')
-rw-r--r-- | android_webview/android_webview.gyp | 2 | ||||
-rw-r--r-- | android_webview/common/url_constants.cc | 20 | ||||
-rw-r--r-- | android_webview/common/url_constants.h | 19 | ||||
-rw-r--r-- | android_webview/java/src/org/chromium/android_webview/AndroidProtocolHandler.java | 233 | ||||
-rw-r--r-- | android_webview/lib/DEPS | 3 | ||||
-rw-r--r-- | android_webview/lib/aw_browser_dependency_factory_impl.cc | 17 | ||||
-rw-r--r-- | android_webview/lib/aw_browser_dependency_factory_impl.h | 4 | ||||
-rw-r--r-- | android_webview/lib/main/aw_main_delegate.cc | 13 | ||||
-rw-r--r-- | android_webview/native/android_protocol_handler.cc | 285 | ||||
-rw-r--r-- | android_webview/native/android_protocol_handler.h | 42 | ||||
-rw-r--r-- | android_webview/native/android_stream_reader_url_request_job.cc | 228 | ||||
-rw-r--r-- | android_webview/native/android_stream_reader_url_request_job.h | 81 | ||||
-rw-r--r-- | android_webview/native/android_webview_jni_registrar.cc | 5 | ||||
-rw-r--r-- | android_webview/native/webview_native.gyp | 18 |
14 files changed, 962 insertions, 8 deletions
diff --git a/android_webview/android_webview.gyp b/android_webview/android_webview.gyp index 25005f9..6962450 100644 --- a/android_webview/android_webview.gyp +++ b/android_webview/android_webview.gyp @@ -26,6 +26,8 @@ 'common/android_webview_message_generator.h', 'common/render_view_messages.cc', 'common/render_view_messages.h', + 'common/url_constants.cc', + 'common/url_constants.h', 'browser/aw_cookie_access_policy.cc', 'browser/aw_cookie_access_policy.h', 'browser/aw_http_auth_handler_base.cc', diff --git a/android_webview/common/url_constants.cc b/android_webview/common/url_constants.cc new file mode 100644 index 0000000..e56bf0f --- /dev/null +++ b/android_webview/common/url_constants.cc @@ -0,0 +1,20 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "android_webview/common/url_constants.h" + +namespace android_webview { + +// The content: scheme is used in Android for interacting with content +// provides. +// See http://developer.android.com/reference/android/content/ContentUris.html +const char kContentScheme[] = "content"; + +// These are special paths used with the file: scheme to access application +// assets and resources. +// See http://developer.android.com/reference/android/webkit/WebSettings.html +const char kAndroidAssetPath[] = "/android_asset/"; +const char kAndroidResourcePath[] = "/android_res/"; + +} // namespace android_webview diff --git a/android_webview/common/url_constants.h b/android_webview/common/url_constants.h new file mode 100644 index 0000000..c340ca7 --- /dev/null +++ b/android_webview/common/url_constants.h @@ -0,0 +1,19 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Contains constants for known URLs and portions thereof. + +#ifndef ANDROID_WEBVIEW_COMMON_URL_CONSTANTS_H_ +#define ANDROID_WEBVIEW_COMMON_URL_CONSTANTS_H_ + +namespace android_webview { + +extern const char kContentScheme[]; +// Special Android file paths. +extern const char kAndroidAssetPath[]; +extern const char kAndroidResourcePath[]; + +} // namespace android_webview + +#endif // ANDROID_WEBVIEW_COMMON_URL_CONSTANTS_H_ diff --git a/android_webview/java/src/org/chromium/android_webview/AndroidProtocolHandler.java b/android_webview/java/src/org/chromium/android_webview/AndroidProtocolHandler.java new file mode 100644 index 0000000..dbec123 --- /dev/null +++ b/android_webview/java/src/org/chromium/android_webview/AndroidProtocolHandler.java @@ -0,0 +1,233 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.android_webview; + +import android.content.Context; +import android.content.res.AssetManager; +import android.net.Uri; +import android.util.Log; +import android.util.TypedValue; + +import java.io.InputStream; +import java.io.IOException; +import java.net.URLConnection; +import java.util.List; + +import org.chromium.base.CalledByNativeUnchecked; + +/** + * Implements the Java side of Android URL protocol jobs. + * See android_protocol_handler.cc. + */ +public class AndroidProtocolHandler { + private static final String TAG = "AndroidProtocolHandler"; + + // Supported URL schemes. This needs to be kept in sync with + // clank/native/framework/chrome/url_request_android_job.cc. + private static final String FILE_SCHEME = "file"; + private static final String CONTENT_SCHEME = "content"; + + /** + * Open an InputStream for an Android resource. + * @param context The context manager. + * @param url The url to load. + * @return An InputStream to the Android resource. + */ + // TODO(bulach): this should have either a throw clause, or + // handle the exception in the java side rather than the native side. + @CalledByNativeUnchecked + public static InputStream open(Context context, String url) { + Uri uri = verifyUrl(url); + if (uri == null) { + return null; + } + String path = uri.getPath(); + if (uri.getScheme().equals(FILE_SCHEME)) { + if (path.startsWith(nativeGetAndroidAssetPath())) { + return openAsset(context, uri); + } else if (path.startsWith(nativeGetAndroidResourcePath())) { + return openResource(context, uri); + } + } else if (uri.getScheme().equals(CONTENT_SCHEME)) { + return openContent(context, uri); + } + return null; + } + + private static int getFieldId(Context context, String assetType, String assetName) + throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException { + Class<?> d = context.getClassLoader() + .loadClass(context.getPackageName() + ".R$" + assetType); + java.lang.reflect.Field field = d.getField(assetName); + int id = field.getInt(null); + return id; + } + + private static int getValueType(Context context, int field_id) { + TypedValue value = new TypedValue(); + context.getResources().getValue(field_id, value, true); + return value.type; + } + + private static InputStream openResource(Context context, Uri uri) { + assert(uri.getScheme().equals(FILE_SCHEME)); + assert(uri.getPath() != null); + assert(uri.getPath().startsWith(nativeGetAndroidResourcePath())); + // The path must be of the form "/android_res/asset_type/asset_name.ext". + List<String> pathSegments = uri.getPathSegments(); + if (pathSegments.size() != 3) { + Log.e(TAG, "Incorrect resource path: " + uri); + return null; + } + String assetPath = pathSegments.get(0); + String assetType = pathSegments.get(1); + String assetName = pathSegments.get(2); + if (!("/" + assetPath + "/").equals(nativeGetAndroidResourcePath())) { + Log.e(TAG, "Resource path does not start with " + nativeGetAndroidResourcePath() + + ": " + uri); + return null; + } + // Drop the file extension. + assetName = assetName.split("\\.")[0]; + try { + // Use the application context for resolving the resource package name so that we do + // not use the browser's own resources. Note that if 'context' here belongs to the + // test suite, it does not have a separate application context. In that case we use + // the original context object directly. + if (context.getApplicationContext() != null) { + context = context.getApplicationContext(); + } + int field_id = getFieldId(context, assetType, assetName); + int value_type = getValueType(context, field_id); + if (value_type == TypedValue.TYPE_STRING) { + return context.getResources().openRawResource(field_id); + } else { + Log.e(TAG, "Asset not of type string: " + uri); + return null; + } + } catch (ClassNotFoundException e) { + Log.e(TAG, "Unable to open resource URL: " + uri, e); + return null; + } catch (NoSuchFieldException e) { + Log.e(TAG, "Unable to open resource URL: " + uri, e); + return null; + } catch (IllegalAccessException e) { + Log.e(TAG, "Unable to open resource URL: " + uri, e); + return null; + } + } + + private static InputStream openAsset(Context context, Uri uri) { + assert(uri.getScheme().equals(FILE_SCHEME)); + assert(uri.getPath() != null); + assert(uri.getPath().startsWith(nativeGetAndroidAssetPath())); + String path = uri.getPath().replaceFirst(nativeGetAndroidAssetPath(), ""); + try { + AssetManager assets = context.getAssets(); + return assets.open(path, AssetManager.ACCESS_STREAMING); + } catch (IOException e) { + Log.e(TAG, "Unable to open asset URL: " + uri); + return null; + } + } + + private static InputStream openContent(Context context, Uri uri) { + assert(uri.getScheme().equals(CONTENT_SCHEME)); + try { + // We strip the query parameters before opening the stream to + // ensure that the URL we try to load exactly matches the URL + // we have permission to read. + Uri baseUri = stripQueryParameters(uri); + return context.getContentResolver().openInputStream(baseUri); + } catch (Exception e) { + Log.e(TAG, "Unable to open content URL: " + uri); + return null; + } + } + + /** + * Determine the mime type for an Android resource. + * @param context The context manager. + * @param stream The opened input stream which to examine. + * @param url The url from which the stream was opened. + * @return The mime type or null if the type is unknown. + */ + // TODO(bulach): this should have either a throw clause, or + // handle the exception in the java side rather than the native side. + @CalledByNativeUnchecked + public static String getMimeType(Context context, InputStream stream, String url) { + Uri uri = verifyUrl(url); + if (uri == null) { + return null; + } + String path = uri.getPath(); + // The content URL type can be queried directly. + if (uri.getScheme().equals(CONTENT_SCHEME)) { + return context.getContentResolver().getType(uri); + // Asset files may have a known extension. + } else if (uri.getScheme().equals(FILE_SCHEME) && + path.startsWith(nativeGetAndroidAssetPath())) { + String mimeType = URLConnection.guessContentTypeFromName(path); + if (mimeType != null) { + return mimeType; + } + } + // Fall back to sniffing the type from the stream. + try { + return URLConnection.guessContentTypeFromStream(stream); + } catch (IOException e) { + return null; + } + } + + /** + * Make sure the given string URL is correctly formed and parse it into a Uri. + * @return a Uri instance, or null if the URL was invalid. + */ + private static Uri verifyUrl(String url) { + if (url == null) { + return null; + } + Uri uri = Uri.parse(url); + if (uri == null) { + Log.e(TAG, "Malformed URL: " + url); + return null; + } + String path = uri.getPath(); + if (path == null || path.length() == 0) { + Log.e(TAG, "URL does not have a path: " + url); + return null; + } + return uri; + } + + /** + * Remove query parameters from a Uri. + * @param uri The input uri. + * @return The given uri without query parameters. + */ + private static Uri stripQueryParameters(Uri uri) { + assert(uri.getAuthority() != null); + assert(uri.getPath() != null); + Uri.Builder builder = new Uri.Builder(); + builder.scheme(uri.getScheme()); + builder.encodedAuthority(uri.getAuthority()); + builder.encodedPath(uri.getPath()); + return builder.build(); + } + + /** + * Set the context to be used for resolving resource queries. + * @param context Context to be used, or null for the default application + * context. + */ + public static void setResourceContextForTesting(Context context) { + nativeSetResourceContextForTesting(context); + } + + private static native void nativeSetResourceContextForTesting(Context context); + private static native String nativeGetAndroidAssetPath(); + private static native String nativeGetAndroidResourcePath(); +} diff --git a/android_webview/lib/DEPS b/android_webview/lib/DEPS index c0baad3..3f7d100 100644 --- a/android_webview/lib/DEPS +++ b/android_webview/lib/DEPS @@ -7,6 +7,9 @@ include_rules = [ "!chrome/common/chrome_content_client.h", "!chrome/browser/chrome_content_browser_client.h", "!chrome/renderer/chrome_content_renderer_client.h", + # Needed for content: scheme registation, will be moved away together with *Client classes. + "!third_party/WebKit/Source/WebKit/chromium/public/platform/WebString.h", + "!third_party/WebKit/Source/WebKit/chromium/public/WebSecurityPolicy.h", # Temporary until services we use no longer depend on Chrome's path service. "!chrome/common/chrome_paths.h", diff --git a/android_webview/lib/aw_browser_dependency_factory_impl.cc b/android_webview/lib/aw_browser_dependency_factory_impl.cc index ff64ca6..4eba90a 100644 --- a/android_webview/lib/aw_browser_dependency_factory_impl.cc +++ b/android_webview/lib/aw_browser_dependency_factory_impl.cc @@ -6,6 +6,7 @@ // TODO(joth): Componentize or remove chrome/... dependencies. #include "android_webview/browser/net/aw_network_delegate.h" +#include "android_webview/native/android_protocol_handler.h" #include "android_webview/native/aw_contents_container.h" #include "base/bind.h" #include "base/bind_helpers.h" @@ -47,7 +48,9 @@ class WebContentsWrapper : public AwContentsContainer { } // namespace -AwBrowserDependencyFactoryImpl::AwBrowserDependencyFactoryImpl() {} +AwBrowserDependencyFactoryImpl::AwBrowserDependencyFactoryImpl() + : context_dependent_hooks_initialized_(false) { +} AwBrowserDependencyFactoryImpl::~AwBrowserDependencyFactoryImpl() {} @@ -69,11 +72,12 @@ void AwBrowserDependencyFactoryImpl::InitializeNetworkDelegateOnIOThread( network_delegate_.get()); } -void AwBrowserDependencyFactoryImpl::EnsureNetworkDelegateInitialized() { - if (initialized_network_delegate_) +void AwBrowserDependencyFactoryImpl::EnsureContextDependentHooksInitialized() +{ + if (context_dependent_hooks_initialized_) return; - initialized_network_delegate_ = true; Profile* profile = g_browser_process->profile_manager()->GetDefaultProfile(); + context_dependent_hooks_initialized_ = true; profile->GetRequestContext()->GetNetworkTaskRunner()->PostTask( FROM_HERE, base::Bind( @@ -82,11 +86,14 @@ void AwBrowserDependencyFactoryImpl::EnsureNetworkDelegateInitialized() { make_scoped_refptr(profile->GetRequestContext()), make_scoped_refptr( profile->GetOffTheRecordProfile()->GetRequestContext()))); + + net::URLRequestContextGetter* context_getter = profile->GetRequestContext(); + AndroidProtocolHandler::RegisterProtocols(context_getter); } content::BrowserContext* AwBrowserDependencyFactoryImpl::GetBrowserContext( bool incognito) { - EnsureNetworkDelegateInitialized(); + EnsureContextDependentHooksInitialized(); Profile* profile = g_browser_process->profile_manager()->GetDefaultProfile(); return incognito ? profile->GetOffTheRecordProfile() : profile; } diff --git a/android_webview/lib/aw_browser_dependency_factory_impl.h b/android_webview/lib/aw_browser_dependency_factory_impl.h index a3e7349..08eb1ef 100644 --- a/android_webview/lib/aw_browser_dependency_factory_impl.h +++ b/android_webview/lib/aw_browser_dependency_factory_impl.h @@ -36,12 +36,12 @@ class AwBrowserDependencyFactoryImpl : public AwBrowserDependencyFactory { void InitializeNetworkDelegateOnIOThread( net::URLRequestContextGetter* normal_context, net::URLRequestContextGetter* incognito_context); - void EnsureNetworkDelegateInitialized(); + void EnsureContextDependentHooksInitialized(); // Constructed and assigned on the IO thread. scoped_ptr<AwNetworkDelegate> network_delegate_; // Set on the UI thread. - bool initialized_network_delegate_; + bool context_dependent_hooks_initialized_; DISALLOW_COPY_AND_ASSIGN(AwBrowserDependencyFactoryImpl); }; diff --git a/android_webview/lib/main/aw_main_delegate.cc b/android_webview/lib/main/aw_main_delegate.cc index cd819ac..0d24080 100644 --- a/android_webview/lib/main/aw_main_delegate.cc +++ b/android_webview/lib/main/aw_main_delegate.cc @@ -4,15 +4,19 @@ #include "android_webview/lib/main/aw_main_delegate.h" +#include "android_webview/common/url_constants.h" #include "android_webview/lib/aw_browser_dependency_factory_impl.h" #include "android_webview/lib/aw_content_browser_client.h" #include "android_webview/renderer/aw_render_view_ext.h" #include "base/lazy_instance.h" #include "base/logging.h" +#include "base/utf_string_conversions.h" #include "chrome/common/chrome_paths.h" #include "chrome/renderer/chrome_content_renderer_client.h" #include "content/public/browser/browser_main_runner.h" #include "content/public/common/content_client.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebString.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebSecurityPolicy.h" namespace android_webview { @@ -21,10 +25,17 @@ namespace { // TODO(joth): Remove chrome/ dependency; move into android_webview/renderer class AwContentRendererClient : public chrome::ChromeContentRendererClient { public: - virtual void RenderViewCreated(content::RenderView* render_view) { + virtual void RenderViewCreated(content::RenderView* render_view) OVERRIDE { chrome::ChromeContentRendererClient::RenderViewCreated(render_view); AwRenderViewExt::RenderViewCreated(render_view); } + + virtual void RenderThreadStarted() OVERRIDE { + chrome::ChromeContentRendererClient::RenderThreadStarted(); + WebKit::WebString content_scheme( + ASCIIToUTF16(android_webview::kContentScheme)); + WebKit::WebSecurityPolicy::registerURLSchemeAsLocal(content_scheme); + } }; } diff --git a/android_webview/native/android_protocol_handler.cc b/android_webview/native/android_protocol_handler.cc new file mode 100644 index 0000000..d8ef423 --- /dev/null +++ b/android_webview/native/android_protocol_handler.cc @@ -0,0 +1,285 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// URL request job for reading from resources and assets. + +#include "android_webview/native/android_protocol_handler.h" + +#include "android_webview/common/url_constants.h" +#include "android_webview/native/android_stream_reader_url_request_job.h" +#include "base/android/jni_android.h" +#include "base/android/jni_helper.h" +#include "base/android/jni_string.h" +#include "base/string_util.h" +#include "content/public/common/url_constants.h" +#include "googleurl/src/gurl.h" +#include "jni/AndroidProtocolHandler_jni.h" +#include "net/base/io_buffer.h" +#include "net/base/mime_util.h" +#include "net/base/net_errors.h" +#include "net/base/net_util.h" +#include "net/http/http_util.h" +#include "net/url_request/url_request_context.h" +#include "net/url_request/url_request_context_getter.h" +#include "net/url_request/url_request_job_factory.h" +#include "net/url_request/url_request_job_manager.h" + +using base::android::AttachCurrentThread; +using base::android::ClearException; +using base::android::ConvertUTF8ToJavaString; +using base::android::ScopedJavaGlobalRef; +using base::android::ScopedJavaLocalRef; + +namespace { + +// Override resource context for reading resource and asset files. Used for +// testing. +JavaObjectWeakGlobalRef* g_resource_context = NULL; + +void ResetResourceContext(JavaObjectWeakGlobalRef* ref) { + if (g_resource_context) + delete g_resource_context; + + g_resource_context = ref; +} + +class AndroidStreamReaderURLRequestJobDelegateImpl + : public AndroidStreamReaderURLRequestJob::Delegate { + public: + AndroidStreamReaderURLRequestJobDelegateImpl(); + + virtual ScopedJavaLocalRef<jobject> OpenInputStream( + JNIEnv* env, + net::URLRequest* request) OVERRIDE; + + virtual bool GetMimeType(JNIEnv* env, + net::URLRequest* request, + jobject stream, + std::string* mime_type) OVERRIDE; + + virtual bool GetCharset(JNIEnv* env, + net::URLRequest* request, + jobject stream, + std::string* charset) OVERRIDE; + + virtual ~AndroidStreamReaderURLRequestJobDelegateImpl(); +}; + +class AssetFileProtocolInterceptor : + public net::URLRequestJobFactory::Interceptor { + public: + AssetFileProtocolInterceptor(); + virtual ~AssetFileProtocolInterceptor() OVERRIDE; + virtual net::URLRequestJob* MaybeIntercept( + net::URLRequest* request, + net::NetworkDelegate* network_delegate) const OVERRIDE; + virtual net::URLRequestJob* MaybeInterceptRedirect( + const GURL& location, + net::URLRequest* request, + net::NetworkDelegate* network_delegate) const OVERRIDE; + virtual net::URLRequestJob* MaybeInterceptResponse( + net::URLRequest* request, + net::NetworkDelegate* network_delegate) const OVERRIDE; + + private: + // file:///android_asset/ + const std::string asset_prefix_; + // file:///android_res/ + const std::string resource_prefix_; +}; + +} // namespace + +// static +net::URLRequestJob* AndroidProtocolHandler::Factory( + net::URLRequest* request, + net::NetworkDelegate* network_delegate, + const std::string& scheme) { + DCHECK(scheme == android_webview::kContentScheme); + return new AndroidStreamReaderURLRequestJob( + request, + network_delegate, + scoped_ptr<AndroidStreamReaderURLRequestJob::Delegate>( + new AndroidStreamReaderURLRequestJobDelegateImpl())); +} + +static void AddFileSchemeInterceptorOnIOThread( + net::URLRequestContextGetter* context_getter) { + // The job factory takes ownership of the interceptor. + const_cast<net::URLRequestJobFactory*>( + context_getter->GetURLRequestContext()->job_factory())->AddInterceptor( + new AssetFileProtocolInterceptor()); +} + +bool RegisterAndroidProtocolHandler(JNIEnv* env) { + return RegisterNativesImpl(env); +} + +// static +void AndroidProtocolHandler::RegisterProtocols( + net::URLRequestContextGetter* context_getter) { + // Register content://. Note that even though a scheme is + // registered here, it cannot be used by child processes until access to it is + // granted via ChildProcessSecurityPolicy::GrantScheme(). This is done in + // RenderViewHost. + // TODO(mnaganov): Convert into a ProtocolHandler. + net::URLRequestJobManager* job_manager = + net::URLRequestJobManager::GetInstance(); + job_manager->RegisterProtocolFactory(android_webview::kContentScheme, + &AndroidProtocolHandler::Factory); + + // Register a file: scheme interceptor for application assets. + context_getter->GetNetworkTaskRunner()->PostTask( + FROM_HERE, + base::Bind(&AddFileSchemeInterceptorOnIOThread, + make_scoped_refptr(context_getter))); + // TODO(mnaganov): Add an interceptor for the incognito profile? +} + +// Set a context object to be used for resolving resource queries. This can +// be used to override the default application context and redirect all +// resource queries to a specific context object, e.g., for the purposes of +// testing. +// +// |context| should be a android.content.Context instance or NULL to enable +// the use of the standard application context. +static void SetResourceContextForTesting(JNIEnv* env, jclass /*clazz*/, + jobject context) { + if (context) { + ResetResourceContext(new JavaObjectWeakGlobalRef(env, context)); + } else { + ResetResourceContext(NULL); + } +} + +static jstring GetAndroidAssetPath(JNIEnv* env, jclass /*clazz*/) { + // OK to release, JNI binding. + return ConvertUTF8ToJavaString( + env, android_webview::kAndroidAssetPath).Release(); +} + +static jstring GetAndroidResourcePath(JNIEnv* env, jclass /*clazz*/) { + // OK to release, JNI binding. + return ConvertUTF8ToJavaString( + env, android_webview::kAndroidResourcePath).Release(); +} + +static ScopedJavaLocalRef<jobject> GetResourceContext(JNIEnv* env) { + if (g_resource_context) + return g_resource_context->get(env); + ScopedJavaLocalRef<jobject> context; + // We have to reset as GetApplicationContext() returns a jobject with a + // global ref. The constructor that takes a jobject would expect a local ref + // and would assert. + context.Reset(env, base::android::GetApplicationContext()); + return context; +} + +AndroidStreamReaderURLRequestJobDelegateImpl:: +AndroidStreamReaderURLRequestJobDelegateImpl() { +} + +AndroidStreamReaderURLRequestJobDelegateImpl:: +~AndroidStreamReaderURLRequestJobDelegateImpl() { +} + +ScopedJavaLocalRef<jobject> +AndroidStreamReaderURLRequestJobDelegateImpl::OpenInputStream( + JNIEnv* env, net::URLRequest* request) { + DCHECK(request); + DCHECK(env); + + // Open the input stream. + ScopedJavaLocalRef<jstring> url = + ConvertUTF8ToJavaString(env, request->url().spec()); + ScopedJavaLocalRef<jobject> stream = Java_AndroidProtocolHandler_open( + env, + GetResourceContext(env).obj(), + url.obj()); + + // Check and clear pending exceptions. + if (ClearException(env) || stream.is_null()) { + DLOG(ERROR) << "Unable to open input stream for Android URL"; + return ScopedJavaLocalRef<jobject>(env, NULL); + } + return stream; +} + +bool AndroidStreamReaderURLRequestJobDelegateImpl::GetMimeType( + JNIEnv* env, + net::URLRequest* request, + jobject stream, + std::string* mime_type) { + DCHECK(env); + DCHECK(request); + DCHECK(mime_type); + + if (!stream) + return false; + + // Query the mime type from the Java side. It is possible for the query to + // fail, as the mime type cannot be determined for all supported schemes. + ScopedJavaLocalRef<jstring> url = + ConvertUTF8ToJavaString(env, request->url().spec()); + ScopedJavaLocalRef<jstring> returned_type = + Java_AndroidProtocolHandler_getMimeType(env, + GetResourceContext(env).obj(), + stream, url.obj()); + if (ClearException(env) || returned_type.is_null()) + return false; + + *mime_type = base::android::ConvertJavaStringToUTF8(returned_type); + return true; +} + +bool AndroidStreamReaderURLRequestJobDelegateImpl::GetCharset( + JNIEnv* env, + net::URLRequest* request, + jobject stream, + std::string* charset) { + // TODO: We should probably be getting this from the managed side. + return false; +} + +AssetFileProtocolInterceptor::AssetFileProtocolInterceptor() + : asset_prefix_(std::string(chrome::kFileScheme) + + std::string(content::kStandardSchemeSeparator) + + android_webview::kAndroidAssetPath), + resource_prefix_(std::string(chrome::kFileScheme) + + std::string(content::kStandardSchemeSeparator) + + android_webview::kAndroidResourcePath) { +} + +AssetFileProtocolInterceptor::~AssetFileProtocolInterceptor() { +} + +net::URLRequestJob* AssetFileProtocolInterceptor::MaybeIntercept( + net::URLRequest* request, net::NetworkDelegate* network_delegate) const { + if (!request->url().SchemeIsFile()) return NULL; + + const std::string& url = request->url().spec(); + if (!StartsWithASCII(url, asset_prefix_, /*case_sensitive=*/ true) && + !StartsWithASCII(url, resource_prefix_, /*case_sensitive=*/ true)) { + return NULL; + } + + return new AndroidStreamReaderURLRequestJob( + request, + network_delegate, + scoped_ptr<AndroidStreamReaderURLRequestJob::Delegate>( + new AndroidStreamReaderURLRequestJobDelegateImpl())); +} + +net::URLRequestJob* AssetFileProtocolInterceptor::MaybeInterceptRedirect( + const GURL& location, + net::URLRequest* request, + net::NetworkDelegate* network_delegate) const { + return NULL; +} + +net::URLRequestJob* AssetFileProtocolInterceptor::MaybeInterceptResponse( + net::URLRequest* request, + net::NetworkDelegate* network_delegate) const { + return NULL; +} diff --git a/android_webview/native/android_protocol_handler.h b/android_webview/native/android_protocol_handler.h new file mode 100644 index 0000000..a60b1f1 --- /dev/null +++ b/android_webview/native/android_protocol_handler.h @@ -0,0 +1,42 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef ANDROID_WEBVIEW_NATIVE_ANDROID_PROTOCOL_HANDLER_H_ +#define ANDROID_WEBVIEW_NATIVE_ANDROID_PROTOCOL_HANDLER_H_ + +#include "base/android/jni_android.h" +#include "net/url_request/url_request.h" + +namespace net { + +class URLRequestContextGetter; + +} + +// This class adds support for Android WebView-specific protocol schemes: +// +// - "content:" scheme is used for accessing data from Android content +// providers, see http://developer.android.com/guide/topics/providers/ +// content-provider-basics.html#ContentURIs +// +// - "file:" scheme extension for accessing application assets and resources +// (file:///android_asset/ and file:///android_res/), see +// http://developer.android.com/reference/android/webkit/ +// WebSettings.html#setAllowFileAccess(boolean) +// +class AndroidProtocolHandler { + public: + static net::URLRequest::ProtocolFactory Factory; + + // Register handlers for all supported Android protocol schemes. + static void RegisterProtocols( + net::URLRequestContextGetter* context_getter); + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(AndroidProtocolHandler); +}; + +bool RegisterAndroidProtocolHandler(JNIEnv* env); + +#endif // ANDROID_WEBVIEW_NATIVE_ANDROID_PROTOCOL_HANDLER_H_ diff --git a/android_webview/native/android_stream_reader_url_request_job.cc b/android_webview/native/android_stream_reader_url_request_job.cc new file mode 100644 index 0000000..b288da6 --- /dev/null +++ b/android_webview/native/android_stream_reader_url_request_job.cc @@ -0,0 +1,228 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "android_webview/native/android_stream_reader_url_request_job.h" + +#include "base/android/jni_android.h" +#include "base/android/jni_string.h" +#include "net/base/io_buffer.h" +#include "net/base/mime_util.h" +#include "net/base/net_errors.h" +#include "net/base/net_util.h" +#include "net/http/http_util.h" +#include "net/url_request/url_request.h" +#include "net/url_request/url_request_error_job.h" +#include "net/url_request/url_request_file_job.h" +#include "net/url_request/url_request_job_manager.h" +// Disable "Warnings treated as errors" for input_stream_jni as it's a Java +// system class and we have to generate C++ hooks for all methods in the class +// even if they're unused. +#pragma GCC diagnostic ignored "-Wunused-function" +#include "jni/InputStream_jni.h" + +using base::android::AttachCurrentThread; +using base::android::ClearException; +using base::android::ConvertUTF8ToJavaString; +using base::android::ScopedJavaGlobalRef; +using base::android::ScopedJavaLocalRef; +using JNI_InputStream::Java_InputStream_available; +using JNI_InputStream::Java_InputStream_skip; +using JNI_InputStream::Java_InputStream_read; + + +namespace { + +// Maximum number of bytes to be read in a single read. +const int kBufferSize = 4096; + +} // namespace + +bool RegisterAndroidStreamReaderUrlRequestJob(JNIEnv* env) { + return JNI_InputStream::RegisterNativesImpl(env); +} + +AndroidStreamReaderURLRequestJob::AndroidStreamReaderURLRequestJob( + net::URLRequest* request, + net::NetworkDelegate* network_delegate, + scoped_ptr<Delegate> delegate) + : URLRequestJob(request, network_delegate), + delegate_(delegate.Pass()) { + DCHECK(delegate_.get()); +} + +AndroidStreamReaderURLRequestJob::~AndroidStreamReaderURLRequestJob() { +} + +void AndroidStreamReaderURLRequestJob::Start() { + JNIEnv* env = AttachCurrentThread(); + DCHECK(env); + + stream_.Reset(env, delegate_->OpenInputStream(env, request()).obj()); + if (!stream_.obj()) { + NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED, + net::ERR_FAILED)); + return; + } + + if (VerifyRequestedRange(env) && SkipToRequestedRange(env)) + NotifyHeadersComplete(); +} + +bool AndroidStreamReaderURLRequestJob::VerifyRequestedRange(JNIEnv* env) { + int32_t size = Java_InputStream_available(env, stream_.obj()); + if (ClearException(env)) { + NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED, + net::ERR_FAILED)); + return false; + } + + if (size <= 0) + return true; + + // Check that the requested range was valid. + if (!byte_range_.ComputeBounds(size)) { + NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED, + net::ERR_REQUEST_RANGE_NOT_SATISFIABLE)); + return false; + } + + size = byte_range_.last_byte_position() - + byte_range_.first_byte_position() + 1; + DCHECK_GE(size, 0); + set_expected_content_size(size); + + return true; +} + +bool AndroidStreamReaderURLRequestJob::SkipToRequestedRange(JNIEnv* env) { + // Skip to the start of the requested data. This has to be done in a loop + // because the underlying InputStream is not guaranteed to skip the requested + // number of bytes. + if (byte_range_.first_byte_position() != 0) { + int64_t skipped, bytes_to_skip = byte_range_.first_byte_position(); + do { + skipped = Java_InputStream_skip(env, stream_.obj(), bytes_to_skip); + if (ClearException(env)) { + NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED, + net::ERR_FAILED)); + return false; + } + if (skipped <= 0) { + NotifyDone( + net::URLRequestStatus(net::URLRequestStatus::FAILED, + net::ERR_REQUEST_RANGE_NOT_SATISFIABLE)); + return false; + } + } while ((bytes_to_skip -= skipped) > 0); + } + return true; +} + +bool AndroidStreamReaderURLRequestJob::ReadRawData(net::IOBuffer* dest, + int dest_size, + int *bytes_read) { + DCHECK_NE(dest_size, 0); + DCHECK(bytes_read); + DCHECK(stream_.obj()); + + JNIEnv* env = AttachCurrentThread(); + DCHECK(env); + + if (!buffer_.obj()) { + // Allocate transfer buffer. + buffer_.Reset(env, env->NewByteArray(kBufferSize)); + if (ClearException(env)) { + NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED, + net::ERR_FAILED)); + return false; + } + } + + jbyteArray buffer = buffer_.obj(); + *bytes_read = 0; + if (!dest_size) + return true; + + // Read data in multiples of the buffer size. + while (dest_size > 0) { + int read_size = std::min(dest_size, kBufferSize); + // TODO(skyostil): Make this non-blocking + int32_t byte_count = + Java_InputStream_read(env, stream_.obj(), buffer, 0, read_size); + if (byte_count <= 0) { + // net::URLRequestJob will call NotifyDone for us after the end of the + // file is reached. + break; + } + + if (ClearException(env)) { + NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED, + net::ERR_FAILED)); + return false; + } + +#ifndef NDEBUG + int32_t buffer_length = env->GetArrayLength(buffer); + DCHECK_GE(read_size, byte_count); + DCHECK_GE(buffer_length, byte_count); +#endif // NDEBUG + + // Copy the data over to the provided C++ side buffer. + DCHECK_GE(dest_size, byte_count); + env->GetByteArrayRegion(buffer, 0, byte_count, + reinterpret_cast<jbyte*>(dest->data() + *bytes_read)); + + if (ClearException(env)) { + NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED, + net::ERR_FAILED)); + return false; + } + + *bytes_read += byte_count; + dest_size -= byte_count; + } + return true; +} + +bool AndroidStreamReaderURLRequestJob::GetMimeType( + std::string* mime_type) const { + JNIEnv* env = AttachCurrentThread(); + DCHECK(env); + + return delegate_->GetMimeType(env, + request(), + stream_.obj(), + mime_type); +} + +bool AndroidStreamReaderURLRequestJob::GetCharset( + std::string* charset) { + JNIEnv* env = AttachCurrentThread(); + DCHECK(env); + + return delegate_->GetCharset(env, + request(), + stream_.obj(), + charset); +} + +void AndroidStreamReaderURLRequestJob::SetExtraRequestHeaders( + const net::HttpRequestHeaders& headers) { + std::string range_header; + if (headers.GetHeader(net::HttpRequestHeaders::kRange, &range_header)) { + // We only care about "Range" header here. + std::vector<net::HttpByteRange> ranges; + if (net::HttpUtil::ParseRangeHeader(range_header, &ranges)) { + if (ranges.size() == 1) { + byte_range_ = ranges[0]; + } else { + // We don't support multiple range requests in one single URL request, + // because we need to do multipart encoding here. + NotifyDone(net::URLRequestStatus( + net::URLRequestStatus::FAILED, + net::ERR_REQUEST_RANGE_NOT_SATISFIABLE)); + } + } + } +} diff --git a/android_webview/native/android_stream_reader_url_request_job.h b/android_webview/native/android_stream_reader_url_request_job.h new file mode 100644 index 0000000..2e46572 --- /dev/null +++ b/android_webview/native/android_stream_reader_url_request_job.h @@ -0,0 +1,81 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef ANDROID_WEBVIEW_NATIVE_ANDROID_STREAM_READER_URL_REQUEST_JOB_H_ +#define ANDROID_WEBVIEW_NATIVE_ANDROID_STREAM_READER_URL_REQUEST_JOB_H_ + +#include "base/android/scoped_java_ref.h" +#include "base/memory/scoped_ptr.h" +#include "base/threading/non_thread_safe.h" +#include "net/http/http_byte_range.h" +#include "net/url_request/url_request_job.h" + +namespace net { +class URLRequest; +} + +// A request job that reads data from a Java InputStream. +class AndroidStreamReaderURLRequestJob : public net::URLRequestJob { + public: + /* + * We use a delegate so that we can share code for this job in slightly + * different contexts. + */ + class Delegate { + public: + virtual base::android::ScopedJavaLocalRef<jobject> OpenInputStream( + JNIEnv* env, + net::URLRequest* request) = 0; + + virtual bool GetMimeType( + JNIEnv* env, + net::URLRequest* request, + jobject stream, + std::string* mime_type) = 0; + + virtual bool GetCharset( + JNIEnv* env, + net::URLRequest* request, + jobject stream, + std::string* charset) = 0; + + virtual ~Delegate() {} + }; + + explicit AndroidStreamReaderURLRequestJob( + net::URLRequest* request, + net::NetworkDelegate* network_delegate, + scoped_ptr<Delegate> delegate); + + // URLRequestJob: + virtual void Start() OVERRIDE; + virtual bool ReadRawData(net::IOBuffer* buf, + int buf_size, + int* bytes_read) OVERRIDE; + virtual void SetExtraRequestHeaders( + const net::HttpRequestHeaders& headers) OVERRIDE; + virtual bool GetMimeType(std::string* mime_type) const OVERRIDE; + virtual bool GetCharset(std::string* charset) OVERRIDE; + + protected: + virtual ~AndroidStreamReaderURLRequestJob(); + + private: + // Verify the requested range against the stream size. + bool VerifyRequestedRange(JNIEnv* env); + + // Skip to the first byte of the requested read range. + bool SkipToRequestedRange(JNIEnv* env); + + net::HttpByteRange byte_range_; + scoped_ptr<Delegate> delegate_; + base::android::ScopedJavaGlobalRef<jobject> stream_; + base::android::ScopedJavaGlobalRef<jbyteArray> buffer_; + + DISALLOW_COPY_AND_ASSIGN(AndroidStreamReaderURLRequestJob); +}; + +bool RegisterAndroidStreamReaderUrlRequestJob(JNIEnv* env); + +#endif // ANDROID_WEBVIEW_NATIVE_ANDROID_STREAM_READER_URL_REQUEST_JOB_H_ diff --git a/android_webview/native/android_webview_jni_registrar.cc b/android_webview/native/android_webview_jni_registrar.cc index ba497db..8e877e1 100644 --- a/android_webview/native/android_webview_jni_registrar.cc +++ b/android_webview/native/android_webview_jni_registrar.cc @@ -4,6 +4,8 @@ #include "android_webview/native/android_webview_jni_registrar.h" +#include "android_webview/native/android_protocol_handler.h" +#include "android_webview/native/android_stream_reader_url_request_job.h" #include "android_webview/native/android_web_view_util.h" #include "android_webview/native/aw_contents.h" #include "android_webview/native/aw_contents_io_thread_client.h" @@ -17,6 +19,9 @@ namespace android_webview { static base::android::RegistrationMethod kWebViewRegisteredMethods[] = { + { "AndroidProtocolHandler", RegisterAndroidProtocolHandler }, + { "AndroidStreamReaderUrlRequestJob", + RegisterAndroidStreamReaderUrlRequestJob }, { "AndroidWebViewUtil", RegisterAndroidWebViewUtil }, { "AwContents", RegisterAwContents }, { "AwContentsIoThreadClient", RegisterAwContentsIoThreadClient}, diff --git a/android_webview/native/webview_native.gyp b/android_webview/native/webview_native.gyp index 83a537f..57d0e89 100644 --- a/android_webview/native/webview_native.gyp +++ b/android_webview/native/webview_native.gyp @@ -20,6 +20,10 @@ '<(SHARED_INTERMEDIATE_DIR)/android_webview', ], 'sources': [ + 'android_protocol_handler.cc', + 'android_protocol_handler.h', + 'android_stream_reader_url_request_job.cc', + 'android_stream_reader_url_request_job.h', 'android_web_view_util.cc', 'android_web_view_util.h', 'android_webview_jni_registrar.cc', @@ -46,9 +50,20 @@ ], }, { + 'target_name': 'android_jar_jni_headers', + 'type': 'none', + 'variables': { + 'jni_gen_dir': 'android_webview', + 'input_java_class': 'java/io/InputStream.class', + 'input_jar_file': '<(android_sdk)/android.jar', + }, + 'includes': [ '../../build/jar_file_jni_generator.gypi' ], + }, + { 'target_name': 'android_webview_native_jni', 'type': 'none', 'sources': [ + '../java/src/org/chromium/android_webview/AndroidProtocolHandler.java', '../java/src/org/chromium/android_webview/AndroidWebViewUtil.java', '../java/src/org/chromium/android_webview/AwContents.java', '../java/src/org/chromium/android_webview/AwContentsIoThreadClient.java', @@ -61,6 +76,9 @@ 'jni_gen_dir': 'android_webview', }, 'includes': [ '../../build/jni_generator.gypi' ], + 'dependencies': [ + 'android_jar_jni_headers', + ], }, ], } |