summaryrefslogtreecommitdiffstats
path: root/android_webview
diff options
context:
space:
mode:
authormnaganov@chromium.org <mnaganov@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-09-27 18:36:26 +0000
committermnaganov@chromium.org <mnaganov@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-09-27 18:36:26 +0000
commite7bee399b0d064cdd281bc567a2736852079e87a (patch)
treee9c1b83b4d70a1d8bc52021db2d96a91f33eb8e7 /android_webview
parent0d2cb888776e6a8e0dd4939390a2b9c220ff40fa (diff)
downloadchromium_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.gyp2
-rw-r--r--android_webview/common/url_constants.cc20
-rw-r--r--android_webview/common/url_constants.h19
-rw-r--r--android_webview/java/src/org/chromium/android_webview/AndroidProtocolHandler.java233
-rw-r--r--android_webview/lib/DEPS3
-rw-r--r--android_webview/lib/aw_browser_dependency_factory_impl.cc17
-rw-r--r--android_webview/lib/aw_browser_dependency_factory_impl.h4
-rw-r--r--android_webview/lib/main/aw_main_delegate.cc13
-rw-r--r--android_webview/native/android_protocol_handler.cc285
-rw-r--r--android_webview/native/android_protocol_handler.h42
-rw-r--r--android_webview/native/android_stream_reader_url_request_job.cc228
-rw-r--r--android_webview/native/android_stream_reader_url_request_job.h81
-rw-r--r--android_webview/native/android_webview_jni_registrar.cc5
-rw-r--r--android_webview/native/webview_native.gyp18
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',
+ ],
},
],
}