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 "ResourceError.h" 32 #include "ResourceHandle.h" 33 #include "ResourceHandleClient.h" 34 #include "ResourceHandleInternal.h" 35 #include "ResourceResponse.h" 36 #include "SkUtils.h" 37 #ifdef ANDROID_INSTRUMENT 38 #include "TimeCounter.h" 39 #endif 40 #include "WebCoreJni.h" 41 42 #include <JNIHelp.h> 43 #include <JNIUtility.h> 44 #include <SkTypes.h> 45 #include <stdlib.h> 46 #include <utils/misc.h> 47 #include <wtf/Platform.h> 48 #include <wtf/text/CString.h> 49 50 namespace android { 51 52 // ---------------------------------------------------------------------------- 53 54 static 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 70 PassRefPtr<WebCore::ResourceLoaderAndroid> WebCoreResourceLoader::create(JNIEnv *env, jobject jLoadListener) 71 { 72 return adoptRef<WebCore::ResourceLoaderAndroid>(new WebCoreResourceLoader(env, jLoadListener)); 73 } 74 75 WebCoreResourceLoader::WebCoreResourceLoader(JNIEnv *env, jobject jLoadListener) 76 : mPausedLoad(false) 77 { 78 mJLoader = env->NewGlobalRef(jLoadListener); 79 } 80 81 WebCoreResourceLoader::~WebCoreResourceLoader() 82 { 83 JNIEnv* env = JSC::Bindings::getJNIEnv(); 84 SET_NATIVE_HANDLE(env, mJLoader, 0); 85 env->DeleteGlobalRef(mJLoader); 86 mJLoader = 0; 87 } 88 89 void WebCoreResourceLoader::cancel() 90 { 91 JNIEnv* env = JSC::Bindings::getJNIEnv(); 92 env->CallVoidMethod(mJLoader, gResourceLoader.mCancelMethodID); 93 checkException(env); 94 } 95 96 void WebCoreResourceLoader::downloadFile() 97 { 98 JNIEnv* env = JSC::Bindings::getJNIEnv(); 99 env->CallVoidMethod(mJLoader, gResourceLoader.mDownloadFileMethodID); 100 checkException(env); 101 } 102 103 void 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 */ 119 bool WebCoreResourceLoader::willLoadFromCache(const WebCore::KURL& url, int64_t identifier) 120 { 121 JNIEnv* env = JSC::Bindings::getJNIEnv(); 122 WTF::String urlStr = url.string(); 123 jstring jUrlStr = wtfStringToJstring(env, urlStr); 124 jclass resourceLoader = env->FindClass("android/webkit/LoadListener"); 125 bool val = env->CallStaticBooleanMethod(resourceLoader, gResourceLoader.mWillLoadFromCacheMethodID, jUrlStr, identifier); 126 checkException(env); 127 env->DeleteLocalRef(resourceLoader); 128 env->DeleteLocalRef(jUrlStr); 129 130 return val; 131 } 132 133 // ---------------------------------------------------------------------------- 134 void 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 response->setHTTPHeaderField(jstringToWtfString(env, key), jstringToWtfString(env, val)); 146 } 147 148 jint WebCoreResourceLoader::CreateResponse(JNIEnv* env, jobject obj, jstring url, jint statusCode, 149 jstring statusText, jstring mimeType, jlong expectedLength, 150 jstring encoding) 151 { 152 #ifdef ANDROID_INSTRUMENT 153 TimeCounterAuto counter(TimeCounter::ResourceTimeCounter); 154 #endif 155 LOG_ASSERT(url, "Must have a url in the response!"); 156 WebCore::KURL kurl(WebCore::ParsedURLString, jstringToWtfString(env, url)); 157 WTF::String encodingStr; 158 WTF::String mimeTypeStr; 159 if (mimeType) { 160 mimeTypeStr = jstringToWtfString(env, mimeType); 161 LOGV("Response setMIMEType: %s", mimeTypeStr.latin1().data()); 162 } 163 if (encoding) { 164 encodingStr = jstringToWtfString(env, encoding); 165 LOGV("Response setTextEncodingName: %s", encodingStr.latin1().data()); 166 } 167 WebCore::ResourceResponse* response = new WebCore::ResourceResponse( 168 kurl, mimeTypeStr, (long long)expectedLength, 169 encodingStr, WTF::String()); 170 response->setHTTPStatusCode(statusCode); 171 if (statusText) { 172 WTF::String status = jstringToWtfString(env, statusText); 173 response->setHTTPStatusText(status); 174 LOGV("Response setStatusText: %s", status.latin1().data()); 175 } 176 return (int)response; 177 } 178 179 void WebCoreResourceLoader::ReceivedResponse(JNIEnv* env, jobject obj, jint nativeResponse) 180 { 181 #ifdef ANDROID_INSTRUMENT 182 TimeCounterAuto counter(TimeCounter::ResourceTimeCounter); 183 #endif 184 WebCore::ResourceHandle* handle = GET_NATIVE_HANDLE(env, obj); 185 LOG_ASSERT(handle, "nativeReceivedResponse must take a valid handle!"); 186 // ResourceLoader::didFail() can set handle to be NULL, we need to check 187 if (!handle) 188 return; 189 190 WebCore::ResourceResponse* response = (WebCore::ResourceResponse*)nativeResponse; 191 LOG_ASSERT(response, "nativeReceivedResponse must take a valid resource pointer!"); 192 handle->client()->didReceiveResponse(handle, *response); 193 // As the client makes a copy of the response, delete it here. 194 delete response; 195 } 196 197 void WebCoreResourceLoader::AddData(JNIEnv* env, jobject obj, jbyteArray dataArray, jint length) 198 { 199 #ifdef ANDROID_INSTRUMENT 200 TimeCounterAuto counter(TimeCounter::ResourceTimeCounter); 201 #endif 202 LOGV("webcore_resourceloader data(%d)", length); 203 204 WebCore::ResourceHandle* handle = GET_NATIVE_HANDLE(env, obj); 205 LOG_ASSERT(handle, "nativeAddData must take a valid handle!"); 206 // ResourceLoader::didFail() can set handle to be NULL, we need to check 207 if (!handle) 208 return; 209 210 SkAutoMemoryUsageProbe mup("android_webcore_resourceloader_nativeAddData"); 211 212 bool result = false; 213 jbyte * data = env->GetByteArrayElements(dataArray, NULL); 214 215 LOG_ASSERT(handle->client(), "Why do we not have a client?"); 216 handle->client()->didReceiveData(handle, (const char *)data, length, length); 217 env->ReleaseByteArrayElements(dataArray, data, JNI_ABORT); 218 } 219 220 void WebCoreResourceLoader::Finished(JNIEnv* env, jobject obj) 221 { 222 #ifdef ANDROID_INSTRUMENT 223 TimeCounterAuto counter(TimeCounter::ResourceTimeCounter); 224 #endif 225 LOGV("webcore_resourceloader finished"); 226 WebCore::ResourceHandle* handle = GET_NATIVE_HANDLE(env, obj); 227 LOG_ASSERT(handle, "nativeFinished must take a valid handle!"); 228 // ResourceLoader::didFail() can set handle to be NULL, we need to check 229 if (!handle) 230 return; 231 232 LOG_ASSERT(handle->client(), "Why do we not have a client?"); 233 handle->client()->didFinishLoading(handle, 0); 234 } 235 236 jstring WebCoreResourceLoader::RedirectedToUrl(JNIEnv* env, jobject obj, 237 jstring baseUrl, jstring redirectTo, jint nativeResponse) 238 { 239 #ifdef ANDROID_INSTRUMENT 240 TimeCounterAuto counter(TimeCounter::ResourceTimeCounter); 241 #endif 242 LOGV("webcore_resourceloader redirectedToUrl"); 243 WebCore::ResourceHandle* handle = GET_NATIVE_HANDLE(env, obj); 244 LOG_ASSERT(handle, "nativeRedirectedToUrl must take a valid handle!"); 245 // ResourceLoader::didFail() can set handle to be NULL, we need to check 246 if (!handle) 247 return NULL; 248 249 LOG_ASSERT(handle->client(), "Why do we not have a client?"); 250 WebCore::ResourceRequest r = handle->firstRequest(); 251 WebCore::KURL url(WebCore::KURL(WebCore::ParsedURLString, jstringToWtfString(env, baseUrl)), 252 jstringToWtfString(env, redirectTo)); 253 WebCore::ResourceResponse* response = (WebCore::ResourceResponse*)nativeResponse; 254 // If the url fails to resolve the relative path, return null. 255 if (url.protocol().isEmpty()) { 256 delete response; 257 return NULL; 258 } else { 259 // Ensure the protocol is lowercase. 260 url.setProtocol(url.protocol().lower()); 261 } 262 // Set the url after updating the protocol. 263 r.setURL(url); 264 if (r.httpMethod() == "POST") { 265 r.setHTTPMethod("GET"); 266 r.clearHTTPReferrer(); 267 r.setHTTPBody(0); 268 r.setHTTPContentType(""); 269 } 270 handle->client()->willSendRequest(handle, r, *response); 271 delete response; 272 return wtfStringToJstring(env, url.string()); 273 } 274 275 void WebCoreResourceLoader::Error(JNIEnv* env, jobject obj, jint id, jstring description, 276 jstring failingUrl) 277 { 278 #ifdef ANDROID_INSTRUMENT 279 TimeCounterAuto counter(TimeCounter::ResourceTimeCounter); 280 #endif 281 LOGV("webcore_resourceloader error"); 282 WebCore::ResourceHandle* handle = GET_NATIVE_HANDLE(env, obj); 283 LOG_ASSERT(handle, "nativeError must take a valid handle!"); 284 // ResourceLoader::didFail() can set handle to be NULL, we need to check 285 if (!handle) 286 return; 287 288 handle->client()->didFail(handle, WebCore::ResourceError("", id, 289 jstringToWtfString(env, failingUrl), jstringToWtfString(env, description))); 290 } 291 292 // ---------------------------------------------------------------------------- 293 294 /* 295 * JNI registration. 296 */ 297 static JNINativeMethod gResourceloaderMethods[] = { 298 /* name, signature, funcPtr */ 299 { "nativeSetResponseHeader", "(ILjava/lang/String;Ljava/lang/String;)V", 300 (void*) WebCoreResourceLoader::SetResponseHeader }, 301 { "nativeCreateResponse", "(Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;JLjava/lang/String;)I", 302 (void*) WebCoreResourceLoader::CreateResponse }, 303 { "nativeReceivedResponse", "(I)V", 304 (void*) WebCoreResourceLoader::ReceivedResponse }, 305 { "nativeAddData", "([BI)V", 306 (void*) WebCoreResourceLoader::AddData }, 307 { "nativeFinished", "()V", 308 (void*) WebCoreResourceLoader::Finished }, 309 { "nativeRedirectedToUrl", "(Ljava/lang/String;Ljava/lang/String;I)Ljava/lang/String;", 310 (void*) WebCoreResourceLoader::RedirectedToUrl }, 311 { "nativeError", "(ILjava/lang/String;Ljava/lang/String;)V", 312 (void*) WebCoreResourceLoader::Error } 313 }; 314 315 int registerResourceLoader(JNIEnv* env) 316 { 317 jclass resourceLoader = env->FindClass("android/webkit/LoadListener"); 318 LOG_FATAL_IF(resourceLoader == NULL, 319 "Unable to find class android/webkit/LoadListener"); 320 321 gResourceLoader.mObject = 322 env->GetFieldID(resourceLoader, "mNativeLoader", "I"); 323 LOG_FATAL_IF(gResourceLoader.mObject == NULL, 324 "Unable to find android/webkit/LoadListener.mNativeLoader"); 325 326 gResourceLoader.mCancelMethodID = 327 env->GetMethodID(resourceLoader, "cancel", "()V"); 328 LOG_FATAL_IF(gResourceLoader.mCancelMethodID == NULL, 329 "Could not find method cancel on LoadListener"); 330 331 gResourceLoader.mDownloadFileMethodID = 332 env->GetMethodID(resourceLoader, "downloadFile", "()V"); 333 LOG_FATAL_IF(gResourceLoader.mDownloadFileMethodID == NULL, 334 "Could not find method downloadFile on LoadListener"); 335 336 gResourceLoader.mPauseLoadMethodID = 337 env->GetMethodID(resourceLoader, "pauseLoad", "(Z)V"); 338 LOG_FATAL_IF(gResourceLoader.mPauseLoadMethodID == NULL, 339 "Could not find method pauseLoad on LoadListener"); 340 341 gResourceLoader.mWillLoadFromCacheMethodID = 342 env->GetStaticMethodID(resourceLoader, "willLoadFromCache", "(Ljava/lang/String;J)Z"); 343 LOG_FATAL_IF(gResourceLoader.mWillLoadFromCacheMethodID == NULL, 344 "Could not find static method willLoadFromCache on LoadListener"); 345 346 env->DeleteLocalRef(resourceLoader); 347 348 return jniRegisterNativeMethods(env, "android/webkit/LoadListener", 349 gResourceloaderMethods, NELEM(gResourceloaderMethods)); 350 } 351 352 } /* namespace android */ 353