1/* 2 * Copyright 2006, The Android Open Source Project 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * * Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * * Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY 14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26#define LOG_TAG "webcoreglue" 27 28#include "config.h" 29#include "WebCoreResourceLoader.h" 30 31#include "CString.h" 32#include "ResourceError.h" 33#include "ResourceHandle.h" 34#include "ResourceHandleClient.h" 35#include "ResourceHandleInternal.h" 36#include "ResourceResponse.h" 37#include "SkUtils.h" 38#ifdef ANDROID_INSTRUMENT 39#include "TimeCounter.h" 40#endif 41#include "WebCoreJni.h" 42 43#include <JNIHelp.h> 44#include <JNIUtility.h> 45#include <SkTypes.h> 46#include <stdlib.h> 47#include <utils/misc.h> 48#include <wtf/Platform.h> 49 50namespace android { 51 52// ---------------------------------------------------------------------------- 53 54static struct resourceloader_t { 55 jfieldID mObject; 56 jmethodID mCancelMethodID; 57 jmethodID mDownloadFileMethodID; 58 jmethodID mWillLoadFromCacheMethodID; 59 jmethodID mPauseLoadMethodID; 60} gResourceLoader; 61 62// ---------------------------------------------------------------------------- 63 64#define GET_NATIVE_HANDLE(env, obj) ((WebCore::ResourceHandle*)env->GetIntField(obj, gResourceLoader.mObject)) 65#define SET_NATIVE_HANDLE(env, obj, handle) (env->SetIntField(obj, gResourceLoader.mObject, handle)) 66 67//----------------------------------------------------------------------------- 68// ResourceLoadHandler 69 70PassRefPtr<WebCore::ResourceLoaderAndroid> WebCoreResourceLoader::create(JNIEnv *env, jobject jLoadListener) 71{ 72 return adoptRef<WebCore::ResourceLoaderAndroid>(new WebCoreResourceLoader(env, jLoadListener)); 73} 74 75WebCoreResourceLoader::WebCoreResourceLoader(JNIEnv *env, jobject jLoadListener) 76 : mPausedLoad(false) 77{ 78 mJLoader = env->NewGlobalRef(jLoadListener); 79} 80 81WebCoreResourceLoader::~WebCoreResourceLoader() 82{ 83 JNIEnv* env = JSC::Bindings::getJNIEnv(); 84 SET_NATIVE_HANDLE(env, mJLoader, 0); 85 env->DeleteGlobalRef(mJLoader); 86 mJLoader = 0; 87} 88 89void WebCoreResourceLoader::cancel() 90{ 91 JNIEnv* env = JSC::Bindings::getJNIEnv(); 92 env->CallVoidMethod(mJLoader, gResourceLoader.mCancelMethodID); 93 checkException(env); 94} 95 96void WebCoreResourceLoader::downloadFile() 97{ 98 JNIEnv* env = JSC::Bindings::getJNIEnv(); 99 env->CallVoidMethod(mJLoader, gResourceLoader.mDownloadFileMethodID); 100 checkException(env); 101} 102 103void WebCoreResourceLoader::pauseLoad(bool pause) 104{ 105 if (mPausedLoad == pause) 106 return; 107 108 mPausedLoad = pause; 109 JNIEnv* env = JSC::Bindings::getJNIEnv(); 110 env->CallVoidMethod(mJLoader, gResourceLoader.mPauseLoadMethodID, pause); 111 checkException(env); 112} 113 114/* 115* This static method is called to check to see if a POST response is in 116* the cache. This may be slow, but is only used during a navigation to 117* a POST response. 118*/ 119bool WebCoreResourceLoader::willLoadFromCache(const WebCore::KURL& url, int64_t identifier) 120{ 121 JNIEnv* env = JSC::Bindings::getJNIEnv(); 122 WebCore::String urlStr = url.string(); 123 jstring jUrlStr = env->NewString(urlStr.characters(), urlStr.length()); 124 jclass resourceLoader = env->FindClass("android/webkit/LoadListener"); 125 bool val = env->CallStaticBooleanMethod(resourceLoader, 126 gResourceLoader.mWillLoadFromCacheMethodID, jUrlStr, identifier); 127 checkException(env); 128 env->DeleteLocalRef(jUrlStr); 129 130 return val; 131} 132 133// ---------------------------------------------------------------------------- 134void WebCoreResourceLoader::SetResponseHeader(JNIEnv* env, jobject obj, jint nativeResponse, jstring key, jstring val) 135{ 136#ifdef ANDROID_INSTRUMENT 137 TimeCounterAuto counter(TimeCounter::ResourceTimeCounter); 138#endif 139 140 WebCore::ResourceResponse* response = (WebCore::ResourceResponse*)nativeResponse; 141 LOG_ASSERT(response, "nativeSetResponseHeader must take a valid response pointer!"); 142 143 LOG_ASSERT(key, "How did a null value become a key?"); 144 if (val) { 145 WebCore::String valStr = to_string(env, val); 146 if (!valStr.isEmpty()) 147 response->setHTTPHeaderField(to_string(env, key), valStr); 148 } 149} 150 151jint WebCoreResourceLoader::CreateResponse(JNIEnv* env, jobject obj, jstring url, jint statusCode, 152 jstring statusText, jstring mimeType, jlong expectedLength, 153 jstring encoding) 154{ 155#ifdef ANDROID_INSTRUMENT 156 TimeCounterAuto counter(TimeCounter::ResourceTimeCounter); 157#endif 158 LOG_ASSERT(url, "Must have a url in the response!"); 159 WebCore::KURL kurl(WebCore::ParsedURLString, to_string(env, url)); 160 WebCore::String encodingStr; 161 WebCore::String mimeTypeStr; 162 if (mimeType) { 163 mimeTypeStr = to_string(env, mimeType); 164 LOGV("Response setMIMEType: %s", mimeTypeStr.latin1().data()); 165 } 166 if (encoding) { 167 encodingStr = to_string(env, encoding); 168 LOGV("Response setTextEncodingName: %s", encodingStr.latin1().data()); 169 } 170 WebCore::ResourceResponse* response = new WebCore::ResourceResponse( 171 kurl, mimeTypeStr, (long long)expectedLength, 172 encodingStr, WebCore::String()); 173 response->setHTTPStatusCode(statusCode); 174 if (statusText) { 175 WebCore::String status = to_string(env, statusText); 176 response->setHTTPStatusText(status); 177 LOGV("Response setStatusText: %s", status.latin1().data()); 178 } 179 return (int)response; 180} 181 182void WebCoreResourceLoader::ReceivedResponse(JNIEnv* env, jobject obj, jint nativeResponse) 183{ 184#ifdef ANDROID_INSTRUMENT 185 TimeCounterAuto counter(TimeCounter::ResourceTimeCounter); 186#endif 187 WebCore::ResourceHandle* handle = GET_NATIVE_HANDLE(env, obj); 188 LOG_ASSERT(handle, "nativeReceivedResponse must take a valid handle!"); 189 // ResourceLoader::didFail() can set handle to be NULL, we need to check 190 if (!handle) 191 return; 192 193 WebCore::ResourceResponse* response = (WebCore::ResourceResponse*)nativeResponse; 194 LOG_ASSERT(response, "nativeReceivedResponse must take a valid resource pointer!"); 195 handle->client()->didReceiveResponse(handle, *response); 196 // As the client makes a copy of the response, delete it here. 197 delete response; 198} 199 200void WebCoreResourceLoader::AddData(JNIEnv* env, jobject obj, jbyteArray dataArray, jint length) 201{ 202#ifdef ANDROID_INSTRUMENT 203 TimeCounterAuto counter(TimeCounter::ResourceTimeCounter); 204#endif 205 LOGV("webcore_resourceloader data(%d)", length); 206 207 WebCore::ResourceHandle* handle = GET_NATIVE_HANDLE(env, obj); 208 LOG_ASSERT(handle, "nativeAddData must take a valid handle!"); 209 // ResourceLoader::didFail() can set handle to be NULL, we need to check 210 if (!handle) 211 return; 212 213 SkAutoMemoryUsageProbe mup("android_webcore_resourceloader_nativeAddData"); 214 215 bool result = false; 216 jbyte * data = env->GetByteArrayElements(dataArray, NULL); 217 218 LOG_ASSERT(handle->client(), "Why do we not have a client?"); 219 handle->client()->didReceiveData(handle, (const char *)data, length, length); 220 env->ReleaseByteArrayElements(dataArray, data, JNI_ABORT); 221} 222 223void WebCoreResourceLoader::Finished(JNIEnv* env, jobject obj) 224{ 225#ifdef ANDROID_INSTRUMENT 226 TimeCounterAuto counter(TimeCounter::ResourceTimeCounter); 227#endif 228 LOGV("webcore_resourceloader finished"); 229 WebCore::ResourceHandle* handle = GET_NATIVE_HANDLE(env, obj); 230 LOG_ASSERT(handle, "nativeFinished must take a valid handle!"); 231 // ResourceLoader::didFail() can set handle to be NULL, we need to check 232 if (!handle) 233 return; 234 235 LOG_ASSERT(handle->client(), "Why do we not have a client?"); 236 handle->client()->didFinishLoading(handle); 237} 238 239jstring WebCoreResourceLoader::RedirectedToUrl(JNIEnv* env, jobject obj, 240 jstring baseUrl, jstring redirectTo, jint nativeResponse) 241{ 242#ifdef ANDROID_INSTRUMENT 243 TimeCounterAuto counter(TimeCounter::ResourceTimeCounter); 244#endif 245 LOGV("webcore_resourceloader redirectedToUrl"); 246 WebCore::ResourceHandle* handle = GET_NATIVE_HANDLE(env, obj); 247 LOG_ASSERT(handle, "nativeRedirectedToUrl must take a valid handle!"); 248 // ResourceLoader::didFail() can set handle to be NULL, we need to check 249 if (!handle) 250 return NULL; 251 252 LOG_ASSERT(handle->client(), "Why do we not have a client?"); 253 WebCore::ResourceRequest r = handle->request(); 254 WebCore::KURL url(WebCore::KURL(WebCore::ParsedURLString, to_string(env, baseUrl)), 255 to_string(env, redirectTo)); 256 WebCore::ResourceResponse* response = (WebCore::ResourceResponse*)nativeResponse; 257 // If the url fails to resolve the relative path, return null. 258 if (url.protocol().isEmpty()) { 259 delete response; 260 return NULL; 261 } else { 262 // Ensure the protocol is lowercase. 263 url.setProtocol(url.protocol().lower()); 264 } 265 // Set the url after updating the protocol. 266 r.setURL(url); 267 if (r.httpMethod() == "POST") { 268 r.setHTTPMethod("GET"); 269 r.clearHTTPReferrer(); 270 r.setHTTPBody(0); 271 r.setHTTPContentType(""); 272 } 273 handle->client()->willSendRequest(handle, r, *response); 274 delete response; 275 WebCore::String s = url.string(); 276 return env->NewString((unsigned short*)s.characters(), s.length()); 277} 278 279void WebCoreResourceLoader::Error(JNIEnv* env, jobject obj, jint id, jstring description, 280 jstring failingUrl) 281{ 282#ifdef ANDROID_INSTRUMENT 283 TimeCounterAuto counter(TimeCounter::ResourceTimeCounter); 284#endif 285 LOGV("webcore_resourceloader error"); 286 WebCore::ResourceHandle* handle = GET_NATIVE_HANDLE(env, obj); 287 LOG_ASSERT(handle, "nativeError must take a valid handle!"); 288 // ResourceLoader::didFail() can set handle to be NULL, we need to check 289 if (!handle) 290 return; 291 292 handle->client()->didFail(handle, WebCore::ResourceError("", id, 293 to_string(env, failingUrl), to_string(env, description))); 294} 295 296// ---------------------------------------------------------------------------- 297 298/* 299 * JNI registration. 300 */ 301static JNINativeMethod gResourceloaderMethods[] = { 302 /* name, signature, funcPtr */ 303 { "nativeSetResponseHeader", "(ILjava/lang/String;Ljava/lang/String;)V", 304 (void*) WebCoreResourceLoader::SetResponseHeader }, 305 { "nativeCreateResponse", "(Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;JLjava/lang/String;)I", 306 (void*) WebCoreResourceLoader::CreateResponse }, 307 { "nativeReceivedResponse", "(I)V", 308 (void*) WebCoreResourceLoader::ReceivedResponse }, 309 { "nativeAddData", "([BI)V", 310 (void*) WebCoreResourceLoader::AddData }, 311 { "nativeFinished", "()V", 312 (void*) WebCoreResourceLoader::Finished }, 313 { "nativeRedirectedToUrl", "(Ljava/lang/String;Ljava/lang/String;I)Ljava/lang/String;", 314 (void*) WebCoreResourceLoader::RedirectedToUrl }, 315 { "nativeError", "(ILjava/lang/String;Ljava/lang/String;)V", 316 (void*) WebCoreResourceLoader::Error } 317}; 318 319int register_resource_loader(JNIEnv* env) 320{ 321 jclass resourceLoader = env->FindClass("android/webkit/LoadListener"); 322 LOG_FATAL_IF(resourceLoader == NULL, 323 "Unable to find class android/webkit/LoadListener"); 324 325 gResourceLoader.mObject = 326 env->GetFieldID(resourceLoader, "mNativeLoader", "I"); 327 LOG_FATAL_IF(gResourceLoader.mObject == NULL, 328 "Unable to find android/webkit/LoadListener.mNativeLoader"); 329 330 gResourceLoader.mCancelMethodID = 331 env->GetMethodID(resourceLoader, "cancel", "()V"); 332 LOG_FATAL_IF(gResourceLoader.mCancelMethodID == NULL, 333 "Could not find method cancel on LoadListener"); 334 335 gResourceLoader.mDownloadFileMethodID = 336 env->GetMethodID(resourceLoader, "downloadFile", "()V"); 337 LOG_FATAL_IF(gResourceLoader.mDownloadFileMethodID == NULL, 338 "Could not find method downloadFile on LoadListener"); 339 340 gResourceLoader.mPauseLoadMethodID = 341 env->GetMethodID(resourceLoader, "pauseLoad", "(Z)V"); 342 LOG_FATAL_IF(gResourceLoader.mPauseLoadMethodID == NULL, 343 "Could not find method pauseLoad on LoadListener"); 344 345 gResourceLoader.mWillLoadFromCacheMethodID = 346 env->GetStaticMethodID(resourceLoader, "willLoadFromCache", "(Ljava/lang/String;J)Z"); 347 LOG_FATAL_IF(gResourceLoader.mWillLoadFromCacheMethodID == NULL, 348 "Could not find static method willLoadFromCache on LoadListener"); 349 350 return jniRegisterNativeMethods(env, "android/webkit/LoadListener", 351 gResourceloaderMethods, NELEM(gResourceloaderMethods)); 352} 353 354} /* namespace android */ 355