diff options
Diffstat (limited to 'Source/WebKit/android/jni')
36 files changed, 13718 insertions, 0 deletions
diff --git a/Source/WebKit/android/jni/CacheManager.cpp b/Source/WebKit/android/jni/CacheManager.cpp new file mode 100644 index 0000000..144b62a --- /dev/null +++ b/Source/WebKit/android/jni/CacheManager.cpp @@ -0,0 +1,143 @@ +/* + * Copyright 2011, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" + +#if USE(CHROME_NETWORK_STACK) + +#include "ChromiumIncludes.h" +#include "WebCache.h" +#include "WebCoreJni.h" + +#include <JNIHelp.h> +#include <platform/FileSystem.h> +#include <platform/text/Base64.h> +#include <wtf/text/CString.h> +#include <wtf/text/WTFString.h> + +using namespace WebCore; +using namespace base; +using namespace disk_cache; +using namespace net; +using namespace std; + +namespace android { + +// JNI for android.webkit.CacheManager +static const char* javaCacheManagerClass = "android/webkit/CacheManager"; + +static void setStringField(JNIEnv* env, const jobject& object, const jfieldID& field, const String& str) +{ + jstring jstr = wtfStringToJstring(env, str); + env->SetObjectField(object, field, jstr); + env->DeleteLocalRef(jstr); +} + +static void setFieldFromHeaderIfPresent(CacheResult* result, const char* header, JNIEnv* env, const jobject& object, const jfieldID& field, bool allowEmptyString) +{ + String value; + if (result->firstResponseHeader(header, &value, allowEmptyString)) + setStringField(env, object, field, value); +} + +static String getCacheFileBaseDir(JNIEnv* env) +{ + static String baseDir; + if (baseDir.isEmpty()) { + jclass cacheManagerClass = env->FindClass("android/webkit/CacheManager"); + jmethodID getCacheFileBaseDirMethod = env->GetStaticMethodID(cacheManagerClass, "getCacheFileBaseDir", "()Ljava/io/File;"); + jclass fileClass = env->FindClass("java/io/File"); + jmethodID getPathMethod = env->GetMethodID(fileClass, "getPath", "()Ljava/lang/String;"); + jobject fileObject = env->CallStaticObjectMethod(cacheManagerClass, getCacheFileBaseDirMethod); + baseDir = jstringToWtfString(env, static_cast<jstring>(env->CallObjectMethod(fileObject, getPathMethod))); + } + return baseDir; +} + +static jobject getCacheResult(JNIEnv* env, jobject, jstring url) +{ + // This is called on the UI thread. + scoped_refptr<CacheResult> result = WebCache::get(false /*privateBrowsing*/)->getCacheResult(jstringToWtfString(env, url)); + if (!result) + return 0; + + // We create and populate a file with the cache entry. This allows us to + // replicate the behaviour of the Android HTTP stack in the Java + // CacheManager, which opens the cache file and provides an input stream to + // the file as part of the Java CacheResult object! + String urlWtfString = jstringToWtfString(env, url); + Vector<char> encodedUrl; + base64Encode(urlWtfString.utf8().data(), urlWtfString.length(), encodedUrl, false /*insertLFs*/); + String filePath = pathByAppendingComponent(getCacheFileBaseDir(env), encodedUrl.data()); + if (!result->writeToFile(filePath)) + return 0; + + jclass cacheResultClass = env->FindClass("android/webkit/CacheManager$CacheResult"); + jmethodID constructor = env->GetMethodID(cacheResultClass, "<init>", "()V"); + // We only bother with the fields that are made accessible through the public API. + jfieldID contentdispositionField = env->GetFieldID(cacheResultClass, "contentdisposition", "Ljava/lang/String;"); + jfieldID contentLengthField = env->GetFieldID(cacheResultClass, "contentLength", "J"); + jfieldID etagField = env->GetFieldID(cacheResultClass, "etag", "Ljava/lang/String;"); + jfieldID encodingField = env->GetFieldID(cacheResultClass, "encoding", "Ljava/lang/String;"); + jfieldID expiresField = env->GetFieldID(cacheResultClass, "expires", "J"); + jfieldID expiresStringField = env->GetFieldID(cacheResultClass, "expiresString", "Ljava/lang/String;"); + jfieldID httpStatusCodeField = env->GetFieldID(cacheResultClass, "httpStatusCode", "I"); + jfieldID lastModifiedField = env->GetFieldID(cacheResultClass, "lastModified", "Ljava/lang/String;"); + jfieldID localPathField = env->GetFieldID(cacheResultClass, "localPath", "Ljava/lang/String;"); + jfieldID locationField = env->GetFieldID(cacheResultClass, "location", "Ljava/lang/String;"); + jfieldID mimeTypeField = env->GetFieldID(cacheResultClass, "mimeType", "Ljava/lang/String;"); + + jobject javaResult = env->NewObject(cacheResultClass, constructor); + setFieldFromHeaderIfPresent(result.get(), "content-disposition", env, javaResult, contentdispositionField, true); + env->SetLongField(javaResult, contentLengthField, result->contentSize()); + setFieldFromHeaderIfPresent(result.get(), "etag", env, javaResult, etagField, false); + setStringField(env, javaResult, encodingField, "TODO"); // TODO: Where does the Android stack set this? + env->SetLongField(javaResult, expiresField, result->expires()); + env->SetIntField(javaResult, httpStatusCodeField, result->responseCode()); + setFieldFromHeaderIfPresent(result.get(), "last-modified", env, javaResult, lastModifiedField, false); + setStringField(env, javaResult, localPathField, encodedUrl.data()); + setFieldFromHeaderIfPresent(result.get(), "location", env, javaResult, locationField, false); + setStringField(env, javaResult, mimeTypeField, result->mimeType()); + + return javaResult; +} + +static JNINativeMethod gCacheManagerMethods[] = { + { "nativeGetCacheResult", "(Ljava/lang/String;)Landroid/webkit/CacheManager$CacheResult;", (void*) getCacheResult }, +}; + +int registerCacheManager(JNIEnv* env) +{ +#ifndef NDEBUG + jclass cacheManager = env->FindClass(javaCacheManagerClass); + LOG_ASSERT(cacheManager, "Unable to find class"); + env->DeleteLocalRef(cacheManager); +#endif + return jniRegisterNativeMethods(env, javaCacheManagerClass, gCacheManagerMethods, NELEM(gCacheManagerMethods)); +} + +} // namespace android + +#endif // USE(CHROME_NETWORK_STACK) diff --git a/Source/WebKit/android/jni/CookieManager.cpp b/Source/WebKit/android/jni/CookieManager.cpp new file mode 100644 index 0000000..0bdf303 --- /dev/null +++ b/Source/WebKit/android/jni/CookieManager.cpp @@ -0,0 +1,201 @@ +/* + * Copyright 2010, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" + +#include "ChromiumIncludes.h" +#include "WebCookieJar.h" +#include "WebCoreJni.h" +#include <JNIHelp.h> + +using namespace base; +using namespace net; + +namespace android { + +// JNI for android.webkit.CookieManager +static const char* javaCookieManagerClass = "android/webkit/CookieManager"; + +static bool acceptCookie(JNIEnv*, jobject) +{ +#if USE(CHROME_NETWORK_STACK) + // This is a static method which gets the cookie policy for all WebViews. We + // always apply the same configuration to the contexts for both regular and + // private browsing, so expect the same result here. + bool regularAcceptCookies = WebCookieJar::get(false)->allowCookies(); + ASSERT(regularAcceptCookies == WebCookieJar::get(true)->allowCookies()); + return regularAcceptCookies; +#else + // The Android HTTP stack is implemented Java-side. + ASSERT_NOT_REACHED(); + return false; +#endif +} + +static jstring getCookie(JNIEnv* env, jobject, jstring url, jboolean privateBrowsing) +{ +#if USE(CHROME_NETWORK_STACK) + GURL gurl(jstringToStdString(env, url)); + CookieOptions options; + options.set_include_httponly(); + std::string cookies = WebCookieJar::get(privateBrowsing)->cookieStore()->GetCookieMonster()->GetCookiesWithOptions(gurl, options); + return stdStringToJstring(env, cookies); +#else + // The Android HTTP stack is implemented Java-side. + ASSERT_NOT_REACHED(); + return jstring(); +#endif +} + +static bool hasCookies(JNIEnv*, jobject, jboolean privateBrowsing) +{ +#if USE(CHROME_NETWORK_STACK) + return WebCookieJar::get(privateBrowsing)->getNumCookiesInDatabase() > 0; +#else + // The Android HTTP stack is implemented Java-side. + ASSERT_NOT_REACHED(); + return false; +#endif +} + +static void removeAllCookie(JNIEnv*, jobject) +{ +#if USE(CHROME_NETWORK_STACK) + WebCookieJar::get(false)->cookieStore()->GetCookieMonster()->DeleteAll(true); + // This will lazily create a new private browsing context. However, if the + // context doesn't already exist, there's no need to create it, as cookies + // for such contexts are cleared up when we're done with them. + // TODO: Consider adding an optimisation to not create the context if it + // doesn't already exist. + WebCookieJar::get(true)->cookieStore()->GetCookieMonster()->DeleteAll(true); + + // The Java code removes cookies directly from the backing database, so we do the same, + // but with a NULL callback so it's asynchronous. + WebCookieJar::get(true)->cookieStore()->GetCookieMonster()->FlushStore(NULL); +#endif +} + +static void removeExpiredCookie(JNIEnv*, jobject) +{ +#if USE(CHROME_NETWORK_STACK) + // This simply forces a GC. The getters delete expired cookies so won't return expired cookies anyway. + WebCookieJar::get(false)->cookieStore()->GetCookieMonster()->GetAllCookies(); + WebCookieJar::get(true)->cookieStore()->GetCookieMonster()->GetAllCookies(); +#endif +} + +static void removeSessionCookies(WebCookieJar* cookieJar) +{ +#if USE(CHROME_NETWORK_STACK) + CookieMonster* cookieMonster = cookieJar->cookieStore()->GetCookieMonster(); + CookieMonster::CookieList cookies = cookieMonster->GetAllCookies(); + for (CookieMonster::CookieList::const_iterator iter = cookies.begin(); iter != cookies.end(); ++iter) { + if (iter->IsSessionCookie()) + cookieMonster->DeleteCanonicalCookie(*iter); + } +#endif +} + +static void removeSessionCookie(JNIEnv*, jobject) +{ +#if USE(CHROME_NETWORK_STACK) + removeSessionCookies(WebCookieJar::get(false)); + removeSessionCookies(WebCookieJar::get(true)); +#endif +} + +static void setAcceptCookie(JNIEnv*, jobject, jboolean accept) +{ +#if USE(CHROME_NETWORK_STACK) + // This is a static method which configures the cookie policy for all + // WebViews, so we configure the contexts for both regular and private + // browsing. + WebCookieJar::get(false)->setAllowCookies(accept); + WebCookieJar::get(true)->setAllowCookies(accept); +#endif +} + +static void setCookie(JNIEnv* env, jobject, jstring url, jstring value, jboolean privateBrowsing) +{ +#if USE(CHROME_NETWORK_STACK) + GURL gurl(jstringToStdString(env, url)); + std::string line(jstringToStdString(env, value)); + CookieOptions options; + options.set_include_httponly(); + WebCookieJar::get(privateBrowsing)->cookieStore()->GetCookieMonster()->SetCookieWithOptions(gurl, line, options); +#endif +} + +static void flushCookieStore(JNIEnv*, jobject) +{ +#if USE(CHROME_NETWORK_STACK) + WebCookieJar::flush(); +#endif +} + +static bool acceptFileSchemeCookies(JNIEnv*, jobject) +{ +#if USE(CHROME_NETWORK_STACK) + return WebCookieJar::acceptFileSchemeCookies(); +#else + // File scheme cookies are always accepted with the Android HTTP stack. + return true; +#endif +} + +static void setAcceptFileSchemeCookies(JNIEnv*, jobject, jboolean accept) +{ +#if USE(CHROME_NETWORK_STACK) + WebCookieJar::setAcceptFileSchemeCookies(accept); +#else + // File scheme cookies are always accepted with the Android HTTP stack. +#endif +} + +static JNINativeMethod gCookieManagerMethods[] = { + { "nativeAcceptCookie", "()Z", (void*) acceptCookie }, + { "nativeGetCookie", "(Ljava/lang/String;Z)Ljava/lang/String;", (void*) getCookie }, + { "nativeHasCookies", "(Z)Z", (void*) hasCookies }, + { "nativeRemoveAllCookie", "()V", (void*) removeAllCookie }, + { "nativeRemoveExpiredCookie", "()V", (void*) removeExpiredCookie }, + { "nativeRemoveSessionCookie", "()V", (void*) removeSessionCookie }, + { "nativeSetAcceptCookie", "(Z)V", (void*) setAcceptCookie }, + { "nativeSetCookie", "(Ljava/lang/String;Ljava/lang/String;Z)V", (void*) setCookie }, + { "nativeFlushCookieStore", "()V", (void*) flushCookieStore }, + { "nativeAcceptFileSchemeCookies", "()Z", (void*) acceptFileSchemeCookies }, + { "nativeSetAcceptFileSchemeCookies", "(Z)V", (void*) setAcceptFileSchemeCookies }, +}; + +int registerCookieManager(JNIEnv* env) +{ +#ifndef NDEBUG + jclass cookieManager = env->FindClass(javaCookieManagerClass); + LOG_ASSERT(cookieManager, "Unable to find class"); + env->DeleteLocalRef(cookieManager); +#endif + return jniRegisterNativeMethods(env, javaCookieManagerClass, gCookieManagerMethods, NELEM(gCookieManagerMethods)); +} + +} // namespace android diff --git a/Source/WebKit/android/jni/DeviceMotionAndOrientationManager.cpp b/Source/WebKit/android/jni/DeviceMotionAndOrientationManager.cpp new file mode 100644 index 0000000..8beb372 --- /dev/null +++ b/Source/WebKit/android/jni/DeviceMotionAndOrientationManager.cpp @@ -0,0 +1,171 @@ +/* + * Copyright 2010, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "DeviceMotionAndOrientationManager.h" + +#include "DeviceMotionClientImpl.h" +#include "DeviceOrientationClientImpl.h" +#include "DeviceOrientationController.h" +#include "WebViewCore.h" +#include "Frame.h" +#include "Page.h" + +#include <DeviceOrientationClientMock.h> +#include <JNIHelp.h> + +using namespace WebCore; + +namespace android { + +DeviceMotionAndOrientationManager::DeviceMotionAndOrientationManager(WebViewCore* webViewCore) + : m_useMock(false) + , m_webViewCore(webViewCore) +{ +} + +void DeviceMotionAndOrientationManager::useMock() +{ + m_useMock = true; +} + +void DeviceMotionAndOrientationManager::setMockMotion(PassRefPtr<DeviceMotionData> motion) +{ + // TODO: There is not yet a DeviceMotion mock. +} + +void DeviceMotionAndOrientationManager::onMotionChange(PassRefPtr<DeviceMotionData> motion) +{ + ASSERT(!m_useMock); + static_cast<DeviceMotionClientImpl*>(m_motionClient.get())->onMotionChange(motion); +} + +void DeviceMotionAndOrientationManager::setMockOrientation(PassRefPtr<DeviceOrientation> orientation) +{ + if (m_useMock) + static_cast<DeviceOrientationClientMock*>(orientationClient())->setOrientation(orientation); +} + +void DeviceMotionAndOrientationManager::onOrientationChange(PassRefPtr<DeviceOrientation> orientation) +{ + ASSERT(!m_useMock); + static_cast<DeviceOrientationClientImpl*>(m_orientationClient.get())->onOrientationChange(orientation); +} + +void DeviceMotionAndOrientationManager::maybeSuspendClients() +{ + if (!m_useMock) { + if (m_motionClient) + static_cast<DeviceMotionClientImpl*>(m_motionClient.get())->suspend(); + if (m_orientationClient) + static_cast<DeviceOrientationClientImpl*>(m_orientationClient.get())->suspend(); + } +} + +void DeviceMotionAndOrientationManager::maybeResumeClients() +{ + if (!m_useMock) { + if (m_motionClient) + static_cast<DeviceMotionClientImpl*>(m_motionClient.get())->resume(); + if (m_orientationClient) + static_cast<DeviceOrientationClientImpl*>(m_orientationClient.get())->resume(); + } +} + +DeviceMotionClient* DeviceMotionAndOrientationManager::motionClient() +{ + // TODO: There is not yet a DeviceMotion mock. + if (!m_motionClient) + m_motionClient.set(m_useMock ? 0 + : static_cast<DeviceMotionClient*>(new DeviceMotionClientImpl(m_webViewCore))); + ASSERT(m_motionClient); + return m_motionClient.get(); +} + +DeviceOrientationClient* DeviceMotionAndOrientationManager::orientationClient() +{ + if (!m_orientationClient) + m_orientationClient.set(m_useMock ? new DeviceOrientationClientMock + : static_cast<DeviceOrientationClient*>(new DeviceOrientationClientImpl(m_webViewCore))); + ASSERT(m_orientationClient); + return m_orientationClient.get(); +} + +// JNI for android.webkit.DeviceMotionAndOrientationManager +static const char* javaDeviceMotionAndOrientationManagerClass = "android/webkit/DeviceMotionAndOrientationManager"; + +static WebViewCore* getWebViewCore(JNIEnv* env, jobject webViewCoreObject) +{ + jclass webViewCoreClass = env->FindClass("android/webkit/WebViewCore"); + jfieldID nativeClassField = env->GetFieldID(webViewCoreClass, "mNativeClass", "I"); + env->DeleteLocalRef(webViewCoreClass); + return reinterpret_cast<WebViewCore*>(env->GetIntField(webViewCoreObject, nativeClassField)); +} + +static void useMock(JNIEnv* env, jobject, jobject webViewCoreObject) +{ + getWebViewCore(env, webViewCoreObject)->deviceMotionAndOrientationManager()->useMock(); +} + +static void onMotionChange(JNIEnv* env, jobject, jobject webViewCoreObject, bool canProvideX, double x, bool canProvideY, double y, bool canProvideZ, double z, double interval) +{ + // We only provide accelerationIncludingGravity. + RefPtr<DeviceMotionData::Acceleration> accelerationIncludingGravity = DeviceMotionData::Acceleration::create(canProvideX, x, canProvideY, y, canProvideZ, z); + bool canProvideInterval = canProvideX || canProvideY || canProvideZ; + RefPtr<DeviceMotionData> motion = DeviceMotionData::create(0, accelerationIncludingGravity.release(), 0, canProvideInterval, interval); + getWebViewCore(env, webViewCoreObject)->deviceMotionAndOrientationManager()->onMotionChange(motion.release()); +} + +static void setMockOrientation(JNIEnv* env, jobject, jobject webViewCoreObject, bool canProvideAlpha, double alpha, bool canProvideBeta, double beta, bool canProvideGamma, double gamma) +{ + RefPtr<DeviceOrientation> orientation = DeviceOrientation::create(canProvideAlpha, alpha, canProvideBeta, beta, canProvideGamma, gamma); + getWebViewCore(env, webViewCoreObject)->deviceMotionAndOrientationManager()->setMockOrientation(orientation.release()); +} + +static void onOrientationChange(JNIEnv* env, jobject, jobject webViewCoreObject, bool canProvideAlpha, double alpha, bool canProvideBeta, double beta, bool canProvideGamma, double gamma) +{ + RefPtr<DeviceOrientation> orientation = DeviceOrientation::create(canProvideAlpha, alpha, canProvideBeta, beta, canProvideGamma, gamma); + getWebViewCore(env, webViewCoreObject)->deviceMotionAndOrientationManager()->onOrientationChange(orientation.release()); +} + +static JNINativeMethod gDeviceMotionAndOrientationManagerMethods[] = { + { "nativeUseMock", "(Landroid/webkit/WebViewCore;)V", (void*) useMock }, + { "nativeOnMotionChange", "(Landroid/webkit/WebViewCore;ZDZDZDD)V", (void*) onMotionChange }, + { "nativeSetMockOrientation", "(Landroid/webkit/WebViewCore;ZDZDZD)V", (void*) setMockOrientation }, + { "nativeOnOrientationChange", "(Landroid/webkit/WebViewCore;ZDZDZD)V", (void*) onOrientationChange } +}; + +int registerDeviceMotionAndOrientationManager(JNIEnv* env) +{ +#ifndef NDEBUG + jclass deviceMotionAndOrientationManager = env->FindClass(javaDeviceMotionAndOrientationManagerClass); + LOG_ASSERT(deviceMotionAndOrientationManager, "Unable to find class"); + env->DeleteLocalRef(deviceMotionAndOrientationManager); +#endif + + return jniRegisterNativeMethods(env, javaDeviceMotionAndOrientationManagerClass, gDeviceMotionAndOrientationManagerMethods, NELEM(gDeviceMotionAndOrientationManagerMethods)); +} + +} // namespace android diff --git a/Source/WebKit/android/jni/DeviceMotionAndOrientationManager.h b/Source/WebKit/android/jni/DeviceMotionAndOrientationManager.h new file mode 100644 index 0000000..44463c1 --- /dev/null +++ b/Source/WebKit/android/jni/DeviceMotionAndOrientationManager.h @@ -0,0 +1,68 @@ +/* + * Copyright 2010, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef DeviceMotionAndOrientationManager_h +#define DeviceMotionAndOrientationManager_h + +#include <DeviceMotionData.h> +#include <DeviceMotionClient.h> +#include <DeviceOrientation.h> +#include <DeviceOrientationClient.h> +#include <OwnPtr.h> +#include <PassRefPtr.h> +#include <RefPtr.h> + +namespace android { + +class WebViewCore; + +// This class takes care of the fact that the clients used for DeviceMotion and +// DeviceOrientation may be either the real implementations or mocks. It also +// handles setting the data on both the real and mock clients. This class is +// owned by WebViewCore and exists to keep cruft out of that class. +class DeviceMotionAndOrientationManager { +public: + DeviceMotionAndOrientationManager(WebViewCore*); + + void useMock(); + void setMockMotion(PassRefPtr<WebCore::DeviceMotionData>); + void onMotionChange(PassRefPtr<WebCore::DeviceMotionData>); + void setMockOrientation(PassRefPtr<WebCore::DeviceOrientation>); + void onOrientationChange(PassRefPtr<WebCore::DeviceOrientation>); + void maybeSuspendClients(); + void maybeResumeClients(); + WebCore::DeviceMotionClient* motionClient(); + WebCore::DeviceOrientationClient* orientationClient(); + +private: + bool m_useMock; + WebViewCore* m_webViewCore; + OwnPtr<WebCore::DeviceMotionClient> m_motionClient; + OwnPtr<WebCore::DeviceOrientationClient> m_orientationClient; +}; + +} // namespace android + +#endif // DeviceMotionAndOrientationManager_h diff --git a/Source/WebKit/android/jni/DeviceMotionClientImpl.cpp b/Source/WebKit/android/jni/DeviceMotionClientImpl.cpp new file mode 100644 index 0000000..82f3c35 --- /dev/null +++ b/Source/WebKit/android/jni/DeviceMotionClientImpl.cpp @@ -0,0 +1,130 @@ +/* + * Copyright 2010, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "DeviceMotionClientImpl.h" + +#include "WebViewCore.h" +#include <DeviceMotionController.h> +#include <Frame.h> +#include <JNIHelp.h> + +namespace android { + +using JSC::Bindings::getJNIEnv; + +enum javaServiceClassMethods { + ServiceMethodStart = 0, + ServiceMethodStop, + ServiceMethodSuspend, + ServiceMethodResume, + ServiceMethodCount +}; +static jmethodID javaServiceClassMethodIDs[ServiceMethodCount]; + +DeviceMotionClientImpl::DeviceMotionClientImpl(WebViewCore* webViewCore) + : m_webViewCore(webViewCore) + , m_javaServiceObject(0) +{ + ASSERT(m_webViewCore); +} + +DeviceMotionClientImpl::~DeviceMotionClientImpl() +{ + releaseJavaInstance(); +} + +jobject DeviceMotionClientImpl::getJavaInstance() +{ + // Lazily get the Java object. We can't do this until the WebViewCore is all + // set up. + if (m_javaServiceObject) + return m_javaServiceObject; + + JNIEnv* env = getJNIEnv(); + + ASSERT(m_webViewCore); + jobject object = m_webViewCore->getDeviceMotionService(); + + // Get the Java DeviceMotionService class. + jclass javaServiceClass = env->GetObjectClass(object); + ASSERT(javaServiceClass); + + // Set up the methods we wish to call on the Java DeviceMotionService + // class. + javaServiceClassMethodIDs[ServiceMethodStart] = + env->GetMethodID(javaServiceClass, "start", "()V"); + javaServiceClassMethodIDs[ServiceMethodStop] = + env->GetMethodID(javaServiceClass, "stop", "()V"); + javaServiceClassMethodIDs[ServiceMethodSuspend] = + env->GetMethodID(javaServiceClass, "suspend", "()V"); + javaServiceClassMethodIDs[ServiceMethodResume] = + env->GetMethodID(javaServiceClass, "resume", "()V"); + env->DeleteLocalRef(javaServiceClass); + + m_javaServiceObject = getJNIEnv()->NewGlobalRef(object); + getJNIEnv()->DeleteLocalRef(object); + + ASSERT(m_javaServiceObject); + return m_javaServiceObject; +} + +void DeviceMotionClientImpl::releaseJavaInstance() +{ + ASSERT(m_javaServiceObject); + getJNIEnv()->DeleteGlobalRef(m_javaServiceObject); +} + +void DeviceMotionClientImpl::startUpdating() +{ + getJNIEnv()->CallVoidMethod(getJavaInstance(), + javaServiceClassMethodIDs[ServiceMethodStart]); +} + +void DeviceMotionClientImpl::stopUpdating() +{ + getJNIEnv()->CallVoidMethod(getJavaInstance(), + javaServiceClassMethodIDs[ServiceMethodStop]); +} + +void DeviceMotionClientImpl::onMotionChange(PassRefPtr<DeviceMotionData> motion) +{ + m_lastMotion = motion; + m_controller->didChangeDeviceMotion(m_lastMotion.get()); +} + +void DeviceMotionClientImpl::suspend() +{ + getJNIEnv()->CallVoidMethod(getJavaInstance(), + javaServiceClassMethodIDs[ServiceMethodSuspend]); +} + +void DeviceMotionClientImpl::resume() +{ + getJNIEnv()->CallVoidMethod(getJavaInstance(), + javaServiceClassMethodIDs[ServiceMethodResume]); +} + +} // namespace android diff --git a/Source/WebKit/android/jni/DeviceMotionClientImpl.h b/Source/WebKit/android/jni/DeviceMotionClientImpl.h new file mode 100644 index 0000000..c979098 --- /dev/null +++ b/Source/WebKit/android/jni/DeviceMotionClientImpl.h @@ -0,0 +1,70 @@ +/* + * Copyright 2010, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef DeviceMotionClientImpl_h +#define DeviceMotionClientImpl_h + +#include <DeviceMotionClient.h> +#include <DeviceMotionData.h> +#include <JNIUtility.h> +#include <PassRefPtr.h> +#include <RefPtr.h> + +using namespace WebCore; + +namespace android { + +class DeviceMotionAndOrientationManager; +class WebViewCore; + +class DeviceMotionClientImpl : public DeviceMotionClient { +public: + DeviceMotionClientImpl(WebViewCore*); + virtual ~DeviceMotionClientImpl(); + + void onMotionChange(PassRefPtr<DeviceMotionData>); + void suspend(); + void resume(); + + // DeviceMotionClient methods + virtual void startUpdating(); + virtual void stopUpdating(); + virtual DeviceMotionData* currentDeviceMotion() const { return m_lastMotion.get(); } + virtual void setController(DeviceMotionController* controller) { m_controller = controller; } + virtual void deviceMotionControllerDestroyed() { } + +private: + jobject getJavaInstance(); + void releaseJavaInstance(); + + WebViewCore* m_webViewCore; + jobject m_javaServiceObject; + DeviceMotionController* m_controller; + RefPtr<DeviceMotionData> m_lastMotion; +}; + +} // namespace android + +#endif // DeviceMotionClientImpl_h diff --git a/Source/WebKit/android/jni/DeviceOrientationClientImpl.cpp b/Source/WebKit/android/jni/DeviceOrientationClientImpl.cpp new file mode 100644 index 0000000..bf3b3c3 --- /dev/null +++ b/Source/WebKit/android/jni/DeviceOrientationClientImpl.cpp @@ -0,0 +1,130 @@ +/* + * Copyright 2010, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "DeviceOrientationClientImpl.h" + +#include "WebViewCore.h" +#include <DeviceOrientationController.h> +#include <Frame.h> +#include <JNIHelp.h> + +namespace android { + +using JSC::Bindings::getJNIEnv; + +enum javaDeviceOrientationServiceClassMethods { + DeviceOrientationServiceMethodStart = 0, + DeviceOrientationServiceMethodStop, + DeviceOrientationServiceMethodSuspend, + DeviceOrientationServiceMethodResume, + DeviceOrientationServiceMethodCount +}; +static jmethodID javaDeviceOrientationServiceClassMethodIDs[DeviceOrientationServiceMethodCount]; + +DeviceOrientationClientImpl::DeviceOrientationClientImpl(WebViewCore* webViewCore) + : m_webViewCore(webViewCore) + , m_javaDeviceOrientationServiceObject(0) +{ + ASSERT(m_webViewCore); +} + +DeviceOrientationClientImpl::~DeviceOrientationClientImpl() +{ + releaseJavaInstance(); +} + +jobject DeviceOrientationClientImpl::getJavaInstance() +{ + // Lazily get the Java object. We can't do this until the WebViewCore is all + // set up. + if (m_javaDeviceOrientationServiceObject) + return m_javaDeviceOrientationServiceObject; + + JNIEnv* env = getJNIEnv(); + + ASSERT(m_webViewCore); + jobject object = m_webViewCore->getDeviceOrientationService(); + + // Get the Java DeviceOrientationService class. + jclass javaDeviceOrientationServiceClass = env->GetObjectClass(object); + ASSERT(javaDeviceOrientationServiceClass); + + // Set up the methods we wish to call on the Java DeviceOrientationService + // class. + javaDeviceOrientationServiceClassMethodIDs[DeviceOrientationServiceMethodStart] = + env->GetMethodID(javaDeviceOrientationServiceClass, "start", "()V"); + javaDeviceOrientationServiceClassMethodIDs[DeviceOrientationServiceMethodStop] = + env->GetMethodID(javaDeviceOrientationServiceClass, "stop", "()V"); + javaDeviceOrientationServiceClassMethodIDs[DeviceOrientationServiceMethodSuspend] = + env->GetMethodID(javaDeviceOrientationServiceClass, "suspend", "()V"); + javaDeviceOrientationServiceClassMethodIDs[DeviceOrientationServiceMethodResume] = + env->GetMethodID(javaDeviceOrientationServiceClass, "resume", "()V"); + env->DeleteLocalRef(javaDeviceOrientationServiceClass); + + m_javaDeviceOrientationServiceObject = getJNIEnv()->NewGlobalRef(object); + getJNIEnv()->DeleteLocalRef(object); + + ASSERT(m_javaDeviceOrientationServiceObject); + return m_javaDeviceOrientationServiceObject; +} + +void DeviceOrientationClientImpl::releaseJavaInstance() +{ + ASSERT(m_javaDeviceOrientationServiceObject); + getJNIEnv()->DeleteGlobalRef(m_javaDeviceOrientationServiceObject); +} + +void DeviceOrientationClientImpl::startUpdating() +{ + getJNIEnv()->CallVoidMethod(getJavaInstance(), + javaDeviceOrientationServiceClassMethodIDs[DeviceOrientationServiceMethodStart]); +} + +void DeviceOrientationClientImpl::stopUpdating() +{ + getJNIEnv()->CallVoidMethod(getJavaInstance(), + javaDeviceOrientationServiceClassMethodIDs[DeviceOrientationServiceMethodStop]); +} + +void DeviceOrientationClientImpl::onOrientationChange(PassRefPtr<DeviceOrientation> orientation) +{ + m_lastOrientation = orientation; + m_controller->didChangeDeviceOrientation(m_lastOrientation.get()); +} + +void DeviceOrientationClientImpl::suspend() +{ + getJNIEnv()->CallVoidMethod(getJavaInstance(), + javaDeviceOrientationServiceClassMethodIDs[DeviceOrientationServiceMethodSuspend]); +} + +void DeviceOrientationClientImpl::resume() +{ + getJNIEnv()->CallVoidMethod(getJavaInstance(), + javaDeviceOrientationServiceClassMethodIDs[DeviceOrientationServiceMethodResume]); +} + +} // namespace android diff --git a/Source/WebKit/android/jni/DeviceOrientationClientImpl.h b/Source/WebKit/android/jni/DeviceOrientationClientImpl.h new file mode 100644 index 0000000..0e3f6b3 --- /dev/null +++ b/Source/WebKit/android/jni/DeviceOrientationClientImpl.h @@ -0,0 +1,70 @@ +/* + * Copyright 2010, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef DeviceOrientationClientImpl_h +#define DeviceOrientationClientImpl_h + +#include <DeviceOrientation.h> +#include <DeviceOrientationClient.h> +#include <JNIUtility.h> +#include <PassRefPtr.h> +#include <RefPtr.h> + +using namespace WebCore; + +namespace android { + +class DeviceMotionAndOrientationManager; +class WebViewCore; + +class DeviceOrientationClientImpl : public DeviceOrientationClient { +public: + DeviceOrientationClientImpl(WebViewCore*); + virtual ~DeviceOrientationClientImpl(); + + void onOrientationChange(PassRefPtr<DeviceOrientation>); + void suspend(); + void resume(); + + // DeviceOrientationClient methods + virtual void startUpdating(); + virtual void stopUpdating(); + virtual DeviceOrientation* lastOrientation() const { return m_lastOrientation.get(); } + virtual void setController(DeviceOrientationController* controller) { m_controller = controller; } + virtual void deviceOrientationControllerDestroyed() { } + +private: + jobject getJavaInstance(); + void releaseJavaInstance(); + + WebViewCore* m_webViewCore; + jobject m_javaDeviceOrientationServiceObject; + DeviceOrientationController* m_controller; + RefPtr<DeviceOrientation> m_lastOrientation; +}; + +} // namespace android + +#endif // DeviceOrientationClientImpl_h diff --git a/Source/WebKit/android/jni/GeolocationPermissionsBridge.cpp b/Source/WebKit/android/jni/GeolocationPermissionsBridge.cpp new file mode 100755 index 0000000..a366601 --- /dev/null +++ b/Source/WebKit/android/jni/GeolocationPermissionsBridge.cpp @@ -0,0 +1,113 @@ +/* + * Copyright 2009, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" + +#include <JNIHelp.h> // For jniRegisterNativeMethods +#include "GeolocationPermissions.h" +#include "WebCoreJni.h" // For jstringToWtfString + + +/** + * This file provides a set of functions to bridge between the Java and C++ + * GeolocationPermissions classes. The java GeolocationPermissions object calls + * the functions provided here, which in turn call static methods on the C++ + * GeolocationPermissions class. + */ + +namespace android { + +static jobject getOrigins(JNIEnv* env, jobject obj) +{ + GeolocationPermissions::OriginSet origins = GeolocationPermissions::getOrigins(); + + jclass setClass = env->FindClass("java/util/HashSet"); + jmethodID constructor = env->GetMethodID(setClass, "<init>", "()V"); + jmethodID addMethod = env->GetMethodID(setClass, "add", "(Ljava/lang/Object;)Z"); + jobject set = env->NewObject(setClass, constructor); + env->DeleteLocalRef(setClass); + + GeolocationPermissions::OriginSet::const_iterator end = origins.end(); + for (GeolocationPermissions::OriginSet::const_iterator iter = origins.begin(); iter != end; ++iter) { + jstring originString = wtfStringToJstring(env, *iter); + env->CallBooleanMethod(set, addMethod, originString); + env->DeleteLocalRef(originString); + } + return set; +} + +static bool getAllowed(JNIEnv* env, jobject obj, jstring origin) +{ + WTF::String originString = jstringToWtfString(env, origin); + return GeolocationPermissions::getAllowed(originString); +} + +static void clear(JNIEnv* env, jobject obj, jstring origin) +{ + WTF::String originString = jstringToWtfString(env, origin); + GeolocationPermissions::clear(originString); +} + +static void allow(JNIEnv* env, jobject obj, jstring origin) +{ + WTF::String originString = jstringToWtfString(env, origin); + GeolocationPermissions::allow(originString); +} + +static void clearAll(JNIEnv* env, jobject obj) +{ + GeolocationPermissions::clearAll(); +} + +/* + * JNI registration + */ +static JNINativeMethod gGeolocationPermissionsMethods[] = { + { "nativeGetOrigins", "()Ljava/util/Set;", + (void*) getOrigins }, + { "nativeGetAllowed", "(Ljava/lang/String;)Z", + (void*) getAllowed }, + { "nativeClear", "(Ljava/lang/String;)V", + (void*) clear }, + { "nativeAllow", "(Ljava/lang/String;)V", + (void*) allow }, + { "nativeClearAll", "()V", + (void*) clearAll } +}; + +int registerGeolocationPermissions(JNIEnv* env) +{ + const char* kGeolocationPermissionsClass = "android/webkit/GeolocationPermissions"; +#ifndef NDEBUG + jclass geolocationPermissions = env->FindClass(kGeolocationPermissionsClass); + LOG_ASSERT(geolocationPermissions, "Unable to find class"); + env->DeleteLocalRef(geolocationPermissions); +#endif + + return jniRegisterNativeMethods(env, kGeolocationPermissionsClass, + gGeolocationPermissionsMethods, NELEM(gGeolocationPermissionsMethods)); +} + +} diff --git a/Source/WebKit/android/jni/JavaBridge.cpp b/Source/WebKit/android/jni/JavaBridge.cpp new file mode 100644 index 0000000..2fa12fc --- /dev/null +++ b/Source/WebKit/android/jni/JavaBridge.cpp @@ -0,0 +1,514 @@ +/* + * Copyright 2006, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#define LOG_TAG "webcoreglue" + +#include "config.h" + +#include "MemoryCache.h" +#include "Connection.h" +#include "CookieClient.h" +#include "FileSystemClient.h" +#include "JavaSharedClient.h" +#include "KeyGeneratorClient.h" +#include "KURL.h" +#include "NetworkStateNotifier.h" +#include "PackageNotifier.h" +#include "Page.h" +#include "PluginClient.h" +#include "PluginDatabase.h" +#include "Timer.h" +#include "TimerClient.h" +#ifdef ANDROID_INSTRUMENT +#include "TimeCounter.h" +#endif +#include "WebCache.h" +#include "WebCoreJni.h" + +#include <JNIHelp.h> +#include <JNIUtility.h> +#include <SkUtils.h> +#include <jni.h> +#include <utils/misc.h> +#include <wtf/Platform.h> +#include <wtf/StdLibExtras.h> +#include <wtf/text/AtomicString.h> + +namespace android { + +// ---------------------------------------------------------------------------- + +static jfieldID gJavaBridge_ObjectID; + +// ---------------------------------------------------------------------------- + +class JavaBridge : public TimerClient, public CookieClient, public PluginClient, public KeyGeneratorClient, public FileSystemClient +{ +public: + JavaBridge(JNIEnv* env, jobject obj); + virtual ~JavaBridge(); + + /* + * WebCore -> Java API + */ + virtual void setSharedTimer(long long timemillis); + virtual void stopSharedTimer(); + + virtual void setCookies(WebCore::KURL const& url, WTF::String const& value); + virtual WTF::String cookies(WebCore::KURL const& url); + virtual bool cookiesEnabled(); + + virtual WTF::Vector<WTF::String> getPluginDirectories(); + virtual WTF::String getPluginSharedDataDirectory(); + + virtual WTF::Vector<String> getSupportedKeyStrengthList(); + virtual WTF::String getSignedPublicKeyAndChallengeString(unsigned index, + const WTF::String& challenge, const WebCore::KURL& url); + virtual WTF::String resolveFilePathForContentUri(const WTF::String& uri); + + //////////////////////////////////////////// + + virtual void setSharedTimerCallback(void (*f)()); + + //////////////////////////////////////////// + + virtual void signalServiceFuncPtrQueue(); + + // jni functions + static void Constructor(JNIEnv* env, jobject obj); + static void Finalize(JNIEnv* env, jobject obj); + static void SharedTimerFired(JNIEnv* env, jobject); + static void SetCacheSize(JNIEnv* env, jobject obj, jint bytes); + static void SetNetworkOnLine(JNIEnv* env, jobject obj, jboolean online); + static void SetNetworkType(JNIEnv* env, jobject obj, jstring type, jstring subtype); + static void SetDeferringTimers(JNIEnv* env, jobject obj, jboolean defer); + static void ServiceFuncPtrQueue(JNIEnv*); + static void UpdatePluginDirectories(JNIEnv* env, jobject obj, jobjectArray array, jboolean reload); + static void AddPackageNames(JNIEnv* env, jobject obj, jobject packageNames); + static void AddPackageName(JNIEnv* env, jobject obj, jstring packageName); + static void RemovePackageName(JNIEnv* env, jobject obj, jstring packageName); + static void UpdateProxy(JNIEnv* env, jobject obj, jstring newProxy); + + +private: + jweak mJavaObject; + jmethodID mSetSharedTimer; + jmethodID mStopSharedTimer; + jmethodID mSetCookies; + jmethodID mCookies; + jmethodID mCookiesEnabled; + jmethodID mGetPluginDirectories; + jmethodID mGetPluginSharedDataDirectory; + jmethodID mSignalFuncPtrQueue; + jmethodID mGetKeyStrengthList; + jmethodID mGetSignedPublicKey; + jmethodID mResolveFilePathForContentUri; + AutoJObject javaObject(JNIEnv* env) { return getRealObject(env, mJavaObject); } +}; + +static void (*sSharedTimerFiredCallback)(); + +JavaBridge::JavaBridge(JNIEnv* env, jobject obj) +{ + mJavaObject = env->NewWeakGlobalRef(obj); + jclass clazz = env->GetObjectClass(obj); + + mSetSharedTimer = env->GetMethodID(clazz, "setSharedTimer", "(J)V"); + mStopSharedTimer = env->GetMethodID(clazz, "stopSharedTimer", "()V"); + mSetCookies = env->GetMethodID(clazz, "setCookies", "(Ljava/lang/String;Ljava/lang/String;)V"); + mCookies = env->GetMethodID(clazz, "cookies", "(Ljava/lang/String;)Ljava/lang/String;"); + mCookiesEnabled = env->GetMethodID(clazz, "cookiesEnabled", "()Z"); + mGetPluginDirectories = env->GetMethodID(clazz, "getPluginDirectories", "()[Ljava/lang/String;"); + mGetPluginSharedDataDirectory = env->GetMethodID(clazz, "getPluginSharedDataDirectory", "()Ljava/lang/String;"); + mSignalFuncPtrQueue = env->GetMethodID(clazz, "signalServiceFuncPtrQueue", "()V"); + mGetKeyStrengthList = env->GetMethodID(clazz, "getKeyStrengthList", "()[Ljava/lang/String;"); + mGetSignedPublicKey = env->GetMethodID(clazz, "getSignedPublicKey", "(ILjava/lang/String;Ljava/lang/String;)Ljava/lang/String;"); + mResolveFilePathForContentUri = env->GetMethodID(clazz, "resolveFilePathForContentUri", "(Ljava/lang/String;)Ljava/lang/String;"); + env->DeleteLocalRef(clazz); + + LOG_ASSERT(mSetSharedTimer, "Could not find method setSharedTimer"); + LOG_ASSERT(mStopSharedTimer, "Could not find method stopSharedTimer"); + LOG_ASSERT(mSetCookies, "Could not find method setCookies"); + LOG_ASSERT(mCookies, "Could not find method cookies"); + LOG_ASSERT(mCookiesEnabled, "Could not find method cookiesEnabled"); + LOG_ASSERT(mGetPluginDirectories, "Could not find method getPluginDirectories"); + LOG_ASSERT(mGetPluginSharedDataDirectory, "Could not find method getPluginSharedDataDirectory"); + LOG_ASSERT(mGetKeyStrengthList, "Could not find method getKeyStrengthList"); + LOG_ASSERT(mGetSignedPublicKey, "Could not find method getSignedPublicKey"); + + JavaSharedClient::SetTimerClient(this); + JavaSharedClient::SetCookieClient(this); + JavaSharedClient::SetPluginClient(this); + JavaSharedClient::SetKeyGeneratorClient(this); + JavaSharedClient::SetFileSystemClient(this); +} + +JavaBridge::~JavaBridge() +{ + if (mJavaObject) { + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->DeleteWeakGlobalRef(mJavaObject); + mJavaObject = 0; + } + + JavaSharedClient::SetTimerClient(NULL); + JavaSharedClient::SetCookieClient(NULL); + JavaSharedClient::SetPluginClient(NULL); + JavaSharedClient::SetKeyGeneratorClient(NULL); + JavaSharedClient::SetFileSystemClient(NULL); +} + +void +JavaBridge::setSharedTimer(long long timemillis) +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + AutoJObject obj = javaObject(env); + env->CallVoidMethod(obj.get(), mSetSharedTimer, timemillis); +} + +void +JavaBridge::stopSharedTimer() +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + AutoJObject obj = javaObject(env); + env->CallVoidMethod(obj.get(), mStopSharedTimer); +} + +void +JavaBridge::setCookies(WebCore::KURL const& url, WTF::String const& value) +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + const WTF::String& urlStr = url.string(); + jstring jUrlStr = wtfStringToJstring(env, urlStr); + jstring jValueStr = wtfStringToJstring(env, value); + + AutoJObject obj = javaObject(env); + env->CallVoidMethod(obj.get(), mSetCookies, jUrlStr, jValueStr); + env->DeleteLocalRef(jUrlStr); + env->DeleteLocalRef(jValueStr); +} + +WTF::String +JavaBridge::cookies(WebCore::KURL const& url) +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + const WTF::String& urlStr = url.string(); + jstring jUrlStr = wtfStringToJstring(env, urlStr); + + AutoJObject obj = javaObject(env); + jstring string = (jstring)(env->CallObjectMethod(obj.get(), mCookies, jUrlStr)); + + WTF::String ret = jstringToWtfString(env, string); + env->DeleteLocalRef(jUrlStr); + env->DeleteLocalRef(string); + return ret; +} + +bool +JavaBridge::cookiesEnabled() +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + AutoJObject obj = javaObject(env); + jboolean ret = env->CallBooleanMethod(obj.get(), mCookiesEnabled); + return (ret != 0); +} + +WTF::Vector<WTF::String> +JavaBridge::getPluginDirectories() +{ + WTF::Vector<WTF::String> directories; + JNIEnv* env = JSC::Bindings::getJNIEnv(); + AutoJObject obj = javaObject(env); + jobjectArray array = (jobjectArray) + env->CallObjectMethod(obj.get(), mGetPluginDirectories); + int count = env->GetArrayLength(array); + for (int i = 0; i < count; i++) { + jstring dir = (jstring) env->GetObjectArrayElement(array, i); + directories.append(jstringToWtfString(env, dir)); + env->DeleteLocalRef(dir); + } + env->DeleteLocalRef(array); + checkException(env); + return directories; +} + +WTF::String +JavaBridge::getPluginSharedDataDirectory() +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + AutoJObject obj = javaObject(env); + jstring ret = (jstring)env->CallObjectMethod(obj.get(), mGetPluginSharedDataDirectory); + WTF::String path = jstringToWtfString(env, ret); + checkException(env); + return path; +} + +void +JavaBridge::setSharedTimerCallback(void (*f)()) +{ + LOG_ASSERT(!sSharedTimerFiredCallback || sSharedTimerFiredCallback==f, + "Shared timer callback may already be set or null!"); + + sSharedTimerFiredCallback = f; +} + +void JavaBridge::signalServiceFuncPtrQueue() +{ + // In order to signal the main thread we must go through JNI. This + // is the only usage on most threads, so we need to ensure a JNI + // environment is setup. + JNIEnv* env = JSC::Bindings::getJNIEnv(); + AutoJObject obj = javaObject(env); + env->CallVoidMethod(obj.get(), mSignalFuncPtrQueue); +} + +WTF::Vector<WTF::String>JavaBridge::getSupportedKeyStrengthList() { + WTF::Vector<WTF::String> list; + JNIEnv* env = JSC::Bindings::getJNIEnv(); + AutoJObject obj = javaObject(env); + jobjectArray array = (jobjectArray) env->CallObjectMethod(obj.get(), + mGetKeyStrengthList); + int count = env->GetArrayLength(array); + for (int i = 0; i < count; ++i) { + jstring keyStrength = (jstring) env->GetObjectArrayElement(array, i); + list.append(jstringToWtfString(env, keyStrength)); + env->DeleteLocalRef(keyStrength); + } + env->DeleteLocalRef(array); + checkException(env); + return list; +} + +WTF::String JavaBridge::getSignedPublicKeyAndChallengeString(unsigned index, + const WTF::String& challenge, const WebCore::KURL& url) { + JNIEnv* env = JSC::Bindings::getJNIEnv(); + jstring jChallenge = wtfStringToJstring(env, challenge); + const WTF::String& urlStr = url.string(); + jstring jUrl = wtfStringToJstring(env, urlStr); + AutoJObject obj = javaObject(env); + jstring key = (jstring) env->CallObjectMethod(obj.get(), + mGetSignedPublicKey, index, jChallenge, jUrl); + WTF::String ret = jstringToWtfString(env, key); + env->DeleteLocalRef(jChallenge); + env->DeleteLocalRef(jUrl); + env->DeleteLocalRef(key); + return ret; +} + +WTF::String JavaBridge::resolveFilePathForContentUri(const WTF::String& uri) { + JNIEnv* env = JSC::Bindings::getJNIEnv(); + jstring jUri = wtfStringToJstring(env, uri); + AutoJObject obj = javaObject(env); + jstring path = static_cast<jstring>(env->CallObjectMethod(obj.get(), mResolveFilePathForContentUri, jUri)); + WTF::String ret = jstringToWtfString(env, path); + env->DeleteLocalRef(jUri); + env->DeleteLocalRef(path); + return ret; +} + +// ---------------------------------------------------------------------------- + +void JavaBridge::Constructor(JNIEnv* env, jobject obj) +{ + JavaBridge* javaBridge = new JavaBridge(env, obj); + env->SetIntField(obj, gJavaBridge_ObjectID, (jint)javaBridge); +} + +void JavaBridge::Finalize(JNIEnv* env, jobject obj) +{ + JavaBridge* javaBridge = (JavaBridge*) + (env->GetIntField(obj, gJavaBridge_ObjectID)); + LOG_ASSERT(javaBridge, "Finalize should not be called twice for the same java bridge!"); + LOGV("webcore_javabridge::nativeFinalize(%p)\n", javaBridge); + delete javaBridge; + env->SetIntField(obj, gJavaBridge_ObjectID, 0); +} + +// we don't use the java bridge object, as we're just looking at a global +void JavaBridge::SharedTimerFired(JNIEnv* env, jobject) +{ + if (sSharedTimerFiredCallback) + { +#ifdef ANDROID_INSTRUMENT + TimeCounter::start(TimeCounter::SharedTimerTimeCounter); +#endif + SkAutoMemoryUsageProbe mup("JavaBridge::sharedTimerFired"); + sSharedTimerFiredCallback(); +#ifdef ANDROID_INSTRUMENT + TimeCounter::record(TimeCounter::SharedTimerTimeCounter, __FUNCTION__); +#endif + } +} + +void JavaBridge::SetCacheSize(JNIEnv* env, jobject obj, jint bytes) +{ + WebCore::cache()->setCapacities(0, bytes/2, bytes); +} + +void JavaBridge::SetNetworkOnLine(JNIEnv* env, jobject obj, jboolean online) +{ + WebCore::networkStateNotifier().networkStateChange(online); +} + +void JavaBridge::SetNetworkType(JNIEnv* env, jobject obj, jstring javatype, jstring javasubtype) +{ + DEFINE_STATIC_LOCAL(AtomicString, wifi, ("wifi")); + DEFINE_STATIC_LOCAL(AtomicString, mobile, ("mobile")); + DEFINE_STATIC_LOCAL(AtomicString, mobileSupl, ("mobile_supl")); + DEFINE_STATIC_LOCAL(AtomicString, gprs, ("gprs")); + DEFINE_STATIC_LOCAL(AtomicString, edge, ("edge")); + DEFINE_STATIC_LOCAL(AtomicString, umts, ("umts")); + + String type = jstringToWtfString(env, javatype); + String subtype = jstringToWtfString(env, javasubtype); + Connection::ConnectionType connectionType = Connection::UNKNOWN; + if (type == wifi) + connectionType = Connection::WIFI; + else if (type == mobile || type == mobileSupl) { + if (subtype == edge || subtype == gprs) + connectionType = Connection::CELL_2G; + else if (subtype == umts) + connectionType = Connection::CELL_3G; + } + WebCore::networkStateNotifier().networkTypeChange(connectionType); +} + +void JavaBridge::ServiceFuncPtrQueue(JNIEnv*) +{ + JavaSharedClient::ServiceFunctionPtrQueue(); +} + +void JavaBridge::UpdatePluginDirectories(JNIEnv* env, jobject obj, + jobjectArray array, jboolean reload) { + WTF::Vector<WTF::String> directories; + int count = env->GetArrayLength(array); + for (int i = 0; i < count; i++) { + jstring dir = (jstring) env->GetObjectArrayElement(array, i); + directories.append(jstringToWtfString(env, dir)); + env->DeleteLocalRef(dir); + } + checkException(env); + WebCore::PluginDatabase *pluginDatabase = + WebCore::PluginDatabase::installedPlugins(); + pluginDatabase->setPluginDirectories(directories); + // refreshPlugins() should refresh both PluginDatabase and Page's PluginData + WebCore::Page::refreshPlugins(reload); +} + +void JavaBridge::AddPackageNames(JNIEnv* env, jobject obj, jobject packageNames) +{ + if (!packageNames) + return; + + // dalvikvm will raise exception if any of these fail + jclass setClass = env->FindClass("java/util/Set"); + jmethodID iterator = env->GetMethodID(setClass, "iterator", + "()Ljava/util/Iterator;"); + jobject iter = env->CallObjectMethod(packageNames, iterator); + + jclass iteratorClass = env->FindClass("java/util/Iterator"); + jmethodID hasNext = env->GetMethodID(iteratorClass, "hasNext", "()Z"); + jmethodID next = env->GetMethodID(iteratorClass, "next", "()Ljava/lang/Object;"); + + HashSet<WTF::String> namesSet; + while (env->CallBooleanMethod(iter, hasNext)) { + jstring name = static_cast<jstring>(env->CallObjectMethod(iter, next)); + namesSet.add(jstringToWtfString(env, name)); + env->DeleteLocalRef(name); + } + + packageNotifier().addPackageNames(namesSet); + + env->DeleteLocalRef(iteratorClass); + env->DeleteLocalRef(iter); + env->DeleteLocalRef(setClass); +} + +void JavaBridge::AddPackageName(JNIEnv* env, jobject obj, jstring packageName) +{ + packageNotifier().addPackageName(jstringToWtfString(env, packageName)); +} + +void JavaBridge::RemovePackageName(JNIEnv* env, jobject obj, jstring packageName) +{ + packageNotifier().removePackageName(jstringToWtfString(env, packageName)); +} + +void JavaBridge::UpdateProxy(JNIEnv* env, jobject obj, jstring newProxy) +{ +#if USE(CHROME_NETWORK_STACK) + std::string proxy = jstringToStdString(env, newProxy); + WebCache::get(false)->proxy()->UpdateProxySettings(proxy); + WebCache::get(true)->proxy()->UpdateProxySettings(proxy); +#endif +} + + +// ---------------------------------------------------------------------------- + +/* + * JNI registration. + */ +static JNINativeMethod gWebCoreJavaBridgeMethods[] = { + /* name, signature, funcPtr */ + { "nativeConstructor", "()V", + (void*) JavaBridge::Constructor }, + { "nativeFinalize", "()V", + (void*) JavaBridge::Finalize }, + { "sharedTimerFired", "()V", + (void*) JavaBridge::SharedTimerFired }, + { "setCacheSize", "(I)V", + (void*) JavaBridge::SetCacheSize }, + { "setNetworkOnLine", "(Z)V", + (void*) JavaBridge::SetNetworkOnLine }, + { "setNetworkType", "(Ljava/lang/String;Ljava/lang/String;)V", + (void*) JavaBridge::SetNetworkType }, + { "nativeServiceFuncPtrQueue", "()V", + (void*) JavaBridge::ServiceFuncPtrQueue }, + { "nativeUpdatePluginDirectories", "([Ljava/lang/String;Z)V", + (void*) JavaBridge::UpdatePluginDirectories }, + { "addPackageNames", "(Ljava/util/Set;)V", + (void*) JavaBridge::AddPackageNames }, + { "addPackageName", "(Ljava/lang/String;)V", + (void*) JavaBridge::AddPackageName }, + { "removePackageName", "(Ljava/lang/String;)V", + (void*) JavaBridge::RemovePackageName }, + { "updateProxy", "(Ljava/lang/String;)V", + (void*) JavaBridge::UpdateProxy } +}; + +int registerJavaBridge(JNIEnv* env) +{ + jclass javaBridge = env->FindClass("android/webkit/JWebCoreJavaBridge"); + LOG_FATAL_IF(javaBridge == NULL, "Unable to find class android/webkit/JWebCoreJavaBridge"); + gJavaBridge_ObjectID = env->GetFieldID(javaBridge, "mNativeBridge", "I"); + LOG_FATAL_IF(gJavaBridge_ObjectID == NULL, "Unable to find android/webkit/JWebCoreJavaBridge.mNativeBridge"); + env->DeleteLocalRef(javaBridge); + + return jniRegisterNativeMethods(env, "android/webkit/JWebCoreJavaBridge", + gWebCoreJavaBridgeMethods, NELEM(gWebCoreJavaBridgeMethods)); +} + +} /* namespace android */ diff --git a/Source/WebKit/android/jni/JavaSharedClient.cpp b/Source/WebKit/android/jni/JavaSharedClient.cpp new file mode 100644 index 0000000..e884c99 --- /dev/null +++ b/Source/WebKit/android/jni/JavaSharedClient.cpp @@ -0,0 +1,137 @@ +/* + * Copyright 2007, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "FileSystemClient.h" +#include "JavaSharedClient.h" +#include "TimerClient.h" +#include "SkDeque.h" +#include "SkThread.h" + +namespace android { + TimerClient* JavaSharedClient::GetTimerClient() + { + return gTimerClient; + } + + CookieClient* JavaSharedClient::GetCookieClient() + { + return gCookieClient; + } + + PluginClient* JavaSharedClient::GetPluginClient() + { + return gPluginClient; + } + + KeyGeneratorClient* JavaSharedClient::GetKeyGeneratorClient() + { + return gKeyGeneratorClient; + } + + FileSystemClient* JavaSharedClient::GetFileSystemClient() + { + return gFileSystemClient; + } + + void JavaSharedClient::SetTimerClient(TimerClient* client) + { + gTimerClient = client; + } + + void JavaSharedClient::SetCookieClient(CookieClient* client) + { + gCookieClient = client; + } + + void JavaSharedClient::SetPluginClient(PluginClient* client) + { + gPluginClient = client; + } + + void JavaSharedClient::SetKeyGeneratorClient(KeyGeneratorClient* client) + { + gKeyGeneratorClient = client; + } + + void JavaSharedClient::SetFileSystemClient(FileSystemClient* client) + { + gFileSystemClient = client; + } + + TimerClient* JavaSharedClient::gTimerClient = NULL; + CookieClient* JavaSharedClient::gCookieClient = NULL; + PluginClient* JavaSharedClient::gPluginClient = NULL; + KeyGeneratorClient* JavaSharedClient::gKeyGeneratorClient = NULL; + FileSystemClient* JavaSharedClient::gFileSystemClient = NULL; + + /////////////////////////////////////////////////////////////////////////// + + struct FuncPtrRec { + void (*fProc)(void* payload); + void* fPayload; + }; + + static SkMutex gFuncPtrQMutex; + static SkDeque gFuncPtrQ(sizeof(FuncPtrRec)); + + void JavaSharedClient::EnqueueFunctionPtr(void (*proc)(void* payload), + void* payload) + { + gFuncPtrQMutex.acquire(); + + FuncPtrRec* rec = (FuncPtrRec*)gFuncPtrQ.push_back(); + rec->fProc = proc; + rec->fPayload = payload; + + gFuncPtrQMutex.release(); + + gTimerClient->signalServiceFuncPtrQueue(); + } + + void JavaSharedClient::ServiceFunctionPtrQueue() + { + for (;;) { + void (*proc)(void*) = 0; + void* payload = 0; + const FuncPtrRec* rec; + + // we have to copy the proc/payload (if present). we do this so we + // don't call the proc inside the mutex (possible deadlock!) + gFuncPtrQMutex.acquire(); + rec = (const FuncPtrRec*)gFuncPtrQ.front(); + if (rec) { + proc = rec->fProc; + payload = rec->fPayload; + gFuncPtrQ.pop_front(); + } + gFuncPtrQMutex.release(); + + if (!rec) + break; + proc(payload); + } + } +} diff --git a/Source/WebKit/android/jni/JavaSharedClient.h b/Source/WebKit/android/jni/JavaSharedClient.h new file mode 100644 index 0000000..9a09280 --- /dev/null +++ b/Source/WebKit/android/jni/JavaSharedClient.h @@ -0,0 +1,65 @@ +/* + * Copyright 2007, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef JAVA_SHARED_CLIENT_H +#define JAVA_SHARED_CLIENT_H + +namespace android { + + class TimerClient; + class CookieClient; + class PluginClient; + class KeyGeneratorClient; + class FileSystemClient; + + class JavaSharedClient + { + public: + static TimerClient* GetTimerClient(); + static CookieClient* GetCookieClient(); + static PluginClient* GetPluginClient(); + static KeyGeneratorClient* GetKeyGeneratorClient(); + static FileSystemClient* GetFileSystemClient(); + + static void SetTimerClient(TimerClient* client); + static void SetCookieClient(CookieClient* client); + static void SetPluginClient(PluginClient* client); + static void SetKeyGeneratorClient(KeyGeneratorClient* client); + static void SetFileSystemClient(FileSystemClient* client); + + // can be called from any thread, to be executed in webkit thread + static void EnqueueFunctionPtr(void (*proc)(void*), void* payload); + // only call this from webkit thread + static void ServiceFunctionPtrQueue(); + + private: + static TimerClient* gTimerClient; + static CookieClient* gCookieClient; + static PluginClient* gPluginClient; + static KeyGeneratorClient* gKeyGeneratorClient; + static FileSystemClient* gFileSystemClient; + }; +} +#endif diff --git a/Source/WebKit/android/jni/JniUtil.cpp b/Source/WebKit/android/jni/JniUtil.cpp new file mode 100644 index 0000000..ee1e3f9 --- /dev/null +++ b/Source/WebKit/android/jni/JniUtil.cpp @@ -0,0 +1,58 @@ +/* + * Copyright 2010, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" + +#include "ChromiumIncludes.h" +#include <JNIHelp.h> + +namespace android { + +static const char* javaJniUtilClass = "android/webkit/JniUtil"; + +static bool useChromiumHttpStack(JNIEnv*, jobject) +{ +#if USE(CHROME_NETWORK_STACK) + return true; +#else + return false; +#endif +} + +static JNINativeMethod gJniUtilMethods[] = { + { "nativeUseChromiumHttpStack", "()Z", (void*) useChromiumHttpStack }, +}; + +int registerJniUtil(JNIEnv* env) +{ +#ifndef NDEBUG + jclass jniUtil = env->FindClass(javaJniUtilClass); + LOG_ASSERT(jniUtil, "Unable to find class"); + env->DeleteLocalRef(jniUtil); +#endif + return jniRegisterNativeMethods(env, javaJniUtilClass, gJniUtilMethods, NELEM(gJniUtilMethods)); +} + +} // namespace android diff --git a/Source/WebKit/android/jni/MIMETypeRegistry.cpp b/Source/WebKit/android/jni/MIMETypeRegistry.cpp new file mode 100644 index 0000000..40f8cef --- /dev/null +++ b/Source/WebKit/android/jni/MIMETypeRegistry.cpp @@ -0,0 +1,67 @@ +/* + * Copyright 2010, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#define LOG_TAG "WebCore" + +#include "config.h" +#include "MIMETypeRegistry.h" + +#include "PlatformString.h" +#include "WebCoreJni.h" + +#include <JNIUtility.h> +#include <jni.h> +#include <utils/Log.h> + +using namespace android; + +namespace WebCore { + +String MIMETypeRegistry::getMIMETypeForExtension(const String& ext) +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + jclass mimeClass = env->FindClass("android/webkit/MimeTypeMap"); + LOG_ASSERT(mimeClass, "Could not find class MimeTypeMap"); + jmethodID mimeTypeFromExtension = env->GetStaticMethodID(mimeClass, + "mimeTypeFromExtension", + "(Ljava/lang/String;)Ljava/lang/String;"); + LOG_ASSERT(mimeTypeFromExtension, + "Could not find method mimeTypeFromExtension"); + jstring extString = wtfStringToJstring(env, ext); + jobject mimeType = env->CallStaticObjectMethod(mimeClass, + mimeTypeFromExtension, extString); + String result = android::jstringToWtfString(env, (jstring) mimeType); + env->DeleteLocalRef(mimeClass); + env->DeleteLocalRef(extString); + env->DeleteLocalRef(mimeType); + return result; +} + +bool MIMETypeRegistry::isApplicationPluginMIMEType(const String&) +{ + return false; +} + +} diff --git a/Source/WebKit/android/jni/MockGeolocation.cpp b/Source/WebKit/android/jni/MockGeolocation.cpp new file mode 100755 index 0000000..1370715 --- /dev/null +++ b/Source/WebKit/android/jni/MockGeolocation.cpp @@ -0,0 +1,84 @@ +/* + * Copyright 2009, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// The functions in this file are used to configure the mock GeolocationService +// for the LayoutTests. + +#include "config.h" + +#include "Coordinates.h" +#include "GeolocationServiceMock.h" +#include "Geoposition.h" +#include "JavaSharedClient.h" +#include "PositionError.h" +#include "WebCoreJni.h" +#include <JNIHelp.h> +#include <JNIUtility.h> +#include <wtf/CurrentTime.h> + +using namespace WebCore; + +namespace android { + +static const char* javaMockGeolocationClass = "android/webkit/MockGeolocation"; + +static void setPosition(JNIEnv* env, jobject, double latitude, double longitude, double accuracy) +{ + RefPtr<Coordinates> coordinates = Coordinates::create(latitude, + longitude, + false, 0.0, // altitude, + accuracy, + false, 0.0, // altitudeAccuracy, + false, 0.0, // heading + false, 0.0); // speed + RefPtr<Geoposition> position = Geoposition::create(coordinates.release(), WTF::currentTimeMS()); + GeolocationServiceMock::setPosition(position.release()); +} + +static void setError(JNIEnv* env, jobject, int code, jstring message) +{ + PositionError::ErrorCode codeEnum = static_cast<PositionError::ErrorCode>(code); + String messageString = jstringToWtfString(env, message); + RefPtr<PositionError> error = PositionError::create(codeEnum, messageString); + GeolocationServiceMock::setError(error.release()); +} + +static JNINativeMethod gMockGeolocationMethods[] = { + { "nativeSetPosition", "(DDD)V", (void*) setPosition }, + { "nativeSetError", "(ILjava/lang/String;)V", (void*) setError } +}; + +int registerMockGeolocation(JNIEnv* env) +{ +#ifndef NDEBUG + jclass mockGeolocation = env->FindClass(javaMockGeolocationClass); + LOG_ASSERT(mockGeolocation, "Unable to find class"); + env->DeleteLocalRef(mockGeolocation); +#endif + + return jniRegisterNativeMethods(env, javaMockGeolocationClass, gMockGeolocationMethods, NELEM(gMockGeolocationMethods)); +} + +} diff --git a/Source/WebKit/android/jni/PictureSet.cpp b/Source/WebKit/android/jni/PictureSet.cpp new file mode 100644 index 0000000..6dafd26 --- /dev/null +++ b/Source/WebKit/android/jni/PictureSet.cpp @@ -0,0 +1,676 @@ +/* + * Copyright 2008, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#define LOG_NDEBUG 0 +#define LOG_TAG "pictureset" + +//#include <config.h> +#include "CachedPrefix.h" +#include "android_graphics.h" +#include "PictureSet.h" +#include "SkBounder.h" +#include "SkCanvas.h" +#include "SkPicture.h" +#include "SkRect.h" +#include "SkRegion.h" +#include "SkStream.h" +#include "TimeCounter.h" + +#define MAX_DRAW_TIME 100 +#define MIN_SPLITTABLE 400 + +#if PICTURE_SET_DEBUG +class MeasureStream : public SkWStream { +public: + MeasureStream() : mTotal(0) {} + virtual bool write(const void* , size_t size) { + mTotal += size; + return true; + } + size_t mTotal; +}; +#endif + +namespace android { + +PictureSet::PictureSet() +{ + mWidth = mHeight = 0; +} + +PictureSet::~PictureSet() +{ + clear(); +} + +void PictureSet::add(const Pictures* temp) +{ + Pictures pictureAndBounds = *temp; + SkSafeRef(pictureAndBounds.mPicture); + pictureAndBounds.mWroteElapsed = false; + mPictures.append(pictureAndBounds); +} + +void PictureSet::add(const SkRegion& area, SkPicture* picture, + uint32_t elapsed, bool split, bool empty) +{ + DBG_SET_LOGD("%p area={%d,%d,r=%d,b=%d} pict=%p elapsed=%d split=%d", this, + area.getBounds().fLeft, area.getBounds().fTop, + area.getBounds().fRight, area.getBounds().fBottom, picture, + elapsed, split); + SkSafeRef(picture); + /* if nothing is drawn beneath part of the new picture, mark it as a base */ + SkRegion diff = SkRegion(area); + Pictures* last = mPictures.end(); + for (Pictures* working = mPictures.begin(); working != last; working++) + diff.op(working->mArea, SkRegion::kDifference_Op); + Pictures pictureAndBounds = {area, picture, area.getBounds(), + elapsed, split, false, diff.isEmpty() == false, empty}; + mPictures.append(pictureAndBounds); +} + +/* +Pictures are discarded when they are fully drawn over. +When a picture is partially drawn over, it is discarded if it is not a base, and +its rectangular bounds is reduced if it is a base. +*/ +bool PictureSet::build() +{ + bool rebuild = false; + DBG_SET_LOGD("%p", this); + // walk pictures back to front, removing or trimming obscured ones + SkRegion drawn; + SkRegion inval; + Pictures* first = mPictures.begin(); + Pictures* last = mPictures.end(); + Pictures* working; + bool checkForNewBases = false; + for (working = last; working != first; ) { + --working; + SkRegion& area = working->mArea; + SkRegion visibleArea(area); + visibleArea.op(drawn, SkRegion::kDifference_Op); +#if PICTURE_SET_DEBUG + const SkIRect& a = area.getBounds(); + const SkIRect& d = drawn.getBounds(); + const SkIRect& i = inval.getBounds(); + const SkIRect& v = visibleArea.getBounds(); + DBG_SET_LOGD("%p [%d] area={%d,%d,r=%d,b=%d} drawn={%d,%d,r=%d,b=%d}" + " inval={%d,%d,r=%d,b=%d} vis={%d,%d,r=%d,b=%d}", + this, working - first, + a.fLeft, a.fTop, a.fRight, a.fBottom, + d.fLeft, d.fTop, d.fRight, d.fBottom, + i.fLeft, i.fTop, i.fRight, i.fBottom, + v.fLeft, v.fTop, v.fRight, v.fBottom); +#endif + bool tossPicture = false; + if (working->mBase == false) { + if (area != visibleArea) { + if (visibleArea.isEmpty() == false) { + DBG_SET_LOGD("[%d] partially overdrawn", working - first); + inval.op(visibleArea, SkRegion::kUnion_Op); + } else + DBG_SET_LOGD("[%d] fully hidden", working - first); + area.setEmpty(); + tossPicture = true; + } + } else { + const SkIRect& visibleBounds = visibleArea.getBounds(); + const SkIRect& areaBounds = area.getBounds(); + if (visibleBounds != areaBounds) { + DBG_SET_LOGD("[%d] base to be reduced", working - first); + area.setRect(visibleBounds); + checkForNewBases = tossPicture = true; + } + if (area.intersects(inval)) { + DBG_SET_LOGD("[%d] base to be redrawn", working - first); + tossPicture = true; + } + } + if (tossPicture) { + SkSafeUnref(working->mPicture); + working->mPicture = NULL; // mark to redraw + } + if (working->mPicture == NULL) // may have been set to null elsewhere + rebuild = true; + drawn.op(area, SkRegion::kUnion_Op); + } + // collapse out empty regions + Pictures* writer = first; + for (working = first; working != last; working++) { + if (working->mArea.isEmpty()) + continue; + *writer++ = *working; + } +#if PICTURE_SET_DEBUG + if ((unsigned) (writer - first) != mPictures.size()) + DBG_SET_LOGD("shrink=%d (was %d)", writer - first, mPictures.size()); +#endif + mPictures.shrink(writer - first); + /* When a base is discarded because it was entirely drawn over, all + remaining pictures are checked to see if one has become a base. */ + if (checkForNewBases) { + drawn.setEmpty(); + Pictures* last = mPictures.end(); + for (working = mPictures.begin(); working != last; working++) { + SkRegion& area = working->mArea; + if (drawn.contains(working->mArea) == false) { + working->mBase = true; + DBG_SET_LOGD("[%d] new base", working - mPictures.begin()); + } + drawn.op(working->mArea, SkRegion::kUnion_Op); + } + } + validate(__FUNCTION__); + return rebuild; +} + +void PictureSet::checkDimensions(int width, int height, SkRegion* inval) +{ + if (mWidth == width && mHeight == height) + return; + DBG_SET_LOGD("%p old:(w=%d,h=%d) new:(w=%d,h=%d)", this, + mWidth, mHeight, width, height); + if (mWidth == width && height > mHeight) { // only grew vertically + SkIRect rect; + rect.set(0, mHeight, width, height - mHeight); + inval->op(rect, SkRegion::kUnion_Op); + } else { + clear(); // if both width/height changed, clear the old cache + inval->setRect(0, 0, width, height); + } + mWidth = width; + mHeight = height; +} + +void PictureSet::clear() +{ + DBG_SET_LOG(""); + Pictures* last = mPictures.end(); + for (Pictures* working = mPictures.begin(); working != last; working++) { + working->mArea.setEmpty(); + SkSafeUnref(working->mPicture); + } + mPictures.clear(); + mWidth = mHeight = 0; +} + +bool PictureSet::draw(SkCanvas* canvas) +{ + validate(__FUNCTION__); + Pictures* first = mPictures.begin(); + Pictures* last = mPictures.end(); + Pictures* working; + SkRect bounds; + if (canvas->getClipBounds(&bounds) == false) + return false; + SkIRect irect; + bounds.roundOut(&irect); + for (working = last; working != first; ) { + --working; + if (working->mArea.contains(irect)) { +#if PICTURE_SET_DEBUG + const SkIRect& b = working->mArea.getBounds(); + DBG_SET_LOGD("contains working->mArea={%d,%d,%d,%d}" + " irect={%d,%d,%d,%d}", b.fLeft, b.fTop, b.fRight, b.fBottom, + irect.fLeft, irect.fTop, irect.fRight, irect.fBottom); +#endif + first = working; + break; + } + } + DBG_SET_LOGD("%p first=%d last=%d", this, first - mPictures.begin(), + last - mPictures.begin()); + uint32_t maxElapsed = 0; + for (working = first; working != last; working++) { + const SkRegion& area = working->mArea; + if (area.quickReject(irect)) { +#if PICTURE_SET_DEBUG + const SkIRect& b = area.getBounds(); + DBG_SET_LOGD("[%d] %p quickReject working->mArea={%d,%d,%d,%d}" + " irect={%d,%d,%d,%d}", working - first, working, + b.fLeft, b.fTop, b.fRight, b.fBottom, + irect.fLeft, irect.fTop, irect.fRight, irect.fBottom); +#endif + working->mElapsed = 0; + continue; + } + int saved = canvas->save(); + SkRect pathBounds; + if (area.isComplex()) { + SkPath pathClip; + area.getBoundaryPath(&pathClip); + canvas->clipPath(pathClip); + pathBounds = pathClip.getBounds(); + } else { + pathBounds.set(area.getBounds()); + canvas->clipRect(pathBounds); + } + canvas->translate(pathBounds.fLeft, pathBounds.fTop); + canvas->save(); + uint32_t startTime = getThreadMsec(); + canvas->drawPicture(*working->mPicture); + size_t elapsed = working->mElapsed = getThreadMsec() - startTime; + working->mWroteElapsed = true; + if (maxElapsed < elapsed && (pathBounds.width() >= MIN_SPLITTABLE || + pathBounds.height() >= MIN_SPLITTABLE)) + maxElapsed = elapsed; + canvas->restoreToCount(saved); +#define DRAW_TEST_IMAGE 01 +#if DRAW_TEST_IMAGE && PICTURE_SET_DEBUG + SkColor color = 0x3f000000 | (0xffffff & (unsigned) working); + canvas->drawColor(color); + SkPaint paint; + color ^= 0x00ffffff; + paint.setColor(color); + char location[256]; + for (int x = area.getBounds().fLeft & ~0x3f; + x < area.getBounds().fRight; x += 0x40) { + for (int y = area.getBounds().fTop & ~0x3f; + y < area.getBounds().fBottom; y += 0x40) { + int len = snprintf(location, sizeof(location) - 1, "(%d,%d)", x, y); + canvas->drawText(location, len, x, y, paint); + } + } +#endif + DBG_SET_LOGD("[%d] %p working->mArea={%d,%d,%d,%d} elapsed=%d base=%s", + working - first, working, + area.getBounds().fLeft, area.getBounds().fTop, + area.getBounds().fRight, area.getBounds().fBottom, + working->mElapsed, working->mBase ? "true" : "false"); + } + // dump(__FUNCTION__); + return maxElapsed >= MAX_DRAW_TIME; +} + +void PictureSet::dump(const char* label) const +{ +#if PICTURE_SET_DUMP + DBG_SET_LOGD("%p %s (%d) (w=%d,h=%d)", this, label, mPictures.size(), + mWidth, mHeight); + const Pictures* last = mPictures.end(); + for (const Pictures* working = mPictures.begin(); working != last; working++) { + const SkIRect& bounds = working->mArea.getBounds(); + const SkIRect& unsplit = working->mUnsplit; + MeasureStream measure; + if (working->mPicture != NULL) + working->mPicture->serialize(&measure); + LOGD(" [%d]" + " mArea.bounds={%d,%d,r=%d,b=%d}" + " mPicture=%p" + " mUnsplit={%d,%d,r=%d,b=%d}" + " mElapsed=%d" + " mSplit=%s" + " mWroteElapsed=%s" + " mBase=%s" + " pict-size=%d", + working - mPictures.begin(), + bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom, + working->mPicture, + unsplit.fLeft, unsplit.fTop, unsplit.fRight, unsplit.fBottom, + working->mElapsed, working->mSplit ? "true" : "false", + working->mWroteElapsed ? "true" : "false", + working->mBase ? "true" : "false", + measure.mTotal); + } +#endif +} + +class IsEmptyBounder : public SkBounder { + virtual bool onIRect(const SkIRect& rect) { + return false; + } +}; + +class IsEmptyCanvas : public SkCanvas { +public: + IsEmptyCanvas(SkBounder* bounder, SkPicture* picture) : + mPicture(picture), mEmpty(true) { + setBounder(bounder); + } + + void notEmpty() { + mEmpty = false; + mPicture->abortPlayback(); + } + + virtual bool clipPath(const SkPath&, SkRegion::Op) { + // this can be expensive to actually do, and doesn't affect the + // question of emptiness, so we make it a no-op + return true; + } + + virtual void commonDrawBitmap(const SkBitmap& bitmap, const SkIRect* rect, + const SkMatrix& , const SkPaint& ) { + if (bitmap.width() <= 1 || bitmap.height() <= 1) + return; + DBG_SET_LOGD("abort {%d,%d}", bitmap.width(), bitmap.height()); + notEmpty(); + } + + virtual void drawPaint(const SkPaint& paint) { + } + + virtual void drawPath(const SkPath& , const SkPaint& paint) { + DBG_SET_LOG("abort"); + notEmpty(); + } + + virtual void drawPoints(PointMode , size_t , const SkPoint [], + const SkPaint& paint) { + } + + virtual void drawRect(const SkRect& , const SkPaint& paint) { + // wait for visual content + if (paint.getColor() != SK_ColorWHITE) + notEmpty(); + } + + virtual void drawSprite(const SkBitmap& , int , int , + const SkPaint* paint = NULL) { + DBG_SET_LOG("abort"); + notEmpty(); + } + + virtual void drawText(const void* , size_t byteLength, SkScalar , + SkScalar , const SkPaint& paint) { + DBG_SET_LOGD("abort %d", byteLength); + notEmpty(); + } + + virtual void drawPosText(const void* , size_t byteLength, + const SkPoint [], const SkPaint& paint) { + DBG_SET_LOGD("abort %d", byteLength); + notEmpty(); + } + + virtual void drawPosTextH(const void* , size_t byteLength, + const SkScalar [], SkScalar , + const SkPaint& paint) { + DBG_SET_LOGD("abort %d", byteLength); + notEmpty(); + } + + virtual void drawTextOnPath(const void* , size_t byteLength, + const SkPath& , const SkMatrix* , + const SkPaint& paint) { + DBG_SET_LOGD("abort %d", byteLength); + notEmpty(); + } + + virtual void drawPicture(SkPicture& picture) { + SkCanvas::drawPicture(picture); + } + + SkPicture* mPicture; + bool mEmpty; +}; + +bool PictureSet::emptyPicture(SkPicture* picture) const +{ + IsEmptyBounder isEmptyBounder; + IsEmptyCanvas checker(&isEmptyBounder, picture); + SkBitmap bitmap; + bitmap.setConfig(SkBitmap::kARGB_8888_Config, mWidth, mHeight); + checker.setBitmapDevice(bitmap); + checker.drawPicture(*picture); + return checker.mEmpty; +} + +bool PictureSet::isEmpty() const +{ + const Pictures* last = mPictures.end(); + for (const Pictures* working = mPictures.begin(); working != last; working++) { + if (!working->mEmpty) + return false; + } + return true; +} + +bool PictureSet::reuseSubdivided(const SkRegion& inval) +{ + validate(__FUNCTION__); + if (inval.isComplex()) + return false; + Pictures* working, * last = mPictures.end(); + const SkIRect& invalBounds = inval.getBounds(); + bool steal = false; + for (working = mPictures.begin(); working != last; working++) { + if (working->mSplit && invalBounds == working->mUnsplit) { + steal = true; + continue; + } + if (steal == false) + continue; + SkRegion temp = SkRegion(inval); + temp.op(working->mArea, SkRegion::kIntersect_Op); + if (temp.isEmpty() || temp == working->mArea) + continue; + return false; + } + if (steal == false) + return false; + for (working = mPictures.begin(); working != last; working++) { + if ((working->mSplit == false || invalBounds != working->mUnsplit) && + inval.contains(working->mArea) == false) + continue; + SkSafeUnref(working->mPicture); + working->mPicture = NULL; + } + return true; +} + +void PictureSet::set(const PictureSet& src) +{ + DBG_SET_LOGD("start %p src=%p", this, &src); + clear(); + mWidth = src.mWidth; + mHeight = src.mHeight; + const Pictures* last = src.mPictures.end(); + for (const Pictures* working = src.mPictures.begin(); working != last; working++) + add(working); + // dump(__FUNCTION__); + validate(__FUNCTION__); + DBG_SET_LOG("end"); +} + +void PictureSet::setDrawTimes(const PictureSet& src) +{ + validate(__FUNCTION__); + if (mWidth != src.mWidth || mHeight != src.mHeight) + return; + Pictures* last = mPictures.end(); + Pictures* working = mPictures.begin(); + if (working == last) + return; + const Pictures* srcLast = src.mPictures.end(); + const Pictures* srcWorking = src.mPictures.begin(); + for (; srcWorking != srcLast; srcWorking++) { + if (srcWorking->mWroteElapsed == false) + continue; + while ((srcWorking->mArea != working->mArea || + srcWorking->mPicture != working->mPicture)) { + if (++working == last) + return; + } + DBG_SET_LOGD("%p [%d] [%d] {%d,%d,r=%d,b=%d} working->mElapsed=%d <- %d", + this, working - mPictures.begin(), srcWorking - src.mPictures.begin(), + working->mArea.getBounds().fLeft, working->mArea.getBounds().fTop, + working->mArea.getBounds().fRight, working->mArea.getBounds().fBottom, + working->mElapsed, srcWorking->mElapsed); + working->mElapsed = srcWorking->mElapsed; + } +} + +void PictureSet::setPicture(size_t i, SkPicture* p) +{ + SkSafeUnref(mPictures[i].mPicture); + mPictures[i].mPicture = p; + mPictures[i].mEmpty = emptyPicture(p); +} + +void PictureSet::split(PictureSet* out) const +{ + dump(__FUNCTION__); + DBG_SET_LOGD("%p", this); + SkIRect totalBounds; + out->mWidth = mWidth; + out->mHeight = mHeight; + totalBounds.set(0, 0, mWidth, mHeight); + SkRegion* total = new SkRegion(totalBounds); + const Pictures* last = mPictures.end(); + const Pictures* working; + uint32_t balance = 0; + int multiUnsplitFastPictures = 0; // > 1 has more than 1 + for (working = mPictures.begin(); working != last; working++) { + if (working->mElapsed >= MAX_DRAW_TIME || working->mSplit) + continue; + if (++multiUnsplitFastPictures > 1) + break; + } + for (working = mPictures.begin(); working != last; working++) { + uint32_t elapsed = working->mElapsed; + if (elapsed < MAX_DRAW_TIME) { + bool split = working->mSplit; + DBG_SET_LOGD("elapsed=%d working=%p total->getBounds()=" + "{%d,%d,r=%d,b=%d} split=%s", elapsed, working, + total->getBounds().fLeft, total->getBounds().fTop, + total->getBounds().fRight, total->getBounds().fBottom, + split ? "true" : "false"); + if (multiUnsplitFastPictures <= 1 || split) { + total->op(working->mArea, SkRegion::kDifference_Op); + out->add(working->mArea, working->mPicture, elapsed, split, + working->mEmpty); + } else if (balance < elapsed) + balance = elapsed; + continue; + } + total->op(working->mArea, SkRegion::kDifference_Op); + const SkIRect& bounds = working->mArea.getBounds(); + int width = bounds.width(); + int height = bounds.height(); + int across = 1; + int down = 1; + while (height >= MIN_SPLITTABLE || width >= MIN_SPLITTABLE) { + if (height >= width) { + height >>= 1; + down <<= 1; + } else { + width >>= 1; + across <<= 1 ; + } + if ((elapsed >>= 1) < MAX_DRAW_TIME) + break; + } + width = bounds.width(); + height = bounds.height(); + int top = bounds.fTop; + for (int indexY = 0; indexY < down; ) { + int bottom = bounds.fTop + height * ++indexY / down; + int left = bounds.fLeft; + for (int indexX = 0; indexX < across; ) { + int right = bounds.fLeft + width * ++indexX / across; + SkIRect cBounds; + cBounds.set(left, top, right, bottom); + out->add(SkRegion(cBounds), (across | down) != 1 ? NULL : + working->mPicture, elapsed, true, + (across | down) != 1 ? false : working->mEmpty); + left = right; + } + top = bottom; + } + } + DBG_SET_LOGD("%p w=%d h=%d total->isEmpty()=%s multiUnsplitFastPictures=%d", + this, mWidth, mHeight, total->isEmpty() ? "true" : "false", + multiUnsplitFastPictures); + if (!total->isEmpty() && multiUnsplitFastPictures > 1) + out->add(*total, NULL, balance, false, false); + delete total; + validate(__FUNCTION__); + out->dump("split-out"); +} + +bool PictureSet::validate(const char* funct) const +{ + bool valid = true; +#if PICTURE_SET_VALIDATE + SkRegion all; + const Pictures* first = mPictures.begin(); + for (const Pictures* working = mPictures.end(); working != first; ) { + --working; + const SkPicture* pict = working->mPicture; + const SkRegion& area = working->mArea; + const SkIRect& bounds = area.getBounds(); + bool localValid = false; + if (working->mUnsplit.isEmpty()) + LOGD("%s working->mUnsplit.isEmpty()", funct); + else if (working->mUnsplit.contains(bounds) == false) + LOGD("%s working->mUnsplit.contains(bounds) == false", funct); + else if (working->mElapsed >= 1000) + LOGD("%s working->mElapsed >= 1000", funct); + else if ((working->mSplit & 0xfe) != 0) + LOGD("%s (working->mSplit & 0xfe) != 0", funct); + else if ((working->mWroteElapsed & 0xfe) != 0) + LOGD("%s (working->mWroteElapsed & 0xfe) != 0", funct); + else if (pict != NULL) { + int pictWidth = pict->width(); + int pictHeight = pict->height(); + if (pictWidth < bounds.width()) + LOGD("%s pictWidth=%d < bounds.width()=%d", funct, pictWidth, bounds.width()); + else if (pictHeight < bounds.height()) + LOGD("%s pictHeight=%d < bounds.height()=%d", funct, pictHeight, bounds.height()); + else if (working->mArea.isEmpty()) + LOGD("%s working->mArea.isEmpty()", funct); + else + localValid = true; + } else + localValid = true; + working->mArea.validate(); + if (localValid == false) { + if (all.contains(area) == true) + LOGD("%s all.contains(area) == true", funct); + else + localValid = true; + } + valid &= localValid; + all.op(area, SkRegion::kUnion_Op); + } + const SkIRect& allBounds = all.getBounds(); + if (valid) { + valid = false; + if (allBounds.width() != mWidth) + LOGD("%s allBounds.width()=%d != mWidth=%d", funct, allBounds.width(), mWidth); + else if (allBounds.height() != mHeight) + LOGD("%s allBounds.height()=%d != mHeight=%d", funct, allBounds.height(), mHeight); + else + valid = true; + } + while (valid == false) + ; +#endif + return valid; +} + +} /* namespace android */ diff --git a/Source/WebKit/android/jni/PictureSet.h b/Source/WebKit/android/jni/PictureSet.h new file mode 100644 index 0000000..b177958 --- /dev/null +++ b/Source/WebKit/android/jni/PictureSet.h @@ -0,0 +1,104 @@ +/* + * Copyright 2008, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef PICTURESET_H +#define PICTURESET_H + +#define PICTURE_SET_DUMP 0 +#define PICTURE_SET_DEBUG 0 +#define PICTURE_SET_VALIDATE 0 + +#if PICTURE_SET_DEBUG +#define DBG_SET_LOG(message) LOGD("%s %s", __FUNCTION__, message) +#define DBG_SET_LOGD(format, ...) LOGD("%s " format, __FUNCTION__, __VA_ARGS__) +#define DEBUG_SET_UI_LOGD(...) LOGD(__VA_ARGS__) +#else +#define DBG_SET_LOG(message) ((void)0) +#define DBG_SET_LOGD(format, ...) ((void)0) +#define DEBUG_SET_UI_LOGD(...) ((void)0) +#endif + +#include "jni.h" +#include "SkRegion.h" +#include <wtf/Vector.h> + +class SkCanvas; +class SkPicture; +class SkIRect; + +namespace android { + + class PictureSet { + public: + PictureSet(); + PictureSet(const PictureSet& src) { set(src); } + virtual ~PictureSet(); + void add(const SkRegion& area, SkPicture* picture, + uint32_t elapsed, bool split) + { + add(area, picture, elapsed, split, emptyPicture(picture)); + } + void add(const SkRegion& area, SkPicture* picture, + uint32_t elapsed, bool split, bool empty); + const SkIRect& bounds(size_t i) const { + return mPictures[i].mArea.getBounds(); } + bool build(); + // Update mWidth/mHeight, and adds any additional inval region + void checkDimensions(int width, int height, SkRegion* inval); + void clear(); + bool draw(SkCanvas* ); + static PictureSet* GetNativePictureSet(JNIEnv* env, jobject jpic); + int height() const { return mHeight; } + bool isEmpty() const; // returns true if empty or only trivial content + bool reuseSubdivided(const SkRegion& ); + void set(const PictureSet& ); + void setDrawTimes(const PictureSet& ); + void setPicture(size_t i, SkPicture* p); + size_t size() const { return mPictures.size(); } + void split(PictureSet* result) const; + bool upToDate(size_t i) const { return mPictures[i].mPicture != NULL; } + int width() const { return mWidth; } + void dump(const char* label) const; + bool validate(const char* label) const; + private: + bool emptyPicture(SkPicture* ) const; // true if no text, images, paths + struct Pictures { + SkRegion mArea; + SkPicture* mPicture; + SkIRect mUnsplit; + uint32_t mElapsed; + bool mSplit : 8; + bool mWroteElapsed : 8; + bool mBase : 8; // true if nothing is drawn underneath this + bool mEmpty : 8; // true if the picture only draws white + }; + void add(const Pictures* temp); + WTF::Vector<Pictures> mPictures; + int mHeight; + int mWidth; + }; +} + +#endif diff --git a/Source/WebKit/android/jni/WebCoreFrameBridge.cpp b/Source/WebKit/android/jni/WebCoreFrameBridge.cpp new file mode 100644 index 0000000..15b6d20 --- /dev/null +++ b/Source/WebKit/android/jni/WebCoreFrameBridge.cpp @@ -0,0 +1,2125 @@ +/* + * Copyright 2006, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#define LOG_TAG "webcoreglue" + +#include "config.h" +#include "WebCoreFrameBridge.h" + +#include "Arena.h" +#include "BackForwardList.h" +#include "MemoryCache.h" +#include "Chrome.h" +#include "ChromeClientAndroid.h" +#include "ChromiumInit.h" +#include "ContextMenuClientAndroid.h" +#include "DeviceMotionClientAndroid.h" +#include "DeviceOrientationClientAndroid.h" +#include "Document.h" +#include "DocumentLoader.h" +#include "DragClientAndroid.h" +#include "EditorClientAndroid.h" +#include "Element.h" +#include "FocusController.h" +#include "Font.h" +#include "Frame.h" +#include "FrameLoader.h" +#include "FrameLoaderClientAndroid.h" +#include "FrameLoadRequest.h" +#include "FrameTree.h" +#include "FrameView.h" +#include "GraphicsContext.h" +#include "HistoryItem.h" +#include "HTMLCollection.h" +#include "HTMLElement.h" +#include "HTMLFormElement.h" +#include "HTMLInputElement.h" +#include "HTMLNames.h" +#include "IconDatabase.h" +#include "Image.h" +#include "InspectorClientAndroid.h" +#include "KURL.h" +#include "Page.h" +#include "PageCache.h" +#include "PlatformString.h" +#include "RenderPart.h" +#include "RenderSkinAndroid.h" +#include "RenderTreeAsText.h" +#include "RenderView.h" +#include "ResourceHandle.h" +#include "ResourceHandleInternal.h" +#include "ScriptController.h" +#include "ScriptValue.h" +#include "SecurityOrigin.h" +#include "SelectionController.h" +#include "Settings.h" +#include "SubstituteData.h" +#include "UrlInterceptResponse.h" +#include "UserGestureIndicator.h" +#include "WebCache.h" +#include "WebCoreJni.h" +#include "WebCoreResourceLoader.h" +#include "WebHistory.h" +#include "WebIconDatabase.h" +#include "WebFrameView.h" +#include "WebUrlLoaderClient.h" +#include "WebViewCore.h" +#include "android_graphics.h" +#include "jni.h" +#include "wds/DebugServer.h" + +#include <JNIUtility.h> +#include <JNIHelp.h> +#include <SkGraphics.h> +#include <android_runtime/android_util_AssetManager.h> +#include <utils/misc.h> +#include <utils/AssetManager.h> +#include <wtf/CurrentTime.h> +#include <wtf/Platform.h> +#include <wtf/text/AtomicString.h> +#include <wtf/text/CString.h> +#include <wtf/text/StringBuilder.h> + +#if USE(JSC) +#include "GCController.h" +#include "JSDOMWindow.h" +#include "JavaInstanceJSC.h" +#include <runtime_object.h> +#include <runtime_root.h> +#include <runtime/JSLock.h> +#elif USE(V8) +#include "JavaNPObjectV8.h" +#include "JavaInstanceV8.h" +#include "V8Counters.h" +#endif // USE(JSC) + +#ifdef ANDROID_INSTRUMENT +#include "TimeCounter.h" +#endif + +#if ENABLE(ARCHIVE) +#include "WebArchiveAndroid.h" +#endif + +#if ENABLE(WEB_AUTOFILL) +#include "autofill/WebAutoFill.h" +#endif + +using namespace JSC::Bindings; + +static String* gUploadFileLabel; +static String* gResetLabel; +static String* gSubmitLabel; +static String* gNoFileChosenLabel; + +String* WebCore::PlatformBridge::globalLocalizedName( + WebCore::PlatformBridge::rawResId resId) +{ + switch (resId) { + case WebCore::PlatformBridge::FileUploadLabel: + return gUploadFileLabel; + case WebCore::PlatformBridge::ResetLabel: + return gResetLabel; + case WebCore::PlatformBridge::SubmitLabel: + return gSubmitLabel; + case WebCore::PlatformBridge::FileUploadNoFileChosenLabel: + return gNoFileChosenLabel; + + default: + return 0; + } +} +/** + * Instantiate the localized name desired. + */ +void initGlobalLocalizedName(WebCore::PlatformBridge::rawResId resId, + android::WebFrame* webFrame) +{ + String** pointer; + switch (resId) { + case WebCore::PlatformBridge::FileUploadLabel: + pointer = &gUploadFileLabel; + break; + case WebCore::PlatformBridge::ResetLabel: + pointer = &gResetLabel; + break; + case WebCore::PlatformBridge::SubmitLabel: + pointer = &gSubmitLabel; + break; + case WebCore::PlatformBridge::FileUploadNoFileChosenLabel: + pointer = &gNoFileChosenLabel; + break; + default: + return; + } + if (!(*pointer) && webFrame) { + (*pointer) = new String(webFrame->getRawResourceFilename(resId).impl()); + } +} + +namespace android { + +// ---------------------------------------------------------------------------- + +#define WEBCORE_MEMORY_CAP 15 * 1024 * 1024 + +// ---------------------------------------------------------------------------- + +struct WebFrame::JavaBrowserFrame +{ + jweak mObj; + jweak mHistoryList; // WebBackForwardList object + jmethodID mStartLoadingResource; + jmethodID mMaybeSavePassword; + jmethodID mShouldInterceptRequest; + jmethodID mLoadStarted; + jmethodID mTransitionToCommitted; + jmethodID mLoadFinished; + jmethodID mReportError; + jmethodID mSetTitle; + jmethodID mWindowObjectCleared; + jmethodID mSetProgress; + jmethodID mDidReceiveIcon; + jmethodID mDidReceiveTouchIconUrl; + jmethodID mUpdateVisitedHistory; + jmethodID mHandleUrl; + jmethodID mCreateWindow; + jmethodID mCloseWindow; + jmethodID mDecidePolicyForFormResubmission; + jmethodID mRequestFocus; + jmethodID mGetRawResFilename; + jmethodID mDensity; + jmethodID mGetFileSize; + jmethodID mGetFile; + jmethodID mDidReceiveAuthenticationChallenge; + jmethodID mReportSslCertError; + jmethodID mDownloadStart; + jmethodID mDidReceiveData; + jmethodID mDidFinishLoading; + jmethodID mSetCertificate; + jmethodID mShouldSaveFormData; + jmethodID mSaveFormData; + jmethodID mAutoLogin; + AutoJObject frame(JNIEnv* env) { + return getRealObject(env, mObj); + } + AutoJObject history(JNIEnv* env) { + return getRealObject(env, mHistoryList); + } +}; + +static jfieldID gFrameField; +#define GET_NATIVE_FRAME(env, obj) ((WebCore::Frame*)env->GetIntField(obj, gFrameField)) +#define SET_NATIVE_FRAME(env, obj, frame) (env->SetIntField(obj, gFrameField, frame)) + +// ---------------------------------------------------------------------------- + +WebFrame::WebFrame(JNIEnv* env, jobject obj, jobject historyList, WebCore::Page* page) + : mPage(page) +{ + jclass clazz = env->GetObjectClass(obj); + mJavaFrame = new JavaBrowserFrame; + mJavaFrame->mObj = env->NewWeakGlobalRef(obj); + mJavaFrame->mHistoryList = env->NewWeakGlobalRef(historyList); + mJavaFrame->mStartLoadingResource = env->GetMethodID(clazz, "startLoadingResource", + "(ILjava/lang/String;Ljava/lang/String;Ljava/util/HashMap;[BJIZZZLjava/lang/String;Ljava/lang/String;)Landroid/webkit/LoadListener;"); + mJavaFrame->mMaybeSavePassword = env->GetMethodID(clazz, "maybeSavePassword", + "([BLjava/lang/String;Ljava/lang/String;)V"); + mJavaFrame->mShouldInterceptRequest = + env->GetMethodID(clazz, "shouldInterceptRequest", + "(Ljava/lang/String;)Landroid/webkit/WebResourceResponse;"); + mJavaFrame->mLoadStarted = env->GetMethodID(clazz, "loadStarted", + "(Ljava/lang/String;Landroid/graphics/Bitmap;IZ)V"); + mJavaFrame->mTransitionToCommitted = env->GetMethodID(clazz, "transitionToCommitted", + "(IZ)V"); + mJavaFrame->mLoadFinished = env->GetMethodID(clazz, "loadFinished", + "(Ljava/lang/String;IZ)V"); + mJavaFrame->mReportError = env->GetMethodID(clazz, "reportError", + "(ILjava/lang/String;Ljava/lang/String;)V"); + mJavaFrame->mSetTitle = env->GetMethodID(clazz, "setTitle", + "(Ljava/lang/String;)V"); + mJavaFrame->mWindowObjectCleared = env->GetMethodID(clazz, "windowObjectCleared", + "(I)V"); + mJavaFrame->mSetProgress = env->GetMethodID(clazz, "setProgress", + "(I)V"); + mJavaFrame->mDidReceiveIcon = env->GetMethodID(clazz, "didReceiveIcon", + "(Landroid/graphics/Bitmap;)V"); + mJavaFrame->mDidReceiveTouchIconUrl = env->GetMethodID(clazz, "didReceiveTouchIconUrl", + "(Ljava/lang/String;Z)V"); + mJavaFrame->mUpdateVisitedHistory = env->GetMethodID(clazz, "updateVisitedHistory", + "(Ljava/lang/String;Z)V"); + mJavaFrame->mHandleUrl = env->GetMethodID(clazz, "handleUrl", + "(Ljava/lang/String;)Z"); + mJavaFrame->mCreateWindow = env->GetMethodID(clazz, "createWindow", + "(ZZ)Landroid/webkit/BrowserFrame;"); + mJavaFrame->mCloseWindow = env->GetMethodID(clazz, "closeWindow", + "(Landroid/webkit/WebViewCore;)V"); + mJavaFrame->mDecidePolicyForFormResubmission = env->GetMethodID(clazz, + "decidePolicyForFormResubmission", "(I)V"); + mJavaFrame->mRequestFocus = env->GetMethodID(clazz, "requestFocus", + "()V"); + mJavaFrame->mGetRawResFilename = env->GetMethodID(clazz, "getRawResFilename", + "(I)Ljava/lang/String;"); + mJavaFrame->mDensity = env->GetMethodID(clazz, "density","()F"); + mJavaFrame->mGetFileSize = env->GetMethodID(clazz, "getFileSize", "(Ljava/lang/String;)I"); + mJavaFrame->mGetFile = env->GetMethodID(clazz, "getFile", "(Ljava/lang/String;[BII)I"); + mJavaFrame->mDidReceiveAuthenticationChallenge = env->GetMethodID(clazz, "didReceiveAuthenticationChallenge", + "(ILjava/lang/String;Ljava/lang/String;Z)V"); + mJavaFrame->mReportSslCertError = env->GetMethodID(clazz, "reportSslCertError", "(II[B)V"); + mJavaFrame->mDownloadStart = env->GetMethodID(clazz, "downloadStart", + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;J)V"); + mJavaFrame->mDidReceiveData = env->GetMethodID(clazz, "didReceiveData", "([BI)V"); + mJavaFrame->mDidFinishLoading = env->GetMethodID(clazz, "didFinishLoading", "()V"); + mJavaFrame->mSetCertificate = env->GetMethodID(clazz, "setCertificate", "([B)V"); + mJavaFrame->mShouldSaveFormData = env->GetMethodID(clazz, "shouldSaveFormData", "()Z"); + mJavaFrame->mSaveFormData = env->GetMethodID(clazz, "saveFormData", "(Ljava/util/HashMap;)V"); + mJavaFrame->mAutoLogin = env->GetMethodID(clazz, "autoLogin", + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"); + env->DeleteLocalRef(clazz); + + LOG_ASSERT(mJavaFrame->mStartLoadingResource, "Could not find method startLoadingResource"); + LOG_ASSERT(mJavaFrame->mMaybeSavePassword, "Could not find method maybeSavePassword"); + LOG_ASSERT(mJavaFrame->mShouldInterceptRequest, "Could not find method shouldInterceptRequest"); + LOG_ASSERT(mJavaFrame->mLoadStarted, "Could not find method loadStarted"); + LOG_ASSERT(mJavaFrame->mTransitionToCommitted, "Could not find method transitionToCommitted"); + LOG_ASSERT(mJavaFrame->mLoadFinished, "Could not find method loadFinished"); + LOG_ASSERT(mJavaFrame->mReportError, "Could not find method reportError"); + LOG_ASSERT(mJavaFrame->mSetTitle, "Could not find method setTitle"); + LOG_ASSERT(mJavaFrame->mWindowObjectCleared, "Could not find method windowObjectCleared"); + LOG_ASSERT(mJavaFrame->mSetProgress, "Could not find method setProgress"); + LOG_ASSERT(mJavaFrame->mDidReceiveIcon, "Could not find method didReceiveIcon"); + LOG_ASSERT(mJavaFrame->mDidReceiveTouchIconUrl, "Could not find method didReceiveTouchIconUrl"); + LOG_ASSERT(mJavaFrame->mUpdateVisitedHistory, "Could not find method updateVisitedHistory"); + LOG_ASSERT(mJavaFrame->mHandleUrl, "Could not find method handleUrl"); + LOG_ASSERT(mJavaFrame->mCreateWindow, "Could not find method createWindow"); + LOG_ASSERT(mJavaFrame->mCloseWindow, "Could not find method closeWindow"); + LOG_ASSERT(mJavaFrame->mDecidePolicyForFormResubmission, "Could not find method decidePolicyForFormResubmission"); + LOG_ASSERT(mJavaFrame->mRequestFocus, "Could not find method requestFocus"); + LOG_ASSERT(mJavaFrame->mGetRawResFilename, "Could not find method getRawResFilename"); + LOG_ASSERT(mJavaFrame->mDensity, "Could not find method density"); + LOG_ASSERT(mJavaFrame->mGetFileSize, "Could not find method getFileSize"); + LOG_ASSERT(mJavaFrame->mGetFile, "Could not find method getFile"); + LOG_ASSERT(mJavaFrame->mDidReceiveAuthenticationChallenge, "Could not find method didReceiveAuthenticationChallenge"); + LOG_ASSERT(mJavaFrame->mReportSslCertError, "Could not find method reportSslCertError"); + LOG_ASSERT(mJavaFrame->mDownloadStart, "Could not find method downloadStart"); + LOG_ASSERT(mJavaFrame->mDidReceiveData, "Could not find method didReceiveData"); + LOG_ASSERT(mJavaFrame->mDidFinishLoading, "Could not find method didFinishLoading"); + LOG_ASSERT(mJavaFrame->mSetCertificate, "Could not find method setCertificate"); + LOG_ASSERT(mJavaFrame->mShouldSaveFormData, "Could not find method shouldSaveFormData"); + LOG_ASSERT(mJavaFrame->mSaveFormData, "Could not find method saveFormData"); + LOG_ASSERT(mJavaFrame->mAutoLogin, "Could not find method autoLogin"); + + mUserAgent = WTF::String(); + mUserInitiatedAction = false; + mBlockNetworkLoads = false; + m_renderSkins = 0; +} + +WebFrame::~WebFrame() +{ + if (mJavaFrame->mObj) { + JNIEnv* env = getJNIEnv(); + env->DeleteWeakGlobalRef(mJavaFrame->mObj); + env->DeleteWeakGlobalRef(mJavaFrame->mHistoryList); + mJavaFrame->mObj = 0; + } + delete mJavaFrame; + delete m_renderSkins; +} + +WebFrame* WebFrame::getWebFrame(const WebCore::Frame* frame) +{ + FrameLoaderClientAndroid* client = + static_cast<FrameLoaderClientAndroid*> (frame->loader()->client()); + return client->webFrame(); +} + +static jobject createJavaMapFromHTTPHeaders(JNIEnv* env, const WebCore::HTTPHeaderMap& map) +{ + jclass mapClass = env->FindClass("java/util/HashMap"); + LOG_ASSERT(mapClass, "Could not find HashMap class!"); + jmethodID init = env->GetMethodID(mapClass, "<init>", "(I)V"); + LOG_ASSERT(init, "Could not find constructor for HashMap"); + jobject hashMap = env->NewObject(mapClass, init, map.size()); + LOG_ASSERT(hashMap, "Could not create a new HashMap"); + jmethodID put = env->GetMethodID(mapClass, "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); + LOG_ASSERT(put, "Could not find put method on HashMap"); + + WebCore::HTTPHeaderMap::const_iterator end = map.end(); + for (WebCore::HTTPHeaderMap::const_iterator i = map.begin(); i != end; ++i) { + if (i->first.length() == 0 || i->second.length() == 0) + continue; + jstring key = wtfStringToJstring(env, i->first); + jstring val = wtfStringToJstring(env, i->second); + if (key && val) { + env->CallObjectMethod(hashMap, put, key, val); + } + env->DeleteLocalRef(key); + env->DeleteLocalRef(val); + } + + env->DeleteLocalRef(mapClass); + + return hashMap; +} + +// This class stores the URI and the size of each file for upload. The URI is +// stored so we do not have to create it again. The size is stored so we can +// compare the actual size of the file with the stated size. If the actual size +// is larger, we will not copy it, since we will not have enough space in our +// buffer. +class FileInfo { +public: + FileInfo(JNIEnv* env, const WTF::String& name) { + m_uri = wtfStringToJstring(env, name); + checkException(env); + m_size = 0; + m_env = env; + } + ~FileInfo() { + m_env->DeleteLocalRef(m_uri); + } + int getSize() { return m_size; } + jstring getUri() { return m_uri; } + void setSize(int size) { m_size = size; } +private: + // This is only a pointer to the JNIEnv* returned by + // JSC::Bindings::getJNIEnv(). Used to delete the jstring when finished. + JNIEnv* m_env; + jstring m_uri; + int m_size; +}; + +PassRefPtr<WebCore::ResourceLoaderAndroid> +WebFrame::startLoadingResource(WebCore::ResourceHandle* loader, + const WebCore::ResourceRequest& request, + bool mainResource, + bool synchronous) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif + LOGV("::WebCore:: startLoadingResource(%p, %s)", + loader, request.url().string().latin1().data()); + + WTF::String method = request.httpMethod(); + WebCore::HTTPHeaderMap headers = request.httpHeaderFields(); + + JNIEnv* env = getJNIEnv(); + WTF::String urlStr = request.url().string(); + int colon = urlStr.find(':'); + bool allLower = true; + for (int index = 0; index < colon; index++) { + UChar ch = urlStr[index]; + if (!WTF::isASCIIAlpha(ch)) + break; + allLower &= WTF::isASCIILower(ch); + if (index == colon - 1 && !allLower) { + urlStr = urlStr.substring(0, colon).lower() + + urlStr.substring(colon); + } + } + LOGV("%s lower=%s", __FUNCTION__, urlStr.latin1().data()); + jstring jUrlStr = wtfStringToJstring(env, urlStr); + jstring jMethodStr = NULL; + if (!method.isEmpty()) + jMethodStr = wtfStringToJstring(env, method); + WebCore::FormData* formdata = request.httpBody(); + jbyteArray jPostDataStr = getPostData(request); + jobject jHeaderMap = createJavaMapFromHTTPHeaders(env, headers); + + // Convert the WebCore Cache Policy to a WebView Cache Policy. + int cacheMode = 0; // WebSettings.LOAD_NORMAL + switch (request.cachePolicy()) { + case WebCore::ReloadIgnoringCacheData: + cacheMode = 2; // WebSettings.LOAD_NO_CACHE + break; + case WebCore::ReturnCacheDataDontLoad: + cacheMode = 3; // WebSettings.LOAD_CACHE_ONLY + break; + case WebCore::ReturnCacheDataElseLoad: + cacheMode = 1; // WebSettings.LOAD_CACHE_ELSE_NETWORK + break; + case WebCore::UseProtocolCachePolicy: + default: + break; + } + + LOGV("::WebCore:: startLoadingResource %s with cacheMode %d", urlStr.ascii().data(), cacheMode); + + ResourceHandleInternal* loaderInternal = loader->getInternal(); + jstring jUsernameString = loaderInternal->m_user.isEmpty() ? + NULL : wtfStringToJstring(env, loaderInternal->m_user); + jstring jPasswordString = loaderInternal->m_pass.isEmpty() ? + NULL : wtfStringToJstring(env, loaderInternal->m_pass); + + bool isUserGesture = UserGestureIndicator::processingUserGesture(); + jobject jLoadListener = + env->CallObjectMethod(mJavaFrame->frame(env).get(), mJavaFrame->mStartLoadingResource, + (int)loader, jUrlStr, jMethodStr, jHeaderMap, + jPostDataStr, formdata ? formdata->identifier(): 0, + cacheMode, mainResource, isUserGesture, + synchronous, jUsernameString, jPasswordString); + + env->DeleteLocalRef(jUrlStr); + env->DeleteLocalRef(jMethodStr); + env->DeleteLocalRef(jPostDataStr); + env->DeleteLocalRef(jHeaderMap); + env->DeleteLocalRef(jUsernameString); + env->DeleteLocalRef(jPasswordString); + if (checkException(env)) + return NULL; + + PassRefPtr<WebCore::ResourceLoaderAndroid> h; + if (jLoadListener) + h = WebCoreResourceLoader::create(env, jLoadListener); + env->DeleteLocalRef(jLoadListener); + return h; +} + +UrlInterceptResponse* +WebFrame::shouldInterceptRequest(const WTF::String& url) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif + LOGV("::WebCore:: shouldInterceptRequest(%s)", url.latin1().data()); + + JNIEnv* env = getJNIEnv(); + jstring urlStr = wtfStringToJstring(env, url); + jobject response = env->CallObjectMethod(mJavaFrame->frame(env).get(), mJavaFrame->mShouldInterceptRequest, urlStr); + env->DeleteLocalRef(urlStr); + if (response == 0) + return 0; + return new UrlInterceptResponse(env, response); +} + +void +WebFrame::reportError(int errorCode, const WTF::String& description, + const WTF::String& failingUrl) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif + LOGV("::WebCore:: reportError(%d, %s)", errorCode, description.ascii().data()); + JNIEnv* env = getJNIEnv(); + + jstring descStr = wtfStringToJstring(env, description); + jstring failUrl = wtfStringToJstring(env, failingUrl); + env->CallVoidMethod(mJavaFrame->frame(env).get(), mJavaFrame->mReportError, + errorCode, descStr, failUrl); + env->DeleteLocalRef(descStr); + env->DeleteLocalRef(failUrl); +} + +void +WebFrame::loadStarted(WebCore::Frame* frame) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif + // activeDocumentLoader() can return null. + DocumentLoader* documentLoader = frame->loader()->activeDocumentLoader(); + if (documentLoader == NULL) + return; + + const WebCore::KURL& url = documentLoader->url(); + if (url.isEmpty()) + return; + LOGV("::WebCore:: loadStarted %s", url.string().ascii().data()); + + bool isMainFrame = (!frame->tree() || !frame->tree()->parent()); + WebCore::FrameLoadType loadType = frame->loader()->loadType(); + + if (loadType == WebCore::FrameLoadTypeReplace || + (loadType == WebCore::FrameLoadTypeRedirectWithLockedBackForwardList && + !isMainFrame)) + return; + + JNIEnv* env = getJNIEnv(); + const WTF::String& urlString = url.string(); + // If this is the main frame and we already have a favicon in the database, + // send it along with the page started notification. + jobject favicon = NULL; + if (isMainFrame) { + WebCore::Image* icon = WebCore::iconDatabase()->iconForPageURL(urlString, WebCore::IntSize(16, 16)); + if (icon) + favicon = webcoreImageToJavaBitmap(env, icon); + LOGV("favicons", "Starting load with icon %p for %s", icon, url.string().utf8().data()); + } + jstring urlStr = wtfStringToJstring(env, urlString); + + env->CallVoidMethod(mJavaFrame->frame(env).get(), mJavaFrame->mLoadStarted, urlStr, favicon, + (int)loadType, isMainFrame); + checkException(env); + env->DeleteLocalRef(urlStr); + if (favicon) + env->DeleteLocalRef(favicon); + + // Inform the client that the main frame has started a new load. + if (isMainFrame && mPage) { + Chrome* chrome = mPage->chrome(); + if (chrome) { + ChromeClientAndroid* client = static_cast<ChromeClientAndroid*>(chrome->client()); + if (client) + client->onMainFrameLoadStarted(); + } + } +} + +void +WebFrame::transitionToCommitted(WebCore::Frame* frame) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif + JNIEnv* env = getJNIEnv(); + WebCore::FrameLoadType loadType = frame->loader()->loadType(); + bool isMainFrame = (!frame->tree() || !frame->tree()->parent()); + env->CallVoidMethod(mJavaFrame->frame(env).get(), mJavaFrame->mTransitionToCommitted, + (int)loadType, isMainFrame); + checkException(env); +} + +void +WebFrame::didFinishLoad(WebCore::Frame* frame) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif + JNIEnv* env = getJNIEnv(); + + // activeDocumentLoader() can return null. + WebCore::FrameLoader* loader = frame->loader(); + DocumentLoader* documentLoader = loader->activeDocumentLoader(); + if (documentLoader == NULL) + return; + + const WebCore::KURL& url = documentLoader->url(); + if (url.isEmpty()) + return; + LOGV("::WebCore:: didFinishLoad %s", url.string().ascii().data()); + + bool isMainFrame = (!frame->tree() || !frame->tree()->parent()); + WebCore::FrameLoadType loadType = loader->loadType(); + const WTF::String& urlString = url.string(); + jstring urlStr = wtfStringToJstring(env, urlString); + env->CallVoidMethod(mJavaFrame->frame(env).get(), mJavaFrame->mLoadFinished, urlStr, + (int)loadType, isMainFrame); + checkException(env); + env->DeleteLocalRef(urlStr); +} + +void +WebFrame::addHistoryItem(WebCore::HistoryItem* item) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif + LOGV("::WebCore:: addHistoryItem"); + JNIEnv* env = getJNIEnv(); + WebHistory::AddItem(mJavaFrame->history(env), item); +} + +void +WebFrame::removeHistoryItem(int index) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif + LOGV("::WebCore:: removeHistoryItem at %d", index); + JNIEnv* env = getJNIEnv(); + WebHistory::RemoveItem(mJavaFrame->history(env), index); +} + +void +WebFrame::updateHistoryIndex(int newIndex) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif + LOGV("::WebCore:: updateHistoryIndex to %d", newIndex); + JNIEnv* env = getJNIEnv(); + WebHistory::UpdateHistoryIndex(mJavaFrame->history(env), newIndex); +} + +void +WebFrame::setTitle(const WTF::String& title) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif +#ifndef NDEBUG + LOGV("setTitle(%s)", title.ascii().data()); +#endif + JNIEnv* env = getJNIEnv(); + jstring jTitleStr = wtfStringToJstring(env, title); + + env->CallVoidMethod(mJavaFrame->frame(env).get(), mJavaFrame->mSetTitle, jTitleStr); + checkException(env); + env->DeleteLocalRef(jTitleStr); +} + +void +WebFrame::windowObjectCleared(WebCore::Frame* frame) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif + LOGV("::WebCore:: windowObjectCleared"); + JNIEnv* env = getJNIEnv(); + + env->CallVoidMethod(mJavaFrame->frame(env).get(), mJavaFrame->mWindowObjectCleared, (int)frame); + checkException(env); +} + +void +WebFrame::setProgress(float newProgress) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif + JNIEnv* env = getJNIEnv(); + int progress = (int) (100 * newProgress); + env->CallVoidMethod(mJavaFrame->frame(env).get(), mJavaFrame->mSetProgress, progress); + checkException(env); +} + +const WTF::String +WebFrame::userAgentForURL(const WebCore::KURL* url) +{ + return mUserAgent; +} + +void +WebFrame::didReceiveIcon(WebCore::Image* icon) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif + LOG_ASSERT(icon, "DidReceiveIcon called without an image!"); + JNIEnv* env = getJNIEnv(); + jobject bitmap = webcoreImageToJavaBitmap(env, icon); + if (!bitmap) + return; + + env->CallVoidMethod(mJavaFrame->frame(env).get(), mJavaFrame->mDidReceiveIcon, bitmap); + env->DeleteLocalRef(bitmap); + checkException(env); +} + +void +WebFrame::didReceiveTouchIconURL(const WTF::String& url, bool precomposed) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif + JNIEnv* env = getJNIEnv(); + jstring jUrlStr = wtfStringToJstring(env, url); + + env->CallVoidMethod(mJavaFrame->frame(env).get(), + mJavaFrame->mDidReceiveTouchIconUrl, jUrlStr, precomposed); + env->DeleteLocalRef(jUrlStr); + checkException(env); +} + +void +WebFrame::updateVisitedHistory(const WebCore::KURL& url, bool reload) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif + const WTF::String& urlStr = url.string(); + JNIEnv* env = getJNIEnv(); + jstring jUrlStr = wtfStringToJstring(env, urlStr); + + env->CallVoidMethod(mJavaFrame->frame(env).get(), mJavaFrame->mUpdateVisitedHistory, jUrlStr, reload); + env->DeleteLocalRef(jUrlStr); + checkException(env); +} + +bool +WebFrame::canHandleRequest(const WebCore::ResourceRequest& request) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif + // always handle "POST" in place + if (equalIgnoringCase(request.httpMethod(), "POST")) + return true; + const WebCore::KURL& requestUrl = request.url(); + bool isUserGesture = UserGestureIndicator::processingUserGesture(); + if (!mUserInitiatedAction && !isUserGesture && + (requestUrl.protocolIs("http") || requestUrl.protocolIs("https") || + requestUrl.protocolIs("file") || requestUrl.protocolIs("about") || + WebCore::protocolIsJavaScript(requestUrl.string()))) + return true; + const WTF::String& url = requestUrl.string(); + // Empty urls should not be sent to java + if (url.isEmpty()) + return true; + JNIEnv* env = getJNIEnv(); + jstring jUrlStr = wtfStringToJstring(env, url); + + // check to see whether browser app wants to hijack url loading. + // if browser app handles the url, we will return false to bail out WebCore loading + jboolean ret = env->CallBooleanMethod(mJavaFrame->frame(env).get(), mJavaFrame->mHandleUrl, jUrlStr); + checkException(env); + env->DeleteLocalRef(jUrlStr); + return (ret == 0); +} + +bool +WebFrame::shouldSaveFormData() +{ + JNIEnv* env = getJNIEnv(); + jboolean ret = env->CallBooleanMethod(mJavaFrame->frame(env).get(), + mJavaFrame->mShouldSaveFormData); + checkException(env); + return ret; +} + +WebCore::Frame* +WebFrame::createWindow(bool dialog, bool userGesture) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif + JNIEnv* env = getJNIEnv(); + jobject obj = env->CallObjectMethod(mJavaFrame->frame(env).get(), + mJavaFrame->mCreateWindow, dialog, userGesture); + if (obj) { + WebCore::Frame* frame = GET_NATIVE_FRAME(env, obj); + return frame; + } + return NULL; +} + +void +WebFrame::requestFocus() const +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif + JNIEnv* env = getJNIEnv(); + env->CallVoidMethod(mJavaFrame->frame(env).get(), mJavaFrame->mRequestFocus); + checkException(env); +} + +void +WebFrame::closeWindow(WebViewCore* webViewCore) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif + assert(webViewCore); + JNIEnv* env = getJNIEnv(); + env->CallVoidMethod(mJavaFrame->frame(env).get(), mJavaFrame->mCloseWindow, + webViewCore->getJavaObject().get()); +} + +struct PolicyFunctionWrapper { + WebCore::FramePolicyFunction func; +}; + +void +WebFrame::decidePolicyForFormResubmission(WebCore::FramePolicyFunction func) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif + JNIEnv* env = getJNIEnv(); + PolicyFunctionWrapper* p = new PolicyFunctionWrapper; + p->func = func; + env->CallVoidMethod(mJavaFrame->frame(env).get(), mJavaFrame->mDecidePolicyForFormResubmission, p); +} + +WTF::String +WebFrame::getRawResourceFilename(WebCore::PlatformBridge::rawResId id) const +{ + JNIEnv* env = getJNIEnv(); + jstring ret = (jstring) env->CallObjectMethod(mJavaFrame->frame(env).get(), + mJavaFrame->mGetRawResFilename, (int)id); + + return jstringToWtfString(env, ret); +} + +float +WebFrame::density() const +{ + JNIEnv* env = getJNIEnv(); + jfloat dpi = env->CallFloatMethod(mJavaFrame->frame(env).get(), mJavaFrame->mDensity); + checkException(env); + return dpi; +} + +#if USE(CHROME_NETWORK_STACK) +void +WebFrame::didReceiveAuthenticationChallenge(WebUrlLoaderClient* client, const std::string& host, const std::string& realm, bool useCachedCredentials) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif + JNIEnv* env = getJNIEnv(); + int jHandle = reinterpret_cast<int>(client); + jstring jHost = stdStringToJstring(env, host, true); + jstring jRealm = stdStringToJstring(env, realm, true); + + env->CallVoidMethod(mJavaFrame->frame(env).get(), + mJavaFrame->mDidReceiveAuthenticationChallenge, jHandle, jHost, jRealm, useCachedCredentials); + env->DeleteLocalRef(jHost); + env->DeleteLocalRef(jRealm); + checkException(env); +} +#endif + +void +WebFrame::reportSslCertError(WebUrlLoaderClient* client, int cert_error, const std::string& cert) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif + JNIEnv* env = getJNIEnv(); + int jHandle = reinterpret_cast<int>(client); + + int len = cert.length(); + jbyteArray jCert = env->NewByteArray(len); + jbyte* bytes = env->GetByteArrayElements(jCert, NULL); + cert.copy(reinterpret_cast<char*>(bytes), len); + + env->CallVoidMethod(mJavaFrame->frame(env).get(), + mJavaFrame->mReportSslCertError, jHandle, cert_error, jCert); + env->DeleteLocalRef(jCert); + checkException(env); +} + +#if USE(CHROME_NETWORK_STACK) +void +WebFrame::downloadStart(const std::string& url, const std::string& userAgent, const std::string& contentDisposition, const std::string& mimetype, long long contentLength) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif + JNIEnv* env = getJNIEnv(); + jstring jUrl = stdStringToJstring(env, url, true); + jstring jUserAgent = stdStringToJstring(env, userAgent, true); + jstring jContentDisposition = stdStringToJstring(env, contentDisposition, true); + jstring jMimetype = stdStringToJstring(env, mimetype, true); + + env->CallVoidMethod(mJavaFrame->frame(env).get(), + mJavaFrame->mDownloadStart, jUrl, jUserAgent, jContentDisposition, jMimetype, contentLength); + + env->DeleteLocalRef(jUrl); + env->DeleteLocalRef(jUserAgent); + env->DeleteLocalRef(jContentDisposition); + env->DeleteLocalRef(jMimetype); + checkException(env); +} + +void +WebFrame::didReceiveData(const char* data, int size) { +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif + JNIEnv* env = getJNIEnv(); + + jbyteArray jData = env->NewByteArray(size); + jbyte* bytes = env->GetByteArrayElements(jData, NULL); + memcpy(reinterpret_cast<char*>(bytes), data, size); + + env->CallVoidMethod(mJavaFrame->frame(env).get(), + mJavaFrame->mDidReceiveData, jData, size); + env->DeleteLocalRef(jData); + checkException(env); +} + +void +WebFrame::didFinishLoading() { +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif + JNIEnv* env = getJNIEnv(); + + env->CallVoidMethod(mJavaFrame->frame(env).get(), mJavaFrame->mDidFinishLoading); + checkException(env); +} + +#endif + +#if USE(CHROME_NETWORK_STACK) +void WebFrame::setCertificate(const std::string& cert) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); +#endif + JNIEnv* env = getJNIEnv(); + + int len = cert.length(); + jbyteArray jCert = env->NewByteArray(len); + jbyte* bytes = env->GetByteArrayElements(jCert, NULL); + cert.copy(reinterpret_cast<char*>(bytes), len); + + env->CallVoidMethod(mJavaFrame->frame(env).get(), mJavaFrame->mSetCertificate, jCert); + + env->DeleteLocalRef(jCert); + checkException(env); +} +#endif + +void WebFrame::autoLogin(const std::string& loginHeader) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimerCoutner::JavaCallbackTimeCounter); +#endif + WTF::String header(loginHeader.c_str(), loginHeader.length()); + WTF::Vector<WTF::String> split; + header.split('&', split); + if (!split.isEmpty()) { + WTF::String realm; + WTF::String account; + WTF::String args; + int len = split.size(); + while (len--) { + WTF::String& str = split[len]; + size_t equals = str.find('='); + if (equals == WTF::notFound) + continue; + + WTF::String* result = 0; + if (str.startsWith("realm", false)) + result = &realm; + else if (str.startsWith("account", false)) + result = &account; + else if (str.startsWith("args", false)) + result = &args; + + if (result) + // Decode url escape sequences before sending to the app. + *result = WebCore::decodeURLEscapeSequences(str.substring(equals + 1)); + } + + // realm and args are required parameters. + if (realm.isEmpty() || args.isEmpty()) + return; + + JNIEnv* env = getJNIEnv(); + jstring jRealm = wtfStringToJstring(env, realm, true); + jstring jAccount = wtfStringToJstring(env, account); + jstring jArgs = wtfStringToJstring(env, args, true); + env->CallVoidMethod(mJavaFrame->frame(env).get(), + mJavaFrame->mAutoLogin, jRealm, jAccount, jArgs); + } +} + +void WebFrame::maybeSavePassword(WebCore::Frame* frame, const WebCore::ResourceRequest& request) +{ + if (request.httpMethod() != "POST") + return; + + WTF::String username; + WTF::String password; + if (!getUsernamePasswordFromDom(frame, username, password)) + return; + + JNIEnv* env = getJNIEnv(); + jstring jUsername = wtfStringToJstring(env, username); + jstring jPassword = wtfStringToJstring(env, password); + jbyteArray jPostData = getPostData(request); + if (jPostData) { + env->CallVoidMethod(mJavaFrame->frame(env).get(), + mJavaFrame->mMaybeSavePassword, jPostData, jUsername, jPassword); + } + + env->DeleteLocalRef(jPostData); + env->DeleteLocalRef(jUsername); + env->DeleteLocalRef(jPassword); + checkException(env); +} + +bool WebFrame::getUsernamePasswordFromDom(WebCore::Frame* frame, WTF::String& username, WTF::String& password) +{ + bool found = false; + WTF::PassRefPtr<WebCore::HTMLCollection> form = frame->document()->forms(); + WebCore::Node* node = form->firstItem(); + while (node && !found && !node->namespaceURI().isNull() && + !node->namespaceURI().isEmpty()) { + const WTF::Vector<WebCore::FormAssociatedElement*>& elements = + ((WebCore::HTMLFormElement*)node)->associatedElements(); + size_t size = elements.size(); + for (size_t i = 0; i< size && !found; i++) { + WebCore::HTMLElement* e = toHTMLElement(elements[i]); + if (e->hasLocalName(WebCore::HTMLNames::inputTag)) { + WebCore::HTMLInputElement* input = (WebCore::HTMLInputElement*)e; + if (input->autoComplete() == false) + continue; + if (input->isPasswordField()) + password = input->value(); + else if (input->isTextField() || input->isEmailField()) + username = input->value(); + if (!username.isNull() && !password.isNull()) + found = true; + } + } + node = form->nextItem(); + } + return found; +} + +jbyteArray WebFrame::getPostData(const WebCore::ResourceRequest& request) +{ + jbyteArray jPostDataStr = NULL; + WebCore::FormData* formdata = request.httpBody(); + if (formdata) { + JNIEnv* env = getJNIEnv(); + AutoJObject obj = mJavaFrame->frame(env); + + // We can use the formdata->flatten() but it will result in two + // memcpys, first through loading up the vector with the form data + // then another to copy it out of the vector and into the java byte + // array. Instead, we copy the form data ourselves below saving a + // memcpy. + const WTF::Vector<WebCore::FormDataElement>& elements = + formdata->elements(); + + // Sizing pass + int size = 0; + size_t n = elements.size(); + FileInfo** fileinfos = new FileInfo*[n]; + for (size_t i = 0; i < n; ++i) { + fileinfos[i] = 0; + const WebCore::FormDataElement& e = elements[i]; + if (e.m_type == WebCore::FormDataElement::data) { + size += e.m_data.size(); + } else if (e.m_type == WebCore::FormDataElement::encodedFile) { + fileinfos[i] = new FileInfo(env, e.m_filename); + int delta = env->CallIntMethod(obj.get(), + mJavaFrame->mGetFileSize, fileinfos[i]->getUri()); + checkException(env); + fileinfos[i]->setSize(delta); + size += delta; + } + } + + // Only create the byte array if there is POST data to pass up. + // The Java code is expecting null if there is no data. + if (size > 0) { + // Copy the actual form data. + jPostDataStr = env->NewByteArray(size); + if (jPostDataStr) { + // Write the form data to the java array. + jbyte* bytes = env->GetByteArrayElements(jPostDataStr, NULL); + int offset = 0; + for (size_t i = 0; i < n; ++i) { + const WebCore::FormDataElement& e = elements[i]; + if (e.m_type == WebCore::FormDataElement::data) { + int delta = e.m_data.size(); + memcpy(bytes + offset, e.m_data.data(), delta); + offset += delta; + } else if (e.m_type + == WebCore::FormDataElement::encodedFile) { + int delta = env->CallIntMethod(obj.get(), + mJavaFrame->mGetFile, fileinfos[i]->getUri(), + jPostDataStr, offset, fileinfos[i]->getSize()); + checkException(env); + offset += delta; + } + } + env->ReleaseByteArrayElements(jPostDataStr, bytes, 0); + } + } + delete[] fileinfos; + } + return jPostDataStr; +} + +// ---------------------------------------------------------------------------- + +static void CallPolicyFunction(JNIEnv* env, jobject obj, jint func, jint decision) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); +#endif + WebCore::Frame* pFrame = GET_NATIVE_FRAME(env, obj); + LOG_ASSERT(pFrame, "nativeCallPolicyFunction must take a valid frame pointer!"); + PolicyFunctionWrapper* pFunc = (PolicyFunctionWrapper*)func; + LOG_ASSERT(pFunc, "nativeCallPolicyFunction must take a valid function pointer!"); + + // If we are resending the form then we should reset the multiple submission protection. + if (decision == WebCore::PolicyUse) + pFrame->loader()->resetMultipleFormSubmissionProtection(); + + (pFrame->loader()->policyChecker()->*(pFunc->func))((WebCore::PolicyAction)decision); +} + +static void CreateFrame(JNIEnv* env, jobject obj, jobject javaview, jobject jAssetManager, jobject historyList) +{ + ScriptController::initializeThreading(); + +#if USE(CHROME_NETWORK_STACK) + // needs to be called before any other chromium code + initChromium(); +#endif + +#ifdef ANDROID_INSTRUMENT +#if USE(V8) + V8Counters::initCounters(); +#endif + TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); +#endif + // Create a new page + ChromeClientAndroid* chromeC = new ChromeClientAndroid; + EditorClientAndroid* editorC = new EditorClientAndroid; + DeviceMotionClientAndroid* deviceMotionC = new DeviceMotionClientAndroid; + DeviceOrientationClientAndroid* deviceOrientationC = new DeviceOrientationClientAndroid; + + WebCore::Page::PageClients pageClients; + pageClients.chromeClient = chromeC; + pageClients.contextMenuClient = new ContextMenuClientAndroid; + pageClients.editorClient = editorC; + pageClients.dragClient = new DragClientAndroid; + pageClients.inspectorClient = new InspectorClientAndroid; + pageClients.deviceMotionClient = deviceMotionC; + pageClients.deviceOrientationClient = deviceOrientationC; + WebCore::Page* page = new WebCore::Page(pageClients); + + editorC->setPage(page); + page->setGroupName("android.webkit"); + + // Create a WebFrame to access the Java BrowserFrame associated with this page + WebFrame* webFrame = new WebFrame(env, obj, historyList, page); + // Attach webFrame to pageClients.chromeClient and release our ownership + chromeC->setWebFrame(webFrame); + Release(webFrame); + + FrameLoaderClientAndroid* loaderC = new FrameLoaderClientAndroid(webFrame); + // Create a Frame and the page holds its reference + WebCore::Frame* frame = WebCore::Frame::create(page, NULL, loaderC).get(); + loaderC->setFrame(frame); +#if ENABLE(WDS) + WDS::server()->addFrame(frame); +#endif + + // Create a WebViewCore to access the Java WebViewCore associated with this page + WebViewCore* webViewCore = new WebViewCore(env, javaview, frame); + +#if ENABLE(WEB_AUTOFILL) + editorC->getAutoFill()->setWebViewCore(webViewCore); +#endif + + // Create a FrameView + RefPtr<WebCore::FrameView> frameView = WebCore::FrameView::create(frame); + // Create a WebFrameView + WebFrameView* webFrameView = new WebFrameView(frameView.get(), webViewCore); + // As webFrameView Retains webViewCore, release our ownership + Release(webViewCore); + // As frameView Retains webFrameView, release our ownership + Release(webFrameView); + // Attach the frameView to the frame and release our ownership + frame->setView(frameView); + // Set the frame to active to turn on keyboard focus. + frame->init(); + frame->selection()->setFocused(true); + frame->page()->focusController()->setFocused(true); + deviceMotionC->setWebViewCore(webViewCore); + deviceOrientationC->setWebViewCore(webViewCore); + + // Allow local access to file:/// and substitute data + WebCore::SecurityOrigin::setLocalLoadPolicy( + WebCore::SecurityOrigin::AllowLocalLoadsForLocalAndSubstituteData); + + LOGV("::WebCore:: createFrame %p", frame); + + // Set the mNativeFrame field in Frame + SET_NATIVE_FRAME(env, obj, (int)frame); + + String directory = webFrame->getRawResourceFilename( + WebCore::PlatformBridge::DrawableDir); + if (directory.isEmpty()) + LOGE("Can't find the drawable directory"); + else { + // Setup the asset manager. + AssetManager* am = assetManagerForJavaObject(env, jAssetManager); + // Initialize our skinning classes + webFrame->setRenderSkins(new WebCore::RenderSkinAndroid(am, directory)); + } + for (int i = WebCore::PlatformBridge::FileUploadLabel; + i <= WebCore::PlatformBridge::FileUploadNoFileChosenLabel; i++) + initGlobalLocalizedName( + static_cast<WebCore::PlatformBridge::rawResId>(i), webFrame); +} + +static void DestroyFrame(JNIEnv* env, jobject obj) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); +#endif + WebCore::Frame* pFrame = GET_NATIVE_FRAME(env, obj); + LOG_ASSERT(pFrame, "nativeDestroyFrame must take a valid frame pointer!"); + + LOGV("::WebCore:: deleting frame %p", pFrame); + + WebCore::FrameView* view = pFrame->view(); + view->ref(); + // detachFromParent will cause the page to be closed. + WebCore::FrameLoader* fl = pFrame->loader(); + // retain a pointer because detachFromParent will set the page to null. + WebCore::Page* page = pFrame->page(); + if (fl) + fl->detachFromParent(); + delete page; + view->deref(); + + SET_NATIVE_FRAME(env, obj, 0); +#if ENABLE(WDS) + WDS::server()->removeFrame(pFrame); +#endif +} + +static void LoadUrl(JNIEnv *env, jobject obj, jstring url, jobject headers) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); +#endif + WebCore::Frame* pFrame = GET_NATIVE_FRAME(env, obj); + LOG_ASSERT(pFrame, "nativeLoadUrl must take a valid frame pointer!"); + + WTF::String webcoreUrl = jstringToWtfString(env, url); + WebCore::KURL kurl(WebCore::KURL(), webcoreUrl); + WebCore::ResourceRequest request(kurl); + if (headers) { + // dalvikvm will raise exception if any of these fail + jclass mapClass = env->FindClass("java/util/Map"); + jmethodID entrySet = env->GetMethodID(mapClass, "entrySet", + "()Ljava/util/Set;"); + jobject set = env->CallObjectMethod(headers, entrySet); + + jclass setClass = env->FindClass("java/util/Set"); + jmethodID iterator = env->GetMethodID(setClass, "iterator", + "()Ljava/util/Iterator;"); + jobject iter = env->CallObjectMethod(set, iterator); + + jclass iteratorClass = env->FindClass("java/util/Iterator"); + jmethodID hasNext = env->GetMethodID(iteratorClass, "hasNext", "()Z"); + jmethodID next = env->GetMethodID(iteratorClass, "next", + "()Ljava/lang/Object;"); + jclass entryClass = env->FindClass("java/util/Map$Entry"); + jmethodID getKey = env->GetMethodID(entryClass, "getKey", + "()Ljava/lang/Object;"); + jmethodID getValue = env->GetMethodID(entryClass, "getValue", + "()Ljava/lang/Object;"); + + while (env->CallBooleanMethod(iter, hasNext)) { + jobject entry = env->CallObjectMethod(iter, next); + jstring key = (jstring) env->CallObjectMethod(entry, getKey); + jstring value = (jstring) env->CallObjectMethod(entry, getValue); + request.setHTTPHeaderField(jstringToWtfString(env, key), jstringToWtfString(env, value)); + env->DeleteLocalRef(entry); + env->DeleteLocalRef(key); + env->DeleteLocalRef(value); + } + + env->DeleteLocalRef(entryClass); + env->DeleteLocalRef(iteratorClass); + env->DeleteLocalRef(iter); + env->DeleteLocalRef(setClass); + env->DeleteLocalRef(set); + env->DeleteLocalRef(mapClass); + } + LOGV("LoadUrl %s", kurl.string().latin1().data()); + pFrame->loader()->load(request, false); +} + +static void PostUrl(JNIEnv *env, jobject obj, jstring url, jbyteArray postData) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); +#endif + WebCore::Frame* pFrame = GET_NATIVE_FRAME(env, obj); + LOG_ASSERT(pFrame, "nativePostUrl must take a valid frame pointer!"); + + WebCore::KURL kurl(WebCore::KURL(), jstringToWtfString(env, url)); + WebCore::ResourceRequest request(kurl); + request.setHTTPMethod("POST"); + request.setHTTPContentType("application/x-www-form-urlencoded"); + + if (postData) { + jsize size = env->GetArrayLength(postData); + jbyte* bytes = env->GetByteArrayElements(postData, NULL); + RefPtr<FormData> formData = FormData::create((const void*)bytes, size); + // the identifier uses the same logic as generateFormDataIdentifier() in + // HTMLFormElement.cpp + formData->setIdentifier(static_cast<int64_t>(WTF::currentTime() * 1000000.0)); + request.setHTTPBody(formData); + env->ReleaseByteArrayElements(postData, bytes, 0); + } + + LOGV("PostUrl %s", kurl.string().latin1().data()); + WebCore::FrameLoadRequest frameRequest(pFrame->document()->securityOrigin(), request); + pFrame->loader()->loadFrameRequest(frameRequest, false, false, 0, 0, WebCore::SendReferrer); +} + +static void LoadData(JNIEnv *env, jobject obj, jstring baseUrl, jstring data, + jstring mimeType, jstring encoding, jstring failUrl) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); +#endif + WebCore::Frame* pFrame = GET_NATIVE_FRAME(env, obj); + LOG_ASSERT(pFrame, "nativeLoadData must take a valid frame pointer!"); + + // Setup the resource request + WebCore::ResourceRequest request(jstringToWtfString(env, baseUrl)); + + // Setup the substituteData + const char* dataStr = env->GetStringUTFChars(data, NULL); + WTF::PassRefPtr<WebCore::SharedBuffer> sharedBuffer = + WebCore::SharedBuffer::create(); + LOG_ASSERT(dataStr, "nativeLoadData has a null data string."); + sharedBuffer->append(dataStr, strlen(dataStr)); + env->ReleaseStringUTFChars(data, dataStr); + + WebCore::SubstituteData substituteData(sharedBuffer, + jstringToWtfString(env, mimeType), jstringToWtfString(env, encoding), + WebCore::KURL(ParsedURLString, jstringToWtfString(env, failUrl))); + + // Perform the load + pFrame->loader()->load(request, substituteData, false); +} + +static void StopLoading(JNIEnv *env, jobject obj) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); +#endif + WebCore::Frame* pFrame = GET_NATIVE_FRAME(env, obj); + LOG_ASSERT(pFrame, "nativeStopLoading must take a valid frame pointer!"); + LOGV("::WebCore:: stopLoading %p", pFrame); + + // Stop loading the page and do not send an unload event + pFrame->loader()->stopForUserCancel(); +} + +#if ENABLE(ARCHIVE) +static String saveArchiveAutoname(String basename, String name, String extension) { + if (name.isNull() || name.isEmpty()) { + name = String("index"); + } + + String testname = basename; + testname.append(name); + testname.append(extension); + + errno = 0; + struct stat permissions; + if (stat(testname.utf8().data(), &permissions) < 0) { + if (errno == ENOENT) + return testname; + return String(); + } + + const int maxAttempts = 100; + for (int i = 1; i < maxAttempts; i++) { + String testname = basename; + testname.append(name); + testname.append("-"); + testname.append(String::number(i)); + testname.append(extension); + + errno = 0; + if (stat(testname.utf8().data(), &permissions) < 0) { + if (errno == ENOENT) + return testname; + return String(); + } + } + + return String(); +} +#endif + +static jstring SaveWebArchive(JNIEnv *env, jobject obj, jstring basename, jboolean autoname) +{ +#if ENABLE(ARCHIVE) + WebCore::Frame* pFrame = GET_NATIVE_FRAME(env, obj); + LOG_ASSERT(pFrame, "nativeSaveWebArchive must take a valid frame pointer!"); + String mimeType = pFrame->loader()->documentLoader()->mainResource()->mimeType(); + if ((mimeType != "text/html") && (mimeType != "application/xhtml+xml")) + return NULL; + + const char* basenameNative = getCharactersFromJStringInEnv(env, basename); + String basenameString = String::fromUTF8(basenameNative); + String filename; + + if (autoname) { + String name = pFrame->loader()->documentLoader()->originalURL().lastPathComponent(); + String extension = String(".webarchivexml"); + filename = saveArchiveAutoname(basenameString, name, extension); + } else { + filename = basenameString; + } + + if (filename.isNull() || filename.isEmpty()) { + LOGD("saveWebArchive: Failed to select a filename to save."); + releaseCharactersForJStringInEnv(env, basename, basenameNative); + return NULL; + } + + const int noCompression = 0; + xmlTextWriterPtr writer = xmlNewTextWriterFilename(filename.utf8().data(), noCompression); + if (writer == NULL) { + LOGD("saveWebArchive: Failed to initialize xml writer."); + releaseCharactersForJStringInEnv(env, basename, basenameNative); + return NULL; + } + + RefPtr<WebArchiveAndroid> archive = WebCore::WebArchiveAndroid::create(pFrame); + + bool result = archive->saveWebArchive(writer); + + releaseCharactersForJStringInEnv(env, basename, basenameNative); + xmlFreeTextWriter(writer); + + if (result) + return wtfStringToJstring(env, filename); + + return NULL; +#endif +} + +static jstring ExternalRepresentation(JNIEnv *env, jobject obj) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); +#endif + WebCore::Frame* pFrame = GET_NATIVE_FRAME(env, obj); + LOG_ASSERT(pFrame, "android_webcore_nativeExternalRepresentation must take a valid frame pointer!"); + + // Request external representation of the render tree + WTF::String renderDump = WebCore::externalRepresentation(pFrame); + return wtfStringToJstring(env, renderDump); +} + +static StringBuilder FrameAsText(WebCore::Frame *pFrame, jboolean dumpChildFrames) { + StringBuilder renderDump; + if (!pFrame) + return renderDump; + WebCore::Element *documentElement = pFrame->document()->documentElement(); + if (!documentElement) + return renderDump; + if (pFrame->tree()->parent()) { + renderDump.append("\n--------\nFrame: '"); + renderDump.append(pFrame->tree()->name()); + renderDump.append("'\n--------\n"); + } + renderDump.append(((WebCore::HTMLElement*)documentElement)->innerText()); + renderDump.append("\n"); + if (dumpChildFrames) { + for (unsigned i = 0; i < pFrame->tree()->childCount(); ++i) { + renderDump.append(FrameAsText(pFrame->tree()->child(i), dumpChildFrames).toString()); + } + } + return renderDump; +} + +static jstring DocumentAsText(JNIEnv *env, jobject obj) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); +#endif + WebCore::Frame* pFrame = GET_NATIVE_FRAME(env, obj); + LOG_ASSERT(pFrame, "android_webcore_nativeDocumentAsText must take a valid frame pointer!"); + + WTF::String renderDump = FrameAsText(pFrame, false /* dumpChildFrames */).toString(); + return wtfStringToJstring(env, renderDump); +} + +static jstring ChildFramesAsText(JNIEnv *env, jobject obj) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); +#endif + WebCore::Frame* pFrame = GET_NATIVE_FRAME(env, obj); + LOG_ASSERT(pFrame, "android_webcore_nativeDocumentAsText must take a valid frame pointer!"); + + StringBuilder renderDumpBuilder; + for (unsigned i = 0; i < pFrame->tree()->childCount(); ++i) { + renderDumpBuilder.append(FrameAsText(pFrame->tree()->child(i), true /* dumpChildFrames */).toString()); + } + WTF::String renderDump = renderDumpBuilder.toString(); + return wtfStringToJstring(env, renderDump); +} + +static void Reload(JNIEnv *env, jobject obj, jboolean allowStale) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); +#endif + WebCore::Frame* pFrame = GET_NATIVE_FRAME(env, obj); + LOG_ASSERT(pFrame, "nativeReload must take a valid frame pointer!"); + + WebCore::FrameLoader* loader = pFrame->loader(); + if (allowStale) { + // load the current page with FrameLoadTypeIndexedBackForward so that it + // will use cache when it is possible + WebCore::Page* page = pFrame->page(); + WebCore::HistoryItem* item = page->backForwardList()->currentItem(); + if (item) + page->goToItem(item, FrameLoadTypeIndexedBackForward); + } else + loader->reload(true); +} + +static void GoBackOrForward(JNIEnv *env, jobject obj, jint pos) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); +#endif + WebCore::Frame* pFrame = GET_NATIVE_FRAME(env, obj); + LOG_ASSERT(pFrame, "nativeGoBackOrForward must take a valid frame pointer!"); + + if (pos == 1) + pFrame->page()->goForward(); + else if (pos == -1) + pFrame->page()->goBack(); + else + pFrame->page()->goBackOrForward(pos); +} + +static jobject StringByEvaluatingJavaScriptFromString(JNIEnv *env, jobject obj, jstring script) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); +#endif + WebCore::Frame* pFrame = GET_NATIVE_FRAME(env, obj); + LOG_ASSERT(pFrame, "stringByEvaluatingJavaScriptFromString must take a valid frame pointer!"); + + WebCore::ScriptValue value = + pFrame->script()->executeScript(jstringToWtfString(env, script), true); + WTF::String result = WTF::String(); + ScriptState* scriptState = mainWorldScriptState(pFrame); + if (!value.getString(scriptState, result)) + return NULL; + return wtfStringToJstring(env, result); +} + +// Wrap the JavaInstance used when binding custom javascript interfaces. Use a +// weak reference so that the gc can collect the WebView. Override virtualBegin +// and virtualEnd and swap the weak reference for the real object. +class WeakJavaInstance : public JavaInstance { +public: +#if USE(JSC) + static PassRefPtr<WeakJavaInstance> create(jobject obj, PassRefPtr<RootObject> root) + { + return adoptRef(new WeakJavaInstance(obj, root)); + } +#elif USE(V8) + static PassRefPtr<WeakJavaInstance> create(jobject obj) + { + return adoptRef(new WeakJavaInstance(obj)); + } +#endif + +private: +#if USE(JSC) + WeakJavaInstance(jobject instance, PassRefPtr<RootObject> rootObject) + : JavaInstance(instance, rootObject) +#elif USE(V8) + WeakJavaInstance(jobject instance) + : JavaInstance(instance) +#endif + , m_beginEndDepth(0) + { + JNIEnv* env = getJNIEnv(); + // JavaInstance creates a global ref to instance in its constructor. + env->DeleteGlobalRef(m_instance->instance()); + // Set the object to a weak reference. + m_instance->setInstance(env->NewWeakGlobalRef(instance)); + } + ~WeakJavaInstance() + { + JNIEnv* env = getJNIEnv(); + // Store the weak reference so we can delete it later. + jweak weak = m_instance->instance(); + // The JavaInstance destructor attempts to delete the global ref stored + // in m_instance. Since we replaced it in our constructor with a weak + // reference, restore the global ref here so the vm will not complain. + m_instance->setInstance(env->NewGlobalRef( + getRealObject(env, m_instance->instance()).get())); + // Delete the weak reference. + env->DeleteWeakGlobalRef(weak); + } + + virtual void virtualBegin() + { + if (m_beginEndDepth++ > 0) + return; + m_weakRef = m_instance->instance(); + JNIEnv* env = getJNIEnv(); + // This is odd. getRealObject returns an AutoJObject which is used to + // cleanly create and delete a local reference. But, here we need to + // maintain the local reference across calls to virtualBegin() and + // virtualEnd(). So, release the local reference from the AutoJObject + // and delete the local reference in virtualEnd(). + m_realObject = getRealObject(env, m_weakRef).release(); + // Point to the real object + m_instance->setInstance(m_realObject); + // Call the base class method + INHERITED::virtualBegin(); + } + + virtual void virtualEnd() + { + if (--m_beginEndDepth > 0) + return; + // Call the base class method first to pop the local frame. + INHERITED::virtualEnd(); + // Get rid of the local reference to the real object. + getJNIEnv()->DeleteLocalRef(m_realObject); + // Point back to the WeakReference. + m_instance->setInstance(m_weakRef); + } + +private: + typedef JavaInstance INHERITED; + jobject m_realObject; + jweak m_weakRef; + // The current depth of nested calls to virtualBegin and virtualEnd. + int m_beginEndDepth; +}; + +static void AddJavascriptInterface(JNIEnv *env, jobject obj, jint nativeFramePointer, + jobject javascriptObj, jstring interfaceName) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); +#endif + WebCore::Frame* pFrame = 0; + if (nativeFramePointer == 0) + pFrame = GET_NATIVE_FRAME(env, obj); + else + pFrame = (WebCore::Frame*)nativeFramePointer; + LOG_ASSERT(pFrame, "nativeAddJavascriptInterface must take a valid frame pointer!"); + + JavaVM* vm; + env->GetJavaVM(&vm); + LOGV("::WebCore:: addJSInterface: %p", pFrame); + +#if USE(JSC) + // Copied from qwebframe.cpp + JSC::JSLock lock(JSC::SilenceAssertionsOnly); + WebCore::JSDOMWindow *window = WebCore::toJSDOMWindow(pFrame, mainThreadNormalWorld()); + if (window) { + RootObject *root = pFrame->script()->bindingRootObject(); + setJavaVM(vm); + // Add the binding to JS environment + JSC::ExecState* exec = window->globalExec(); + JSC::JSObject* addedObject = WeakJavaInstance::create(javascriptObj, + root)->createRuntimeObject(exec); + const jchar* s = env->GetStringChars(interfaceName, NULL); + if (s) { + // Add the binding name to the window's table of child objects. + JSC::PutPropertySlot slot; + window->put(exec, JSC::Identifier(exec, (const UChar *)s, + env->GetStringLength(interfaceName)), addedObject, slot); + env->ReleaseStringChars(interfaceName, s); + checkException(env); + } + } +#elif USE(V8) + if (pFrame) { + RefPtr<JavaInstance> addedObject = WeakJavaInstance::create(javascriptObj); + const char* name = getCharactersFromJStringInEnv(env, interfaceName); + // Pass ownership of the added object to bindToWindowObject. + NPObject* npObject = JavaInstanceToNPObject(addedObject.get()); + pFrame->script()->bindToWindowObject(pFrame, name, npObject); + // bindToWindowObject calls NPN_RetainObject on the + // returned one (see createV8ObjectForNPObject in V8NPObject.cpp). + // bindToWindowObject also increases obj's ref count and decreases + // the ref count when the object is not reachable from JavaScript + // side. Code here must release the reference count increased by + // bindToWindowObject. + + // Note that while this function is declared in WebCore/bridge/npruntime.h, for V8 builds + // we use WebCore/bindings/v8/npruntime.cpp (rather than + // WebCore/bridge/npruntime.cpp), so the function is implemented there. + // TODO: Combine the two versions of these NPAPI files. + NPN_ReleaseObject(npObject); + releaseCharactersForJString(interfaceName, name); + } +#endif + +} + +static void SetCacheDisabled(JNIEnv *env, jobject obj, jboolean disabled) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); +#endif + WebCore::cache()->setDisabled(disabled); +} + +static jboolean CacheDisabled(JNIEnv *env, jobject obj) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); +#endif + return WebCore::cache()->disabled(); +} + +static void ClearWebCoreCache() +{ + if (!WebCore::cache()->disabled()) { + // Disabling the cache will remove all resources from the cache. They may + // still live on if they are referenced by some Web page though. + WebCore::cache()->setDisabled(true); + WebCore::cache()->setDisabled(false); + } + + // clear page cache + int pageCapacity = WebCore::pageCache()->capacity(); + // Setting size to 0, makes all pages be released. + WebCore::pageCache()->setCapacity(0); + WebCore::pageCache()->releaseAutoreleasedPagesNow(); + WebCore::pageCache()->setCapacity(pageCapacity); +} + +static void ClearWebViewCache() +{ +#if USE(CHROME_NETWORK_STACK) + WebCache::get(false /*privateBrowsing*/)->clear(); +#else + // The Android network stack provides a WebView cache in CacheManager.java. + // Clearing this is handled entirely Java-side. +#endif +} + +static void ClearCache(JNIEnv *env, jobject obj) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); +#if USE(JSC) + JSC::JSLock lock(false); + JSC::Heap::Statistics jsHeapStatistics = WebCore::JSDOMWindow::commonJSGlobalData()->heap.statistics(); + LOGD("About to gc and JavaScript heap size is %d and has %d bytes free", + jsHeapStatistics.size, jsHeapStatistics.free); +#endif // USE(JSC) + LOGD("About to clear cache and current cache has %d bytes live and %d bytes dead", + cache()->getLiveSize(), cache()->getDeadSize()); +#endif // ANDROID_INSTRUMENT + ClearWebCoreCache(); + ClearWebViewCache(); +#if USE(JSC) + // force JavaScript to GC when clear cache + WebCore::gcController().garbageCollectSoon(); +#elif USE(V8) + WebCore::Frame* pFrame = GET_NATIVE_FRAME(env, obj); + pFrame->script()->lowMemoryNotification(); +#endif // USE(JSC) +} + +static jboolean DocumentHasImages(JNIEnv *env, jobject obj) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); +#endif + WebCore::Frame* pFrame = GET_NATIVE_FRAME(env, obj); + LOG_ASSERT(pFrame, "DocumentHasImages must take a valid frame pointer!"); + + return pFrame->document()->images()->length() > 0; +} + +static jboolean HasPasswordField(JNIEnv *env, jobject obj) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); +#endif + WebCore::Frame* pFrame = GET_NATIVE_FRAME(env, obj); + LOG_ASSERT(pFrame, "HasPasswordField must take a valid frame pointer!"); + + bool found = false; + WTF::PassRefPtr<WebCore::HTMLCollection> form = pFrame->document()->forms(); + WebCore::Node* node = form->firstItem(); + // Null/Empty namespace means that node is not created in HTMLFormElement + // class, but just normal Element class. + while (node && !found && !node->namespaceURI().isNull() && + !node->namespaceURI().isEmpty()) { + const WTF::Vector<WebCore::FormAssociatedElement*>& elements = + ((WebCore::HTMLFormElement*)node)->associatedElements(); + size_t size = elements.size(); + for (size_t i = 0; i< size && !found; i++) { + WebCore::HTMLElement* e = toHTMLElement(elements[i]); + if (e->hasLocalName(WebCore::HTMLNames::inputTag)) { + if (static_cast<WebCore::HTMLInputElement*>(e)->isPasswordField()) + found = true; + } + } + node = form->nextItem(); + } + return found; +} + +static jobjectArray GetUsernamePassword(JNIEnv *env, jobject obj) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); +#endif + WebCore::Frame* pFrame = GET_NATIVE_FRAME(env, obj); + LOG_ASSERT(pFrame, "GetUsernamePassword must take a valid frame pointer!"); + jobjectArray strArray = NULL; + WTF::String username; + WTF::String password; + if (WebFrame::getWebFrame(pFrame)->getUsernamePasswordFromDom(pFrame, username, password)) { + jclass stringClass = env->FindClass("java/lang/String"); + strArray = env->NewObjectArray(2, stringClass, NULL); + env->DeleteLocalRef(stringClass); + env->SetObjectArrayElement(strArray, 0, wtfStringToJstring(env, username)); + env->SetObjectArrayElement(strArray, 1, wtfStringToJstring(env, password)); + } + return strArray; +} + +static void SetUsernamePassword(JNIEnv *env, jobject obj, + jstring username, jstring password) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); +#endif + WebCore::Frame* pFrame = GET_NATIVE_FRAME(env, obj); + LOG_ASSERT(pFrame, "SetUsernamePassword must take a valid frame pointer!"); + + WebCore::HTMLInputElement* usernameEle = NULL; + WebCore::HTMLInputElement* passwordEle = NULL; + bool found = false; + WTF::PassRefPtr<WebCore::HTMLCollection> form = pFrame->document()->forms(); + WebCore::Node* node = form->firstItem(); + while (node && !found && !node->namespaceURI().isNull() && + !node->namespaceURI().isEmpty()) { + const WTF::Vector<WebCore::FormAssociatedElement*>& elements = + ((WebCore::HTMLFormElement*)node)->associatedElements(); + size_t size = elements.size(); + for (size_t i = 0; i< size && !found; i++) { + WebCore::HTMLElement* e = toHTMLElement(elements[i]); + if (e->hasLocalName(WebCore::HTMLNames::inputTag)) { + WebCore::HTMLInputElement* input = (WebCore::HTMLInputElement*)e; + if (input->autoComplete() == false) + continue; + if (input->isPasswordField()) + passwordEle = input; + else if (input->isTextField() || input->isEmailField()) + usernameEle = input; + if (usernameEle != NULL && passwordEle != NULL) + found = true; + } + } + node = form->nextItem(); + } + if (found) { + usernameEle->setValue(jstringToWtfString(env, username)); + passwordEle->setValue(jstringToWtfString(env, password)); + } +} + +void +WebFrame::saveFormData(HTMLFormElement* form) +{ + if (form->autoComplete()) { + JNIEnv* env = getJNIEnv(); + jclass mapClass = env->FindClass("java/util/HashMap"); + LOG_ASSERT(mapClass, "Could not find HashMap class!"); + jmethodID init = env->GetMethodID(mapClass, "<init>", "(I)V"); + LOG_ASSERT(init, "Could not find constructor for HashMap"); + jobject hashMap = env->NewObject(mapClass, init, 1); + LOG_ASSERT(hashMap, "Could not create a new HashMap"); + jmethodID put = env->GetMethodID(mapClass, "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); + LOG_ASSERT(put, "Could not find put method on HashMap"); + WTF::Vector<WebCore::FormAssociatedElement*> elements = form->associatedElements(); + size_t size = elements.size(); + for (size_t i = 0; i < size; i++) { + WebCore::HTMLElement* e = toHTMLElement(elements[i]); + if (e->hasTagName(WebCore::HTMLNames::inputTag)) { + WebCore::HTMLInputElement* input = static_cast<WebCore::HTMLInputElement*>(e); + if (input->isTextField() && !input->isPasswordField() + && input->autoComplete()) { + WTF::String value = input->value(); + int len = value.length(); + if (len) { + const WTF::AtomicString& name = input->name(); + jstring key = wtfStringToJstring(env, name); + jstring val = wtfStringToJstring(env, value); + LOG_ASSERT(key && val, "name or value not set"); + env->CallObjectMethod(hashMap, put, key, val); + env->DeleteLocalRef(key); + env->DeleteLocalRef(val); + } + } + } + } + env->CallVoidMethod(mJavaFrame->frame(env).get(), mJavaFrame->mSaveFormData, hashMap); + env->DeleteLocalRef(hashMap); + env->DeleteLocalRef(mapClass); + } +} + +static void OrientationChanged(JNIEnv *env, jobject obj, int orientation) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); +#endif + WebCore::Frame* pFrame = GET_NATIVE_FRAME(env, obj); + LOGV("Sending orientation: %d", orientation); + pFrame->sendOrientationChangeEvent(orientation); +} + +#if USE(CHROME_NETWORK_STACK) + +static void AuthenticationProceed(JNIEnv *env, jobject obj, int handle, jstring jUsername, jstring jPassword) +{ + WebUrlLoaderClient* client = reinterpret_cast<WebUrlLoaderClient*>(handle); + std::string username = jstringToStdString(env, jUsername); + std::string password = jstringToStdString(env, jPassword); + client->setAuth(username, password); +} + +static void AuthenticationCancel(JNIEnv *env, jobject obj, int handle) +{ + WebUrlLoaderClient* client = reinterpret_cast<WebUrlLoaderClient*>(handle); + client->cancelAuth(); +} + +static void SslCertErrorProceed(JNIEnv *env, jobject obj, int handle) +{ + WebUrlLoaderClient* client = reinterpret_cast<WebUrlLoaderClient*>(handle); + client->proceedSslCertError(); +} + +static void SslCertErrorCancel(JNIEnv *env, jobject obj, int handle, int cert_error) +{ + WebUrlLoaderClient* client = reinterpret_cast<WebUrlLoaderClient*>(handle); + client->cancelSslCertError(cert_error); +} + +#else + +static void AuthenticationProceed(JNIEnv *env, jobject obj, int handle, jstring jUsername, jstring jPassword) +{ + LOGW("Chromium authentication API called, but libchromium is not available"); +} + +static void AuthenticationCancel(JNIEnv *env, jobject obj, int handle) +{ + LOGW("Chromium authentication API called, but libchromium is not available"); +} + +static void SslCertErrorProceed(JNIEnv *env, jobject obj, int handle) +{ + LOGW("Chromium SSL API called, but libchromium is not available"); +} + +static void SslCertErrorCancel(JNIEnv *env, jobject obj, int handle, int cert_error) +{ + LOGW("Chromium SSL API called, but libchromium is not available"); +} + +#endif // USE(CHROME_NETWORK_STACK) + +// ---------------------------------------------------------------------------- + +/* + * JNI registration. + */ +static JNINativeMethod gBrowserFrameNativeMethods[] = { + /* name, signature, funcPtr */ + { "nativeCallPolicyFunction", "(II)V", + (void*) CallPolicyFunction }, + { "nativeCreateFrame", "(Landroid/webkit/WebViewCore;Landroid/content/res/AssetManager;Landroid/webkit/WebBackForwardList;)V", + (void*) CreateFrame }, + { "nativeDestroyFrame", "()V", + (void*) DestroyFrame }, + { "nativeStopLoading", "()V", + (void*) StopLoading }, + { "nativeLoadUrl", "(Ljava/lang/String;Ljava/util/Map;)V", + (void*) LoadUrl }, + { "nativePostUrl", "(Ljava/lang/String;[B)V", + (void*) PostUrl }, + { "nativeLoadData", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V", + (void*) LoadData }, + { "nativeSaveWebArchive", "(Ljava/lang/String;Z)Ljava/lang/String;", + (void*) SaveWebArchive }, + { "externalRepresentation", "()Ljava/lang/String;", + (void*) ExternalRepresentation }, + { "documentAsText", "()Ljava/lang/String;", + (void*) DocumentAsText }, + { "childFramesAsText", "()Ljava/lang/String;", + (void*) ChildFramesAsText }, + { "reload", "(Z)V", + (void*) Reload }, + { "nativeGoBackOrForward", "(I)V", + (void*) GoBackOrForward }, + { "nativeAddJavascriptInterface", "(ILjava/lang/Object;Ljava/lang/String;)V", + (void*) AddJavascriptInterface }, + { "stringByEvaluatingJavaScriptFromString", + "(Ljava/lang/String;)Ljava/lang/String;", + (void*) StringByEvaluatingJavaScriptFromString }, + { "setCacheDisabled", "(Z)V", + (void*) SetCacheDisabled }, + { "cacheDisabled", "()Z", + (void*) CacheDisabled }, + { "clearCache", "()V", + (void*) ClearCache }, + { "documentHasImages", "()Z", + (void*) DocumentHasImages }, + { "hasPasswordField", "()Z", + (void*) HasPasswordField }, + { "getUsernamePassword", "()[Ljava/lang/String;", + (void*) GetUsernamePassword }, + { "setUsernamePassword", "(Ljava/lang/String;Ljava/lang/String;)V", + (void*) SetUsernamePassword }, + { "nativeOrientationChanged", "(I)V", + (void*) OrientationChanged }, + { "nativeAuthenticationProceed", "(ILjava/lang/String;Ljava/lang/String;)V", + (void*) AuthenticationProceed }, + { "nativeAuthenticationCancel", "(I)V", + (void*) AuthenticationCancel }, + { "nativeSslCertErrorProceed", "(I)V", + (void*) SslCertErrorProceed }, + { "nativeSslCertErrorCancel", "(II)V", + (void*) SslCertErrorCancel }, +}; + +int registerWebFrame(JNIEnv* env) +{ + jclass clazz = env->FindClass("android/webkit/BrowserFrame"); + LOG_ASSERT(clazz, "Cannot find BrowserFrame"); + gFrameField = env->GetFieldID(clazz, "mNativeFrame", "I"); + LOG_ASSERT(gFrameField, "Cannot find mNativeFrame on BrowserFrame"); + env->DeleteLocalRef(clazz); + + return jniRegisterNativeMethods(env, "android/webkit/BrowserFrame", + gBrowserFrameNativeMethods, NELEM(gBrowserFrameNativeMethods)); +} + +} /* namespace android */ diff --git a/Source/WebKit/android/jni/WebCoreFrameBridge.h b/Source/WebKit/android/jni/WebCoreFrameBridge.h new file mode 100644 index 0000000..6522a5f --- /dev/null +++ b/Source/WebKit/android/jni/WebCoreFrameBridge.h @@ -0,0 +1,173 @@ +/* + * Copyright 2006, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// TODO: change name to WebFrame.h + +#ifndef WEBFRAME_H +#define WEBFRAME_H + +#include "FrameLoaderClient.h" +#include "PlatformBridge.h" +#include "PlatformString.h" +#include "WebCoreRefObject.h" +#include <jni.h> +#include <string> +#include <wtf/RefCounted.h> + +namespace WebCore { + class HTMLFormElement; + class Frame; + class HistoryItem; + class Image; + class Page; + class RenderPart; + class RenderSkinAndroid; + class ResourceHandle; + class ResourceLoaderAndroid; + class ResourceRequest; +} + +namespace android { + +class WebViewCore; +class WebUrlLoaderClient; +class UrlInterceptResponse; + +// one instance of WebFrame per Page for calling into Java's BrowserFrame +class WebFrame : public WebCoreRefObject { + public: + WebFrame(JNIEnv* env, jobject obj, jobject historyList, WebCore::Page* page); + ~WebFrame(); + + // helper function + static WebFrame* getWebFrame(const WebCore::Frame* frame); + + virtual PassRefPtr<WebCore::ResourceLoaderAndroid> startLoadingResource(WebCore::ResourceHandle*, + const WebCore::ResourceRequest& request, bool mainResource, + bool synchronous); + + UrlInterceptResponse* shouldInterceptRequest(const WTF::String& url); + + void reportError(int errorCode, const WTF::String& description, + const WTF::String& failingUrl); + + void loadStarted(WebCore::Frame* frame); + + void transitionToCommitted(WebCore::Frame* frame); + + void didFinishLoad(WebCore::Frame* frame); + + void addHistoryItem(WebCore::HistoryItem* item); + + void removeHistoryItem(int index); + + void updateHistoryIndex(int newIndex); + + void setTitle(const WTF::String& title); + + void windowObjectCleared(WebCore::Frame* frame); + + void setProgress(float newProgress); + + const WTF::String userAgentForURL(const WebCore::KURL* url); + + void didReceiveIcon(WebCore::Image* icon); + + void didReceiveTouchIconURL(const WTF::String& url, bool precomposed); + + void updateVisitedHistory(const WebCore::KURL& url, bool reload); + + virtual bool canHandleRequest(const WebCore::ResourceRequest& request); + + WebCore::Frame* createWindow(bool dialog, bool userGesture); + + void requestFocus() const; + + void closeWindow(WebViewCore* webViewCore); + + void decidePolicyForFormResubmission(WebCore::FramePolicyFunction func); + + void setUserAgent(WTF::String userAgent) { mUserAgent = userAgent; } + + WTF::String getRawResourceFilename(WebCore::PlatformBridge::rawResId) const; + + float density() const; + + void didReceiveAuthenticationChallenge(WebUrlLoaderClient*, const std::string& host, const std::string& realm, bool useCachedCredentials); + + void reportSslCertError(WebUrlLoaderClient* client, int cert_error, const std::string& cert); + + void downloadStart(const std::string& url, const std::string& userAgent, const std::string& contentDisposition, const std::string& mimetype, long long contentLength); + + void didReceiveData(const char* data, int size); + + void didFinishLoading(); + + void maybeSavePassword(WebCore::Frame* frame, const WebCore::ResourceRequest& request); + + void setCertificate(const std::string& cert); + + // Parse the x-auto-login header and propagate the parameters to the + // application. + void autoLogin(const std::string& loginHeader); + + /** + * When the user initiates a click, we set mUserInitiatedAction to true. + * If a load happens due to this click, then we ask the application if it wants + * to override the load. Otherwise, we attempt to load the resource internally. + */ + void setUserInitiatedAction(bool userInitiatedAction) { mUserInitiatedAction = userInitiatedAction; } + + WebCore::Page* page() const { return mPage; } + + // Currently used only by the chrome net stack. A similar field is used by + // FrameLoader.java to block java network loads. + void setBlockNetworkLoads(bool block) { mBlockNetworkLoads = block; } + bool blockNetworkLoads() const { return mBlockNetworkLoads; } + + /** + * Helper methods. These are typically chunks of code that are called in + * slightly different ways by the Apache and Chrome HTTP stacks. + */ + bool getUsernamePasswordFromDom(WebCore::Frame* frame, WTF::String& username, WTF::String& password); + jbyteArray getPostData(const WebCore::ResourceRequest& request); + + bool shouldSaveFormData(); + void saveFormData(WebCore::HTMLFormElement*); + const WebCore::RenderSkinAndroid* renderSkins() const { return m_renderSkins; } + void setRenderSkins(const WebCore::RenderSkinAndroid* skins) { m_renderSkins = skins; } +private: + struct JavaBrowserFrame; + JavaBrowserFrame* mJavaFrame; + WebCore::Page* mPage; + WTF::String mUserAgent; + bool mBlockNetworkLoads; + bool mUserInitiatedAction; + const WebCore::RenderSkinAndroid* m_renderSkins; +}; + +} // namespace android + +#endif // WEBFRAME_H diff --git a/Source/WebKit/android/jni/WebCoreJni.cpp b/Source/WebKit/android/jni/WebCoreJni.cpp new file mode 100644 index 0000000..2a07999 --- /dev/null +++ b/Source/WebKit/android/jni/WebCoreJni.cpp @@ -0,0 +1,117 @@ +/* + * Copyright 2007, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#define LOG_TAG "webcoreglue" + +#include "config.h" +#include "WebCoreJni.h" + +#include "NotImplemented.h" +#include <JNIUtility.h> +#include <jni.h> +#include <utils/Log.h> + +namespace android { + +AutoJObject getRealObject(JNIEnv* env, jobject obj) +{ + jobject real = env->NewLocalRef(obj); + LOG_ASSERT(real, "The real object has been deleted!"); + return AutoJObject(env, real); +} + +/** + * Helper method for checking java exceptions + * @return true if an exception occurred. + */ +bool checkException(JNIEnv* env) +{ + if (env->ExceptionCheck() != 0) + { + LOGE("*** Uncaught exception returned from Java call!\n"); + env->ExceptionDescribe(); + return true; + } + return false; +} + +// This method is safe to call from the ui thread and the WebCore thread. +WTF::String jstringToWtfString(JNIEnv* env, jstring str) +{ + if (!str || !env) + return WTF::String(); + const jchar* s = env->GetStringChars(str, NULL); + if (!s) + return WTF::String(); + WTF::String ret(s, env->GetStringLength(str)); + env->ReleaseStringChars(str, s); + checkException(env); + return ret; +} + +jstring wtfStringToJstring(JNIEnv* env, const WTF::String& str, bool validOnZeroLength) +{ + int length = str.length(); + return length || validOnZeroLength ? env->NewString(str.characters(), length) : 0; +} + + +#if USE(CHROME_NETWORK_STACK) +string16 jstringToString16(JNIEnv* env, jstring jstr) +{ + if (!jstr || !env) + return string16(); + + const char* s = env->GetStringUTFChars(jstr, 0); + if (!s) + return string16(); + string16 str = UTF8ToUTF16(s); + env->ReleaseStringUTFChars(jstr, s); + checkException(env); + return str; +} + +std::string jstringToStdString(JNIEnv* env, jstring jstr) +{ + if (!jstr || !env) + return std::string(); + + const char* s = env->GetStringUTFChars(jstr, 0); + if (!s) + return std::string(); + std::string str(s); + env->ReleaseStringUTFChars(jstr, s); + checkException(env); + return str; +} + +jstring stdStringToJstring(JNIEnv* env, const std::string& str, bool validOnZeroLength) +{ + return !str.empty() || validOnZeroLength ? env->NewStringUTF(str.c_str()) : 0; +} + +#endif + +} diff --git a/Source/WebKit/android/jni/WebCoreJni.h b/Source/WebKit/android/jni/WebCoreJni.h new file mode 100644 index 0000000..ec25c8f --- /dev/null +++ b/Source/WebKit/android/jni/WebCoreJni.h @@ -0,0 +1,96 @@ +/* + * Copyright 2008, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef ANDROID_WEBKIT_WEBCOREJNI_H +#define ANDROID_WEBKIT_WEBCOREJNI_H + +#include "ChromiumIncludes.h" +#include "PlatformString.h" +#include <jni.h> + +namespace android { + +// A helper class that automatically deletes the local reference to the jobject +// returned from getRealObject. +class AutoJObject { +public: + AutoJObject(const AutoJObject& other) + : m_env(other.m_env) + , m_obj(other.m_obj ? other.m_env->NewLocalRef(other.m_obj) : NULL) {} + ~AutoJObject() { + if (m_obj) + m_env->DeleteLocalRef(m_obj); + } + jobject get() const { + return m_obj; + } + // Releases the local reference to the caller. The caller *must* delete the + // local reference when it is done with it. + jobject release() { + jobject obj = m_obj; + m_obj = 0; + return obj; + } + JNIEnv* env() const { + return m_env; + } +private: + AutoJObject(); // Not permitted. + AutoJObject(JNIEnv* env, jobject obj) + : m_env(env) + , m_obj(obj) {} + JNIEnv* m_env; + jobject m_obj; + friend AutoJObject getRealObject(JNIEnv*, jobject); +}; + +// Get the real object stored in the weak reference returned as an +// AutoJObject. +AutoJObject getRealObject(JNIEnv*, jobject); + +// Helper method for check java exceptions. Returns true if an exception +// occurred and logs the exception. +bool checkException(JNIEnv* env); + +// Create a WTF::String object from a jstring object. +WTF::String jstringToWtfString(JNIEnv*, jstring); +// Returns a local reference to a new jstring. If validOnZeroLength is true then +// passing in an empty WTF String will result in an empty jstring. Otherwise +// an empty WTF String returns 0. +jstring wtfStringToJstring(JNIEnv*, const WTF::String&, bool validOnZeroLength = false); + +#if USE(CHROME_NETWORK_STACK) +string16 jstringToString16(JNIEnv*, jstring); + +std::string jstringToStdString(JNIEnv*, jstring); +// Returns a local reference to a new jstring. If validOnZeroLength is true then +// passing in an empty std::string will result in an empty jstring. Otherwise +// an empty std::string returns 0. +jstring stdStringToJstring(JNIEnv*, const std::string&, bool validOnZeroLength = false); +#endif + +} + +#endif diff --git a/Source/WebKit/android/jni/WebCoreJniOnLoad.cpp b/Source/WebKit/android/jni/WebCoreJniOnLoad.cpp new file mode 100644 index 0000000..1f264a2 --- /dev/null +++ b/Source/WebKit/android/jni/WebCoreJniOnLoad.cpp @@ -0,0 +1,319 @@ +/* + * Copyright 2009, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#define LOG_TAG "webcoreglue" + +#include "config.h" + +#include "BackForwardList.h" +#include "ChromeClientAndroid.h" +#include "ContextMenuClientAndroid.h" +#include "CookieClient.h" +#include "DeviceMotionClientAndroid.h" +#include "DeviceOrientationClientAndroid.h" +#include "DragClientAndroid.h" +#include "EditorClientAndroid.h" +#include "FocusController.h" +#include "Frame.h" +#include "FrameLoader.h" +#include "FrameLoaderClientAndroid.h" +#include "FrameView.h" +#include "GraphicsContext.h" +#include "HistoryItem.h" +#include "InspectorClientAndroid.h" +#include "IntRect.h" +#include "JavaSharedClient.h" +#include "Page.h" +#include "PlatformGraphicsContext.h" +#include "ResourceRequest.h" +#include "ScriptController.h" +#include "SecurityOrigin.h" +#include "SelectionController.h" +#include "Settings.h" +#include "SharedBuffer.h" +#include "SkBitmap.h" +#include "SkCanvas.h" +#include "SkImageEncoder.h" +#include "SubstituteData.h" +#include "TimerClient.h" +#include "TextEncoding.h" +#include "WebCoreViewBridge.h" +#include "WebFrameView.h" +#include "WebViewCore.h" +#include "benchmark/Intercept.h" +#include "benchmark/MyJavaVM.h" + +#include <JNIUtility.h> +#include <jni.h> +#include <utils/Log.h> + +#define EXPORT __attribute__((visibility("default"))) + +namespace android { + +extern int registerWebFrame(JNIEnv*); +extern int registerJavaBridge(JNIEnv*); +extern int registerJniUtil(JNIEnv*); +extern int registerResourceLoader(JNIEnv*); +extern int registerWebViewCore(JNIEnv*); +extern int registerWebHistory(JNIEnv*); +extern int registerWebIconDatabase(JNIEnv*); +extern int registerWebSettings(JNIEnv*); +extern int registerWebView(JNIEnv*); +#if ENABLE(DATABASE) +extern int registerWebStorage(JNIEnv*); +#endif +extern int registerGeolocationPermissions(JNIEnv*); +extern int registerMockGeolocation(JNIEnv*); +#if ENABLE(VIDEO) +extern int registerMediaPlayerAudio(JNIEnv*); +extern int registerMediaPlayerVideo(JNIEnv*); +#endif +extern int registerDeviceMotionAndOrientationManager(JNIEnv*); +extern int registerCookieManager(JNIEnv*); +#if USE(CHROME_NETWORK_STACK) +extern int registerCacheManager(JNIEnv*); +#endif + +} + +struct RegistrationMethod { + const char* name; + int (*func)(JNIEnv*); +}; + +static RegistrationMethod gWebCoreRegMethods[] = { + { "JavaBridge", android::registerJavaBridge }, + { "JniUtil", android::registerJniUtil }, + { "WebFrame", android::registerWebFrame }, + { "WebCoreResourceLoader", android::registerResourceLoader }, + { "WebViewCore", android::registerWebViewCore }, + { "WebHistory", android::registerWebHistory }, + { "WebIconDatabase", android::registerWebIconDatabase }, + { "WebSettings", android::registerWebSettings }, +#if ENABLE(DATABASE) + { "WebStorage", android::registerWebStorage }, +#endif + { "WebView", android::registerWebView }, + { "GeolocationPermissions", android::registerGeolocationPermissions }, + { "MockGeolocation", android::registerMockGeolocation }, +#if ENABLE(VIDEO) + { "HTML5Audio", android::registerMediaPlayerAudio }, + { "HTML5VideoViewProxy", android::registerMediaPlayerVideo }, +#endif + { "DeviceMotionAndOrientationManager", android::registerDeviceMotionAndOrientationManager }, + { "CookieManager", android::registerCookieManager }, +#if USE(CHROME_NETWORK_STACK) + { "CacheManager", android::registerCacheManager }, +#endif +}; + +EXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) +{ + // Save the JavaVM pointer for use globally. + JSC::Bindings::setJavaVM(vm); + + JNIEnv* env = NULL; + jint result = -1; + + if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) { + LOGE("GetEnv failed!"); + return result; + } + LOG_ASSERT(env, "Could not retrieve the env!"); + + const RegistrationMethod* method = gWebCoreRegMethods; + const RegistrationMethod* end = method + sizeof(gWebCoreRegMethods)/sizeof(RegistrationMethod); + while (method != end) { + if (method->func(env) < 0) { + LOGE("%s registration failed!", method->name); + return result; + } + method++; + } + + // Initialize rand() function. The rand() function is used in + // FileSystemAndroid to create a random temporary filename. + srand(time(NULL)); + + return JNI_VERSION_1_4; +} + +class MyJavaSharedClient : public TimerClient, public CookieClient { +public: + MyJavaSharedClient() : m_hasTimer(false) {} + virtual void setSharedTimer(long long timemillis) { m_hasTimer = true; } + virtual void stopSharedTimer() { m_hasTimer = false; } + virtual void setSharedTimerCallback(void (*f)()) { m_func = f; } + virtual void signalServiceFuncPtrQueue() {} + + // Cookie methods that do nothing. + virtual void setCookies(const KURL&, const String&) {} + virtual String cookies(const KURL&) { return ""; } + virtual bool cookiesEnabled() { return false; } + + bool m_hasTimer; + void (*m_func)(); +}; + +static void historyItemChanged(HistoryItem* i) { + if (i->bridge()) + i->bridge()->updateHistoryItem(i); +} + +namespace android { + +EXPORT void benchmark(const char* url, int reloadCount, int width, int height) { + ScriptController::initializeThreading(); + + // Setting this allows data: urls to load from a local file. + SecurityOrigin::setLocalLoadPolicy(SecurityOrigin::AllowLocalLoadsForAll); + + // Create the fake JNIEnv and JavaVM + InitializeJavaVM(); + + // The real function is private to libwebcore but we know what it does. + notifyHistoryItemChanged = historyItemChanged; + + // Implement the shared timer callback + MyJavaSharedClient client; + JavaSharedClient::SetTimerClient(&client); + JavaSharedClient::SetCookieClient(&client); + + // Create the page with all the various clients + ChromeClientAndroid* chrome = new ChromeClientAndroid; + EditorClientAndroid* editor = new EditorClientAndroid; + DeviceMotionClientAndroid* deviceMotion = new DeviceMotionClientAndroid; + DeviceOrientationClientAndroid* deviceOrientation = new DeviceOrientationClientAndroid; + WebCore::Page::PageClients pageClients; + pageClients.chromeClient = chrome; + pageClients.contextMenuClient = new ContextMenuClientAndroid; + pageClients.editorClient = editor; + pageClients.dragClient = new DragClientAndroid; + pageClients.inspectorClient = new InspectorClientAndroid; + pageClients.deviceMotionClient = deviceMotion; + pageClients.deviceOrientationClient = deviceOrientation; + WebCore::Page* page = new WebCore::Page(pageClients); + editor->setPage(page); + + // Create MyWebFrame that intercepts network requests + MyWebFrame* webFrame = new MyWebFrame(page); + webFrame->setUserAgent("Performance testing"); // needs to be non-empty + chrome->setWebFrame(webFrame); + // ChromeClientAndroid maintains the reference. + Release(webFrame); + + // Create the Frame and the FrameLoaderClient + FrameLoaderClientAndroid* loader = new FrameLoaderClientAndroid(webFrame); + RefPtr<Frame> frame = Frame::create(page, NULL, loader); + loader->setFrame(frame.get()); + + // Build our View system, resize it to the given dimensions and release our + // references. Note: We keep a referenec to frameView so we can layout and + // draw later without risk of it being deleted. + WebViewCore* webViewCore = new WebViewCore(JSC::Bindings::getJNIEnv(), + MY_JOBJECT, frame.get()); + RefPtr<FrameView> frameView = FrameView::create(frame.get()); + WebFrameView* webFrameView = new WebFrameView(frameView.get(), webViewCore); + frame->setView(frameView); + frameView->resize(width, height); + Release(webViewCore); + Release(webFrameView); + + // Initialize the frame and turn of low-bandwidth display (it fails an + // assertion in the Cache code) + frame->init(); + frame->selection()->setFocused(true); + frame->page()->focusController()->setFocused(true); + + deviceMotion->setWebViewCore(webViewCore); + deviceOrientation->setWebViewCore(webViewCore); + + // Set all the default settings the Browser normally uses. + Settings* s = frame->settings(); +#ifdef ANDROID_LAYOUT + s->setLayoutAlgorithm(Settings::kLayoutNormal); // Normal layout for now +#endif + s->setStandardFontFamily("sans-serif"); + s->setFixedFontFamily("monospace"); + s->setSansSerifFontFamily("sans-serif"); + s->setSerifFontFamily("serif"); + s->setCursiveFontFamily("cursive"); + s->setFantasyFontFamily("fantasy"); + s->setMinimumFontSize(8); + s->setMinimumLogicalFontSize(8); + s->setDefaultFontSize(16); + s->setDefaultFixedFontSize(13); + s->setLoadsImagesAutomatically(true); + s->setJavaScriptEnabled(true); + s->setDefaultTextEncodingName("latin1"); + s->setPluginsEnabled(false); + s->setShrinksStandaloneImagesToFit(false); +#ifdef ANDROID_LAYOUT + s->setUseWideViewport(false); +#endif + + // Finally, load the actual data + ResourceRequest req(url); + frame->loader()->load(req, false); + + do { + // Layout the page and service the timer + frame->view()->layout(); + while (client.m_hasTimer) { + client.m_func(); + JavaSharedClient::ServiceFunctionPtrQueue(); + } + JavaSharedClient::ServiceFunctionPtrQueue(); + + // Layout more if needed. + while (frame->view()->needsLayout()) + frame->view()->layout(); + JavaSharedClient::ServiceFunctionPtrQueue(); + + if (reloadCount) + frame->loader()->reload(true); + } while (reloadCount--); + + // Draw into an offscreen bitmap + SkBitmap bmp; + bmp.setConfig(SkBitmap::kARGB_8888_Config, width, height); + bmp.allocPixels(); + SkCanvas canvas(bmp); + PlatformGraphicsContext ctx(&canvas, NULL); + GraphicsContext gc(&ctx); + frame->view()->paintContents(&gc, IntRect(0, 0, width, height)); + + // Write the bitmap to the sdcard + SkImageEncoder* enc = SkImageEncoder::Create(SkImageEncoder::kPNG_Type); + enc->encodeFile("/sdcard/webcore_test.png", bmp, 100); + delete enc; + + // Tear down the world. + frame->loader()->detachFromParent(); + delete page; +} + +} // namespace android diff --git a/Source/WebKit/android/jni/WebCoreRefObject.h b/Source/WebKit/android/jni/WebCoreRefObject.h new file mode 100644 index 0000000..4228db6 --- /dev/null +++ b/Source/WebKit/android/jni/WebCoreRefObject.h @@ -0,0 +1,46 @@ +/* + * Copyright 2007, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef WEBCORE_FOUNDATION_h +#define WEBCORE_FOUNDATION_h + +#include "SkRefCnt.h" + +typedef SkRefCnt WebCoreRefObject; + +static inline WebCoreRefObject* Retain(WebCoreRefObject* obj) +{ + if (obj) + obj->ref(); + return obj; +} + +static inline void Release(WebCoreRefObject* obj) +{ + if (obj) + obj->unref(); +} + +#endif // WEBCORE_FOUNDATION_h diff --git a/Source/WebKit/android/jni/WebCoreResourceLoader.cpp b/Source/WebKit/android/jni/WebCoreResourceLoader.cpp new file mode 100644 index 0000000..f9acc97 --- /dev/null +++ b/Source/WebKit/android/jni/WebCoreResourceLoader.cpp @@ -0,0 +1,352 @@ +/* + * Copyright 2006, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#define LOG_TAG "webcoreglue" + +#include "config.h" +#include "WebCoreResourceLoader.h" + +#include "ResourceError.h" +#include "ResourceHandle.h" +#include "ResourceHandleClient.h" +#include "ResourceHandleInternal.h" +#include "ResourceResponse.h" +#include "SkUtils.h" +#ifdef ANDROID_INSTRUMENT +#include "TimeCounter.h" +#endif +#include "WebCoreJni.h" + +#include <JNIHelp.h> +#include <JNIUtility.h> +#include <SkTypes.h> +#include <stdlib.h> +#include <utils/misc.h> +#include <wtf/Platform.h> +#include <wtf/text/CString.h> + +namespace android { + +// ---------------------------------------------------------------------------- + +static struct resourceloader_t { + jfieldID mObject; + jmethodID mCancelMethodID; + jmethodID mDownloadFileMethodID; + jmethodID mWillLoadFromCacheMethodID; + jmethodID mPauseLoadMethodID; +} gResourceLoader; + +// ---------------------------------------------------------------------------- + +#define GET_NATIVE_HANDLE(env, obj) ((WebCore::ResourceHandle*)env->GetIntField(obj, gResourceLoader.mObject)) +#define SET_NATIVE_HANDLE(env, obj, handle) (env->SetIntField(obj, gResourceLoader.mObject, handle)) + +//----------------------------------------------------------------------------- +// ResourceLoadHandler + +PassRefPtr<WebCore::ResourceLoaderAndroid> WebCoreResourceLoader::create(JNIEnv *env, jobject jLoadListener) +{ + return adoptRef<WebCore::ResourceLoaderAndroid>(new WebCoreResourceLoader(env, jLoadListener)); +} + +WebCoreResourceLoader::WebCoreResourceLoader(JNIEnv *env, jobject jLoadListener) + : mPausedLoad(false) +{ + mJLoader = env->NewGlobalRef(jLoadListener); +} + +WebCoreResourceLoader::~WebCoreResourceLoader() +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + SET_NATIVE_HANDLE(env, mJLoader, 0); + env->DeleteGlobalRef(mJLoader); + mJLoader = 0; +} + +void WebCoreResourceLoader::cancel() +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(mJLoader, gResourceLoader.mCancelMethodID); + checkException(env); +} + +void WebCoreResourceLoader::downloadFile() +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(mJLoader, gResourceLoader.mDownloadFileMethodID); + checkException(env); +} + +void WebCoreResourceLoader::pauseLoad(bool pause) +{ + if (mPausedLoad == pause) + return; + + mPausedLoad = pause; + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(mJLoader, gResourceLoader.mPauseLoadMethodID, pause); + checkException(env); +} + +/* +* This static method is called to check to see if a POST response is in +* the cache. This may be slow, but is only used during a navigation to +* a POST response. +*/ +bool WebCoreResourceLoader::willLoadFromCache(const WebCore::KURL& url, int64_t identifier) +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + WTF::String urlStr = url.string(); + jstring jUrlStr = wtfStringToJstring(env, urlStr); + jclass resourceLoader = env->FindClass("android/webkit/LoadListener"); + bool val = env->CallStaticBooleanMethod(resourceLoader, gResourceLoader.mWillLoadFromCacheMethodID, jUrlStr, identifier); + checkException(env); + env->DeleteLocalRef(resourceLoader); + env->DeleteLocalRef(jUrlStr); + + return val; +} + +// ---------------------------------------------------------------------------- +void WebCoreResourceLoader::SetResponseHeader(JNIEnv* env, jobject obj, jint nativeResponse, jstring key, jstring val) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::ResourceTimeCounter); +#endif + + WebCore::ResourceResponse* response = (WebCore::ResourceResponse*)nativeResponse; + LOG_ASSERT(response, "nativeSetResponseHeader must take a valid response pointer!"); + + LOG_ASSERT(key, "How did a null value become a key?"); + if (val) + response->setHTTPHeaderField(jstringToWtfString(env, key), jstringToWtfString(env, val)); +} + +jint WebCoreResourceLoader::CreateResponse(JNIEnv* env, jobject obj, jstring url, jint statusCode, + jstring statusText, jstring mimeType, jlong expectedLength, + jstring encoding) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::ResourceTimeCounter); +#endif + LOG_ASSERT(url, "Must have a url in the response!"); + WebCore::KURL kurl(WebCore::ParsedURLString, jstringToWtfString(env, url)); + WTF::String encodingStr; + WTF::String mimeTypeStr; + if (mimeType) { + mimeTypeStr = jstringToWtfString(env, mimeType); + LOGV("Response setMIMEType: %s", mimeTypeStr.latin1().data()); + } + if (encoding) { + encodingStr = jstringToWtfString(env, encoding); + LOGV("Response setTextEncodingName: %s", encodingStr.latin1().data()); + } + WebCore::ResourceResponse* response = new WebCore::ResourceResponse( + kurl, mimeTypeStr, (long long)expectedLength, + encodingStr, WTF::String()); + response->setHTTPStatusCode(statusCode); + if (statusText) { + WTF::String status = jstringToWtfString(env, statusText); + response->setHTTPStatusText(status); + LOGV("Response setStatusText: %s", status.latin1().data()); + } + return (int)response; +} + +void WebCoreResourceLoader::ReceivedResponse(JNIEnv* env, jobject obj, jint nativeResponse) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::ResourceTimeCounter); +#endif + WebCore::ResourceHandle* handle = GET_NATIVE_HANDLE(env, obj); + LOG_ASSERT(handle, "nativeReceivedResponse must take a valid handle!"); + // ResourceLoader::didFail() can set handle to be NULL, we need to check + if (!handle) + return; + + WebCore::ResourceResponse* response = (WebCore::ResourceResponse*)nativeResponse; + LOG_ASSERT(response, "nativeReceivedResponse must take a valid resource pointer!"); + handle->client()->didReceiveResponse(handle, *response); + // As the client makes a copy of the response, delete it here. + delete response; +} + +void WebCoreResourceLoader::AddData(JNIEnv* env, jobject obj, jbyteArray dataArray, jint length) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::ResourceTimeCounter); +#endif + LOGV("webcore_resourceloader data(%d)", length); + + WebCore::ResourceHandle* handle = GET_NATIVE_HANDLE(env, obj); + LOG_ASSERT(handle, "nativeAddData must take a valid handle!"); + // ResourceLoader::didFail() can set handle to be NULL, we need to check + if (!handle) + return; + + SkAutoMemoryUsageProbe mup("android_webcore_resourceloader_nativeAddData"); + + bool result = false; + jbyte * data = env->GetByteArrayElements(dataArray, NULL); + + LOG_ASSERT(handle->client(), "Why do we not have a client?"); + handle->client()->didReceiveData(handle, (const char *)data, length, length); + env->ReleaseByteArrayElements(dataArray, data, JNI_ABORT); +} + +void WebCoreResourceLoader::Finished(JNIEnv* env, jobject obj) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::ResourceTimeCounter); +#endif + LOGV("webcore_resourceloader finished"); + WebCore::ResourceHandle* handle = GET_NATIVE_HANDLE(env, obj); + LOG_ASSERT(handle, "nativeFinished must take a valid handle!"); + // ResourceLoader::didFail() can set handle to be NULL, we need to check + if (!handle) + return; + + LOG_ASSERT(handle->client(), "Why do we not have a client?"); + handle->client()->didFinishLoading(handle, 0); +} + +jstring WebCoreResourceLoader::RedirectedToUrl(JNIEnv* env, jobject obj, + jstring baseUrl, jstring redirectTo, jint nativeResponse) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::ResourceTimeCounter); +#endif + LOGV("webcore_resourceloader redirectedToUrl"); + WebCore::ResourceHandle* handle = GET_NATIVE_HANDLE(env, obj); + LOG_ASSERT(handle, "nativeRedirectedToUrl must take a valid handle!"); + // ResourceLoader::didFail() can set handle to be NULL, we need to check + if (!handle) + return NULL; + + LOG_ASSERT(handle->client(), "Why do we not have a client?"); + WebCore::ResourceRequest r = handle->firstRequest(); + WebCore::KURL url(WebCore::KURL(WebCore::ParsedURLString, jstringToWtfString(env, baseUrl)), + jstringToWtfString(env, redirectTo)); + WebCore::ResourceResponse* response = (WebCore::ResourceResponse*)nativeResponse; + // If the url fails to resolve the relative path, return null. + if (url.protocol().isEmpty()) { + delete response; + return NULL; + } else { + // Ensure the protocol is lowercase. + url.setProtocol(url.protocol().lower()); + } + // Set the url after updating the protocol. + r.setURL(url); + if (r.httpMethod() == "POST") { + r.setHTTPMethod("GET"); + r.clearHTTPReferrer(); + r.setHTTPBody(0); + r.setHTTPContentType(""); + } + handle->client()->willSendRequest(handle, r, *response); + delete response; + return wtfStringToJstring(env, url.string()); +} + +void WebCoreResourceLoader::Error(JNIEnv* env, jobject obj, jint id, jstring description, + jstring failingUrl) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::ResourceTimeCounter); +#endif + LOGV("webcore_resourceloader error"); + WebCore::ResourceHandle* handle = GET_NATIVE_HANDLE(env, obj); + LOG_ASSERT(handle, "nativeError must take a valid handle!"); + // ResourceLoader::didFail() can set handle to be NULL, we need to check + if (!handle) + return; + + handle->client()->didFail(handle, WebCore::ResourceError("", id, + jstringToWtfString(env, failingUrl), jstringToWtfString(env, description))); +} + +// ---------------------------------------------------------------------------- + +/* + * JNI registration. + */ +static JNINativeMethod gResourceloaderMethods[] = { + /* name, signature, funcPtr */ + { "nativeSetResponseHeader", "(ILjava/lang/String;Ljava/lang/String;)V", + (void*) WebCoreResourceLoader::SetResponseHeader }, + { "nativeCreateResponse", "(Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;JLjava/lang/String;)I", + (void*) WebCoreResourceLoader::CreateResponse }, + { "nativeReceivedResponse", "(I)V", + (void*) WebCoreResourceLoader::ReceivedResponse }, + { "nativeAddData", "([BI)V", + (void*) WebCoreResourceLoader::AddData }, + { "nativeFinished", "()V", + (void*) WebCoreResourceLoader::Finished }, + { "nativeRedirectedToUrl", "(Ljava/lang/String;Ljava/lang/String;I)Ljava/lang/String;", + (void*) WebCoreResourceLoader::RedirectedToUrl }, + { "nativeError", "(ILjava/lang/String;Ljava/lang/String;)V", + (void*) WebCoreResourceLoader::Error } +}; + +int registerResourceLoader(JNIEnv* env) +{ + jclass resourceLoader = env->FindClass("android/webkit/LoadListener"); + LOG_FATAL_IF(resourceLoader == NULL, + "Unable to find class android/webkit/LoadListener"); + + gResourceLoader.mObject = + env->GetFieldID(resourceLoader, "mNativeLoader", "I"); + LOG_FATAL_IF(gResourceLoader.mObject == NULL, + "Unable to find android/webkit/LoadListener.mNativeLoader"); + + gResourceLoader.mCancelMethodID = + env->GetMethodID(resourceLoader, "cancel", "()V"); + LOG_FATAL_IF(gResourceLoader.mCancelMethodID == NULL, + "Could not find method cancel on LoadListener"); + + gResourceLoader.mDownloadFileMethodID = + env->GetMethodID(resourceLoader, "downloadFile", "()V"); + LOG_FATAL_IF(gResourceLoader.mDownloadFileMethodID == NULL, + "Could not find method downloadFile on LoadListener"); + + gResourceLoader.mPauseLoadMethodID = + env->GetMethodID(resourceLoader, "pauseLoad", "(Z)V"); + LOG_FATAL_IF(gResourceLoader.mPauseLoadMethodID == NULL, + "Could not find method pauseLoad on LoadListener"); + + gResourceLoader.mWillLoadFromCacheMethodID = + env->GetStaticMethodID(resourceLoader, "willLoadFromCache", "(Ljava/lang/String;J)Z"); + LOG_FATAL_IF(gResourceLoader.mWillLoadFromCacheMethodID == NULL, + "Could not find static method willLoadFromCache on LoadListener"); + + env->DeleteLocalRef(resourceLoader); + + return jniRegisterNativeMethods(env, "android/webkit/LoadListener", + gResourceloaderMethods, NELEM(gResourceloaderMethods)); +} + +} /* namespace android */ diff --git a/Source/WebKit/android/jni/WebCoreResourceLoader.h b/Source/WebKit/android/jni/WebCoreResourceLoader.h new file mode 100644 index 0000000..c60b3f5 --- /dev/null +++ b/Source/WebKit/android/jni/WebCoreResourceLoader.h @@ -0,0 +1,78 @@ +/* + * Copyright 2006, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef ANDROID_WEBKIT_RESOURCELOADLISTENER_H +#define ANDROID_WEBKIT_RESOURCELOADLISTENER_H + +#include <KURL.h> +#include <ResourceLoaderAndroid.h> +#include <jni.h> + +namespace android { + +class WebCoreResourceLoader : public WebCore::ResourceLoaderAndroid +{ +public: + static PassRefPtr<WebCore::ResourceLoaderAndroid> create(JNIEnv *env, jobject jLoadListener); + virtual ~WebCoreResourceLoader(); + + /** + * Call to java to cancel the current load. + */ + virtual void cancel(); + + /** + * Call to java to download the current load rather than feed it + * back to WebCore + */ + virtual void downloadFile(); + + virtual void pauseLoad(bool); + + /** + * Call to java to find out if this URL is in the cache + */ + static bool willLoadFromCache(const WebCore::KURL& url, int64_t identifier); + + // Native jni functions + static void SetResponseHeader(JNIEnv*, jobject, jint, jstring, jstring); + static jint CreateResponse(JNIEnv*, jobject, jstring, jint, jstring, + jstring, jlong, jstring); + static void ReceivedResponse(JNIEnv*, jobject, jint); + static void AddData(JNIEnv*, jobject, jbyteArray, jint); + static void Finished(JNIEnv*, jobject); + static jstring RedirectedToUrl(JNIEnv*, jobject, jstring, jstring, jint); + static void Error(JNIEnv*, jobject, jint, jstring, jstring); + +protected: + WebCoreResourceLoader(JNIEnv *env, jobject jLoadListener); +private: + jobject mJLoader; + bool mPausedLoad; +}; + +} // end namespace android + +#endif diff --git a/Source/WebKit/android/jni/WebCoreViewBridge.h b/Source/WebKit/android/jni/WebCoreViewBridge.h new file mode 100644 index 0000000..59e1c9a --- /dev/null +++ b/Source/WebKit/android/jni/WebCoreViewBridge.h @@ -0,0 +1,106 @@ +/* + * Copyright 2007, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef WEBCORE_VIEW_BRIDGE_H +#define WEBCORE_VIEW_BRIDGE_H + +// TODO: move this outside of jni directory + +#include "IntRect.h" +#include "WebCoreRefObject.h" + +namespace WebCore +{ + class GraphicsContext; +} + +class WebCoreViewBridge : public WebCoreRefObject { +public: + WebCoreViewBridge() { } + virtual ~WebCoreViewBridge() { } + + virtual void draw(WebCore::GraphicsContext* ctx, + const WebCore::IntRect& rect) = 0; + + const WebCore::IntRect& getBounds() const + { + return m_bounds; + } + + const WebCore::IntRect& getVisibleBounds() const + { + return m_visibleBounds; + } + + const WebCore::IntRect& getWindowBounds() const + { + return m_windowBounds; + } + + void setSize(int w, int h) + { + m_bounds.setWidth(w); + m_bounds.setHeight(h); + } + + void setVisibleSize(int w, int h) + { + m_visibleBounds.setWidth(w); + m_visibleBounds.setHeight(h); + } + + void setLocation(int x, int y) + { + m_bounds.setX(x); + m_bounds.setY(y); + m_visibleBounds.setX(x); + m_visibleBounds.setY(y); + } + + void setWindowBounds(int x, int y, int h, int v) + { + m_windowBounds = WebCore::IntRect(x, y, h, v); + } + + int width() const { return m_bounds.width(); } + int height() const { return m_bounds.height(); } + int locX() const { return m_bounds.x(); } + int locY() const { return m_bounds.y(); } + + int visibleWidth() const { return m_visibleBounds.width(); } + int visibleHeight() const { return m_visibleBounds.height(); } + int visibleX() const { return m_visibleBounds.x(); } + int visibleY() const { return m_visibleBounds.y(); } + + virtual bool forFrameView() const { return false; } + virtual bool forPluginView() const { return false; } + +private: + WebCore::IntRect m_bounds; + WebCore::IntRect m_windowBounds; + WebCore::IntRect m_visibleBounds; +}; + +#endif // WEBCORE_VIEW_BRIDGE_H diff --git a/Source/WebKit/android/jni/WebFrameView.cpp b/Source/WebKit/android/jni/WebFrameView.cpp new file mode 100644 index 0000000..8e5eac4 --- /dev/null +++ b/Source/WebKit/android/jni/WebFrameView.cpp @@ -0,0 +1,104 @@ +/* + * Copyright 2008, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#define LOG_TAG "webcoreglue" + +#include <config.h> +#include "WebFrameView.h" + +#include "android_graphics.h" +#include "GraphicsContext.h" +#include "Frame.h" +#include "FrameTree.h" +#include "FrameView.h" +#include "HostWindow.h" +#include "PlatformGraphicsContext.h" +#include "WebViewCore.h" + +#include <SkCanvas.h> + +namespace android { + +WebFrameView::WebFrameView(WebCore::FrameView* frameView, WebViewCore* webViewCore) + : WebCoreViewBridge() + , mFrameView(frameView) + , mWebViewCore(webViewCore) { + // attach itself to mFrameView + mFrameView->setPlatformWidget(this); + Retain(mWebViewCore); +} + +WebFrameView::~WebFrameView() { + Release(mWebViewCore); +} + +void WebFrameView::draw(WebCore::GraphicsContext* ctx, const WebCore::IntRect& rect) { + WebCore::Frame* frame = mFrameView->frame(); + + if (NULL == frame->contentRenderer()) { + // We only do this if there is nothing else to draw. + // If there is a renderer, it will fill the bg itself, so we don't want to + // double-draw (slow) + SkCanvas* canvas = ctx->platformContext()->mCanvas; + canvas->drawColor(SK_ColorWHITE); + } else if (frame->tree()->parent()) { + // Note: this code was moved from FrameLoaderClientAndroid + // + // For subframe, create a new translated rect from the given rectangle. + WebCore::IntRect transRect(rect); + // In Frame::markAllMatchesForText(), it does a fake paint. So we need + // to handle the case where platformContext() is null. However, we still + // want to call paint, since WebKit must have called the paint for a reason. + SkCanvas* canvas = ctx->platformContext() ? ctx->platformContext()->mCanvas : NULL; + if (canvas) { + const WebCore::IntRect& bounds = getBounds(); + + // Grab the intersection of transRect and the frame's bounds. + transRect.intersect(bounds); + if (transRect.isEmpty()) + return; + + // Move the transRect into the frame's local coordinates. + transRect.move(-bounds.x(), -bounds.y()); + + // Translate the canvas, add a clip. + canvas->save(); + canvas->translate(SkIntToScalar(bounds.x()), SkIntToScalar(bounds.y())); + canvas->clipRect(transRect); + } + mFrameView->paintContents(ctx, transRect); + if (canvas) + canvas->restore(); + } else { + mFrameView->paintContents(ctx, rect); + } +} + +void WebFrameView::setView(WebCore::FrameView* frameView) { + mFrameView = frameView; + mFrameView->setPlatformWidget(this); +} + +} // namespace android diff --git a/Source/WebKit/android/jni/WebFrameView.h b/Source/WebKit/android/jni/WebFrameView.h new file mode 100644 index 0000000..823f2b4 --- /dev/null +++ b/Source/WebKit/android/jni/WebFrameView.h @@ -0,0 +1,65 @@ +/* + * Copyright 2008, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef WEB_FRAMEVIEW_H +#define WEB_FRAMEVIEW_H + +#include "WebCoreViewBridge.h" + +namespace WebCore { + class FrameView; +} + +namespace android { + class WebViewCore; + + class WebFrameView: public WebCoreViewBridge { + public: + WebFrameView(WebCore::FrameView* frameView, WebViewCore* webViewCore); + virtual ~WebFrameView(); + + virtual void draw(WebCore::GraphicsContext* ctx, + const WebCore::IntRect& rect); + + WebViewCore* webViewCore() const { + return mWebViewCore; + } + + void setView(WebCore::FrameView* frameView); + + WebCore::FrameView* view() const { + return mFrameView; + } + + virtual bool forFrameView() const { return true; } + + private: + WebCore::FrameView* mFrameView; + WebViewCore* mWebViewCore; + }; + +} // namespace android + +#endif // WEB_FRAMEVIEW_H diff --git a/Source/WebKit/android/jni/WebHistory.cpp b/Source/WebKit/android/jni/WebHistory.cpp new file mode 100644 index 0000000..97ce23b --- /dev/null +++ b/Source/WebKit/android/jni/WebHistory.cpp @@ -0,0 +1,857 @@ +/* + * Copyright 2007, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#define LOG_TAG "webhistory" + +#include "config.h" +#include "WebHistory.h" + +#include "BackForwardList.h" +#include "BackForwardListImpl.h" +#include "DocumentLoader.h" +#include "Frame.h" +#include "FrameLoader.h" +#include "FrameLoaderClientAndroid.h" +#include "FrameTree.h" +#include "HistoryItem.h" +#include "IconDatabase.h" +#include "Page.h" +#include "TextEncoding.h" +#include "WebCoreFrameBridge.h" +#include "WebCoreJni.h" +#include "WebIconDatabase.h" + +#include <JNIHelp.h> +#include "JNIUtility.h" +#include <SkUtils.h> +#include <utils/misc.h> +#include <wtf/OwnPtr.h> +#include <wtf/Platform.h> +#include <wtf/text/CString.h> + +namespace android { + +// Forward declarations +static void write_item(WTF::Vector<char>& v, WebCore::HistoryItem* item); +static void write_children_recursive(WTF::Vector<char>& v, WebCore::HistoryItem* parent); +static bool read_item_recursive(WebCore::HistoryItem* child, const char** pData, int length); + +// Field ids for WebHistoryItems +struct WebHistoryItemFields { + jmethodID mInit; + jmethodID mUpdate; + jfieldID mTitle; + jfieldID mUrl; +} gWebHistoryItem; + +struct WebBackForwardListFields { + jmethodID mAddHistoryItem; + jmethodID mRemoveHistoryItem; + jmethodID mSetCurrentIndex; +} gWebBackForwardList; + +//-------------------------------------------------------------------------- +// WebBackForwardList native methods. +//-------------------------------------------------------------------------- + +static void WebHistoryClose(JNIEnv* env, jobject obj, jint frame) +{ + LOG_ASSERT(frame, "Close needs a valid Frame pointer!"); + WebCore::Frame* pFrame = (WebCore::Frame*)frame; + + WebCore::BackForwardListImpl* list = static_cast<WebCore::BackForwardListImpl*>(pFrame->page()->backForwardList()); + RefPtr<WebCore::HistoryItem> current = list->currentItem(); + // Remove each item instead of using close(). close() is intended to be used + // right before the list is deleted. + WebCore::HistoryItemVector& entries = list->entries(); + int size = entries.size(); + for (int i = size - 1; i >= 0; --i) + list->removeItem(entries[i].get()); + // Add the current item back to the list. + if (current) { + current->setBridge(0); + // addItem will update the children to match the newly created bridge + list->addItem(current); + + /* + * The Grand Prix site uses anchor navigations to change the display. + * WebKit tries to be smart and not load child frames that have the + * same history urls during an anchor navigation. This means that the + * current history item stored in the child frame's loader does not + * match the item found in the history tree. If we remove all the + * entries in the back/foward list, we have to restore the entire tree + * or else a HistoryItem might have a deleted parent. + * + * In order to restore the history tree correctly, we have to look up + * all the frames first and then look up the history item. We do this + * because the history item in the tree may be null at this point. + * Unfortunately, a HistoryItem can only search its immediately + * children so we do a breadth-first rebuild of the tree. + */ + + // Keep a small list of child frames to traverse. + WTF::Vector<WebCore::Frame*> frameQueue; + // Fix the top-level item. + pFrame->loader()->history()->setCurrentItem(current.get()); + WebCore::Frame* child = pFrame->tree()->firstChild(); + // Remember the parent history item so we can search for a child item. + RefPtr<WebCore::HistoryItem> parent = current; + while (child) { + // Use the old history item since the current one may have a + // deleted parent. + WebCore::HistoryItem* item = parent->childItemWithTarget(child->tree()->name()); + child->loader()->history()->setCurrentItem(item); + // Append the first child to the queue if it exists. If there is no + // item, then we do not need to traverse the children since there + // will be no parent history item. + WebCore::Frame* firstChild; + if (item && (firstChild = child->tree()->firstChild())) + frameQueue.append(firstChild); + child = child->tree()->nextSibling(); + // If we don't have a sibling for this frame and the queue isn't + // empty, use the next entry in the queue. + if (!child && !frameQueue.isEmpty()) { + child = frameQueue.at(0); + frameQueue.remove(0); + // Figure out the parent history item used when searching for + // the history item to use. + parent = child->tree()->parent()->loader()->history()->currentItem(); + } + } + } +} + +static void WebHistoryRestoreIndex(JNIEnv* env, jobject obj, jint frame, jint index) +{ + LOG_ASSERT(frame, "RestoreState needs a valid Frame pointer!"); + WebCore::Frame* pFrame = (WebCore::Frame*)frame; + WebCore::Page* page = pFrame->page(); + WebCore::HistoryItem* currentItem = + static_cast<WebCore::BackForwardListImpl*>(page->backForwardList())->entries()[index].get(); + + // load the current page with FrameLoadTypeIndexedBackForward so that it + // will use cache when it is possible + page->goToItem(currentItem, FrameLoadTypeIndexedBackForward); +} + +static void WebHistoryInflate(JNIEnv* env, jobject obj, jint frame, jbyteArray data) +{ + LOG_ASSERT(frame, "Inflate needs a valid frame pointer!"); + LOG_ASSERT(data, "Inflate needs a valid data pointer!"); + + // Get the actual bytes and the length from the java array. + const jbyte* bytes = env->GetByteArrayElements(data, NULL); + jsize size = env->GetArrayLength(data); + + // Inflate the history tree into one HistoryItem or null if the inflation + // failed. + RefPtr<WebCore::HistoryItem> newItem = WebCore::HistoryItem::create(); + WebHistoryItem* bridge = new WebHistoryItem(env, obj, newItem.get()); + newItem->setBridge(bridge); + + // Inflate the item recursively. If it fails, that is ok. We'll have an + // incomplete HistoryItem but that is better than crashing due to a null + // item. + // We have a 2nd local variable since read_item_recursive may change the + // ptr's value. We can't pass &bytes since we have to send bytes to + // ReleaseByteArrayElements unchanged. + const char* ptr = reinterpret_cast<const char*>(bytes); + read_item_recursive(newItem.get(), &ptr, (int)size); + env->ReleaseByteArrayElements(data, const_cast<jbyte*>(bytes), JNI_ABORT); + bridge->setActive(); + + // Add the new item to the back/forward list. + WebCore::Frame* pFrame = (WebCore::Frame*)frame; + pFrame->page()->backForwardList()->addItem(newItem); + + // Update the item. + bridge->updateHistoryItem(newItem.get()); +} + +// 6 empty strings + no document state + children count + 2 scales = 10 unsigned values +// 1 char for isTargetItem. +#define HISTORY_MIN_SIZE ((int)(sizeof(unsigned) * 10 + sizeof(char))) + +jbyteArray WebHistory::Flatten(JNIEnv* env, WTF::Vector<char>& v, WebCore::HistoryItem* item) +{ + if (!item) + return NULL; + + // Reserve a vector of chars with an initial size of HISTORY_MIN_SIZE. + v.reserveCapacity(HISTORY_MIN_SIZE); + + // Write the top-level history item and then write all the children + // recursively. + LOG_ASSERT(item->bridge(), "Why don't we have a bridge object here?"); + write_item(v, item); + write_children_recursive(v, item); + + // Try to create a new java byte array. + jbyteArray b = env->NewByteArray(v.size()); + if (!b) + return NULL; + + // Write our flattened data to the java array. + env->SetByteArrayRegion(b, 0, v.size(), (const jbyte*)v.data()); + return b; +} + +WebHistoryItem::WebHistoryItem(JNIEnv* env, jobject obj, + WebCore::HistoryItem* item) : WebCore::AndroidWebHistoryBridge(item) { + m_object = env->NewWeakGlobalRef(obj); + m_parent = 0; +} + +WebHistoryItem::~WebHistoryItem() { + if (m_object) { + JNIEnv* env = JSC::Bindings::getJNIEnv(); + if (!env) + return; + env->DeleteWeakGlobalRef(m_object); + } +} + +void WebHistoryItem::updateHistoryItem(WebCore::HistoryItem* item) { + // Do not want to update during inflation. + if (!m_active) + return; + WebHistoryItem* webItem = this; + // Now we need to update the top-most WebHistoryItem based on the top-most + // HistoryItem. + if (m_parent) { + webItem = m_parent.get(); + if (webItem->hasOneRef()) { + // if the parent only has one ref, it is from this WebHistoryItem. + // This means that the matching WebCore::HistoryItem has been freed. + // This can happen during clear(). + LOGW("Can't updateHistoryItem as the top HistoryItem is gone"); + return; + } + while (webItem->parent()) + webItem = webItem->parent(); + item = webItem->historyItem(); + if (!item) { + // If a HistoryItem only exists for page cache, it is possible that + // the parent HistoryItem destroyed before the child HistoryItem. If + // it happens, skip updating. + LOGW("Can't updateHistoryItem as the top HistoryItem is gone"); + return; + } + } + JNIEnv* env = JSC::Bindings::getJNIEnv(); + if (!env) + return; + + // Don't do anything if the item has been gc'd already + AutoJObject realItem = getRealObject(env, webItem->m_object); + if (!realItem.get()) + return; + + const WTF::String& urlString = item->urlString(); + jstring urlStr = NULL; + if (!urlString.isNull()) + urlStr = wtfStringToJstring(env, urlString); + const WTF::String& originalUrlString = item->originalURLString(); + jstring originalUrlStr = NULL; + if (!originalUrlString.isNull()) + originalUrlStr = wtfStringToJstring(env, originalUrlString); + const WTF::String& titleString = item->title(); + jstring titleStr = NULL; + if (!titleString.isNull()) + titleStr = wtfStringToJstring(env, titleString); + + // Try to get the favicon from the history item. For some pages like Grand + // Prix, there are history items with anchors. If the icon fails for the + // item, try to get the icon using the url without the ref. + jobject favicon = NULL; + WTF::String url = item->urlString(); + if (item->url().hasFragmentIdentifier()) { + int refIndex = url.reverseFind('#'); + url = url.substring(0, refIndex); + } + WebCore::Image* icon = WebCore::iconDatabase()->iconForPageURL(url, + WebCore::IntSize(16, 16)); + + if (icon) + favicon = webcoreImageToJavaBitmap(env, icon); + + WTF::Vector<char> data; + jbyteArray array = WebHistory::Flatten(env, data, item); + env->CallVoidMethod(realItem.get(), gWebHistoryItem.mUpdate, urlStr, + originalUrlStr, titleStr, favicon, array); + env->DeleteLocalRef(urlStr); + env->DeleteLocalRef(originalUrlStr); + env->DeleteLocalRef(titleStr); + if (favicon) + env->DeleteLocalRef(favicon); + env->DeleteLocalRef(array); +} + +static void historyItemChanged(WebCore::HistoryItem* item) { + LOG_ASSERT(item, "historyItemChanged called with a null item"); + + if (item->bridge()) + item->bridge()->updateHistoryItem(item); +} + +void WebHistory::AddItem(const AutoJObject& list, WebCore::HistoryItem* item) +{ + LOG_ASSERT(item, "newItem must take a valid HistoryItem!"); + // Item already added. Should only happen when we are inflating the list. + if (item->bridge() || !list.get()) + return; + + JNIEnv* env = list.env(); + // Allocate a blank WebHistoryItem + jclass clazz = env->FindClass("android/webkit/WebHistoryItem"); + jobject newItem = env->NewObject(clazz, gWebHistoryItem.mInit); + env->DeleteLocalRef(clazz); + + // Create the bridge, make it active, and attach it to the item. + WebHistoryItem* bridge = new WebHistoryItem(env, newItem, item); + bridge->setActive(); + item->setBridge(bridge); + + // Update the history item which will flatten the data and call update on + // the java item. + bridge->updateHistoryItem(item); + + // Add it to the list. + env->CallVoidMethod(list.get(), gWebBackForwardList.mAddHistoryItem, newItem); + + // Delete our local reference. + env->DeleteLocalRef(newItem); +} + +void WebHistory::RemoveItem(const AutoJObject& list, int index) +{ + if (list.get()) + list.env()->CallVoidMethod(list.get(), gWebBackForwardList.mRemoveHistoryItem, index); +} + +void WebHistory::UpdateHistoryIndex(const AutoJObject& list, int newIndex) +{ + if (list.get()) + list.env()->CallVoidMethod(list.get(), gWebBackForwardList.mSetCurrentIndex, newIndex); +} + +static void write_string(WTF::Vector<char>& v, const WTF::String& str) +{ + unsigned strLen = str.length(); + // Only do work if the string has data. + if (strLen) { + // Determine how much to grow the vector. Use the worst case for utf8 to + // avoid reading the string twice. Add sizeof(unsigned) to hold the + // string length in utf8. + unsigned vectorLen = v.size() + sizeof(unsigned); + unsigned length = (strLen << 2) + vectorLen; + // Grow the vector. This will change the value of v.size() but we + // remember the original size above. + v.grow(length); + // Grab the position to write to. + char* data = v.begin() + vectorLen; + // Write the actual string + int l = SkUTF16_ToUTF8(str.characters(), strLen, data); + LOGV("Writing string %d %.*s", l, l, data); + // Go back and write the utf8 length. Subtract sizeof(unsigned) from + // data to get the position to write the length. + memcpy(data - sizeof(unsigned), (char*)&l, sizeof(unsigned)); + // Shrink the internal state of the vector so we match what was + // actually written. + v.shrink(vectorLen + l); + } else + v.append((char*)&strLen, sizeof(unsigned)); +} + +static void write_item(WTF::Vector<char>& v, WebCore::HistoryItem* item) +{ + // Original url + write_string(v, item->originalURLString()); + + // Url + write_string(v, item->urlString()); + + // Title + write_string(v, item->title()); + + // Form content type + write_string(v, item->formContentType()); + + // Form data + const WebCore::FormData* formData = item->formData(); + if (formData) { + write_string(v, formData->flattenToString()); + // save the identifier as it is not included in the flatten data + int64_t id = formData->identifier(); + v.append((char*)&id, sizeof(int64_t)); + } else + write_string(v, WTF::String()); // Empty constructor does not allocate a buffer. + + // Target + write_string(v, item->target()); + + AndroidWebHistoryBridge* bridge = item->bridge(); + LOG_ASSERT(bridge, "We should have a bridge here!"); + // Screen scale + const float scale = bridge->scale(); + LOGV("Writing scale %f", scale); + v.append((char*)&scale, sizeof(float)); + const float textWrapScale = bridge->textWrapScale(); + LOGV("Writing text wrap scale %f", textWrapScale); + v.append((char*)&textWrapScale, sizeof(float)); + + // Scroll position. + const int scrollX = item->scrollPoint().x(); + v.append((char*)&scrollX, sizeof(int)); + const int scrollY = item->scrollPoint().y(); + v.append((char*)&scrollY, sizeof(int)); + + // Document state + const WTF::Vector<WTF::String>& docState = item->documentState(); + WTF::Vector<WTF::String>::const_iterator end = docState.end(); + unsigned stateSize = docState.size(); + LOGV("Writing docState %d", stateSize); + v.append((char*)&stateSize, sizeof(unsigned)); + for (WTF::Vector<WTF::String>::const_iterator i = docState.begin(); i != end; ++i) { + write_string(v, *i); + } + + // Is target item + LOGV("Writing isTargetItem %d", item->isTargetItem()); + v.append((char)item->isTargetItem()); + + // Children count + unsigned childCount = item->children().size(); + LOGV("Writing childCount %d", childCount); + v.append((char*)&childCount, sizeof(unsigned)); +} + +static void write_children_recursive(WTF::Vector<char>& v, WebCore::HistoryItem* parent) +{ + const WebCore::HistoryItemVector& children = parent->children(); + WebCore::HistoryItemVector::const_iterator end = children.end(); + for (WebCore::HistoryItemVector::const_iterator i = children.begin(); i != end; ++i) { + WebCore::HistoryItem* item = (*i).get(); + LOG_ASSERT(parent->bridge(), + "The parent item should have a bridge object!"); + if (!item->bridge()) { + WebHistoryItem* bridge = new WebHistoryItem(static_cast<WebHistoryItem*>(parent->bridge())); + item->setBridge(bridge); + bridge->setActive(); + } else { + // The only time this item's parent may not be the same as the + // parent's bridge is during history close. In that case, the + // parent must not have a parent bridge. + WebHistoryItem* bridge = static_cast<WebHistoryItem*>(item->bridge()); + WebHistoryItem* parentBridge = static_cast<WebHistoryItem*>(parent->bridge()); + LOG_ASSERT(parentBridge->parent() == 0 || + bridge->parent() == parentBridge, + "Somehow this item has an incorrect parent"); + bridge->setParent(parentBridge); + } + write_item(v, item); + write_children_recursive(v, item); + } +} + +static bool read_item_recursive(WebCore::HistoryItem* newItem, + const char** pData, int length) +{ + if (!pData || length < HISTORY_MIN_SIZE) + return false; + + const WebCore::TextEncoding& e = WebCore::UTF8Encoding(); + const char* data = *pData; + const char* end = data + length; + int sizeofUnsigned = (int)sizeof(unsigned); + + // Read the original url + // Read the expected length of the string. + int l; + memcpy(&l, data, sizeofUnsigned); + // Increment data pointer by the size of an unsigned int. + data += sizeofUnsigned; + if (l) { + LOGV("Original url %d %.*s", l, l, data); + // If we have a length, check if that length exceeds the data length + // and return null if there is not enough data. + if (data + l < end) + newItem->setOriginalURLString(e.decode(data, l)); + else + return false; + // Increment the data pointer by the length of the string. + data += l; + } + // Check if we have enough data left to continue. + if (end - data < sizeofUnsigned) + return false; + + // Read the url + memcpy(&l, data, sizeofUnsigned); + data += sizeofUnsigned; + if (l) { + LOGV("Url %d %.*s", l, l, data); + if (data + l < end) + newItem->setURLString(e.decode(data, l)); + else + return false; + data += l; + } + if (end - data < sizeofUnsigned) + return false; + + // Read the title + memcpy(&l, data, sizeofUnsigned); + data += sizeofUnsigned; + if (l) { + LOGV("Title %d %.*s", l, l, data); + if (data + l < end) + newItem->setTitle(e.decode(data, l)); + else + return false; + data += l; + } + if (end - data < sizeofUnsigned) + return false; + + // Generate a new ResourceRequest object for populating form information. + WTF::String formContentType; + WTF::PassRefPtr<WebCore::FormData> formData = NULL; + + // Read the form content type + memcpy(&l, data, sizeofUnsigned); + data += sizeofUnsigned; + if (l) { + LOGV("Content type %d %.*s", l, l, data); + if (data + l < end) + formContentType = e.decode(data, l); + else + return false; + data += l; + } + if (end - data < sizeofUnsigned) + return false; + + // Read the form data + memcpy(&l, data, sizeofUnsigned); + data += sizeofUnsigned; + if (l) { + LOGV("Form data %d %.*s", l, l, data); + if (data + l < end) + formData = WebCore::FormData::create(data, l); + else + return false; + data += l; + // Read the identifier + { + int64_t id; + int size = (int)sizeof(int64_t); + memcpy(&id, data, size); + data += size; + if (id) + formData->setIdentifier(id); + } + } + if (end - data < sizeofUnsigned) + return false; + + // Set up the form info + if (formData != NULL) { + WebCore::ResourceRequest r; + r.setHTTPMethod("POST"); + r.setHTTPContentType(formContentType); + r.setHTTPBody(formData); + newItem->setFormInfoFromRequest(r); + } + + // Read the target + memcpy(&l, data, sizeofUnsigned); + data += sizeofUnsigned; + if (l) { + LOGV("Target %d %.*s", l, l, data); + if (data + l < end) + newItem->setTarget(e.decode(data, l)); + else + return false; + data += l; + } + if (end - data < sizeofUnsigned) + return false; + + AndroidWebHistoryBridge* bridge = newItem->bridge(); + LOG_ASSERT(bridge, "There should be a bridge object during inflate"); + float fValue; + // Read the screen scale + memcpy(&fValue, data, sizeof(float)); + LOGV("Screen scale %f", fValue); + bridge->setScale(fValue); + data += sizeof(float); + memcpy(&fValue, data, sizeofUnsigned); + LOGV("Text wrap scale %f", fValue); + bridge->setTextWrapScale(fValue); + data += sizeof(float); + + if (end - data < sizeofUnsigned) + return false; + + // Read scroll position. + int scrollX = 0; + memcpy(&scrollX, data, sizeofUnsigned); + data += sizeofUnsigned; + int scrollY = 0; + memcpy(&scrollY, data, sizeofUnsigned); + data += sizeofUnsigned; + newItem->setScrollPoint(IntPoint(scrollX, scrollY)); + + if (end - data < sizeofUnsigned) + return false; + + // Read the document state + memcpy(&l, data, sizeofUnsigned); + LOGV("Document state %d", l); + data += sizeofUnsigned; + if (l) { + // Check if we have enough data to at least parse the sizes of each + // document state string. + if (data + l * sizeofUnsigned >= end) + return false; + // Create a new vector and reserve enough space for the document state. + WTF::Vector<WTF::String> docState; + docState.reserveCapacity(l); + while (l--) { + // Check each time if we have enough to parse the length of the next + // string. + if (end - data < sizeofUnsigned) + return false; + int strLen; + memcpy(&strLen, data, sizeofUnsigned); + data += sizeofUnsigned; + if (data + strLen < end) + docState.append(e.decode(data, strLen)); + else + return false; + LOGV("\t\t%d %.*s", strLen, strLen, data); + data += strLen; + } + newItem->setDocumentState(docState); + } + // Check if we have enough to read the next byte + if (data >= end) + return false; + + // Read is target item + // Cast the value to unsigned char in order to make a negative value larger + // than 1. A value that is not 0 or 1 is a failure. + unsigned char c = (unsigned char)data[0]; + if (c > 1) + return false; + LOGV("Target item %d", c); + newItem->setIsTargetItem((bool)c); + data++; + if (end - data < sizeofUnsigned) + return false; + + // Read the child count + memcpy(&l, data, sizeofUnsigned); + LOGV("Child count %d", l); + data += sizeofUnsigned; + *pData = data; + if (l) { + // Check if we have the minimum amount need to parse l children. + if (data + l * HISTORY_MIN_SIZE >= end) + return false; + while (l--) { + // No need to check the length each time because read_item_recursive + // will return null if there isn't enough data left to parse. + WTF::PassRefPtr<WebCore::HistoryItem> child = WebCore::HistoryItem::create(); + // Set a bridge that will not call into java. + child->setBridge(new WebHistoryItem(static_cast<WebHistoryItem*>(bridge))); + // Read the child item. + if (!read_item_recursive(child.get(), pData, end - data)) { + child.clear(); + return false; + } + child->bridge()->setActive(); + newItem->addChildItem(child); + } + } + return true; +} + +// On arm, this test will cause memory corruption since converting char* will +// byte align the result and this test does not use memset (it probably +// should). +// On the simulator, using HistoryItem will invoke the IconDatabase which will +// initialize the main thread. Since this is invoked by the Zygote process, the +// main thread will be incorrect and an assert will fire later. +// In conclusion, define UNIT_TEST only if you know what you are doing. +#ifdef UNIT_TEST +static void unit_test() +{ + LOGD("Entering history unit test!"); + const char* test1 = new char[0]; + WTF::RefPtr<WebCore::HistoryItem> item = WebCore::HistoryItem::create(); + WebCore::HistoryItem* testItem = item.get(); + testItem->setBridge(new WebHistoryItem(0)); + LOG_ASSERT(!read_item_recursive(testItem, &test1, 0), "0 length array should fail!"); + delete[] test1; + const char* test2 = new char[2]; + LOG_ASSERT(!read_item_recursive(testItem, &test2, 2), "Small array should fail!"); + delete[] test2; + LOG_ASSERT(!read_item_recursive(testItem, NULL, HISTORY_MIN_SIZE), "Null data should fail!"); + // Original Url + char* test3 = new char[HISTORY_MIN_SIZE]; + const char* ptr = (const char*)test3; + memset(test3, 0, HISTORY_MIN_SIZE); + *(int*)test3 = 4000; + LOG_ASSERT(!read_item_recursive(testItem, &ptr, HISTORY_MIN_SIZE), "4000 length originalUrl should fail!"); + // Url + int offset = 4; + memset(test3, 0, HISTORY_MIN_SIZE); + ptr = (const char*)test3; + *(int*)(test3 + offset) = 4000; + LOG_ASSERT(!read_item_recursive(testItem, &ptr, HISTORY_MIN_SIZE), "4000 length url should fail!"); + // Title + offset += 4; + memset(test3, 0, HISTORY_MIN_SIZE); + ptr = (const char*)test3; + *(int*)(test3 + offset) = 4000; + LOG_ASSERT(!read_item_recursive(testItem, &ptr, HISTORY_MIN_SIZE), "4000 length title should fail!"); + // Form content type + offset += 4; + memset(test3, 0, HISTORY_MIN_SIZE); + ptr = (const char*)test3; + *(int*)(test3 + offset) = 4000; + LOG_ASSERT(!read_item_recursive(testItem, &ptr, HISTORY_MIN_SIZE), "4000 length contentType should fail!"); + // Form data + offset += 4; + memset(test3, 0, HISTORY_MIN_SIZE); + ptr = (const char*)test3; + *(int*)(test3 + offset) = 4000; + LOG_ASSERT(!read_item_recursive(testItem, &ptr, HISTORY_MIN_SIZE), "4000 length form data should fail!"); + // Target + offset += 4; + memset(test3, 0, HISTORY_MIN_SIZE); + ptr = (const char*)test3; + *(int*)(test3 + offset) = 4000; + LOG_ASSERT(!read_item_recursive(testItem, &ptr, HISTORY_MIN_SIZE), "4000 length target should fail!"); + offset += 4; // Scale + // Document state + offset += 4; + memset(test3, 0, HISTORY_MIN_SIZE); + ptr = (const char*)test3; + *(int*)(test3 + offset) = 4000; + LOG_ASSERT(!read_item_recursive(testItem, &ptr, HISTORY_MIN_SIZE), "4000 length document state should fail!"); + // Is target item + offset += 1; + memset(test3, 0, HISTORY_MIN_SIZE); + ptr = (const char*)test3; + *(char*)(test3 + offset) = '!'; + LOG_ASSERT(!read_item_recursive(testItem, &ptr, HISTORY_MIN_SIZE), "IsTargetItem should fail with ! as the value!"); + // Child count + offset += 4; + memset(test3, 0, HISTORY_MIN_SIZE); + ptr = (const char*)test3; + *(int*)(test3 + offset) = 4000; + LOG_ASSERT(!read_item_recursive(testItem, &ptr, HISTORY_MIN_SIZE), "4000 kids should fail!"); + offset = 36; + // Test document state + delete[] test3; + test3 = new char[HISTORY_MIN_SIZE + sizeof(unsigned)]; + memset(test3, 0, HISTORY_MIN_SIZE + sizeof(unsigned)); + ptr = (const char*)test3; + *(int*)(test3 + offset) = 1; + *(int*)(test3 + offset + 4) = 20; + LOG_ASSERT(!read_item_recursive(testItem, &ptr, HISTORY_MIN_SIZE + sizeof(unsigned)), "1 20 length document state string should fail!"); + delete[] test3; + test3 = new char[HISTORY_MIN_SIZE + 2 * sizeof(unsigned)]; + memset(test3, 0, HISTORY_MIN_SIZE + 2 * sizeof(unsigned)); + ptr = (const char*)test3; + *(int*)(test3 + offset) = 2; + *(int*)(test3 + offset + 4) = 0; + *(int*)(test3 + offset + 8) = 20; + LOG_ASSERT(!read_item_recursive(testItem, &ptr, HISTORY_MIN_SIZE + 2 * sizeof(unsigned) ), "2 20 length document state string should fail!"); + delete[] test3; +} +#endif + +//--------------------------------------------------------- +// JNI registration +//--------------------------------------------------------- +static JNINativeMethod gWebBackForwardListMethods[] = { + { "nativeClose", "(I)V", + (void*) WebHistoryClose }, + { "restoreIndex", "(II)V", + (void*) WebHistoryRestoreIndex } +}; + +static JNINativeMethod gWebHistoryItemMethods[] = { + { "inflate", "(I[B)V", + (void*) WebHistoryInflate } +}; + +int registerWebHistory(JNIEnv* env) +{ + // Get notified of all changes to history items. + WebCore::notifyHistoryItemChanged = historyItemChanged; +#ifdef UNIT_TEST + unit_test(); +#endif + // Find WebHistoryItem, its constructor, and the update method. + jclass clazz = env->FindClass("android/webkit/WebHistoryItem"); + LOG_ASSERT(clazz, "Unable to find class android/webkit/WebHistoryItem"); + gWebHistoryItem.mInit = env->GetMethodID(clazz, "<init>", "()V"); + LOG_ASSERT(gWebHistoryItem.mInit, "Could not find WebHistoryItem constructor"); + gWebHistoryItem.mUpdate = env->GetMethodID(clazz, "update", + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Landroid/graphics/Bitmap;[B)V"); + LOG_ASSERT(gWebHistoryItem.mUpdate, "Could not find method update in WebHistoryItem"); + + // Find the field ids for mTitle and mUrl. + gWebHistoryItem.mTitle = env->GetFieldID(clazz, "mTitle", "Ljava/lang/String;"); + LOG_ASSERT(gWebHistoryItem.mTitle, "Could not find field mTitle in WebHistoryItem"); + gWebHistoryItem.mUrl = env->GetFieldID(clazz, "mUrl", "Ljava/lang/String;"); + LOG_ASSERT(gWebHistoryItem.mUrl, "Could not find field mUrl in WebHistoryItem"); + env->DeleteLocalRef(clazz); + + // Find the WebBackForwardList object and method. + clazz = env->FindClass("android/webkit/WebBackForwardList"); + LOG_ASSERT(clazz, "Unable to find class android/webkit/WebBackForwardList"); + gWebBackForwardList.mAddHistoryItem = env->GetMethodID(clazz, "addHistoryItem", + "(Landroid/webkit/WebHistoryItem;)V"); + LOG_ASSERT(gWebBackForwardList.mAddHistoryItem, "Could not find method addHistoryItem"); + gWebBackForwardList.mRemoveHistoryItem = env->GetMethodID(clazz, "removeHistoryItem", + "(I)V"); + LOG_ASSERT(gWebBackForwardList.mRemoveHistoryItem, "Could not find method removeHistoryItem"); + gWebBackForwardList.mSetCurrentIndex = env->GetMethodID(clazz, "setCurrentIndex", "(I)V"); + LOG_ASSERT(gWebBackForwardList.mSetCurrentIndex, "Could not find method setCurrentIndex"); + env->DeleteLocalRef(clazz); + + int result = jniRegisterNativeMethods(env, "android/webkit/WebBackForwardList", + gWebBackForwardListMethods, NELEM(gWebBackForwardListMethods)); + return (result < 0) ? result : jniRegisterNativeMethods(env, "android/webkit/WebHistoryItem", + gWebHistoryItemMethods, NELEM(gWebHistoryItemMethods)); +} + +} /* namespace android */ diff --git a/Source/WebKit/android/jni/WebHistory.h b/Source/WebKit/android/jni/WebHistory.h new file mode 100644 index 0000000..2d86aa4 --- /dev/null +++ b/Source/WebKit/android/jni/WebHistory.h @@ -0,0 +1,68 @@ +/* + * Copyright 2006, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef ANDROID_WEBKIT_WEBHISTORY_H +#define ANDROID_WEBKIT_WEBHISTORY_H + +#include "AndroidWebHistoryBridge.h" + +#include <jni.h> +#include <wtf/RefCounted.h> +#include <wtf/Vector.h> + +namespace android { + +class AutoJObject; + +class WebHistory { +public: + static jbyteArray Flatten(JNIEnv*, WTF::Vector<char>&, WebCore::HistoryItem*); + static void AddItem(const AutoJObject&, WebCore::HistoryItem*); + static void RemoveItem(const AutoJObject&, int); + static void UpdateHistoryIndex(const AutoJObject&, int); +}; + +// there are two scale factors saved with each history item. m_scale reflects the +// viewport scale factor, default to 100 means 100%. m_textWrapScale records +// the scale factor for wrapping the text paragraph. +class WebHistoryItem : public WebCore::AndroidWebHistoryBridge { +public: + WebHistoryItem(WebHistoryItem* parent) + : WebCore::AndroidWebHistoryBridge(0) + , m_parent(parent) + , m_object(NULL) { } + WebHistoryItem(JNIEnv*, jobject, WebCore::HistoryItem*); + ~WebHistoryItem(); + void updateHistoryItem(WebCore::HistoryItem* item); + void setParent(WebHistoryItem* parent) { m_parent = parent; } + WebHistoryItem* parent() const { return m_parent.get(); } +private: + RefPtr<WebHistoryItem> m_parent; + jweak m_object; +}; + +}; + +#endif diff --git a/Source/WebKit/android/jni/WebIconDatabase.cpp b/Source/WebKit/android/jni/WebIconDatabase.cpp new file mode 100644 index 0000000..2a660d1 --- /dev/null +++ b/Source/WebKit/android/jni/WebIconDatabase.cpp @@ -0,0 +1,237 @@ +/* + * Copyright 2006, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#define LOG_TAG "favicons" + +#include "config.h" +#include "WebIconDatabase.h" + +#include "FileSystem.h" +#include "GraphicsJNI.h" +#include "IconDatabase.h" +#include "Image.h" +#include "IntRect.h" +#include "JavaSharedClient.h" +#include "KURL.h" +#include "WebCoreJni.h" + +#include <JNIHelp.h> +#include <JNIUtility.h> +#include <SharedBuffer.h> +#include <SkBitmap.h> +#include <SkImageDecoder.h> +#include <SkTemplates.h> +#include <pthread.h> +#include <utils/misc.h> +#include <wtf/Platform.h> +#include <wtf/text/CString.h> + +namespace android { + +jobject webcoreImageToJavaBitmap(JNIEnv* env, WebCore::Image* icon) +{ + if (!icon) + return NULL; + SkBitmap bm; + WebCore::SharedBuffer* buffer = icon->data(); + if (!buffer || !SkImageDecoder::DecodeMemory(buffer->data(), buffer->size(), + &bm, SkBitmap::kNo_Config, + SkImageDecoder::kDecodePixels_Mode)) + return NULL; + + return GraphicsJNI::createBitmap(env, new SkBitmap(bm), false, NULL); +} + +static WebIconDatabase* gIconDatabaseClient = new WebIconDatabase(); + +// XXX: Called by the IconDatabase thread +void WebIconDatabase::dispatchDidAddIconForPageURL(const WTF::String& pageURL) +{ + mNotificationsMutex.lock(); + mNotifications.append(pageURL); + if (!mDeliveryRequested) { + mDeliveryRequested = true; + JavaSharedClient::EnqueueFunctionPtr(DeliverNotifications, this); + } + mNotificationsMutex.unlock(); +} + +// Called in the WebCore thread +void WebIconDatabase::RegisterForIconNotification(WebIconDatabaseClient* client) +{ + WebIconDatabase* db = gIconDatabaseClient; + for (unsigned i = 0; i < db->mClients.size(); ++i) { + // Do not add the same client twice. + if (db->mClients[i] == client) + return; + } + gIconDatabaseClient->mClients.append(client); +} + +// Called in the WebCore thread +void WebIconDatabase::UnregisterForIconNotification(WebIconDatabaseClient* client) +{ + WebIconDatabase* db = gIconDatabaseClient; + for (unsigned i = 0; i < db->mClients.size(); ++i) { + if (db->mClients[i] == client) { + db->mClients.remove(i); + break; + } + } +} + +// Called in the WebCore thread +void WebIconDatabase::DeliverNotifications(void* v) +{ + ASSERT(v); + ((WebIconDatabase*)v)->deliverNotifications(); +} + +// Called in the WebCore thread +void WebIconDatabase::deliverNotifications() +{ + ASSERT(mDeliveryRequested); + + // Swap the notifications queue + Vector<WTF::String> queue; + mNotificationsMutex.lock(); + queue.swap(mNotifications); + mDeliveryRequested = false; + mNotificationsMutex.unlock(); + + // Swap the clients queue + Vector<WebIconDatabaseClient*> clients; + clients.swap(mClients); + + for (unsigned i = 0; i < queue.size(); ++i) { + for (unsigned j = 0; j < clients.size(); ++j) { + clients[j]->didAddIconForPageUrl(queue[i]); + } + } +} + +static void Open(JNIEnv* env, jobject obj, jstring path) +{ + WebCore::IconDatabase* iconDb = WebCore::iconDatabase(); + if (iconDb->isOpen()) + return; + iconDb->setEnabled(true); + iconDb->setClient(gIconDatabaseClient); + LOG_ASSERT(path, "No path given to nativeOpen"); + WTF::String pathStr = jstringToWtfString(env, path); + WTF::CString fullPath = WebCore::pathByAppendingComponent(pathStr, + WebCore::IconDatabase::defaultDatabaseFilename()).utf8(); + mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP; + bool didSetPermissions = false; + if (access(fullPath.data(), F_OK) == 0) { + if (chmod(fullPath.data(), mode) == 0) + didSetPermissions = true; + } else { + int fd = open(fullPath.data(), O_CREAT, mode); + if (fd >= 0) { + close(fd); + didSetPermissions = true; + } + } + if (didSetPermissions) { + LOGV("Opening WebIconDatabase file '%s'", pathStr.latin1().data()); + bool res = iconDb->open(pathStr); + if (!res) + LOGE("Open failed!"); + } else + LOGE("Failed to set permissions on '%s'", fullPath.data()); +} + +static void Close(JNIEnv* env, jobject obj) +{ + WebCore::iconDatabase()->close(); +} + +static void RemoveAllIcons(JNIEnv* env, jobject obj) +{ + LOGV("Removing all icons"); + WebCore::iconDatabase()->removeAllIcons(); +} + +static jobject IconForPageUrl(JNIEnv* env, jobject obj, jstring url) +{ + LOG_ASSERT(url, "No url given to iconForPageUrl"); + WTF::String urlStr = jstringToWtfString(env, url); + + WebCore::Image* icon = WebCore::iconDatabase()->iconForPageURL(urlStr, + WebCore::IntSize(16, 16)); + LOGV("Retrieving icon for '%s' %p", urlStr.latin1().data(), icon); + return webcoreImageToJavaBitmap(env, icon); +} + +static void RetainIconForPageUrl(JNIEnv* env, jobject obj, jstring url) +{ + LOG_ASSERT(url, "No url given to retainIconForPageUrl"); + WTF::String urlStr = jstringToWtfString(env, url); + + LOGV("Retaining icon for '%s'", urlStr.latin1().data()); + WebCore::iconDatabase()->retainIconForPageURL(urlStr); +} + +static void ReleaseIconForPageUrl(JNIEnv* env, jobject obj, jstring url) +{ + LOG_ASSERT(url, "No url given to releaseIconForPageUrl"); + WTF::String urlStr = jstringToWtfString(env, url); + + LOGV("Releasing icon for '%s'", urlStr.latin1().data()); + WebCore::iconDatabase()->releaseIconForPageURL(urlStr); +} + +/* + * JNI registration + */ +static JNINativeMethod gWebIconDatabaseMethods[] = { + { "nativeOpen", "(Ljava/lang/String;)V", + (void*) Open }, + { "nativeClose", "()V", + (void*) Close }, + { "nativeRemoveAllIcons", "()V", + (void*) RemoveAllIcons }, + { "nativeIconForPageUrl", "(Ljava/lang/String;)Landroid/graphics/Bitmap;", + (void*) IconForPageUrl }, + { "nativeRetainIconForPageUrl", "(Ljava/lang/String;)V", + (void*) RetainIconForPageUrl }, + { "nativeReleaseIconForPageUrl", "(Ljava/lang/String;)V", + (void*) ReleaseIconForPageUrl } +}; + +int registerWebIconDatabase(JNIEnv* env) +{ +#ifndef NDEBUG + jclass webIconDatabase = env->FindClass("android/webkit/WebIconDatabase"); + LOG_ASSERT(webIconDatabase, "Unable to find class android.webkit.WebIconDatabase"); + env->DeleteLocalRef(webIconDatabase); +#endif + + return jniRegisterNativeMethods(env, "android/webkit/WebIconDatabase", + gWebIconDatabaseMethods, NELEM(gWebIconDatabaseMethods)); +} + +} diff --git a/Source/WebKit/android/jni/WebIconDatabase.h b/Source/WebKit/android/jni/WebIconDatabase.h new file mode 100644 index 0000000..b2169aa --- /dev/null +++ b/Source/WebKit/android/jni/WebIconDatabase.h @@ -0,0 +1,75 @@ +/* + * Copyright 2006, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef ANDROID_WEBKIT_WEBICONDATABASE_H +#define ANDROID_WEBKIT_WEBICONDATABASE_H + +#include "IconDatabaseClient.h" +#include "PlatformString.h" +#include "utils/threads.h" +#include <jni.h> +#include <wtf/Vector.h> + +namespace WebCore { + class Image; +} + +namespace android { + + class WebIconDatabaseClient { + public: + virtual ~WebIconDatabaseClient() {} + virtual void didAddIconForPageUrl(const WTF::String& pageUrl) = 0; + }; + + class WebIconDatabase : public WebCore::IconDatabaseClient { + public: + WebIconDatabase() : mDeliveryRequested(false) {} + // IconDatabaseClient method + virtual void dispatchDidAddIconForPageURL(const WTF::String& pageURL); + + static void RegisterForIconNotification(WebIconDatabaseClient* client); + static void UnregisterForIconNotification(WebIconDatabaseClient* client); + static void DeliverNotifications(void*); + + private: + // Deliver all the icon notifications + void deliverNotifications(); + + // List of clients. + Vector<WebIconDatabaseClient*> mClients; + + // Queue of page urls that have received an icon. + Vector<WTF::String> mNotifications; + android::Mutex mNotificationsMutex; + // Flag to indicate that we have requested a delivery of notifications. + bool mDeliveryRequested; + }; + + jobject webcoreImageToJavaBitmap(JNIEnv* env, WebCore::Image* icon); + +}; + +#endif diff --git a/Source/WebKit/android/jni/WebSettings.cpp b/Source/WebKit/android/jni/WebSettings.cpp new file mode 100644 index 0000000..468e7b0 --- /dev/null +++ b/Source/WebKit/android/jni/WebSettings.cpp @@ -0,0 +1,599 @@ +/* + * Copyright 2007, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#define LOG_TAG "websettings" + +#include <config.h> +#include <wtf/Platform.h> + +#include "ApplicationCacheStorage.h" +#include "BitmapAllocatorAndroid.h" +#include "CachedResourceLoader.h" +#include "ChromiumIncludes.h" +#include "DatabaseTracker.h" +#include "Database.h" +#include "Document.h" +#include "EditorClientAndroid.h" +#include "FileSystem.h" +#include "Frame.h" +#include "FrameLoader.h" +#include "FrameView.h" +#include "GeolocationPermissions.h" +#include "GeolocationPositionCache.h" +#include "Page.h" +#include "PageCache.h" +#include "RenderTable.h" +#include "SQLiteFileSystem.h" +#include "Settings.h" +#include "WebCoreFrameBridge.h" +#include "WebCoreJni.h" +#if USE(V8) +#include "WorkerContextExecutionProxy.h" +#endif +#include "WebRequestContext.h" +#include "WebViewCore.h" + +#include <JNIHelp.h> +#include <utils/misc.h> +#include <wtf/text/CString.h> + +namespace android { + +static const int permissionFlags660 = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP; + +struct FieldIds { + FieldIds(JNIEnv* env, jclass clazz) { + mLayoutAlgorithm = env->GetFieldID(clazz, "mLayoutAlgorithm", + "Landroid/webkit/WebSettings$LayoutAlgorithm;"); + mTextSize = env->GetFieldID(clazz, "mTextSize", + "Landroid/webkit/WebSettings$TextSize;"); + mStandardFontFamily = env->GetFieldID(clazz, "mStandardFontFamily", + "Ljava/lang/String;"); + mFixedFontFamily = env->GetFieldID(clazz, "mFixedFontFamily", + "Ljava/lang/String;"); + mSansSerifFontFamily = env->GetFieldID(clazz, "mSansSerifFontFamily", + "Ljava/lang/String;"); + mSerifFontFamily = env->GetFieldID(clazz, "mSerifFontFamily", + "Ljava/lang/String;"); + mCursiveFontFamily = env->GetFieldID(clazz, "mCursiveFontFamily", + "Ljava/lang/String;"); + mFantasyFontFamily = env->GetFieldID(clazz, "mFantasyFontFamily", + "Ljava/lang/String;"); + mDefaultTextEncoding = env->GetFieldID(clazz, "mDefaultTextEncoding", + "Ljava/lang/String;"); + mUserAgent = env->GetFieldID(clazz, "mUserAgent", + "Ljava/lang/String;"); + mAcceptLanguage = env->GetFieldID(clazz, "mAcceptLanguage", "Ljava/lang/String;"); + mMinimumFontSize = env->GetFieldID(clazz, "mMinimumFontSize", "I"); + mMinimumLogicalFontSize = env->GetFieldID(clazz, "mMinimumLogicalFontSize", "I"); + mDefaultFontSize = env->GetFieldID(clazz, "mDefaultFontSize", "I"); + mDefaultFixedFontSize = env->GetFieldID(clazz, "mDefaultFixedFontSize", "I"); + mLoadsImagesAutomatically = env->GetFieldID(clazz, "mLoadsImagesAutomatically", "Z"); +#ifdef ANDROID_BLOCK_NETWORK_IMAGE + mBlockNetworkImage = env->GetFieldID(clazz, "mBlockNetworkImage", "Z"); +#endif + mBlockNetworkLoads = env->GetFieldID(clazz, "mBlockNetworkLoads", "Z"); + mJavaScriptEnabled = env->GetFieldID(clazz, "mJavaScriptEnabled", "Z"); + mPluginState = env->GetFieldID(clazz, "mPluginState", + "Landroid/webkit/WebSettings$PluginState;"); +#if ENABLE(DATABASE) + mDatabaseEnabled = env->GetFieldID(clazz, "mDatabaseEnabled", "Z"); +#endif +#if ENABLE(DOM_STORAGE) + mDomStorageEnabled = env->GetFieldID(clazz, "mDomStorageEnabled", "Z"); +#endif +#if ENABLE(DATABASE) || ENABLE(DOM_STORAGE) + // The databases saved to disk for both the SQL and DOM Storage APIs are stored + // in the same base directory. + mDatabasePath = env->GetFieldID(clazz, "mDatabasePath", "Ljava/lang/String;"); + mDatabasePathHasBeenSet = env->GetFieldID(clazz, "mDatabasePathHasBeenSet", "Z"); +#endif +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + mAppCacheEnabled = env->GetFieldID(clazz, "mAppCacheEnabled", "Z"); + mAppCachePath = env->GetFieldID(clazz, "mAppCachePath", "Ljava/lang/String;"); + mAppCacheMaxSize = env->GetFieldID(clazz, "mAppCacheMaxSize", "J"); +#endif +#if ENABLE(WORKERS) + mWorkersEnabled = env->GetFieldID(clazz, "mWorkersEnabled", "Z"); +#endif + mGeolocationEnabled = env->GetFieldID(clazz, "mGeolocationEnabled", "Z"); + mGeolocationDatabasePath = env->GetFieldID(clazz, "mGeolocationDatabasePath", "Ljava/lang/String;"); + mXSSAuditorEnabled = env->GetFieldID(clazz, "mXSSAuditorEnabled", "Z"); + mJavaScriptCanOpenWindowsAutomatically = env->GetFieldID(clazz, + "mJavaScriptCanOpenWindowsAutomatically", "Z"); + mUseWideViewport = env->GetFieldID(clazz, "mUseWideViewport", "Z"); + mSupportMultipleWindows = env->GetFieldID(clazz, "mSupportMultipleWindows", "Z"); + mShrinksStandaloneImagesToFit = env->GetFieldID(clazz, "mShrinksStandaloneImagesToFit", "Z"); + mMaximumDecodedImageSize = env->GetFieldID(clazz, "mMaximumDecodedImageSize", "J"); + mPrivateBrowsingEnabled = env->GetFieldID(clazz, "mPrivateBrowsingEnabled", "Z"); + mSyntheticLinksEnabled = env->GetFieldID(clazz, "mSyntheticLinksEnabled", "Z"); + mUseDoubleTree = env->GetFieldID(clazz, "mUseDoubleTree", "Z"); + mPageCacheCapacity = env->GetFieldID(clazz, "mPageCacheCapacity", "I"); +#if ENABLE(WEB_AUTOFILL) + mAutoFillEnabled = env->GetFieldID(clazz, "mAutoFillEnabled", "Z"); + mAutoFillProfile = env->GetFieldID(clazz, "mAutoFillProfile", "Landroid/webkit/WebSettings$AutoFillProfile;"); + jclass autoFillProfileClass = env->FindClass("android/webkit/WebSettings$AutoFillProfile"); + mAutoFillProfileFullName = env->GetFieldID(autoFillProfileClass, "mFullName", "Ljava/lang/String;"); + mAutoFillProfileEmailAddress = env->GetFieldID(autoFillProfileClass, "mEmailAddress", "Ljava/lang/String;"); + mAutoFillProfileCompanyName = env->GetFieldID(autoFillProfileClass, "mCompanyName", "Ljava/lang/String;"); + mAutoFillProfileAddressLine1 = env->GetFieldID(autoFillProfileClass, "mAddressLine1", "Ljava/lang/String;"); + mAutoFillProfileAddressLine2 = env->GetFieldID(autoFillProfileClass, "mAddressLine2", "Ljava/lang/String;"); + mAutoFillProfileCity = env->GetFieldID(autoFillProfileClass, "mCity", "Ljava/lang/String;"); + mAutoFillProfileState = env->GetFieldID(autoFillProfileClass, "mState", "Ljava/lang/String;"); + mAutoFillProfileZipCode = env->GetFieldID(autoFillProfileClass, "mZipCode", "Ljava/lang/String;"); + mAutoFillProfileCountry = env->GetFieldID(autoFillProfileClass, "mCountry", "Ljava/lang/String;"); + mAutoFillProfilePhoneNumber = env->GetFieldID(autoFillProfileClass, "mPhoneNumber", "Ljava/lang/String;"); + env->DeleteLocalRef(autoFillProfileClass); +#endif +#if USE(CHROME_NETWORK_STACK) + mOverrideCacheMode = env->GetFieldID(clazz, "mOverrideCacheMode", "I"); +#endif + + LOG_ASSERT(mLayoutAlgorithm, "Could not find field mLayoutAlgorithm"); + LOG_ASSERT(mTextSize, "Could not find field mTextSize"); + LOG_ASSERT(mStandardFontFamily, "Could not find field mStandardFontFamily"); + LOG_ASSERT(mFixedFontFamily, "Could not find field mFixedFontFamily"); + LOG_ASSERT(mSansSerifFontFamily, "Could not find field mSansSerifFontFamily"); + LOG_ASSERT(mSerifFontFamily, "Could not find field mSerifFontFamily"); + LOG_ASSERT(mCursiveFontFamily, "Could not find field mCursiveFontFamily"); + LOG_ASSERT(mFantasyFontFamily, "Could not find field mFantasyFontFamily"); + LOG_ASSERT(mDefaultTextEncoding, "Could not find field mDefaultTextEncoding"); + LOG_ASSERT(mUserAgent, "Could not find field mUserAgent"); + LOG_ASSERT(mAcceptLanguage, "Could not find field mAcceptLanguage"); + LOG_ASSERT(mMinimumFontSize, "Could not find field mMinimumFontSize"); + LOG_ASSERT(mMinimumLogicalFontSize, "Could not find field mMinimumLogicalFontSize"); + LOG_ASSERT(mDefaultFontSize, "Could not find field mDefaultFontSize"); + LOG_ASSERT(mDefaultFixedFontSize, "Could not find field mDefaultFixedFontSize"); + LOG_ASSERT(mLoadsImagesAutomatically, "Could not find field mLoadsImagesAutomatically"); +#ifdef ANDROID_BLOCK_NETWORK_IMAGE + LOG_ASSERT(mBlockNetworkImage, "Could not find field mBlockNetworkImage"); +#endif + LOG_ASSERT(mBlockNetworkLoads, "Could not find field mBlockNetworkLoads"); + LOG_ASSERT(mJavaScriptEnabled, "Could not find field mJavaScriptEnabled"); + LOG_ASSERT(mPluginState, "Could not find field mPluginState"); +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + LOG_ASSERT(mAppCacheEnabled, "Could not find field mAppCacheEnabled"); + LOG_ASSERT(mAppCachePath, "Could not find field mAppCachePath"); + LOG_ASSERT(mAppCacheMaxSize, "Could not find field mAppCacheMaxSize"); +#endif +#if ENABLE(WORKERS) + LOG_ASSERT(mWorkersEnabled, "Could not find field mWorkersEnabled"); +#endif + LOG_ASSERT(mJavaScriptCanOpenWindowsAutomatically, + "Could not find field mJavaScriptCanOpenWindowsAutomatically"); + LOG_ASSERT(mUseWideViewport, "Could not find field mUseWideViewport"); + LOG_ASSERT(mSupportMultipleWindows, "Could not find field mSupportMultipleWindows"); + LOG_ASSERT(mShrinksStandaloneImagesToFit, "Could not find field mShrinksStandaloneImagesToFit"); + LOG_ASSERT(mMaximumDecodedImageSize, "Could not find field mMaximumDecodedImageSize"); + LOG_ASSERT(mUseDoubleTree, "Could not find field mUseDoubleTree"); + LOG_ASSERT(mPageCacheCapacity, "Could not find field mPageCacheCapacity"); + + jclass enumClass = env->FindClass("java/lang/Enum"); + LOG_ASSERT(enumClass, "Could not find Enum class!"); + mOrdinal = env->GetMethodID(enumClass, "ordinal", "()I"); + LOG_ASSERT(mOrdinal, "Could not find method ordinal"); + env->DeleteLocalRef(enumClass); + + jclass textSizeClass = env->FindClass("android/webkit/WebSettings$TextSize"); + LOG_ASSERT(textSizeClass, "Could not find TextSize enum"); + mTextSizeValue = env->GetFieldID(textSizeClass, "value", "I"); + env->DeleteLocalRef(textSizeClass); + } + + // Field ids + jfieldID mLayoutAlgorithm; + jfieldID mTextSize; + jfieldID mStandardFontFamily; + jfieldID mFixedFontFamily; + jfieldID mSansSerifFontFamily; + jfieldID mSerifFontFamily; + jfieldID mCursiveFontFamily; + jfieldID mFantasyFontFamily; + jfieldID mDefaultTextEncoding; + jfieldID mUserAgent; + jfieldID mAcceptLanguage; + jfieldID mMinimumFontSize; + jfieldID mMinimumLogicalFontSize; + jfieldID mDefaultFontSize; + jfieldID mDefaultFixedFontSize; + jfieldID mLoadsImagesAutomatically; +#ifdef ANDROID_BLOCK_NETWORK_IMAGE + jfieldID mBlockNetworkImage; +#endif + jfieldID mBlockNetworkLoads; + jfieldID mJavaScriptEnabled; + jfieldID mPluginState; +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + jfieldID mAppCacheEnabled; + jfieldID mAppCachePath; + jfieldID mAppCacheMaxSize; +#endif +#if ENABLE(WORKERS) + jfieldID mWorkersEnabled; +#endif + jfieldID mJavaScriptCanOpenWindowsAutomatically; + jfieldID mUseWideViewport; + jfieldID mSupportMultipleWindows; + jfieldID mShrinksStandaloneImagesToFit; + jfieldID mMaximumDecodedImageSize; + jfieldID mPrivateBrowsingEnabled; + jfieldID mSyntheticLinksEnabled; + jfieldID mUseDoubleTree; + jfieldID mPageCacheCapacity; + // Ordinal() method and value field for enums + jmethodID mOrdinal; + jfieldID mTextSizeValue; + +#if ENABLE(DATABASE) + jfieldID mDatabaseEnabled; +#endif +#if ENABLE(DOM_STORAGE) + jfieldID mDomStorageEnabled; +#endif + jfieldID mGeolocationEnabled; + jfieldID mGeolocationDatabasePath; + jfieldID mXSSAuditorEnabled; +#if ENABLE(DATABASE) || ENABLE(DOM_STORAGE) + jfieldID mDatabasePath; + jfieldID mDatabasePathHasBeenSet; +#endif +#if ENABLE(WEB_AUTOFILL) + jfieldID mAutoFillEnabled; + jfieldID mAutoFillProfile; + jfieldID mAutoFillProfileFullName; + jfieldID mAutoFillProfileEmailAddress; + jfieldID mAutoFillProfileCompanyName; + jfieldID mAutoFillProfileAddressLine1; + jfieldID mAutoFillProfileAddressLine2; + jfieldID mAutoFillProfileCity; + jfieldID mAutoFillProfileState; + jfieldID mAutoFillProfileZipCode; + jfieldID mAutoFillProfileCountry; + jfieldID mAutoFillProfilePhoneNumber; +#endif +#if USE(CHROME_NETWORK_STACK) + jfieldID mOverrideCacheMode; +#endif +}; + +static struct FieldIds* gFieldIds; + +// Note: This is moved from the old FrameAndroid.cpp +static void recursiveCleanupForFullLayout(WebCore::RenderObject* obj) +{ + obj->setNeedsLayout(true, false); +#ifdef ANDROID_LAYOUT + if (obj->isTable()) + (static_cast<WebCore::RenderTable *>(obj))->clearSingleColumn(); +#endif + for (WebCore::RenderObject* n = obj->firstChild(); n; n = n->nextSibling()) + recursiveCleanupForFullLayout(n); +} + +#if ENABLE(WEB_AUTOFILL) +inline string16 getStringFieldAsString16(JNIEnv* env, jobject autoFillProfile, jfieldID fieldId) +{ + jstring str = static_cast<jstring>(env->GetObjectField(autoFillProfile, fieldId)); + return str ? jstringToString16(env, str) : string16(); +} + +void syncAutoFillProfile(JNIEnv* env, jobject autoFillProfile, WebAutoFill* webAutoFill) +{ + string16 fullName = getStringFieldAsString16(env, autoFillProfile, gFieldIds->mAutoFillProfileFullName); + string16 emailAddress = getStringFieldAsString16(env, autoFillProfile, gFieldIds->mAutoFillProfileEmailAddress); + string16 companyName = getStringFieldAsString16(env, autoFillProfile, gFieldIds->mAutoFillProfileCompanyName); + string16 addressLine1 = getStringFieldAsString16(env, autoFillProfile, gFieldIds->mAutoFillProfileAddressLine1); + string16 addressLine2 = getStringFieldAsString16(env, autoFillProfile, gFieldIds->mAutoFillProfileAddressLine2); + string16 city = getStringFieldAsString16(env, autoFillProfile, gFieldIds->mAutoFillProfileCity); + string16 state = getStringFieldAsString16(env, autoFillProfile, gFieldIds->mAutoFillProfileState); + string16 zipCode = getStringFieldAsString16(env, autoFillProfile, gFieldIds->mAutoFillProfileZipCode); + string16 country = getStringFieldAsString16(env, autoFillProfile, gFieldIds->mAutoFillProfileCountry); + string16 phoneNumber = getStringFieldAsString16(env, autoFillProfile, gFieldIds->mAutoFillProfilePhoneNumber); + + webAutoFill->setProfile(fullName, emailAddress, companyName, addressLine1, addressLine2, city, state, zipCode, country, phoneNumber); +} +#endif + +class WebSettings { +public: + static void Sync(JNIEnv* env, jobject obj, jint frame) + { + WebCore::Frame* pFrame = (WebCore::Frame*)frame; + LOG_ASSERT(pFrame, "%s must take a valid frame pointer!", __FUNCTION__); + WebCore::Settings* s = pFrame->settings(); + if (!s) + return; + WebCore::CachedResourceLoader* cachedResourceLoader = pFrame->document()->cachedResourceLoader(); + +#ifdef ANDROID_LAYOUT + jobject layout = env->GetObjectField(obj, gFieldIds->mLayoutAlgorithm); + WebCore::Settings::LayoutAlgorithm l = (WebCore::Settings::LayoutAlgorithm) + env->CallIntMethod(layout, gFieldIds->mOrdinal); + if (s->layoutAlgorithm() != l) { + s->setLayoutAlgorithm(l); + if (pFrame->document()) { + pFrame->document()->styleSelectorChanged(WebCore::RecalcStyleImmediately); + if (pFrame->document()->renderer()) { + recursiveCleanupForFullLayout(pFrame->document()->renderer()); + LOG_ASSERT(pFrame->view(), "No view for this frame when trying to relayout"); + pFrame->view()->layout(); + // FIXME: This call used to scroll the page to put the focus into view. + // It worked on the WebViewCore, but now scrolling is done outside of the + // WebViewCore, on the UI side, so there needs to be a new way to do this. + //pFrame->makeFocusVisible(); + } + } + } +#endif + jobject textSize = env->GetObjectField(obj, gFieldIds->mTextSize); + float zoomFactor = env->GetIntField(textSize, gFieldIds->mTextSizeValue) / 100.0f; + if (pFrame->textZoomFactor() != zoomFactor) + pFrame->setTextZoomFactor(zoomFactor); + + jstring str = (jstring)env->GetObjectField(obj, gFieldIds->mStandardFontFamily); + s->setStandardFontFamily(jstringToWtfString(env, str)); + + str = (jstring)env->GetObjectField(obj, gFieldIds->mFixedFontFamily); + s->setFixedFontFamily(jstringToWtfString(env, str)); + + str = (jstring)env->GetObjectField(obj, gFieldIds->mSansSerifFontFamily); + s->setSansSerifFontFamily(jstringToWtfString(env, str)); + + str = (jstring)env->GetObjectField(obj, gFieldIds->mSerifFontFamily); + s->setSerifFontFamily(jstringToWtfString(env, str)); + + str = (jstring)env->GetObjectField(obj, gFieldIds->mCursiveFontFamily); + s->setCursiveFontFamily(jstringToWtfString(env, str)); + + str = (jstring)env->GetObjectField(obj, gFieldIds->mFantasyFontFamily); + s->setFantasyFontFamily(jstringToWtfString(env, str)); + + str = (jstring)env->GetObjectField(obj, gFieldIds->mDefaultTextEncoding); + s->setDefaultTextEncodingName(jstringToWtfString(env, str)); + + str = (jstring)env->GetObjectField(obj, gFieldIds->mUserAgent); + WebFrame::getWebFrame(pFrame)->setUserAgent(jstringToWtfString(env, str)); +#if USE(CHROME_NETWORK_STACK) + WebViewCore::getWebViewCore(pFrame->view())->setWebRequestContextUserAgent(); + + jint cacheMode = env->GetIntField(obj, gFieldIds->mOverrideCacheMode); + WebViewCore::getWebViewCore(pFrame->view())->setWebRequestContextCacheMode(cacheMode); + + str = (jstring)env->GetObjectField(obj, gFieldIds->mAcceptLanguage); + WebRequestContext::setAcceptLanguage(jstringToWtfString(env, str)); +#endif + + jint size = env->GetIntField(obj, gFieldIds->mMinimumFontSize); + s->setMinimumFontSize(size); + + size = env->GetIntField(obj, gFieldIds->mMinimumLogicalFontSize); + s->setMinimumLogicalFontSize(size); + + size = env->GetIntField(obj, gFieldIds->mDefaultFontSize); + s->setDefaultFontSize(size); + + size = env->GetIntField(obj, gFieldIds->mDefaultFixedFontSize); + s->setDefaultFixedFontSize(size); + + jboolean flag = env->GetBooleanField(obj, gFieldIds->mLoadsImagesAutomatically); + s->setLoadsImagesAutomatically(flag); + if (flag) + cachedResourceLoader->setAutoLoadImages(true); + +#ifdef ANDROID_BLOCK_NETWORK_IMAGE + flag = env->GetBooleanField(obj, gFieldIds->mBlockNetworkImage); + s->setBlockNetworkImage(flag); + if(!flag) + cachedResourceLoader->setBlockNetworkImage(false); +#endif + flag = env->GetBooleanField(obj, gFieldIds->mBlockNetworkLoads); + WebFrame* webFrame = WebFrame::getWebFrame(pFrame); + webFrame->setBlockNetworkLoads(flag); + + flag = env->GetBooleanField(obj, gFieldIds->mJavaScriptEnabled); + s->setJavaScriptEnabled(flag); + + // ON = 0 + // ON_DEMAND = 1 + // OFF = 2 + jobject pluginState = env->GetObjectField(obj, gFieldIds->mPluginState); + int state = env->CallIntMethod(pluginState, gFieldIds->mOrdinal); + s->setPluginsEnabled(state < 2); +#ifdef ANDROID_PLUGINS + s->setPluginsOnDemand(state == 1); +#endif + +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + flag = env->GetBooleanField(obj, gFieldIds->mAppCacheEnabled); + s->setOfflineWebApplicationCacheEnabled(flag); + str = (jstring)env->GetObjectField(obj, gFieldIds->mAppCachePath); + if (str) { + String path = jstringToWtfString(env, str); + if (path.length() && cacheStorage().cacheDirectory().isNull()) { + cacheStorage().setCacheDirectory(path); + // This database is created on the first load. If the file + // doesn't exist, we create it and set its permissions. The + // filename must match that in ApplicationCacheStorage.cpp. + String filename = pathByAppendingComponent(path, "ApplicationCache.db"); + int fd = open(filename.utf8().data(), O_CREAT | O_EXCL, permissionFlags660); + if (fd >= 0) + close(fd); + } + } + jlong maxsize = env->GetLongField(obj, gFieldIds->mAppCacheMaxSize); + cacheStorage().setMaximumSize(maxsize); +#endif + + flag = env->GetBooleanField(obj, gFieldIds->mJavaScriptCanOpenWindowsAutomatically); + s->setJavaScriptCanOpenWindowsAutomatically(flag); + +#ifdef ANDROID_LAYOUT + flag = env->GetBooleanField(obj, gFieldIds->mUseWideViewport); + s->setUseWideViewport(flag); +#endif + +#ifdef ANDROID_MULTIPLE_WINDOWS + flag = env->GetBooleanField(obj, gFieldIds->mSupportMultipleWindows); + s->setSupportMultipleWindows(flag); +#endif + flag = env->GetBooleanField(obj, gFieldIds->mShrinksStandaloneImagesToFit); + s->setShrinksStandaloneImagesToFit(flag); + jlong maxImage = env->GetLongField(obj, gFieldIds->mMaximumDecodedImageSize); + // Since in ImageSourceAndroid.cpp, the image will always not exceed + // MAX_SIZE_BEFORE_SUBSAMPLE, there's no need to pass the max value to + // WebCore, which checks (image_width * image_height * 4) as an + // estimation against the max value, which is done in CachedImage.cpp. + // And there're cases where the decoded image size will not + // exceed the max, but the WebCore estimation will. So the following + // code is commented out to fix those cases. + // if (maxImage == 0) + // maxImage = computeMaxBitmapSizeForCache(); + s->setMaximumDecodedImageSize(maxImage); + + flag = env->GetBooleanField(obj, gFieldIds->mPrivateBrowsingEnabled); + s->setPrivateBrowsingEnabled(flag); + + flag = env->GetBooleanField(obj, gFieldIds->mSyntheticLinksEnabled); + s->setDefaultFormatDetection(flag); + s->setFormatDetectionAddress(flag); + s->setFormatDetectionEmail(flag); + s->setFormatDetectionTelephone(flag); +#if ENABLE(DATABASE) + flag = env->GetBooleanField(obj, gFieldIds->mDatabaseEnabled); + WebCore::Database::setIsAvailable(flag); + + flag = env->GetBooleanField(obj, gFieldIds->mDatabasePathHasBeenSet); + if (flag) { + // If the user has set the database path, sync it to the DatabaseTracker. + str = (jstring)env->GetObjectField(obj, gFieldIds->mDatabasePath); + if (str) { + String path = jstringToWtfString(env, str); + DatabaseTracker::tracker().setDatabaseDirectoryPath(path); + // This database is created when the first HTML5 Database object is + // instantiated. If the file doesn't exist, we create it and set its + // permissions. The filename must match that in + // DatabaseTracker.cpp. + String filename = SQLiteFileSystem::appendDatabaseFileNameToPath(path, "Databases.db"); + int fd = open(filename.utf8().data(), O_CREAT | O_EXCL, permissionFlags660); + if (fd >= 0) + close(fd); + } + } +#endif +#if ENABLE(DOM_STORAGE) + flag = env->GetBooleanField(obj, gFieldIds->mDomStorageEnabled); + s->setLocalStorageEnabled(flag); + str = (jstring)env->GetObjectField(obj, gFieldIds->mDatabasePath); + if (str) { + WTF::String localStorageDatabasePath = jstringToWtfString(env,str); + if (localStorageDatabasePath.length()) { + localStorageDatabasePath = WebCore::pathByAppendingComponent( + localStorageDatabasePath, "localstorage"); + // We need 770 for folders + mkdir(localStorageDatabasePath.utf8().data(), + permissionFlags660 | S_IXUSR | S_IXGRP); + s->setLocalStorageDatabasePath(localStorageDatabasePath); + } + } +#endif + + flag = env->GetBooleanField(obj, gFieldIds->mGeolocationEnabled); + GeolocationPermissions::setAlwaysDeny(!flag); + str = (jstring)env->GetObjectField(obj, gFieldIds->mGeolocationDatabasePath); + if (str) { + String path = jstringToWtfString(env, str); + GeolocationPermissions::setDatabasePath(path); + GeolocationPositionCache::instance()->setDatabasePath(path); + // This database is created when the first Geolocation object is + // instantiated. If the file doesn't exist, we create it and set its + // permissions. The filename must match that in + // GeolocationPositionCache.cpp. + String filename = SQLiteFileSystem::appendDatabaseFileNameToPath(path, "CachedGeoposition.db"); + int fd = open(filename.utf8().data(), O_CREAT | O_EXCL, permissionFlags660); + if (fd >= 0) + close(fd); + } + + flag = env->GetBooleanField(obj, gFieldIds->mXSSAuditorEnabled); + s->setXSSAuditorEnabled(flag); + + size = env->GetIntField(obj, gFieldIds->mPageCacheCapacity); + if (size > 0) { + s->setUsesPageCache(true); + WebCore::pageCache()->setCapacity(size); + } else + s->setUsesPageCache(false); + +#if ENABLE(WEB_AUTOFILL) + flag = env->GetBooleanField(obj, gFieldIds->mAutoFillEnabled); + // TODO: This updates the Settings WebCore side with the user's + // preference for autofill and will stop WebCore making requests + // into the chromium autofill code. That code in Chromium also has + // a notion of being enabled/disabled that gets read from the users + // preferences. At the moment, it's hardcoded to true on Android + // (see chrome/browser/autofill/autofill_manager.cc:405). This + // setting should probably be synced into Chromium also. + + s->setAutoFillEnabled(flag); + + if (flag) { + EditorClientAndroid* editorC = static_cast<EditorClientAndroid*>(pFrame->page()->editorClient()); + WebAutoFill* webAutoFill = editorC->getAutoFill(); + // Set the active AutoFillProfile data. + jobject autoFillProfile = env->GetObjectField(obj, gFieldIds->mAutoFillProfile); + if (autoFillProfile) + syncAutoFillProfile(env, autoFillProfile, webAutoFill); + else { + // The autofill profile is null. We need to tell Chromium about this because + // this may be because the user just deleted their profile but left the + // autofill feature setting enabled. + webAutoFill->clearProfiles(); + } + } +#endif + } +}; + + +//------------------------------------------------------------- +// JNI registration +//------------------------------------------------------------- + +static JNINativeMethod gWebSettingsMethods[] = { + { "nativeSync", "(I)V", + (void*) WebSettings::Sync } +}; + +int registerWebSettings(JNIEnv* env) +{ + jclass clazz = env->FindClass("android/webkit/WebSettings"); + LOG_ASSERT(clazz, "Unable to find class WebSettings!"); + gFieldIds = new FieldIds(env, clazz); + env->DeleteLocalRef(clazz); + return jniRegisterNativeMethods(env, "android/webkit/WebSettings", + gWebSettingsMethods, NELEM(gWebSettingsMethods)); +} + +} diff --git a/Source/WebKit/android/jni/WebStorage.cpp b/Source/WebKit/android/jni/WebStorage.cpp new file mode 100644 index 0000000..9ce207d --- /dev/null +++ b/Source/WebKit/android/jni/WebStorage.cpp @@ -0,0 +1,188 @@ +/* + * Copyright 2009, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" + +#if ENABLE(DATABASE) + +#include "ApplicationCacheStorage.h" +#include "DatabaseTracker.h" +#include "JNIUtility.h" +#include "JavaSharedClient.h" +#include "KURL.h" +#include "PageGroup.h" +#include "SecurityOrigin.h" +#include "WebCoreJni.h" + +#include <JNIHelp.h> + +namespace android { + +static jobject GetOrigins(JNIEnv* env, jobject obj) +{ + Vector<RefPtr<WebCore::SecurityOrigin> > coreOrigins; + WebCore::DatabaseTracker::tracker().origins(coreOrigins); + Vector<WebCore::KURL> manifestUrls; + if (WebCore::cacheStorage().manifestURLs(&manifestUrls)) { + int size = manifestUrls.size(); + for (int i = 0; i < size; ++i) { + RefPtr<WebCore::SecurityOrigin> manifestOrigin = WebCore::SecurityOrigin::create(manifestUrls[i]); + if (manifestOrigin.get() == 0) + continue; + coreOrigins.append(manifestOrigin); + } + } + + jclass setClass = env->FindClass("java/util/HashSet"); + jmethodID cid = env->GetMethodID(setClass, "<init>", "()V"); + jmethodID mid = env->GetMethodID(setClass, "add", "(Ljava/lang/Object;)Z"); + jobject set = env->NewObject(setClass, cid); + env->DeleteLocalRef(setClass); + + for (unsigned i = 0; i < coreOrigins.size(); ++i) { + WebCore::SecurityOrigin* origin = coreOrigins[i].get(); + WTF::String url = origin->toString(); + jstring jUrl = wtfStringToJstring(env, url); + env->CallBooleanMethod(set, mid, jUrl); + env->DeleteLocalRef(jUrl); + } + + return set; +} + +static unsigned long long GetQuotaForOrigin(JNIEnv* env, jobject obj, jstring origin) +{ + WTF::String originStr = jstringToWtfString(env, origin); + RefPtr<WebCore::SecurityOrigin> securityOrigin = WebCore::SecurityOrigin::createFromString(originStr); + unsigned long long quota = WebCore::DatabaseTracker::tracker().quotaForOrigin(securityOrigin.get()); + return quota; +} + +static unsigned long long GetUsageForOrigin(JNIEnv* env, jobject obj, jstring origin) +{ + WTF::String originStr = jstringToWtfString(env, origin); + RefPtr<WebCore::SecurityOrigin> securityOrigin = WebCore::SecurityOrigin::createFromString(originStr); + unsigned long long usage = WebCore::DatabaseTracker::tracker().usageForOrigin(securityOrigin.get()); + Vector<WebCore::KURL> manifestUrls; + if (!WebCore::cacheStorage().manifestURLs(&manifestUrls)) + return usage; + int size = manifestUrls.size(); + for (int i = 0; i < size; ++i) { + RefPtr<WebCore::SecurityOrigin> manifestOrigin = WebCore::SecurityOrigin::create(manifestUrls[i]); + if (manifestOrigin.get() == 0) + continue; + if (manifestOrigin->isSameSchemeHostPort(securityOrigin.get())) { + int64_t cacheSize = 0; + WebCore::cacheStorage().cacheGroupSize(manifestUrls[i].string(), &cacheSize); + usage += cacheSize; + } + } + return usage; +} + +static void SetQuotaForOrigin(JNIEnv* env, jobject obj, jstring origin, unsigned long long quota) +{ + WTF::String originStr = jstringToWtfString(env, origin); + RefPtr<WebCore::SecurityOrigin> securityOrigin = WebCore::SecurityOrigin::createFromString(originStr); + WebCore::DatabaseTracker::tracker().setQuota(securityOrigin.get(), quota); +} + +static void DeleteOrigin(JNIEnv* env, jobject obj, jstring origin) +{ + WTF::String originStr = jstringToWtfString(env, origin); + RefPtr<WebCore::SecurityOrigin> securityOrigin = WebCore::SecurityOrigin::createFromString(originStr); + WebCore::DatabaseTracker::tracker().deleteOrigin(securityOrigin.get()); + + Vector<WebCore::KURL> manifestUrls; + if (!WebCore::cacheStorage().manifestURLs(&manifestUrls)) + return; + int size = manifestUrls.size(); + for (int i = 0; i < size; ++i) { + RefPtr<WebCore::SecurityOrigin> manifestOrigin = WebCore::SecurityOrigin::create(manifestUrls[i]); + if (manifestOrigin.get() == 0) + continue; + if (manifestOrigin->isSameSchemeHostPort(securityOrigin.get())) + WebCore::cacheStorage().deleteCacheGroup(manifestUrls[i]); + } +} + +static void DeleteAllData(JNIEnv* env, jobject obj) +{ + WebCore::DatabaseTracker::tracker().deleteAllDatabases(); + + Vector<WebCore::KURL> manifestUrls; + if (!WebCore::cacheStorage().manifestURLs(&manifestUrls)) + return; + int size = manifestUrls.size(); + for (int i = 0; i < size; ++i) + WebCore::cacheStorage().deleteCacheGroup(manifestUrls[i]); + + // FIXME: this is a workaround for eliminating any DOM Storage data (both + // session and local storage) as there is no functionality inside WebKit at the + // moment to do it. That functionality is a WIP in https://bugs.webkit.org/show_bug.cgi?id=51878 + // and when that patch lands and we merge it, we should move towards that approach instead. + WebCore::PageGroup::clearDomStorage(); +} + +static void SetAppCacheMaximumSize(JNIEnv* env, jobject obj, unsigned long long size) +{ + WebCore::cacheStorage().setMaximumSize(size); +} + +/* + * JNI registration + */ +static JNINativeMethod gWebStorageMethods[] = { + { "nativeGetOrigins", "()Ljava/util/Set;", + (void*) GetOrigins }, + { "nativeGetUsageForOrigin", "(Ljava/lang/String;)J", + (void*) GetUsageForOrigin }, + { "nativeGetQuotaForOrigin", "(Ljava/lang/String;)J", + (void*) GetQuotaForOrigin }, + { "nativeSetQuotaForOrigin", "(Ljava/lang/String;J)V", + (void*) SetQuotaForOrigin }, + { "nativeDeleteOrigin", "(Ljava/lang/String;)V", + (void*) DeleteOrigin }, + { "nativeDeleteAllData", "()V", + (void*) DeleteAllData }, + { "nativeSetAppCacheMaximumSize", "(J)V", + (void*) SetAppCacheMaximumSize } +}; + +int registerWebStorage(JNIEnv* env) +{ +#ifndef NDEBUG + jclass webStorage = env->FindClass("android/webkit/WebStorage"); + LOG_ASSERT(webStorage, "Unable to find class android.webkit.WebStorage"); + env->DeleteLocalRef(webStorage); +#endif + + return jniRegisterNativeMethods(env, "android/webkit/WebStorage", + gWebStorageMethods, NELEM(gWebStorageMethods)); +} + +} + +#endif //ENABLE(DATABASE) diff --git a/Source/WebKit/android/jni/WebViewCore.cpp b/Source/WebKit/android/jni/WebViewCore.cpp new file mode 100644 index 0000000..f2680b5 --- /dev/null +++ b/Source/WebKit/android/jni/WebViewCore.cpp @@ -0,0 +1,4598 @@ +/* + * Copyright 2006, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#define LOG_TAG "webcoreglue" + +#include "config.h" +#include "WebViewCore.h" + +#include "AccessibilityObject.h" +#include "Attribute.h" +#include "BaseLayerAndroid.h" +#include "CachedNode.h" +#include "CachedRoot.h" +#include "Chrome.h" +#include "ChromeClientAndroid.h" +#include "ChromiumIncludes.h" +#include "ClientRect.h" +#include "ClientRectList.h" +#include "Color.h" +#include "CSSPropertyNames.h" +#include "CSSValueKeywords.h" +#include "DatabaseTracker.h" +#include "Document.h" +#include "DOMWindow.h" +#include "DOMSelection.h" +#include "Element.h" +#include "Editor.h" +#include "EditorClientAndroid.h" +#include "EventHandler.h" +#include "EventNames.h" +#include "ExceptionCode.h" +#include "FocusController.h" +#include "Font.h" +#include "Frame.h" +#include "FrameLoader.h" +#include "FrameLoaderClientAndroid.h" +#include "FrameTree.h" +#include "FrameView.h" +#include "Geolocation.h" +#include "GraphicsContext.h" +#include "GraphicsJNI.h" +#include "HTMLAnchorElement.h" +#include "HTMLAreaElement.h" +#include "HTMLElement.h" +#include "HTMLFormControlElement.h" +#include "HTMLImageElement.h" +#include "HTMLInputElement.h" +#include "HTMLLabelElement.h" +#include "HTMLMapElement.h" +#include "HTMLNames.h" +#include "HTMLOptGroupElement.h" +#include "HTMLOptionElement.h" +#include "HTMLSelectElement.h" +#include "HTMLTextAreaElement.h" +#include "HistoryItem.h" +#include "HitTestRequest.h" +#include "HitTestResult.h" +#include "InlineTextBox.h" +#include "MemoryUsage.h" +#include "NamedNodeMap.h" +#include "Navigator.h" +#include "Node.h" +#include "NodeList.h" +#include "Page.h" +#include "PageGroup.h" +#include "PlatformKeyboardEvent.h" +#include "PlatformString.h" +#include "PluginWidgetAndroid.h" +#include "PluginView.h" +#include "Position.h" +#include "ProgressTracker.h" +#include "Range.h" +#include "RenderBox.h" +#include "RenderInline.h" +#include "RenderLayer.h" +#include "RenderPart.h" +#include "RenderText.h" +#include "RenderTextControl.h" +#include "RenderThemeAndroid.h" +#include "RenderView.h" +#include "ResourceRequest.h" +#include "SchemeRegistry.h" +#include "SelectionController.h" +#include "Settings.h" +#include "SkANP.h" +#include "SkTemplates.h" +#include "SkTDArray.h" +#include "SkTypes.h" +#include "SkCanvas.h" +#include "SkPicture.h" +#include "SkUtils.h" +#include "Text.h" +#include "TypingCommand.h" +#include "WebCoreFrameBridge.h" +#include "WebFrameView.h" +#include "WindowsKeyboardCodes.h" +#include "android_graphics.h" +#include "autofill/WebAutoFill.h" +#include "htmlediting.h" +#include "markup.h" + +#include <JNIHelp.h> +#include <JNIUtility.h> +#include <ui/KeycodeLabels.h> +#include <wtf/CurrentTime.h> +#include <wtf/text/AtomicString.h> +#include <wtf/text/StringImpl.h> + +#if USE(V8) +#include "ScriptController.h" +#include "V8Counters.h" +#include <wtf/text/CString.h> +#endif + +#if DEBUG_NAV_UI +#include "SkTime.h" +#endif + +#if ENABLE(TOUCH_EVENTS) // Android +#include "PlatformTouchEvent.h" +#endif + +#ifdef ANDROID_DOM_LOGGING +#include "AndroidLog.h" +#include "RenderTreeAsText.h" +#include <wtf/text/CString.h> + +FILE* gDomTreeFile = 0; +FILE* gRenderTreeFile = 0; +#endif + +#ifdef ANDROID_INSTRUMENT +#include "TimeCounter.h" +#endif + +#if USE(ACCELERATED_COMPOSITING) +#include "GraphicsLayerAndroid.h" +#include "RenderLayerCompositor.h" +#endif + +/* We pass this flag when recording the actual content, so that we don't spend + time actually regionizing complex path clips, when all we really want to do + is record them. + */ +#define PICT_RECORD_FLAGS SkPicture::kUsePathBoundsForClip_RecordingFlag + +//////////////////////////////////////////////////////////////////////////////////////////////// + +namespace android { + +static SkTDArray<WebViewCore*> gInstanceList; + +void WebViewCore::addInstance(WebViewCore* inst) { + *gInstanceList.append() = inst; +} + +void WebViewCore::removeInstance(WebViewCore* inst) { + int index = gInstanceList.find(inst); + LOG_ASSERT(index >= 0, "RemoveInstance inst not found"); + if (index >= 0) { + gInstanceList.removeShuffle(index); + } +} + +bool WebViewCore::isInstance(WebViewCore* inst) { + return gInstanceList.find(inst) >= 0; +} + +jobject WebViewCore::getApplicationContext() { + + // check to see if there is a valid webviewcore object + if (gInstanceList.isEmpty()) + return 0; + + // get the context from the webview + jobject context = gInstanceList[0]->getContext(); + + if (!context) + return 0; + + // get the application context using JNI + JNIEnv* env = JSC::Bindings::getJNIEnv(); + jclass contextClass = env->GetObjectClass(context); + jmethodID appContextMethod = env->GetMethodID(contextClass, "getApplicationContext", "()Landroid/content/Context;"); + env->DeleteLocalRef(contextClass); + jobject result = env->CallObjectMethod(context, appContextMethod); + checkException(env); + return result; +} + + +struct WebViewCoreStaticMethods { + jmethodID m_isSupportedMediaMimeType; +} gWebViewCoreStaticMethods; + +// Check whether a media mimeType is supported in Android media framework. +bool WebViewCore::isSupportedMediaMimeType(const WTF::String& mimeType) { + JNIEnv* env = JSC::Bindings::getJNIEnv(); + jstring jMimeType = wtfStringToJstring(env, mimeType); + jclass webViewCore = env->FindClass("android/webkit/WebViewCore"); + bool val = env->CallStaticBooleanMethod(webViewCore, + gWebViewCoreStaticMethods.m_isSupportedMediaMimeType, jMimeType); + checkException(env); + env->DeleteLocalRef(webViewCore); + env->DeleteLocalRef(jMimeType); + + return val; +} + +// ---------------------------------------------------------------------------- + +#define GET_NATIVE_VIEW(env, obj) ((WebViewCore*)env->GetIntField(obj, gWebViewCoreFields.m_nativeClass)) + +// Field ids for WebViewCore +struct WebViewCoreFields { + jfieldID m_nativeClass; + jfieldID m_viewportWidth; + jfieldID m_viewportHeight; + jfieldID m_viewportInitialScale; + jfieldID m_viewportMinimumScale; + jfieldID m_viewportMaximumScale; + jfieldID m_viewportUserScalable; + jfieldID m_viewportDensityDpi; + jfieldID m_webView; + jfieldID m_drawIsPaused; + jfieldID m_lowMemoryUsageMb; + jfieldID m_highMemoryUsageMb; + jfieldID m_highUsageDeltaMb; +} gWebViewCoreFields; + +// ---------------------------------------------------------------------------- + +struct WebViewCore::JavaGlue { + jweak m_obj; + jmethodID m_scrollTo; + jmethodID m_contentDraw; + jmethodID m_layersDraw; + jmethodID m_requestListBox; + jmethodID m_openFileChooser; + jmethodID m_requestSingleListBox; + jmethodID m_jsAlert; + jmethodID m_jsConfirm; + jmethodID m_jsPrompt; + jmethodID m_jsUnload; + jmethodID m_jsInterrupt; + jmethodID m_didFirstLayout; + jmethodID m_updateViewport; + jmethodID m_sendNotifyProgressFinished; + jmethodID m_sendViewInvalidate; + jmethodID m_updateTextfield; + jmethodID m_updateTextSelection; + jmethodID m_clearTextEntry; + jmethodID m_restoreScale; + jmethodID m_needTouchEvents; + jmethodID m_requestKeyboard; + jmethodID m_requestKeyboardWithSelection; + jmethodID m_exceededDatabaseQuota; + jmethodID m_reachedMaxAppCacheSize; + jmethodID m_populateVisitedLinks; + jmethodID m_geolocationPermissionsShowPrompt; + jmethodID m_geolocationPermissionsHidePrompt; + jmethodID m_getDeviceMotionService; + jmethodID m_getDeviceOrientationService; + jmethodID m_addMessageToConsole; + jmethodID m_formDidBlur; + jmethodID m_getPluginClass; + jmethodID m_showFullScreenPlugin; + jmethodID m_hideFullScreenPlugin; + jmethodID m_createSurface; + jmethodID m_addSurface; + jmethodID m_updateSurface; + jmethodID m_destroySurface; + jmethodID m_getContext; + jmethodID m_keepScreenOn; + jmethodID m_sendFindAgain; + jmethodID m_showRect; + jmethodID m_centerFitRect; + jmethodID m_setScrollbarModes; + jmethodID m_setInstallableWebApp; + jmethodID m_enterFullscreenForVideoLayer; + jmethodID m_setWebTextViewAutoFillable; + jmethodID m_selectAt; + AutoJObject object(JNIEnv* env) { + return getRealObject(env, m_obj); + } +}; + +/* + * WebViewCore Implementation + */ + +static jmethodID GetJMethod(JNIEnv* env, jclass clazz, const char name[], const char signature[]) +{ + jmethodID m = env->GetMethodID(clazz, name, signature); + LOG_ASSERT(m, "Could not find method %s", name); + return m; +} + +Mutex WebViewCore::gFrameCacheMutex; +Mutex WebViewCore::gButtonMutex; +Mutex WebViewCore::gCursorBoundsMutex; + +WebViewCore::WebViewCore(JNIEnv* env, jobject javaWebViewCore, WebCore::Frame* mainframe) + : m_pluginInvalTimer(this, &WebViewCore::pluginInvalTimerFired) + , m_deviceMotionAndOrientationManager(this) +{ + m_mainFrame = mainframe; + + m_popupReply = 0; + m_moveGeneration = 0; + m_lastGeneration = 0; + m_touchGeneration = 0; + m_blockTextfieldUpdates = false; + // just initial values. These should be set by client + m_maxXScroll = 320/4; + m_maxYScroll = 240/4; + m_textGeneration = 0; + m_screenWidth = 320; + m_textWrapWidth = 320; + m_scale = 1; +#if ENABLE(TOUCH_EVENTS) + m_forwardingTouchEvents = false; +#endif + m_isPaused = false; + m_screenOnCounter = 0; + m_shouldPaintCaret = true; + + LOG_ASSERT(m_mainFrame, "Uh oh, somehow a frameview was made without an initial frame!"); + + jclass clazz = env->GetObjectClass(javaWebViewCore); + m_javaGlue = new JavaGlue; + m_javaGlue->m_obj = env->NewWeakGlobalRef(javaWebViewCore); + m_javaGlue->m_scrollTo = GetJMethod(env, clazz, "contentScrollTo", "(IIZZ)V"); + m_javaGlue->m_contentDraw = GetJMethod(env, clazz, "contentDraw", "()V"); + m_javaGlue->m_layersDraw = GetJMethod(env, clazz, "layersDraw", "()V"); + m_javaGlue->m_requestListBox = GetJMethod(env, clazz, "requestListBox", "([Ljava/lang/String;[I[I)V"); + m_javaGlue->m_openFileChooser = GetJMethod(env, clazz, "openFileChooser", "(Ljava/lang/String;)Ljava/lang/String;"); + m_javaGlue->m_requestSingleListBox = GetJMethod(env, clazz, "requestListBox", "([Ljava/lang/String;[II)V"); + m_javaGlue->m_jsAlert = GetJMethod(env, clazz, "jsAlert", "(Ljava/lang/String;Ljava/lang/String;)V"); + m_javaGlue->m_jsConfirm = GetJMethod(env, clazz, "jsConfirm", "(Ljava/lang/String;Ljava/lang/String;)Z"); + m_javaGlue->m_jsPrompt = GetJMethod(env, clazz, "jsPrompt", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;"); + m_javaGlue->m_jsUnload = GetJMethod(env, clazz, "jsUnload", "(Ljava/lang/String;Ljava/lang/String;)Z"); + m_javaGlue->m_jsInterrupt = GetJMethod(env, clazz, "jsInterrupt", "()Z"); + m_javaGlue->m_didFirstLayout = GetJMethod(env, clazz, "didFirstLayout", "(Z)V"); + m_javaGlue->m_updateViewport = GetJMethod(env, clazz, "updateViewport", "()V"); + m_javaGlue->m_sendNotifyProgressFinished = GetJMethod(env, clazz, "sendNotifyProgressFinished", "()V"); + m_javaGlue->m_sendViewInvalidate = GetJMethod(env, clazz, "sendViewInvalidate", "(IIII)V"); + m_javaGlue->m_updateTextfield = GetJMethod(env, clazz, "updateTextfield", "(IZLjava/lang/String;I)V"); + m_javaGlue->m_updateTextSelection = GetJMethod(env, clazz, "updateTextSelection", "(IIII)V"); + m_javaGlue->m_clearTextEntry = GetJMethod(env, clazz, "clearTextEntry", "()V"); + m_javaGlue->m_restoreScale = GetJMethod(env, clazz, "restoreScale", "(FF)V"); + m_javaGlue->m_needTouchEvents = GetJMethod(env, clazz, "needTouchEvents", "(Z)V"); + m_javaGlue->m_requestKeyboard = GetJMethod(env, clazz, "requestKeyboard", "(Z)V"); + m_javaGlue->m_requestKeyboardWithSelection = GetJMethod(env, clazz, "requestKeyboardWithSelection", "(IIII)V"); + m_javaGlue->m_exceededDatabaseQuota = GetJMethod(env, clazz, "exceededDatabaseQuota", "(Ljava/lang/String;Ljava/lang/String;JJ)V"); + m_javaGlue->m_reachedMaxAppCacheSize = GetJMethod(env, clazz, "reachedMaxAppCacheSize", "(J)V"); + m_javaGlue->m_populateVisitedLinks = GetJMethod(env, clazz, "populateVisitedLinks", "()V"); + m_javaGlue->m_geolocationPermissionsShowPrompt = GetJMethod(env, clazz, "geolocationPermissionsShowPrompt", "(Ljava/lang/String;)V"); + m_javaGlue->m_geolocationPermissionsHidePrompt = GetJMethod(env, clazz, "geolocationPermissionsHidePrompt", "()V"); + m_javaGlue->m_getDeviceMotionService = GetJMethod(env, clazz, "getDeviceMotionService", "()Landroid/webkit/DeviceMotionService;"); + m_javaGlue->m_getDeviceOrientationService = GetJMethod(env, clazz, "getDeviceOrientationService", "()Landroid/webkit/DeviceOrientationService;"); + m_javaGlue->m_addMessageToConsole = GetJMethod(env, clazz, "addMessageToConsole", "(Ljava/lang/String;ILjava/lang/String;I)V"); + m_javaGlue->m_formDidBlur = GetJMethod(env, clazz, "formDidBlur", "(I)V"); + m_javaGlue->m_getPluginClass = GetJMethod(env, clazz, "getPluginClass", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Class;"); + m_javaGlue->m_showFullScreenPlugin = GetJMethod(env, clazz, "showFullScreenPlugin", "(Landroid/webkit/ViewManager$ChildView;I)V"); + m_javaGlue->m_hideFullScreenPlugin = GetJMethod(env, clazz, "hideFullScreenPlugin", "()V"); + m_javaGlue->m_createSurface = GetJMethod(env, clazz, "createSurface", "(Landroid/view/View;)Landroid/webkit/ViewManager$ChildView;"); + m_javaGlue->m_addSurface = GetJMethod(env, clazz, "addSurface", "(Landroid/view/View;IIII)Landroid/webkit/ViewManager$ChildView;"); + m_javaGlue->m_updateSurface = GetJMethod(env, clazz, "updateSurface", "(Landroid/webkit/ViewManager$ChildView;IIII)V"); + m_javaGlue->m_destroySurface = GetJMethod(env, clazz, "destroySurface", "(Landroid/webkit/ViewManager$ChildView;)V"); + m_javaGlue->m_getContext = GetJMethod(env, clazz, "getContext", "()Landroid/content/Context;"); + m_javaGlue->m_keepScreenOn = GetJMethod(env, clazz, "keepScreenOn", "(Z)V"); + m_javaGlue->m_sendFindAgain = GetJMethod(env, clazz, "sendFindAgain", "()V"); + m_javaGlue->m_showRect = GetJMethod(env, clazz, "showRect", "(IIIIIIFFFF)V"); + m_javaGlue->m_centerFitRect = GetJMethod(env, clazz, "centerFitRect", "(IIII)V"); + m_javaGlue->m_setScrollbarModes = GetJMethod(env, clazz, "setScrollbarModes", "(II)V"); + m_javaGlue->m_setInstallableWebApp = GetJMethod(env, clazz, "setInstallableWebApp", "()V"); +#if ENABLE(VIDEO) + m_javaGlue->m_enterFullscreenForVideoLayer = GetJMethod(env, clazz, "enterFullscreenForVideoLayer", "(ILjava/lang/String;)V"); +#endif + m_javaGlue->m_setWebTextViewAutoFillable = GetJMethod(env, clazz, "setWebTextViewAutoFillable", "(ILjava/lang/String;)V"); + m_javaGlue->m_selectAt = GetJMethod(env, clazz, "selectAt", "(II)V"); + env->DeleteLocalRef(clazz); + + env->SetIntField(javaWebViewCore, gWebViewCoreFields.m_nativeClass, (jint)this); + + m_scrollOffsetX = m_scrollOffsetY = 0; + + PageGroup::setShouldTrackVisitedLinks(true); + + reset(true); + + MemoryUsage::setLowMemoryUsageMb(env->GetIntField(javaWebViewCore, gWebViewCoreFields.m_lowMemoryUsageMb)); + MemoryUsage::setHighMemoryUsageMb(env->GetIntField(javaWebViewCore, gWebViewCoreFields.m_highMemoryUsageMb)); + MemoryUsage::setHighUsageDeltaMb(env->GetIntField(javaWebViewCore, gWebViewCoreFields.m_highUsageDeltaMb)); + + WebViewCore::addInstance(this); + +#if USE(CHROME_NETWORK_STACK) + AndroidNetworkLibraryImpl::InitWithApplicationContext(env, 0); +#endif +} + +WebViewCore::~WebViewCore() +{ + WebViewCore::removeInstance(this); + + // Release the focused view + Release(m_popupReply); + + if (m_javaGlue->m_obj) { + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->DeleteWeakGlobalRef(m_javaGlue->m_obj); + m_javaGlue->m_obj = 0; + } + delete m_javaGlue; + delete m_frameCacheKit; + delete m_navPictureKit; +} + +WebViewCore* WebViewCore::getWebViewCore(const WebCore::FrameView* view) +{ + return getWebViewCore(static_cast<const WebCore::ScrollView*>(view)); +} + +WebViewCore* WebViewCore::getWebViewCore(const WebCore::ScrollView* view) +{ + if (!view) + return 0; + + WebFrameView* webFrameView = static_cast<WebFrameView*>(view->platformWidget()); + if (!webFrameView) + return 0; + return webFrameView->webViewCore(); +} + +void WebViewCore::reset(bool fromConstructor) +{ + DBG_SET_LOG(""); + if (fromConstructor) { + m_frameCacheKit = 0; + m_navPictureKit = 0; + } else { + gFrameCacheMutex.lock(); + delete m_frameCacheKit; + delete m_navPictureKit; + m_frameCacheKit = 0; + m_navPictureKit = 0; + gFrameCacheMutex.unlock(); + } + + m_lastFocused = 0; + m_blurringNodePointer = 0; + m_lastFocusedBounds = WebCore::IntRect(0,0,0,0); + m_focusBoundsChanged = false; + m_lastFocusedSelStart = 0; + m_lastFocusedSelEnd = 0; + clearContent(); + m_updatedFrameCache = true; + m_frameCacheOutOfDate = true; + m_skipContentDraw = false; + m_findIsUp = false; + m_domtree_version = 0; + m_check_domtree_version = true; + m_progressDone = false; + m_hasCursorBounds = false; + + m_scrollOffsetX = 0; + m_scrollOffsetY = 0; + m_screenWidth = 0; + m_screenHeight = 0; + m_groupForVisitedLinks = 0; + m_currentNodeDomNavigationAxis = 0; +} + +static bool layoutIfNeededRecursive(WebCore::Frame* f) +{ + if (!f) + return true; + + WebCore::FrameView* v = f->view(); + if (!v) + return true; + + if (v->needsLayout()) + v->layout(f->tree()->parent()); + + WebCore::Frame* child = f->tree()->firstChild(); + bool success = true; + while (child) { + success &= layoutIfNeededRecursive(child); + child = child->tree()->nextSibling(); + } + + return success && !v->needsLayout(); +} + +CacheBuilder& WebViewCore::cacheBuilder() +{ + return FrameLoaderClientAndroid::get(m_mainFrame)->getCacheBuilder(); +} + +WebCore::Node* WebViewCore::currentFocus() +{ + return cacheBuilder().currentFocus(); +} + +void WebViewCore::recordPicture(SkPicture* picture) +{ + // if there is no document yet, just return + if (!m_mainFrame->document()) { + DBG_NAV_LOG("no document"); + return; + } + // Call layout to ensure that the contentWidth and contentHeight are correct + if (!layoutIfNeededRecursive(m_mainFrame)) { + DBG_NAV_LOG("layout failed"); + return; + } + // draw into the picture's recording canvas + WebCore::FrameView* view = m_mainFrame->view(); + DBG_NAV_LOGD("view=(w=%d,h=%d)", view->contentsWidth(), + view->contentsHeight()); + SkAutoPictureRecord arp(picture, view->contentsWidth(), + view->contentsHeight(), PICT_RECORD_FLAGS); + SkAutoMemoryUsageProbe mup(__FUNCTION__); + + // Copy m_buttons so we can pass it to our graphics context. + gButtonMutex.lock(); + WTF::Vector<Container> buttons(m_buttons); + gButtonMutex.unlock(); + + WebCore::PlatformGraphicsContext pgc(arp.getRecordingCanvas(), &buttons); + WebCore::GraphicsContext gc(&pgc); + view->platformWidget()->draw(&gc, WebCore::IntRect(0, 0, + view->contentsWidth(), view->contentsHeight())); + + gButtonMutex.lock(); + updateButtonList(&buttons); + gButtonMutex.unlock(); +} + +void WebViewCore::recordPictureSet(PictureSet* content) +{ + // if there is no document yet, just return + if (!m_mainFrame->document()) { + DBG_SET_LOG("!m_mainFrame->document()"); + return; + } + // If there is a pending style recalculation, just return. + if (m_mainFrame->document()->isPendingStyleRecalc()) { + LOGW("recordPictureSet: pending style recalc, ignoring."); + return; + } + if (m_addInval.isEmpty()) { + DBG_SET_LOG("m_addInval.isEmpty()"); + return; + } + // Call layout to ensure that the contentWidth and contentHeight are correct + // it's fine for layout to gather invalidates, but defeat sending a message + // back to java to call webkitDraw, since we're already in the middle of + // doing that + m_skipContentDraw = true; + bool success = layoutIfNeededRecursive(m_mainFrame); + m_skipContentDraw = false; + + // We may be mid-layout and thus cannot draw. + if (!success) + return; + + { // collect WebViewCoreRecordTimeCounter after layoutIfNeededRecursive +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreRecordTimeCounter); +#endif + + // if the webkit page dimensions changed, discard the pictureset and redraw. + WebCore::FrameView* view = m_mainFrame->view(); + int width = view->contentsWidth(); + int height = view->contentsHeight(); + + // Use the contents width and height as a starting point. + SkIRect contentRect; + contentRect.set(0, 0, width, height); + SkIRect total(contentRect); + + // Traverse all the frames and add their sizes if they are in the visible + // rectangle. + for (WebCore::Frame* frame = m_mainFrame->tree()->traverseNext(); frame; + frame = frame->tree()->traverseNext()) { + // If the frame doesn't have an owner then it is the top frame and the + // view size is the frame size. + WebCore::RenderPart* owner = frame->ownerRenderer(); + if (owner && owner->style()->visibility() == VISIBLE) { + int x = owner->x(); + int y = owner->y(); + + // Traverse the tree up to the parent to find the absolute position + // of this frame. + WebCore::Frame* parent = frame->tree()->parent(); + while (parent) { + WebCore::RenderPart* parentOwner = parent->ownerRenderer(); + if (parentOwner) { + x += parentOwner->x(); + y += parentOwner->y(); + } + parent = parent->tree()->parent(); + } + // Use the owner dimensions so that padding and border are + // included. + int right = x + owner->width(); + int bottom = y + owner->height(); + SkIRect frameRect = {x, y, right, bottom}; + // Ignore a width or height that is smaller than 1. Some iframes + // have small dimensions in order to be hidden. The iframe + // expansion code does not expand in that case so we should ignore + // them here. + if (frameRect.width() > 1 && frameRect.height() > 1 + && SkIRect::Intersects(total, frameRect)) + total.join(x, y, right, bottom); + } + } + + // If the new total is larger than the content, resize the view to include + // all the content. + if (!contentRect.contains(total)) { + // Resize the view to change the overflow clip. + view->resize(total.fRight, total.fBottom); + + // We have to force a layout in order for the clip to change. + m_mainFrame->contentRenderer()->setNeedsLayoutAndPrefWidthsRecalc(); + view->forceLayout(); + + // Relayout similar to above + m_skipContentDraw = true; + bool success = layoutIfNeededRecursive(m_mainFrame); + m_skipContentDraw = false; + if (!success) + return; + + // Set the computed content width + width = view->contentsWidth(); + height = view->contentsHeight(); + } + + if (cacheBuilder().pictureSetDisabled()) + content->clear(); + + content->checkDimensions(width, height, &m_addInval); + + // The inval region may replace existing pictures. The existing pictures + // may have already been split into pieces. If reuseSubdivided() returns + // true, the split pieces are the last entries in the picture already. They + // are marked as invalid, and are rebuilt by rebuildPictureSet(). + + // If the new region doesn't match a set of split pieces, add it to the end. + if (!content->reuseSubdivided(m_addInval)) { + const SkIRect& inval = m_addInval.getBounds(); + SkPicture* picture = rebuildPicture(inval); + DBG_SET_LOGD("{%d,%d,w=%d,h=%d}", inval.fLeft, + inval.fTop, inval.width(), inval.height()); + content->add(m_addInval, picture, 0, false); + SkSafeUnref(picture); + } + // Remove any pictures already in the set that are obscured by the new one, + // and check to see if any already split pieces need to be redrawn. + if (content->build()) + rebuildPictureSet(content); + } // WebViewCoreRecordTimeCounter + WebCore::Node* oldFocusNode = currentFocus(); + m_frameCacheOutOfDate = true; + WebCore::IntRect oldBounds; + int oldSelStart = 0; + int oldSelEnd = 0; + if (oldFocusNode) { + oldBounds = oldFocusNode->getRect(); + RenderObject* renderer = oldFocusNode->renderer(); + if (renderer && (renderer->isTextArea() || renderer->isTextField())) { + WebCore::RenderTextControl* rtc = + static_cast<WebCore::RenderTextControl*>(renderer); + oldSelStart = rtc->selectionStart(); + oldSelEnd = rtc->selectionEnd(); + } + } else + oldBounds = WebCore::IntRect(0,0,0,0); + unsigned latestVersion = 0; + if (m_check_domtree_version) { + // as domTreeVersion only increment, we can just check the sum to see + // whether we need to update the frame cache + for (Frame* frame = m_mainFrame; frame; frame = frame->tree()->traverseNext()) { + const Document* doc = frame->document(); + latestVersion += doc->domTreeVersion() + doc->styleVersion(); + } + } + DBG_NAV_LOGD("m_lastFocused=%p oldFocusNode=%p" + " m_lastFocusedBounds={%d,%d,%d,%d} oldBounds={%d,%d,%d,%d}" + " m_lastFocusedSelection={%d,%d} oldSelection={%d,%d}" + " m_check_domtree_version=%s latestVersion=%d m_domtree_version=%d", + m_lastFocused, oldFocusNode, + m_lastFocusedBounds.x(), m_lastFocusedBounds.y(), + m_lastFocusedBounds.width(), m_lastFocusedBounds.height(), + oldBounds.x(), oldBounds.y(), oldBounds.width(), oldBounds.height(), + m_lastFocusedSelStart, m_lastFocusedSelEnd, oldSelStart, oldSelEnd, + m_check_domtree_version ? "true" : "false", + latestVersion, m_domtree_version); + if (m_lastFocused == oldFocusNode && m_lastFocusedBounds == oldBounds + && m_lastFocusedSelStart == oldSelStart + && m_lastFocusedSelEnd == oldSelEnd + && !m_findIsUp + && (!m_check_domtree_version || latestVersion == m_domtree_version)) + { + return; + } + m_focusBoundsChanged |= m_lastFocused == oldFocusNode + && m_lastFocusedBounds != oldBounds; + m_lastFocused = oldFocusNode; + m_lastFocusedBounds = oldBounds; + m_lastFocusedSelStart = oldSelStart; + m_lastFocusedSelEnd = oldSelEnd; + m_domtree_version = latestVersion; + DBG_NAV_LOG("call updateFrameCache"); + updateFrameCache(); + if (m_findIsUp) { + LOG_ASSERT(m_javaGlue->m_obj, + "A Java widget was not associated with this view bridge!"); + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue->object(env).get(), + m_javaGlue->m_sendFindAgain); + checkException(env); + } +} + +void WebViewCore::updateButtonList(WTF::Vector<Container>* buttons) +{ + // All the entries in buttons are either updates of previous entries in + // m_buttons or they need to be added to it. + Container* end = buttons->end(); + for (Container* updatedContainer = buttons->begin(); + updatedContainer != end; updatedContainer++) { + bool updated = false; + // Search for a previous entry that references the same node as our new + // data + Container* lastPossibleMatch = m_buttons.end(); + for (Container* possibleMatch = m_buttons.begin(); + possibleMatch != lastPossibleMatch; possibleMatch++) { + if (updatedContainer->matches(possibleMatch->node())) { + // Update our record, and skip to the next one. + possibleMatch->setRect(updatedContainer->rect()); + updated = true; + break; + } + } + if (!updated) { + // This is a brand new button, so append it to m_buttons + m_buttons.append(*updatedContainer); + } + } + size_t i = 0; + // count will decrease each time one is removed, so check count each time. + while (i < m_buttons.size()) { + if (m_buttons[i].canBeRemoved()) { + m_buttons[i] = m_buttons.last(); + m_buttons.removeLast(); + } else { + i++; + } + } +} + +// note: updateCursorBounds is called directly by the WebView thread +// This needs to be called each time we call CachedRoot::setCursor() with +// non-null CachedNode/CachedFrame, since otherwise the WebViewCore's data +// about the cursor is incorrect. When we call setCursor(0,0), we need +// to set hasCursorBounds to false. +void WebViewCore::updateCursorBounds(const CachedRoot* root, + const CachedFrame* cachedFrame, const CachedNode* cachedNode) +{ + LOG_ASSERT(root, "updateCursorBounds: root cannot be null"); + LOG_ASSERT(cachedNode, "updateCursorBounds: cachedNode cannot be null"); + LOG_ASSERT(cachedFrame, "updateCursorBounds: cachedFrame cannot be null"); + gCursorBoundsMutex.lock(); + m_hasCursorBounds = !cachedNode->isHidden(); + // If m_hasCursorBounds is false, we never look at the other + // values, so do not bother setting them. + if (m_hasCursorBounds) { + WebCore::IntRect bounds = cachedNode->bounds(cachedFrame); + if (m_cursorBounds != bounds) + DBG_NAV_LOGD("new cursor bounds=(%d,%d,w=%d,h=%d)", + bounds.x(), bounds.y(), bounds.width(), bounds.height()); + m_cursorBounds = bounds; + m_cursorHitBounds = cachedNode->hitBounds(cachedFrame); + m_cursorFrame = cachedFrame->framePointer(); + root->getSimulatedMousePosition(&m_cursorLocation); + m_cursorNode = cachedNode->nodePointer(); + } + gCursorBoundsMutex.unlock(); +} + +void WebViewCore::clearContent() +{ + DBG_SET_LOG(""); + m_content.clear(); + m_addInval.setEmpty(); + m_rebuildInval.setEmpty(); +} + +bool WebViewCore::focusBoundsChanged() +{ + bool result = m_focusBoundsChanged; + m_focusBoundsChanged = false; + return result; +} + +SkPicture* WebViewCore::rebuildPicture(const SkIRect& inval) +{ + WebCore::FrameView* view = m_mainFrame->view(); + int width = view->contentsWidth(); + int height = view->contentsHeight(); + SkPicture* picture = new SkPicture(); + SkAutoPictureRecord arp(picture, width, height, PICT_RECORD_FLAGS); + SkAutoMemoryUsageProbe mup(__FUNCTION__); + SkCanvas* recordingCanvas = arp.getRecordingCanvas(); + + gButtonMutex.lock(); + WTF::Vector<Container> buttons(m_buttons); + gButtonMutex.unlock(); + + WebCore::PlatformGraphicsContext pgc(recordingCanvas, &buttons); + WebCore::GraphicsContext gc(&pgc); + recordingCanvas->translate(-inval.fLeft, -inval.fTop); + recordingCanvas->save(); + view->platformWidget()->draw(&gc, WebCore::IntRect(inval.fLeft, + inval.fTop, inval.width(), inval.height())); + m_rebuildInval.op(inval, SkRegion::kUnion_Op); + DBG_SET_LOGD("m_rebuildInval={%d,%d,r=%d,b=%d}", + m_rebuildInval.getBounds().fLeft, m_rebuildInval.getBounds().fTop, + m_rebuildInval.getBounds().fRight, m_rebuildInval.getBounds().fBottom); + + gButtonMutex.lock(); + updateButtonList(&buttons); + gButtonMutex.unlock(); + + return picture; +} + +void WebViewCore::rebuildPictureSet(PictureSet* pictureSet) +{ + WebCore::FrameView* view = m_mainFrame->view(); + size_t size = pictureSet->size(); + for (size_t index = 0; index < size; index++) { + if (pictureSet->upToDate(index)) + continue; + const SkIRect& inval = pictureSet->bounds(index); + DBG_SET_LOGD("pictSet=%p [%d] {%d,%d,w=%d,h=%d}", pictureSet, index, + inval.fLeft, inval.fTop, inval.width(), inval.height()); + pictureSet->setPicture(index, rebuildPicture(inval)); + } + pictureSet->validate(__FUNCTION__); +} + +BaseLayerAndroid* WebViewCore::createBaseLayer() +{ + BaseLayerAndroid* base = new BaseLayerAndroid(); + base->setContent(m_content); + + bool layoutSucceeded = layoutIfNeededRecursive(m_mainFrame); + // Layout only fails if called during a layout. + LOG_ASSERT(layoutSucceeded, "Can never be called recursively"); + +#if USE(ACCELERATED_COMPOSITING) + // We set the background color + if (m_mainFrame && m_mainFrame->document() + && m_mainFrame->document()->body()) { + Document* document = m_mainFrame->document(); + RefPtr<RenderStyle> style = document->styleForElementIgnoringPendingStylesheets(document->body()); + if (style->hasBackground()) { + Color color = style->visitedDependentColor(CSSPropertyBackgroundColor); + if (color.isValid() && color.alpha() > 0) + base->setBackgroundColor(color); + } + } + + // We update the layers + ChromeClientAndroid* chromeC = static_cast<ChromeClientAndroid*>(m_mainFrame->page()->chrome()->client()); + GraphicsLayerAndroid* root = static_cast<GraphicsLayerAndroid*>(chromeC->layersSync()); + if (root) { + root->notifyClientAnimationStarted(); + LayerAndroid* copyLayer = new LayerAndroid(*root->contentLayer()); + base->addChild(copyLayer); + copyLayer->unref(); + } +#endif + + return base; +} + +BaseLayerAndroid* WebViewCore::recordContent(SkRegion* region, SkIPoint* point) +{ + DBG_SET_LOG("start"); + float progress = (float) m_mainFrame->page()->progress()->estimatedProgress(); + m_progressDone = progress <= 0.0f || progress >= 1.0f; + recordPictureSet(&m_content); + if (!m_progressDone && m_content.isEmpty()) { + DBG_SET_LOGD("empty (progress=%g)", progress); + return 0; + } + region->set(m_addInval); + m_addInval.setEmpty(); + region->op(m_rebuildInval, SkRegion::kUnion_Op); + m_rebuildInval.setEmpty(); + point->fX = m_content.width(); + point->fY = m_content.height(); + DBG_SET_LOGD("region={%d,%d,r=%d,b=%d}", region->getBounds().fLeft, + region->getBounds().fTop, region->getBounds().fRight, + region->getBounds().fBottom); + DBG_SET_LOG("end"); + + return createBaseLayer(); +} + +void WebViewCore::splitContent(PictureSet* content) +{ + bool layoutSucceeded = layoutIfNeededRecursive(m_mainFrame); + LOG_ASSERT(layoutSucceeded, "Can never be called recursively"); + content->split(&m_content); + rebuildPictureSet(&m_content); + content->set(m_content); +} + +void WebViewCore::scrollTo(int x, int y, bool animate) +{ + LOG_ASSERT(m_javaGlue->m_obj, "A Java widget was not associated with this view bridge!"); + +// LOGD("WebViewCore::scrollTo(%d %d)\n", x, y); + + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue->object(env).get(), m_javaGlue->m_scrollTo, + x, y, animate, false); + checkException(env); +} + +void WebViewCore::sendNotifyProgressFinished() +{ + LOG_ASSERT(m_javaGlue->m_obj, "A Java widget was not associated with this view bridge!"); + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue->object(env).get(), m_javaGlue->m_sendNotifyProgressFinished); + checkException(env); +} + +void WebViewCore::viewInvalidate(const WebCore::IntRect& rect) +{ + LOG_ASSERT(m_javaGlue->m_obj, "A Java widget was not associated with this view bridge!"); + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue->object(env).get(), + m_javaGlue->m_sendViewInvalidate, + rect.x(), rect.y(), rect.right(), rect.bottom()); + checkException(env); +} + +void WebViewCore::contentDraw() +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue->object(env).get(), m_javaGlue->m_contentDraw); + checkException(env); +} + +void WebViewCore::layersDraw() +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue->object(env).get(), m_javaGlue->m_layersDraw); + checkException(env); +} + +void WebViewCore::contentInvalidate(const WebCore::IntRect &r) +{ + DBG_SET_LOGD("rect={%d,%d,w=%d,h=%d}", r.x(), r.y(), r.width(), r.height()); + SkIRect rect(r); + if (!rect.intersect(0, 0, INT_MAX, INT_MAX)) + return; + m_addInval.op(rect, SkRegion::kUnion_Op); + DBG_SET_LOGD("m_addInval={%d,%d,r=%d,b=%d}", + m_addInval.getBounds().fLeft, m_addInval.getBounds().fTop, + m_addInval.getBounds().fRight, m_addInval.getBounds().fBottom); + if (!m_skipContentDraw) + contentDraw(); +} + +void WebViewCore::contentInvalidateAll() +{ + WebCore::FrameView* view = m_mainFrame->view(); + contentInvalidate(WebCore::IntRect(0, 0, + view->contentsWidth(), view->contentsHeight())); +} + +void WebViewCore::offInvalidate(const WebCore::IntRect &r) +{ + // FIXME: these invalidates are offscreen, and can be throttled or + // deferred until the area is visible. For now, treat them as + // regular invals so that drawing happens (inefficiently) for now. + contentInvalidate(r); +} + +static int pin_pos(int x, int width, int targetWidth) +{ + if (x + width > targetWidth) + x = targetWidth - width; + if (x < 0) + x = 0; + return x; +} + +void WebViewCore::didFirstLayout() +{ + DEBUG_NAV_UI_LOGD("%s", __FUNCTION__); + LOG_ASSERT(m_javaGlue->m_obj, "A Java widget was not associated with this view bridge!"); + + WebCore::FrameLoader* loader = m_mainFrame->loader(); + const WebCore::KURL& url = loader->url(); + if (url.isEmpty()) + return; + LOGV("::WebCore:: didFirstLayout %s", url.string().ascii().data()); + + WebCore::FrameLoadType loadType = loader->loadType(); + + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue->object(env).get(), m_javaGlue->m_didFirstLayout, + loadType == WebCore::FrameLoadTypeStandard + // When redirect with locked history, we would like to reset the + // scale factor. This is important for www.yahoo.com as it is + // redirected to www.yahoo.com/?rs=1 on load. + || loadType == WebCore::FrameLoadTypeRedirectWithLockedBackForwardList); + checkException(env); + + DBG_NAV_LOG("call updateFrameCache"); + m_check_domtree_version = false; + updateFrameCache(); + m_history.setDidFirstLayout(true); +} + +void WebViewCore::updateViewport() +{ + DEBUG_NAV_UI_LOGD("%s", __FUNCTION__); + LOG_ASSERT(m_javaGlue->m_obj, "A Java widget was not associated with this view bridge!"); + + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue->object(env).get(), m_javaGlue->m_updateViewport); + checkException(env); +} + +void WebViewCore::restoreScale(float scale, float textWrapScale) +{ + DEBUG_NAV_UI_LOGD("%s", __FUNCTION__); + LOG_ASSERT(m_javaGlue->m_obj, "A Java widget was not associated with this view bridge!"); + + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue->object(env).get(), m_javaGlue->m_restoreScale, scale, textWrapScale); + checkException(env); +} + +void WebViewCore::needTouchEvents(bool need) +{ + DEBUG_NAV_UI_LOGD("%s", __FUNCTION__); + LOG_ASSERT(m_javaGlue->m_obj, "A Java widget was not associated with this view bridge!"); + +#if ENABLE(TOUCH_EVENTS) + if (m_forwardingTouchEvents == need) + return; + + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue->object(env).get(), m_javaGlue->m_needTouchEvents, need); + checkException(env); + + m_forwardingTouchEvents = need; +#endif +} + +void WebViewCore::requestKeyboardWithSelection(const WebCore::Node* node, + int selStart, int selEnd) +{ + DEBUG_NAV_UI_LOGD("%s", __FUNCTION__); + LOG_ASSERT(m_javaGlue->m_obj, "A Java widget was not associated with this view bridge!"); + + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue->object(env).get(), + m_javaGlue->m_requestKeyboardWithSelection, + reinterpret_cast<int>(node), selStart, selEnd, m_textGeneration); + checkException(env); +} + +void WebViewCore::requestKeyboard(bool showKeyboard) +{ + DEBUG_NAV_UI_LOGD("%s", __FUNCTION__); + LOG_ASSERT(m_javaGlue->m_obj, "A Java widget was not associated with this view bridge!"); + + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue->object(env).get(), + m_javaGlue->m_requestKeyboard, showKeyboard); + checkException(env); +} + +void WebViewCore::notifyProgressFinished() +{ + m_check_domtree_version = true; + sendNotifyProgressFinished(); +} + +void WebViewCore::doMaxScroll(CacheBuilder::Direction dir) +{ + int dx = 0, dy = 0; + + switch (dir) { + case CacheBuilder::LEFT: + dx = -m_maxXScroll; + break; + case CacheBuilder::UP: + dy = -m_maxYScroll; + break; + case CacheBuilder::RIGHT: + dx = m_maxXScroll; + break; + case CacheBuilder::DOWN: + dy = m_maxYScroll; + break; + case CacheBuilder::UNINITIALIZED: + default: + LOG_ASSERT(0, "unexpected focus selector"); + } + WebCore::FrameView* view = m_mainFrame->view(); + this->scrollTo(view->scrollX() + dx, view->scrollY() + dy, true); +} + +void WebViewCore::setScrollOffset(int moveGeneration, bool sendScrollEvent, int dx, int dy) +{ + DBG_NAV_LOGD("{%d,%d} m_scrollOffset=(%d,%d), sendScrollEvent=%d", dx, dy, + m_scrollOffsetX, m_scrollOffsetY, sendScrollEvent); + if (m_scrollOffsetX != dx || m_scrollOffsetY != dy) { + m_scrollOffsetX = dx; + m_scrollOffsetY = dy; + // The visible rect is located within our coordinate space so it + // contains the actual scroll position. Setting the location makes hit + // testing work correctly. + m_mainFrame->view()->platformWidget()->setLocation(m_scrollOffsetX, + m_scrollOffsetY); + if (sendScrollEvent) { + m_mainFrame->eventHandler()->sendScrollEvent(); + + // Only update history position if it's user scrolled. + // Update history item to reflect the new scroll position. + // This also helps save the history information when the browser goes to + // background, so scroll position will be restored if browser gets + // killed while in background. + WebCore::HistoryController* history = m_mainFrame->loader()->history(); + // Because the history item saving could be heavy for large sites and + // scrolling can generate lots of small scroll offset, the following code + // reduces the saving frequency. + static const int MIN_SCROLL_DIFF = 32; + if (history->currentItem()) { + WebCore::IntPoint currentPoint = history->currentItem()->scrollPoint(); + if (std::abs(currentPoint.x() - dx) >= MIN_SCROLL_DIFF || + std::abs(currentPoint.y() - dy) >= MIN_SCROLL_DIFF) { + history->saveScrollPositionAndViewStateToItem(history->currentItem()); + } + } + } + + // update the currently visible screen + sendPluginVisibleScreen(); + } + gCursorBoundsMutex.lock(); + bool hasCursorBounds = m_hasCursorBounds; + Frame* frame = (Frame*) m_cursorFrame; + IntPoint location = m_cursorLocation; + gCursorBoundsMutex.unlock(); + if (!hasCursorBounds) + return; + moveMouseIfLatest(moveGeneration, frame, location.x(), location.y()); +} + +void WebViewCore::setGlobalBounds(int x, int y, int h, int v) +{ + DBG_NAV_LOGD("{%d,%d}", x, y); + m_mainFrame->view()->platformWidget()->setWindowBounds(x, y, h, v); +} + +void WebViewCore::setSizeScreenWidthAndScale(int width, int height, + int textWrapWidth, float scale, int screenWidth, int screenHeight, + int anchorX, int anchorY, bool ignoreHeight) +{ + WebCoreViewBridge* window = m_mainFrame->view()->platformWidget(); + int ow = window->width(); + int oh = window->height(); + int osw = m_screenWidth; + int osh = m_screenHeight; + int otw = m_textWrapWidth; + float oldScale = m_scale; + DBG_NAV_LOGD("old:(w=%d,h=%d,sw=%d,scale=%g) new:(w=%d,h=%d,sw=%d,scale=%g)", + ow, oh, osw, m_scale, width, height, screenWidth, scale); + m_screenWidth = screenWidth; + m_screenHeight = screenHeight; + m_textWrapWidth = textWrapWidth; + if (scale >= 0) // negative means keep the current scale + m_scale = scale; + m_maxXScroll = screenWidth >> 2; + m_maxYScroll = m_maxXScroll * height / width; + // Don't reflow if the diff is small. + const bool reflow = otw && textWrapWidth && + ((float) abs(otw - textWrapWidth) / textWrapWidth) >= 0.01f; + + // When the screen size change, fixed positioned element should be updated. + // This is supposed to be light weighted operation without a full layout. + if (osh != screenHeight || osw != screenWidth) + m_mainFrame->view()->updatePositionedObjects(); + + if (ow != width || (!ignoreHeight && oh != height) || reflow) { + WebCore::RenderObject *r = m_mainFrame->contentRenderer(); + DBG_NAV_LOGD("renderer=%p view=(w=%d,h=%d)", r, + screenWidth, screenHeight); + if (r) { + WebCore::IntPoint anchorPoint = WebCore::IntPoint(anchorX, anchorY); + DBG_NAV_LOGD("anchorX=%d anchorY=%d", anchorX, anchorY); + RefPtr<WebCore::Node> node; + WebCore::IntRect bounds; + WebCore::IntPoint offset; + // If the text wrap changed, it is probably zoom change or + // orientation change. Try to keep the anchor at the same place. + if (otw && textWrapWidth && otw != textWrapWidth && + (anchorX != 0 || anchorY != 0)) { + WebCore::HitTestResult hitTestResult = + m_mainFrame->eventHandler()->hitTestResultAtPoint( + anchorPoint, false); + node = hitTestResult.innerNode(); + } + if (node) { + bounds = node->getRect(); + DBG_NAV_LOGD("ob:(x=%d,y=%d,w=%d,h=%d)", + bounds.x(), bounds.y(), bounds.width(), bounds.height()); + // sites like nytimes.com insert a non-standard tag <nyt_text> + // in the html. If it is the HitTestResult, it may have zero + // width and height. In this case, use its parent node. + if (bounds.width() == 0) { + node = node->parentOrHostNode(); + if (node) { + bounds = node->getRect(); + DBG_NAV_LOGD("found a zero width node and use its parent, whose ob:(x=%d,y=%d,w=%d,h=%d)", + bounds.x(), bounds.y(), bounds.width(), bounds.height()); + } + } + } + + // Set the size after finding the old anchor point as + // hitTestResultAtPoint causes a layout. + window->setSize(width, height); + window->setVisibleSize(screenWidth, screenHeight); + if (width != screenWidth) { + m_mainFrame->view()->setUseFixedLayout(true); + m_mainFrame->view()->setFixedLayoutSize(IntSize(width, height)); + } else { + m_mainFrame->view()->setUseFixedLayout(false); + } + r->setNeedsLayoutAndPrefWidthsRecalc(); + m_mainFrame->view()->forceLayout(); + + // scroll to restore current screen center + if (node) { + const WebCore::IntRect& newBounds = node->getRect(); + DBG_NAV_LOGD("nb:(x=%d,y=%d,w=%d," + "h=%d)", newBounds.x(), newBounds.y(), + newBounds.width(), newBounds.height()); + if ((osw && osh && bounds.width() && bounds.height()) + && (bounds != newBounds)) { + WebCore::FrameView* view = m_mainFrame->view(); + // force left align if width is not changed while height changed. + // the anchorPoint is probably at some white space in the node + // which is affected by text wrap around the screen width. + const bool leftAlign = (otw != textWrapWidth) + && (bounds.width() == newBounds.width()) + && (bounds.height() != newBounds.height()); + const float xPercentInDoc = + leftAlign ? 0.0 : (float) (anchorX - bounds.x()) / bounds.width(); + const float xPercentInView = + leftAlign ? 0.0 : (float) (anchorX - m_scrollOffsetX) / osw; + const float yPercentInDoc = (float) (anchorY - bounds.y()) / bounds.height(); + const float yPercentInView = (float) (anchorY - m_scrollOffsetY) / osh; + showRect(newBounds.x(), newBounds.y(), newBounds.width(), + newBounds.height(), view->contentsWidth(), + view->contentsHeight(), + xPercentInDoc, xPercentInView, + yPercentInDoc, yPercentInView); + } + } + } + } else { + window->setSize(width, height); + window->setVisibleSize(screenWidth, screenHeight); + m_mainFrame->view()->resize(width, height); + if (width != screenWidth) { + m_mainFrame->view()->setUseFixedLayout(true); + m_mainFrame->view()->setFixedLayoutSize(IntSize(width, height)); + } else { + m_mainFrame->view()->setUseFixedLayout(false); + } + } + + // update the currently visible screen as perceived by the plugin + sendPluginVisibleScreen(); +} + +void WebViewCore::dumpDomTree(bool useFile) +{ +#ifdef ANDROID_DOM_LOGGING + if (useFile) + gDomTreeFile = fopen(DOM_TREE_LOG_FILE, "w"); + m_mainFrame->document()->showTreeForThis(); + if (gDomTreeFile) { + fclose(gDomTreeFile); + gDomTreeFile = 0; + } +#endif +} + +void WebViewCore::dumpRenderTree(bool useFile) +{ +#ifdef ANDROID_DOM_LOGGING + WTF::CString renderDump = WebCore::externalRepresentation(m_mainFrame).utf8(); + const char* data = renderDump.data(); + if (useFile) { + gRenderTreeFile = fopen(RENDER_TREE_LOG_FILE, "w"); + DUMP_RENDER_LOGD("%s", data); + fclose(gRenderTreeFile); + gRenderTreeFile = 0; + } else { + // adb log can only output 1024 characters, so write out line by line. + // exclude '\n' as adb log adds it for each output. + int length = renderDump.length(); + for (int i = 0, last = 0; i < length; i++) { + if (data[i] == '\n') { + if (i != last) + DUMP_RENDER_LOGD("%.*s", (i - last), &(data[last])); + last = i + 1; + } + } + } +#endif +} + +void WebViewCore::dumpNavTree() +{ +#if DUMP_NAV_CACHE + cacheBuilder().mDebug.print(); +#endif +} + +HTMLElement* WebViewCore::retrieveElement(int x, int y, + const QualifiedName& tagName) +{ + HitTestResult hitTestResult = m_mainFrame->eventHandler() + ->hitTestResultAtPoint(IntPoint(x, y), false, false, + DontHitTestScrollbars, HitTestRequest::Active | HitTestRequest::ReadOnly, + IntSize(1, 1)); + if (!hitTestResult.innerNode() || !hitTestResult.innerNode()->inDocument()) { + LOGE("Should not happen: no in document Node found"); + return 0; + } + const ListHashSet<RefPtr<Node> >& list = hitTestResult.rectBasedTestResult(); + if (list.isEmpty()) { + LOGE("Should not happen: no rect-based-test nodes found"); + return 0; + } + Node* node = hitTestResult.innerNode(); + Node* element = node; + while (element && (!element->isElementNode() + || !element->hasTagName(tagName))) { + element = element->parentNode(); + } + DBG_NAV_LOGD("node=%p element=%p x=%d y=%d nodeName=%s tagName=%s", node, + element, x, y, node->nodeName().utf8().data(), + element ? ((Element*) element)->tagName().utf8().data() : "<none>"); + return static_cast<WebCore::HTMLElement*>(element); +} + +HTMLAnchorElement* WebViewCore::retrieveAnchorElement(int x, int y) +{ + return static_cast<HTMLAnchorElement*> + (retrieveElement(x, y, HTMLNames::aTag)); +} + +HTMLImageElement* WebViewCore::retrieveImageElement(int x, int y) +{ + return static_cast<HTMLImageElement*> + (retrieveElement(x, y, HTMLNames::imgTag)); +} + +WTF::String WebViewCore::retrieveHref(int x, int y) +{ + WebCore::HTMLAnchorElement* anchor = retrieveAnchorElement(x, y); + return anchor ? anchor->href() : WTF::String(); +} + +WTF::String WebViewCore::retrieveAnchorText(int x, int y) +{ + WebCore::HTMLAnchorElement* anchor = retrieveAnchorElement(x, y); + return anchor ? anchor->text() : WTF::String(); +} + +WTF::String WebViewCore::retrieveImageSource(int x, int y) +{ + HTMLImageElement* image = retrieveImageElement(x, y); + return image ? image->src().string() : WTF::String(); +} + +WTF::String WebViewCore::requestLabel(WebCore::Frame* frame, + WebCore::Node* node) +{ + if (node && CacheBuilder::validNode(m_mainFrame, frame, node)) { + RefPtr<WebCore::NodeList> list = node->document()->getElementsByTagName("label"); + unsigned length = list->length(); + for (unsigned i = 0; i < length; i++) { + WebCore::HTMLLabelElement* label = static_cast<WebCore::HTMLLabelElement*>( + list->item(i)); + if (label->control() == node) { + Node* node = label; + String result; + while ((node = node->traverseNextNode(label))) { + if (node->isTextNode()) { + Text* textNode = static_cast<Text*>(node); + result += textNode->dataImpl(); + } + } + return result; + } + } + } + return WTF::String(); +} + +static bool isContentEditable(const WebCore::Node* node) +{ + if (!node) return false; + return node->document()->frame()->selection()->isContentEditable(); +} + +// Returns true if the node is a textfield, textarea, or contentEditable +static bool isTextInput(const WebCore::Node* node) +{ + if (isContentEditable(node)) + return true; + if (!node) + return false; + WebCore::RenderObject* renderer = node->renderer(); + return renderer && (renderer->isTextField() || renderer->isTextArea()); +} + +void WebViewCore::revealSelection() +{ + WebCore::Node* focus = currentFocus(); + if (!focus) + return; + if (!isTextInput(focus)) + return; + WebCore::Frame* focusedFrame = focus->document()->frame(); + if (!focusedFrame->page()->focusController()->isActive()) + return; + focusedFrame->selection()->revealSelection(ScrollAlignment::alignToEdgeIfNeeded); +} + +void WebViewCore::updateCacheOnNodeChange() +{ + gCursorBoundsMutex.lock(); + bool hasCursorBounds = m_hasCursorBounds; + Frame* frame = (Frame*) m_cursorFrame; + Node* node = (Node*) m_cursorNode; + IntRect bounds = m_cursorHitBounds; + gCursorBoundsMutex.unlock(); + if (!hasCursorBounds || !node) + return; + if (CacheBuilder::validNode(m_mainFrame, frame, node)) { + RenderObject* renderer = node->renderer(); + if (renderer && renderer->style()->visibility() != HIDDEN) { + IntRect absBox = renderer->absoluteBoundingBoxRect(); + int globalX, globalY; + CacheBuilder::GetGlobalOffset(frame, &globalX, &globalY); + absBox.move(globalX, globalY); + if (absBox == bounds) + return; + DBG_NAV_LOGD("absBox=(%d,%d,%d,%d) bounds=(%d,%d,%d,%d)", + absBox.x(), absBox.y(), absBox.width(), absBox.height(), + bounds.x(), bounds.y(), bounds.width(), bounds.height()); + } + } + DBG_NAV_LOGD("updateFrameCache node=%p", node); + updateFrameCache(); +} + +void WebViewCore::updateFrameCache() +{ + if (!m_frameCacheOutOfDate) { + DBG_NAV_LOG("!m_frameCacheOutOfDate"); + return; + } + + // If there is a pending style recalculation, do not update the frame cache. + // Until the recalculation is complete, there may be internal objects that + // are in an inconsistent state (such as font pointers). + // In any event, there's not much point to updating the cache while a style + // recalculation is pending, since it will simply have to be updated again + // once the recalculation is complete. + // TODO: Do we need to reschedule an update for after the style is recalculated? + if (m_mainFrame && m_mainFrame->document() && m_mainFrame->document()->isPendingStyleRecalc()) { + LOGW("updateFrameCache: pending style recalc, ignoring."); + return; + } +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreBuildNavTimeCounter); +#endif + m_frameCacheOutOfDate = false; +#if DEBUG_NAV_UI + m_now = SkTime::GetMSecs(); +#endif + m_temp = new CachedRoot(); + m_temp->init(m_mainFrame, &m_history); +#if USE(ACCELERATED_COMPOSITING) + GraphicsLayerAndroid* graphicsLayer = graphicsRootLayer(); + if (graphicsLayer) + m_temp->setRootLayer(graphicsLayer->contentLayer()); +#endif + CacheBuilder& builder = cacheBuilder(); + WebCore::Settings* settings = m_mainFrame->page()->settings(); + builder.allowAllTextDetection(); +#ifdef ANDROID_META_SUPPORT + if (settings) { + if (!settings->formatDetectionAddress()) + builder.disallowAddressDetection(); + if (!settings->formatDetectionEmail()) + builder.disallowEmailDetection(); + if (!settings->formatDetectionTelephone()) + builder.disallowPhoneDetection(); + } +#endif + builder.buildCache(m_temp); + m_tempPict = new SkPicture(); + recordPicture(m_tempPict); + m_temp->setPicture(m_tempPict); + m_temp->setTextGeneration(m_textGeneration); + WebCoreViewBridge* window = m_mainFrame->view()->platformWidget(); + m_temp->setVisibleRect(WebCore::IntRect(m_scrollOffsetX, + m_scrollOffsetY, window->width(), window->height())); + gFrameCacheMutex.lock(); + delete m_frameCacheKit; + delete m_navPictureKit; + m_frameCacheKit = m_temp; + m_navPictureKit = m_tempPict; + m_updatedFrameCache = true; +#if DEBUG_NAV_UI + const CachedNode* cachedFocusNode = m_frameCacheKit->currentFocus(); + DBG_NAV_LOGD("cachedFocusNode=%d (nodePointer=%p)", + cachedFocusNode ? cachedFocusNode->index() : 0, + cachedFocusNode ? cachedFocusNode->nodePointer() : 0); +#endif + gFrameCacheMutex.unlock(); +} + +void WebViewCore::updateFrameCacheIfLoading() +{ + if (!m_check_domtree_version) + updateFrameCache(); +} + +struct TouchNodeData { + Node* mNode; + IntRect mBounds; +}; + +// get the bounding box of the Node +static IntRect getAbsoluteBoundingBox(Node* node) { + IntRect rect; + RenderObject* render = node->renderer(); + if (render->isRenderInline()) + rect = toRenderInline(render)->linesVisualOverflowBoundingBox(); + else if (render->isBox()) + rect = toRenderBox(render)->visualOverflowRect(); + else if (render->isText()) + rect = toRenderText(render)->linesBoundingBox(); + else + LOGE("getAbsoluteBoundingBox failed for node %p, name %s", node, render->renderName()); + FloatPoint absPos = render->localToAbsolute(); + rect.move(absPos.x(), absPos.y()); + return rect; +} + +// get the highlight rectangles for the touch point (x, y) with the slop +Vector<IntRect> WebViewCore::getTouchHighlightRects(int x, int y, int slop) +{ + Vector<IntRect> rects; + m_mousePos = IntPoint(x - m_scrollOffsetX, y - m_scrollOffsetY); + HitTestResult hitTestResult = m_mainFrame->eventHandler()->hitTestResultAtPoint(IntPoint(x, y), + false, false, DontHitTestScrollbars, HitTestRequest::Active | HitTestRequest::ReadOnly, IntSize(slop, slop)); + if (!hitTestResult.innerNode() || !hitTestResult.innerNode()->inDocument()) { + LOGE("Should not happen: no in document Node found"); + return rects; + } + const ListHashSet<RefPtr<Node> >& list = hitTestResult.rectBasedTestResult(); + if (list.isEmpty()) { + LOGE("Should not happen: no rect-based-test nodes found"); + return rects; + } + Frame* frame = hitTestResult.innerNode()->document()->frame(); + Vector<TouchNodeData> nodeDataList; + ListHashSet<RefPtr<Node> >::const_iterator last = list.end(); + for (ListHashSet<RefPtr<Node> >::const_iterator it = list.begin(); it != last; ++it) { + // TODO: it seems reasonable to not search across the frame. Isn't it? + // if the node is not in the same frame as the innerNode, skip it + if (it->get()->document()->frame() != frame) + continue; + // traverse up the tree to find the first node that needs highlight + bool found = false; + Node* eventNode = it->get(); + while (eventNode) { + RenderObject* render = eventNode->renderer(); + if (render->isBody() || render->isRenderView()) + break; + if (eventNode->supportsFocus() + || eventNode->hasEventListeners(eventNames().clickEvent) + || eventNode->hasEventListeners(eventNames().mousedownEvent) + || eventNode->hasEventListeners(eventNames().mouseupEvent)) { + found = true; + break; + } + // the nodes in the rectBasedTestResult() are ordered based on z-index during hit testing. + // so do not search for the eventNode across explicit z-index border. + // TODO: this is a hard one to call. z-index is quite complicated as its value only + // matters when you compare two RenderLayer in the same hierarchy level. e.g. in + // the following example, "b" is on the top as its z level is the highest. even "c" + // has 100 as z-index, it is still below "d" as its parent has the same z-index as + // "d" and logically before "d". Of course "a" is the lowest in the z level. + // + // z-index:auto "a" + // z-index:2 "b" + // z-index:1 + // z-index:100 "c" + // z-index:1 "d" + // + // If the fat point touches everyone, the order in the list should be "b", "d", "c" + // and "a". When we search for the event node for "b", we really don't want "a" as + // in the z-order it is behind everything else. + if (!render->style()->hasAutoZIndex()) + break; + eventNode = eventNode->parentNode(); + } + // didn't find any eventNode, skip it + if (!found) + continue; + // first quick check whether it is a duplicated node before computing bounding box + Vector<TouchNodeData>::const_iterator nlast = nodeDataList.end(); + for (Vector<TouchNodeData>::const_iterator n = nodeDataList.begin(); n != nlast; ++n) { + // found the same node, skip it + if (eventNode == n->mNode) { + found = false; + break; + } + } + if (!found) + continue; + // next check whether the node is fully covered by or fully covering another node. + found = false; + IntRect rect = getAbsoluteBoundingBox(eventNode); + if (rect.isEmpty()) { + // if the node's bounds is empty and it is not a ContainerNode, skip it. + if (!eventNode->isContainerNode()) + continue; + // if the node's children are all positioned objects, its bounds can be empty. + // Walk through the children to find the bounding box. + Node* child = static_cast<const ContainerNode*>(eventNode)->firstChild(); + while (child) { + IntRect childrect; + if (child->renderer()) + childrect = getAbsoluteBoundingBox(child); + if (!childrect.isEmpty()) { + rect.unite(childrect); + child = child->traverseNextSibling(eventNode); + } else + child = child->traverseNextNode(eventNode); + } + } + for (int i = nodeDataList.size() - 1; i >= 0; i--) { + TouchNodeData n = nodeDataList.at(i); + // the new node is enclosing an existing node, skip it + if (rect.contains(n.mBounds)) { + found = true; + break; + } + // the new node is fully inside an existing node, remove the existing node + if (n.mBounds.contains(rect)) + nodeDataList.remove(i); + } + if (!found) { + TouchNodeData newNode; + newNode.mNode = eventNode; + newNode.mBounds = rect; + nodeDataList.append(newNode); + } + } + if (!nodeDataList.size()) + return rects; + // finally select the node with the largest overlap with the fat point + TouchNodeData final; + final.mNode = 0; + IntPoint docPos = frame->view()->windowToContents(m_mousePos); + IntRect testRect(docPos.x() - slop, docPos.y() - slop, 2 * slop + 1, 2 * slop + 1); + int area = 0; + Vector<TouchNodeData>::const_iterator nlast = nodeDataList.end(); + for (Vector<TouchNodeData>::const_iterator n = nodeDataList.begin(); n != nlast; ++n) { + IntRect rect = n->mBounds; + rect.intersect(testRect); + int a = rect.width() * rect.height(); + if (a > area) { + final = *n; + area = a; + } + } + // now get the node's highlight rectangles in the page coordinate system + if (final.mNode) { + IntPoint frameAdjust; + if (frame != m_mainFrame) { + frameAdjust = frame->view()->contentsToWindow(IntPoint()); + frameAdjust.move(m_scrollOffsetX, m_scrollOffsetY); + } + if (final.mNode->isLink()) { + // most of the links are inline instead of box style. So the bounding box is not + // a good representation for the highlights. Get the list of rectangles instead. + RenderObject* render = final.mNode->renderer(); + IntPoint offset = roundedIntPoint(render->localToAbsolute()); + render->absoluteRects(rects, offset.x() + frameAdjust.x(), offset.y() + frameAdjust.y()); + bool inside = false; + int distance = INT_MAX; + int newx = x, newy = y; + int i = rects.size(); + while (i--) { + if (rects[i].isEmpty()) { + rects.remove(i); + continue; + } + // check whether the point (x, y) is inside one of the rectangles. + if (inside) + continue; + if (rects[i].contains(x, y)) { + inside = true; + continue; + } + if (x >= rects[i].x() && x < rects[i].right()) { + if (y < rects[i].y()) { + if (rects[i].y() - y < distance) { + newx = x; + newy = rects[i].y(); + distance = rects[i].y() - y; + } + } else if (y >= rects[i].bottom()) { + if (y - rects[i].bottom() + 1 < distance) { + newx = x; + newy = rects[i].bottom() - 1; + distance = y - rects[i].bottom() + 1; + } + } + } else if (y >= rects[i].y() && y < rects[i].bottom()) { + if (x < rects[i].x()) { + if (rects[i].x() - x < distance) { + newx = rects[i].x(); + newy = y; + distance = rects[i].x() - x; + } + } else if (x >= rects[i].right()) { + if (x - rects[i].right() + 1 < distance) { + newx = rects[i].right() - 1; + newy = y; + distance = x - rects[i].right() + 1; + } + } + } + } + if (!rects.isEmpty()) { + if (!inside) { + // if neither x nor y has overlap, just pick the top/left of the first rectangle + if (newx == x && newy == y) { + newx = rects[0].x(); + newy = rects[0].y(); + } + m_mousePos.setX(newx - m_scrollOffsetX); + m_mousePos.setY(newy - m_scrollOffsetY); + DBG_NAV_LOGD("Move x/y from (%d, %d) to (%d, %d) scrollOffset is (%d, %d)", + x, y, m_mousePos.x() + m_scrollOffsetX, m_mousePos.y() + m_scrollOffsetY, + m_scrollOffsetX, m_scrollOffsetY); + } + return rects; + } + } + IntRect rect = final.mBounds; + rect.move(frameAdjust.x(), frameAdjust.y()); + rects.append(rect); + // adjust m_mousePos if it is not inside the returned highlight rectangle + testRect.move(frameAdjust.x(), frameAdjust.y()); + testRect.intersect(rect); + if (!testRect.contains(x, y)) { + m_mousePos = testRect.center(); + m_mousePos.move(-m_scrollOffsetX, -m_scrollOffsetY); + DBG_NAV_LOGD("Move x/y from (%d, %d) to (%d, %d) scrollOffset is (%d, %d)", + x, y, m_mousePos.x() + m_scrollOffsetX, m_mousePos.y() + m_scrollOffsetY, + m_scrollOffsetX, m_scrollOffsetY); + } + } + return rects; +} + +/////////////////////////////////////////////////////////////////////////////// + +void WebViewCore::addPlugin(PluginWidgetAndroid* w) +{ +// SkDebugf("----------- addPlugin %p", w); + /* The plugin must be appended to the end of the array. This ensures that if + the plugin is added while iterating through the array (e.g. sendEvent(...)) + that the iteration process is not corrupted. + */ + *m_plugins.append() = w; +} + +void WebViewCore::removePlugin(PluginWidgetAndroid* w) +{ +// SkDebugf("----------- removePlugin %p", w); + int index = m_plugins.find(w); + if (index < 0) { + SkDebugf("--------------- pluginwindow not found! %p\n", w); + } else { + m_plugins.removeShuffle(index); + } +} + +bool WebViewCore::isPlugin(PluginWidgetAndroid* w) const +{ + return m_plugins.find(w) >= 0; +} + +void WebViewCore::invalPlugin(PluginWidgetAndroid* w) +{ + const double PLUGIN_INVAL_DELAY = 1.0 / 60; + + if (!m_pluginInvalTimer.isActive()) { + m_pluginInvalTimer.startOneShot(PLUGIN_INVAL_DELAY); + } +} + +void WebViewCore::drawPlugins() +{ + SkRegion inval; // accumualte what needs to be redrawn + PluginWidgetAndroid** iter = m_plugins.begin(); + PluginWidgetAndroid** stop = m_plugins.end(); + + for (; iter < stop; ++iter) { + PluginWidgetAndroid* w = *iter; + SkIRect dirty; + if (w->isDirty(&dirty)) { + w->draw(); + inval.op(dirty, SkRegion::kUnion_Op); + } + } + + if (!inval.isEmpty()) { + // inval.getBounds() is our rectangle + const SkIRect& bounds = inval.getBounds(); + WebCore::IntRect r(bounds.fLeft, bounds.fTop, + bounds.width(), bounds.height()); + this->viewInvalidate(r); + } +} + +void WebViewCore::notifyPluginsOnFrameLoad(const Frame* frame) { + // if frame is the parent then notify all plugins + if (!frame->tree()->parent()) { + // trigger an event notifying the plugins that the page has loaded + ANPEvent event; + SkANP::InitEvent(&event, kLifecycle_ANPEventType); + event.data.lifecycle.action = kOnLoad_ANPLifecycleAction; + sendPluginEvent(event); + // trigger the on/off screen notification if the page was reloaded + sendPluginVisibleScreen(); + } + // else if frame's parent has completed + else if (!frame->tree()->parent()->loader()->isLoading()) { + // send to all plugins who have this frame in their heirarchy + PluginWidgetAndroid** iter = m_plugins.begin(); + PluginWidgetAndroid** stop = m_plugins.end(); + for (; iter < stop; ++iter) { + Frame* currentFrame = (*iter)->pluginView()->parentFrame(); + while (currentFrame) { + if (frame == currentFrame) { + ANPEvent event; + SkANP::InitEvent(&event, kLifecycle_ANPEventType); + event.data.lifecycle.action = kOnLoad_ANPLifecycleAction; + (*iter)->sendEvent(event); + + // trigger the on/off screen notification if the page was reloaded + ANPRectI visibleRect; + getVisibleScreen(visibleRect); + (*iter)->setVisibleScreen(visibleRect, m_scale); + + break; + } + currentFrame = currentFrame->tree()->parent(); + } + } + } +} + +void WebViewCore::getVisibleScreen(ANPRectI& visibleRect) +{ + visibleRect.left = m_scrollOffsetX; + visibleRect.top = m_scrollOffsetY; + visibleRect.right = m_scrollOffsetX + m_screenWidth; + visibleRect.bottom = m_scrollOffsetY + m_screenHeight; +} + +void WebViewCore::sendPluginVisibleScreen() +{ + /* We may want to cache the previous values and only send the notification + to the plugin in the event that one of the values has changed. + */ + + ANPRectI visibleRect; + getVisibleScreen(visibleRect); + + PluginWidgetAndroid** iter = m_plugins.begin(); + PluginWidgetAndroid** stop = m_plugins.end(); + for (; iter < stop; ++iter) { + (*iter)->setVisibleScreen(visibleRect, m_scale); + } +} + +void WebViewCore::sendPluginEvent(const ANPEvent& evt) +{ + /* The list of plugins may be manipulated as we iterate through the list. + This implementation allows for the addition of new plugins during an + iteration, but may fail if a plugin is removed. Currently, there are not + any use cases where a plugin is deleted while processing this loop, but + if it does occur we will have to use an alternate data structure and/or + iteration mechanism. + */ + for (int x = 0; x < m_plugins.count(); x++) { + m_plugins[x]->sendEvent(evt); + } +} + +PluginWidgetAndroid* WebViewCore::getPluginWidget(NPP npp) +{ + PluginWidgetAndroid** iter = m_plugins.begin(); + PluginWidgetAndroid** stop = m_plugins.end(); + for (; iter < stop; ++iter) { + if ((*iter)->pluginView()->instance() == npp) { + return (*iter); + } + } + return 0; +} + +static PluginView* nodeIsPlugin(Node* node) { + RenderObject* renderer = node->renderer(); + if (renderer && renderer->isWidget()) { + Widget* widget = static_cast<RenderWidget*>(renderer)->widget(); + if (widget && widget->isPluginView()) + return static_cast<PluginView*>(widget); + } + return 0; +} + +Node* WebViewCore::cursorNodeIsPlugin() { + gCursorBoundsMutex.lock(); + bool hasCursorBounds = m_hasCursorBounds; + Frame* frame = (Frame*) m_cursorFrame; + Node* node = (Node*) m_cursorNode; + gCursorBoundsMutex.unlock(); + if (hasCursorBounds && CacheBuilder::validNode(m_mainFrame, frame, node) + && nodeIsPlugin(node)) { + return node; + } + return 0; +} + +/////////////////////////////////////////////////////////////////////////////// +void WebViewCore::moveMouseIfLatest(int moveGeneration, + WebCore::Frame* frame, int x, int y) +{ + DBG_NAV_LOGD("m_moveGeneration=%d moveGeneration=%d" + " frame=%p x=%d y=%d", + m_moveGeneration, moveGeneration, frame, x, y); + if (m_moveGeneration > moveGeneration) { + DBG_NAV_LOGD("m_moveGeneration=%d > moveGeneration=%d", + m_moveGeneration, moveGeneration); + return; // short-circuit if a newer move has already been generated + } + m_lastGeneration = moveGeneration; + moveMouse(frame, x, y); +} + +void WebViewCore::moveFocus(WebCore::Frame* frame, WebCore::Node* node) +{ + DBG_NAV_LOGD("frame=%p node=%p", frame, node); + if (!node || !CacheBuilder::validNode(m_mainFrame, frame, node) + || !node->isElementNode()) + return; + // Code borrowed from FocusController::advanceFocus + WebCore::FocusController* focusController + = m_mainFrame->page()->focusController(); + WebCore::Document* oldDoc + = focusController->focusedOrMainFrame()->document(); + if (oldDoc->focusedNode() == node) + return; + if (node->document() != oldDoc) + oldDoc->setFocusedNode(0); + focusController->setFocusedFrame(frame); + static_cast<WebCore::Element*>(node)->focus(false); +} + +// Update mouse position +void WebViewCore::moveMouse(WebCore::Frame* frame, int x, int y) +{ + DBG_NAV_LOGD("frame=%p x=%d y=%d scrollOffset=(%d,%d)", frame, + x, y, m_scrollOffsetX, m_scrollOffsetY); + if (!frame || !CacheBuilder::validNode(m_mainFrame, frame, 0)) + frame = m_mainFrame; + // mouse event expects the position in the window coordinate + m_mousePos = WebCore::IntPoint(x - m_scrollOffsetX, y - m_scrollOffsetY); + // validNode will still return true if the node is null, as long as we have + // a valid frame. Do not want to make a call on frame unless it is valid. + WebCore::PlatformMouseEvent mouseEvent(m_mousePos, m_mousePos, + WebCore::NoButton, WebCore::MouseEventMoved, 1, false, false, false, + false, WTF::currentTime()); + frame->eventHandler()->handleMouseMoveEvent(mouseEvent); + updateCacheOnNodeChange(); +} + +void WebViewCore::setSelection(int start, int end) +{ + WebCore::Node* focus = currentFocus(); + if (!focus) + return; + WebCore::RenderObject* renderer = focus->renderer(); + if (!renderer || (!renderer->isTextField() && !renderer->isTextArea())) + return; + if (start > end) { + int temp = start; + start = end; + end = temp; + } + // Tell our EditorClient that this change was generated from the UI, so it + // does not need to echo it to the UI. + EditorClientAndroid* client = static_cast<EditorClientAndroid*>( + m_mainFrame->editor()->client()); + client->setUiGeneratedSelectionChange(true); + setSelectionRange(focus, start, end); + client->setUiGeneratedSelectionChange(false); + WebCore::Frame* focusedFrame = focus->document()->frame(); + bool isPasswordField = false; + if (focus->isElementNode()) { + WebCore::Element* element = static_cast<WebCore::Element*>(focus); + if (WebCore::InputElement* inputElement = WebCore::toInputElement(element)) + isPasswordField = static_cast<WebCore::HTMLInputElement*>(inputElement)->isPasswordField(); + } + // For password fields, this is done in the UI side via + // bringPointIntoView, since the UI does the drawing. + if (renderer->isTextArea() || !isPasswordField) + revealSelection(); +} + +String WebViewCore::modifySelection(const int direction, const int axis) +{ + DOMSelection* selection = m_mainFrame->domWindow()->getSelection(); + if (selection->rangeCount() > 1) + selection->removeAllRanges(); + switch (axis) { + case AXIS_CHARACTER: + case AXIS_WORD: + case AXIS_SENTENCE: + return modifySelectionTextNavigationAxis(selection, direction, axis); + case AXIS_HEADING: + case AXIS_SIBLING: + case AXIS_PARENT_FIRST_CHILD: + case AXIS_DOCUMENT: + return modifySelectionDomNavigationAxis(selection, direction, axis); + default: + LOGE("Invalid navigation axis: %d", axis); + return String(); + } +} + +void WebViewCore::scrollNodeIntoView(Frame* frame, Node* node) +{ + if (!frame || !node) + return; + + Element* elementNode = 0; + + // If not an Element, find a visible predecessor + // Element to scroll into view. + if (!node->isElementNode()) { + HTMLElement* body = frame->document()->body(); + do { + if (!node || node == body) + return; + node = node->parentNode(); + } while (!node->isElementNode() && !isVisible(node)); + } + + elementNode = static_cast<Element*>(node); + elementNode->scrollIntoViewIfNeeded(true); +} + +String WebViewCore::modifySelectionTextNavigationAxis(DOMSelection* selection, int direction, int axis) +{ + Node* body = m_mainFrame->document()->body(); + + ExceptionCode ec = 0; + String markup; + + // initialize the selection if necessary + if (selection->rangeCount() == 0) { + if (m_currentNodeDomNavigationAxis + && CacheBuilder::validNode(m_mainFrame, + m_mainFrame, m_currentNodeDomNavigationAxis)) { + PassRefPtr<Range> rangeRef = + selection->frame()->document()->createRange(); + rangeRef->selectNode(m_currentNodeDomNavigationAxis, ec); + m_currentNodeDomNavigationAxis = 0; + if (ec) + return String(); + selection->addRange(rangeRef.get()); + } else if (currentFocus()) { + selection->setPosition(currentFocus(), 0, ec); + } else if (m_cursorNode + && CacheBuilder::validNode(m_mainFrame, + m_mainFrame, m_cursorNode)) { + PassRefPtr<Range> rangeRef = + selection->frame()->document()->createRange(); + rangeRef->selectNode(reinterpret_cast<Node*>(m_cursorNode), ec); + if (ec) + return String(); + selection->addRange(rangeRef.get()); + } else { + selection->setPosition(body, 0, ec); + } + if (ec) + return String(); + } + + // collapse the selection + if (direction == DIRECTION_FORWARD) + selection->collapseToEnd(ec); + else + selection->collapseToStart(ec); + if (ec) + return String(); + + // Make sure the anchor node is a text node since we are generating + // the markup of the selection which includes the anchor, the focus, + // and any crossed nodes. Forcing the condition that the selection + // starts and ends on text nodes guarantees symmetric selection markup. + // Also this way the text content, rather its container, is highlighted. + Node* anchorNode = selection->anchorNode(); + if (anchorNode->isElementNode()) { + // Collapsed selection while moving forward points to the + // next unvisited node and while moving backward to the + // last visited node. + if (direction == DIRECTION_FORWARD) + advanceAnchorNode(selection, direction, markup, false, ec); + else + advanceAnchorNode(selection, direction, markup, true, ec); + if (ec) + return String(); + if (!markup.isEmpty()) + return markup; + } + + // If the selection is at the end of a non white space text move + // it to the next visible text node with non white space content. + // This is a workaround for the selection getting stuck. + anchorNode = selection->anchorNode(); + if (anchorNode->isTextNode()) { + if (direction == DIRECTION_FORWARD) { + String suffix = anchorNode->textContent().substring( + selection->anchorOffset(), caretMaxOffset(anchorNode)); + // If at the end of non white space text we advance the + // anchor node to either an input element or non empty text. + if (suffix.stripWhiteSpace().isEmpty()) { + advanceAnchorNode(selection, direction, markup, true, ec); + } + } else { + String prefix = anchorNode->textContent().substring(0, + selection->anchorOffset()); + // If at the end of non white space text we advance the + // anchor node to either an input element or non empty text. + if (prefix.stripWhiteSpace().isEmpty()) { + advanceAnchorNode(selection, direction, markup, true, ec); + } + } + if (ec) + return String(); + if (!markup.isEmpty()) + return markup; + } + + // extend the selection + String directionStr; + if (direction == DIRECTION_FORWARD) + directionStr = "forward"; + else + directionStr = "backward"; + + String axisStr; + if (axis == AXIS_CHARACTER) + axisStr = "character"; + else if (axis == AXIS_WORD) + axisStr = "word"; + else + axisStr = "sentence"; + + selection->modify("extend", directionStr, axisStr); + + // Make sure the focus node is a text node in order to have the + // selection generate symmetric markup because the latter + // includes all nodes crossed by the selection. Also this way + // the text content, rather its container, is highlighted. + Node* focusNode = selection->focusNode(); + if (focusNode->isElementNode()) { + focusNode = getImplicitBoundaryNode(selection->focusNode(), + selection->focusOffset(), direction); + if (!focusNode) + return String(); + if (direction == DIRECTION_FORWARD) { + focusNode = focusNode->traversePreviousSiblingPostOrder(body); + if (focusNode && !isContentTextNode(focusNode)) { + Node* textNode = traverseNextContentTextNode(focusNode, + anchorNode, DIRECTION_BACKWARD); + if (textNode) + anchorNode = textNode; + } + if (focusNode && isContentTextNode(focusNode)) { + selection->extend(focusNode, caretMaxOffset(focusNode), ec); + if (ec) + return String(); + } + } else { + focusNode = focusNode->traverseNextSibling(); + if (focusNode && !isContentTextNode(focusNode)) { + Node* textNode = traverseNextContentTextNode(focusNode, + anchorNode, DIRECTION_FORWARD); + if (textNode) + anchorNode = textNode; + } + if (anchorNode && isContentTextNode(anchorNode)) { + selection->extend(focusNode, 0, ec); + if (ec) + return String(); + } + } + } + + // Enforce that the selection does not cross anchor boundaries. This is + // a workaround for the asymmetric behavior of WebKit while crossing + // anchors. + anchorNode = getImplicitBoundaryNode(selection->anchorNode(), + selection->anchorOffset(), direction); + focusNode = getImplicitBoundaryNode(selection->focusNode(), + selection->focusOffset(), direction); + if (anchorNode && focusNode && anchorNode != focusNode) { + Node* inputControl = getIntermediaryInputElement(anchorNode, focusNode, + direction); + if (inputControl) { + if (direction == DIRECTION_FORWARD) { + if (isDescendantOf(inputControl, anchorNode)) { + focusNode = inputControl; + } else { + focusNode = inputControl->traversePreviousSiblingPostOrder( + body); + if (!focusNode) + focusNode = inputControl; + } + // We prefer a text node contained in the input element. + if (!isContentTextNode(focusNode)) { + Node* textNode = traverseNextContentTextNode(focusNode, + anchorNode, DIRECTION_BACKWARD); + if (textNode) + focusNode = textNode; + } + // If we found text in the input select it. + // Otherwise, select the input element itself. + if (isContentTextNode(focusNode)) { + selection->extend(focusNode, caretMaxOffset(focusNode), ec); + } else if (anchorNode != focusNode) { + // Note that the focusNode always has parent and that + // the offset can be one more that the index of the last + // element - this is how WebKit selects such elements. + selection->extend(focusNode->parentNode(), + focusNode->nodeIndex() + 1, ec); + } + if (ec) + return String(); + } else { + if (isDescendantOf(inputControl, anchorNode)) { + focusNode = inputControl; + } else { + focusNode = inputControl->traverseNextSibling(); + if (!focusNode) + focusNode = inputControl; + } + // We prefer a text node contained in the input element. + if (!isContentTextNode(focusNode)) { + Node* textNode = traverseNextContentTextNode(focusNode, + anchorNode, DIRECTION_FORWARD); + if (textNode) + focusNode = textNode; + } + // If we found text in the input select it. + // Otherwise, select the input element itself. + if (isContentTextNode(focusNode)) { + selection->extend(focusNode, caretMinOffset(focusNode), ec); + } else if (anchorNode != focusNode) { + // Note that the focusNode always has parent and that + // the offset can be one more that the index of the last + // element - this is how WebKit selects such elements. + selection->extend(focusNode->parentNode(), + focusNode->nodeIndex() + 1, ec); + } + if (ec) + return String(); + } + } + } + + // make sure the selection is visible + if (direction == DIRECTION_FORWARD) + scrollNodeIntoView(m_mainFrame, selection->focusNode()); + else + scrollNodeIntoView(m_mainFrame, selection->anchorNode()); + + // format markup for the visible content + PassRefPtr<Range> range = selection->getRangeAt(0, ec); + if (ec) + return String(); + IntRect bounds = range->boundingBox(); + selectAt(bounds.center().x(), bounds.center().y()); + markup = formatMarkup(selection); + LOGV("Selection markup: %s", markup.utf8().data()); + + return markup; +} + +Node* WebViewCore::getImplicitBoundaryNode(Node* node, unsigned offset, int direction) +{ + if (node->offsetInCharacters()) + return node; + if (!node->hasChildNodes()) + return node; + if (offset < node->childNodeCount()) + return node->childNode(offset); + else + if (direction == DIRECTION_FORWARD) + return node->traverseNextSibling(); + else + return node->traversePreviousNodePostOrder( + node->document()->body()); +} + +Node* WebViewCore::getNextAnchorNode(Node* anchorNode, bool ignoreFirstNode, int direction) +{ + Node* body = 0; + Node* currentNode = 0; + if (direction == DIRECTION_FORWARD) { + if (ignoreFirstNode) + currentNode = anchorNode->traverseNextNode(body); + else + currentNode = anchorNode; + } else { + body = anchorNode->document()->body(); + if (ignoreFirstNode) + currentNode = anchorNode->traversePreviousSiblingPostOrder(body); + else + currentNode = anchorNode; + } + while (currentNode) { + if (isContentTextNode(currentNode) + || isContentInputElement(currentNode)) + return currentNode; + if (direction == DIRECTION_FORWARD) + currentNode = currentNode->traverseNextNode(); + else + currentNode = currentNode->traversePreviousNodePostOrder(body); + } + return 0; +} + +void WebViewCore::advanceAnchorNode(DOMSelection* selection, int direction, + String& markup, bool ignoreFirstNode, ExceptionCode& ec) +{ + Node* anchorNode = getImplicitBoundaryNode(selection->anchorNode(), + selection->anchorOffset(), direction); + if (!anchorNode) { + ec = NOT_FOUND_ERR; + return; + } + // If the anchor offset is invalid i.e. the anchor node has no + // child with that index getImplicitAnchorNode returns the next + // logical node in the current direction. In such a case our + // position in the DOM tree was has already been advanced, + // therefore we there is no need to do that again. + if (selection->anchorNode()->isElementNode()) { + unsigned anchorOffset = selection->anchorOffset(); + unsigned childNodeCount = selection->anchorNode()->childNodeCount(); + if (anchorOffset >= childNodeCount) + ignoreFirstNode = false; + } + // Find the next anchor node given our position in the DOM and + // whether we want the current node to be considered as well. + Node* nextAnchorNode = getNextAnchorNode(anchorNode, ignoreFirstNode, + direction); + if (!nextAnchorNode) { + ec = NOT_FOUND_ERR; + return; + } + if (nextAnchorNode->isElementNode()) { + // If this is an input element tell the WebView thread + // to set the cursor to that control. + if (isContentInputElement(nextAnchorNode)) { + IntRect bounds = nextAnchorNode->getRect(); + selectAt(bounds.center().x(), bounds.center().y()); + } + Node* textNode = 0; + // Treat the text content of links as any other text but + // for the rest input elements select the control itself. + if (nextAnchorNode->hasTagName(WebCore::HTMLNames::aTag)) + textNode = traverseNextContentTextNode(nextAnchorNode, + nextAnchorNode, direction); + // We prefer to select the text content of the link if such, + // otherwise just select the element itself. + if (textNode) { + nextAnchorNode = textNode; + } else { + if (direction == DIRECTION_FORWARD) { + selection->setBaseAndExtent(nextAnchorNode, + caretMinOffset(nextAnchorNode), nextAnchorNode, + caretMaxOffset(nextAnchorNode), ec); + } else { + selection->setBaseAndExtent(nextAnchorNode, + caretMaxOffset(nextAnchorNode), nextAnchorNode, + caretMinOffset(nextAnchorNode), ec); + } + if (!ec) + markup = formatMarkup(selection); + // make sure the selection is visible + scrollNodeIntoView(selection->frame(), nextAnchorNode); + return; + } + } + if (direction == DIRECTION_FORWARD) + selection->setPosition(nextAnchorNode, + caretMinOffset(nextAnchorNode), ec); + else + selection->setPosition(nextAnchorNode, + caretMaxOffset(nextAnchorNode), ec); +} + +bool WebViewCore::isContentInputElement(Node* node) +{ + return (isVisible(node) + && (node->hasTagName(WebCore::HTMLNames::selectTag) + || node->hasTagName(WebCore::HTMLNames::aTag) + || node->hasTagName(WebCore::HTMLNames::inputTag) + || node->hasTagName(WebCore::HTMLNames::buttonTag))); +} + +bool WebViewCore::isContentTextNode(Node* node) +{ + if (!node || !node->isTextNode()) + return false; + Text* textNode = static_cast<Text*>(node); + return (isVisible(textNode) && textNode->length() > 0 + && !textNode->containsOnlyWhitespace()); +} + +Text* WebViewCore::traverseNextContentTextNode(Node* fromNode, Node* toNode, int direction) +{ + Node* currentNode = fromNode; + do { + if (direction == DIRECTION_FORWARD) + currentNode = currentNode->traverseNextNode(toNode); + else + currentNode = currentNode->traversePreviousNodePostOrder(toNode); + } while (currentNode && !isContentTextNode(currentNode)); + return static_cast<Text*>(currentNode); +} + +Node* WebViewCore::getIntermediaryInputElement(Node* fromNode, Node* toNode, int direction) +{ + if (fromNode == toNode) + return 0; + if (direction == DIRECTION_FORWARD) { + Node* currentNode = fromNode; + while (currentNode && currentNode != toNode) { + if (isContentInputElement(currentNode)) + return currentNode; + currentNode = currentNode->traverseNextNodePostOrder(); + } + currentNode = fromNode; + while (currentNode && currentNode != toNode) { + if (isContentInputElement(currentNode)) + return currentNode; + currentNode = currentNode->traverseNextNode(); + } + } else { + Node* currentNode = fromNode->traversePreviousNode(); + while (currentNode && currentNode != toNode) { + if (isContentInputElement(currentNode)) + return currentNode; + currentNode = currentNode->traversePreviousNode(); + } + currentNode = fromNode->traversePreviousNodePostOrder(); + while (currentNode && currentNode != toNode) { + if (isContentInputElement(currentNode)) + return currentNode; + currentNode = currentNode->traversePreviousNodePostOrder(); + } + } + return 0; +} + +bool WebViewCore::isDescendantOf(Node* parent, Node* node) +{ + Node* currentNode = node; + while (currentNode) { + if (currentNode == parent) { + return true; + } + currentNode = currentNode->parentNode(); + } + return false; +} + +String WebViewCore::modifySelectionDomNavigationAxis(DOMSelection* selection, int direction, int axis) +{ + HTMLElement* body = m_mainFrame->document()->body(); + if (!m_currentNodeDomNavigationAxis && selection->focusNode()) { + m_currentNodeDomNavigationAxis = selection->focusNode(); + selection->empty(); + if (m_currentNodeDomNavigationAxis->isTextNode()) + m_currentNodeDomNavigationAxis = + m_currentNodeDomNavigationAxis->parentNode(); + } + if (!m_currentNodeDomNavigationAxis) + m_currentNodeDomNavigationAxis = currentFocus(); + if (!m_currentNodeDomNavigationAxis + || !CacheBuilder::validNode(m_mainFrame, m_mainFrame, + m_currentNodeDomNavigationAxis)) + m_currentNodeDomNavigationAxis = body; + Node* currentNode = m_currentNodeDomNavigationAxis; + if (axis == AXIS_HEADING) { + if (currentNode == body && direction == DIRECTION_BACKWARD) + currentNode = currentNode->lastDescendant(); + do { + if (direction == DIRECTION_FORWARD) + currentNode = currentNode->traverseNextNode(body); + else + currentNode = currentNode->traversePreviousNode(body); + } while (currentNode && (currentNode->isTextNode() + || !isVisible(currentNode) || !isHeading(currentNode))); + } else if (axis == AXIS_PARENT_FIRST_CHILD) { + if (direction == DIRECTION_FORWARD) { + currentNode = currentNode->firstChild(); + while (currentNode && (currentNode->isTextNode() + || !isVisible(currentNode))) + currentNode = currentNode->nextSibling(); + } else { + do { + if (currentNode == body) + return String(); + currentNode = currentNode->parentNode(); + } while (currentNode && (currentNode->isTextNode() + || !isVisible(currentNode))); + } + } else if (axis == AXIS_SIBLING) { + do { + if (direction == DIRECTION_FORWARD) + currentNode = currentNode->nextSibling(); + else { + if (currentNode == body) + return String(); + currentNode = currentNode->previousSibling(); + } + } while (currentNode && (currentNode->isTextNode() + || !isVisible(currentNode))); + } else if (axis == AXIS_DOCUMENT) { + currentNode = body; + if (direction == DIRECTION_FORWARD) + currentNode = currentNode->lastDescendant(); + } else { + LOGE("Invalid axis: %d", axis); + return String(); + } + if (currentNode) { + m_currentNodeDomNavigationAxis = currentNode; + scrollNodeIntoView(m_mainFrame, currentNode); + String selectionString = createMarkup(currentNode); + LOGV("Selection markup: %s", selectionString.utf8().data()); + return selectionString; + } + return String(); +} + +bool WebViewCore::isHeading(Node* node) +{ + if (node->hasTagName(WebCore::HTMLNames::h1Tag) + || node->hasTagName(WebCore::HTMLNames::h2Tag) + || node->hasTagName(WebCore::HTMLNames::h3Tag) + || node->hasTagName(WebCore::HTMLNames::h4Tag) + || node->hasTagName(WebCore::HTMLNames::h5Tag) + || node->hasTagName(WebCore::HTMLNames::h6Tag)) { + return true; + } + + if (node->isElementNode()) { + Element* element = static_cast<Element*>(node); + String roleAttribute = + element->getAttribute(WebCore::HTMLNames::roleAttr).string(); + if (equalIgnoringCase(roleAttribute, "heading")) + return true; + } + + return false; +} + +bool WebViewCore::isVisible(Node* node) +{ + // start off an element + Element* element = 0; + if (node->isElementNode()) + element = static_cast<Element*>(node); + else + element = node->parentElement(); + // check renderer + if (!element->renderer()) { + return false; + } + // check size + if (element->offsetHeight() == 0 || element->offsetWidth() == 0) { + return false; + } + // check style + Node* body = m_mainFrame->document()->body(); + Node* currentNode = element; + while (currentNode && currentNode != body) { + RenderStyle* style = currentNode->computedStyle(); + if (style && + (style->display() == NONE || style->visibility() == HIDDEN)) { + return false; + } + currentNode = currentNode->parentNode(); + } + return true; +} + +String WebViewCore::formatMarkup(DOMSelection* selection) +{ + ExceptionCode ec = 0; + String markup = String(); + PassRefPtr<Range> wholeRange = selection->getRangeAt(0, ec); + if (ec) + return String(); + if (!wholeRange->startContainer() || !wholeRange->startContainer()) + return String(); + // Since formatted markup contains invisible nodes it + // is created from the concatenation of the visible fragments. + Node* firstNode = wholeRange->firstNode(); + Node* pastLastNode = wholeRange->pastLastNode(); + Node* currentNode = firstNode; + PassRefPtr<Range> currentRange; + + while (currentNode != pastLastNode) { + Node* nextNode = currentNode->traverseNextNode(); + if (!isVisible(currentNode)) { + if (currentRange) { + markup = markup + currentRange->toHTML().utf8().data(); + currentRange = 0; + } + } else { + if (!currentRange) { + currentRange = selection->frame()->document()->createRange(); + if (ec) + break; + if (currentNode == firstNode) { + currentRange->setStart(wholeRange->startContainer(), + wholeRange->startOffset(), ec); + if (ec) + break; + } else { + currentRange->setStart(currentNode->parentNode(), + currentNode->nodeIndex(), ec); + if (ec) + break; + } + } + if (nextNode == pastLastNode) { + currentRange->setEnd(wholeRange->endContainer(), + wholeRange->endOffset(), ec); + if (ec) + break; + markup = markup + currentRange->toHTML().utf8().data(); + } else { + if (currentNode->offsetInCharacters()) + currentRange->setEnd(currentNode, + currentNode->maxCharacterOffset(), ec); + else + currentRange->setEnd(currentNode->parentNode(), + currentNode->nodeIndex() + 1, ec); + if (ec) + break; + } + } + currentNode = nextNode; + } + return markup.stripWhiteSpace(); +} + +void WebViewCore::selectAt(int x, int y) +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue->object(env).get(), m_javaGlue->m_selectAt, + x, y); + checkException(env); +} + +void WebViewCore::deleteSelection(int start, int end, int textGeneration) +{ + setSelection(start, end); + if (start == end) + return; + WebCore::Node* focus = currentFocus(); + if (!focus) + return; + // Prevent our editor client from passing a message to change the + // selection. + EditorClientAndroid* client = static_cast<EditorClientAndroid*>( + m_mainFrame->editor()->client()); + client->setUiGeneratedSelectionChange(true); + PlatformKeyboardEvent down(AKEYCODE_DEL, 0, 0, true, false, false, false); + PlatformKeyboardEvent up(AKEYCODE_DEL, 0, 0, false, false, false, false); + key(down); + key(up); + client->setUiGeneratedSelectionChange(false); + m_textGeneration = textGeneration; + m_shouldPaintCaret = true; +} + +void WebViewCore::replaceTextfieldText(int oldStart, + int oldEnd, const WTF::String& replace, int start, int end, + int textGeneration) +{ + WebCore::Node* focus = currentFocus(); + if (!focus) + return; + setSelection(oldStart, oldEnd); + // Prevent our editor client from passing a message to change the + // selection. + EditorClientAndroid* client = static_cast<EditorClientAndroid*>( + m_mainFrame->editor()->client()); + client->setUiGeneratedSelectionChange(true); + WebCore::TypingCommand::insertText(focus->document(), replace, + false); + client->setUiGeneratedSelectionChange(false); + // setSelection calls revealSelection, so there is no need to do it here. + setSelection(start, end); + m_textGeneration = textGeneration; + m_shouldPaintCaret = true; +} + +void WebViewCore::passToJs(int generation, const WTF::String& current, + const PlatformKeyboardEvent& event) +{ + WebCore::Node* focus = currentFocus(); + if (!focus) { + DBG_NAV_LOG("!focus"); + clearTextEntry(); + return; + } + WebCore::RenderObject* renderer = focus->renderer(); + if (!renderer || (!renderer->isTextField() && !renderer->isTextArea())) { + DBG_NAV_LOGD("renderer==%p || not text", renderer); + clearTextEntry(); + return; + } + // Block text field updates during a key press. + m_blockTextfieldUpdates = true; + // Also prevent our editor client from passing a message to change the + // selection. + EditorClientAndroid* client = static_cast<EditorClientAndroid*>( + m_mainFrame->editor()->client()); + client->setUiGeneratedSelectionChange(true); + key(event); + client->setUiGeneratedSelectionChange(false); + m_blockTextfieldUpdates = false; + m_textGeneration = generation; + WebCore::RenderTextControl* renderText = + static_cast<WebCore::RenderTextControl*>(renderer); + WTF::String test = renderText->text(); + if (test != current) { + // If the text changed during the key event, update the UI text field. + updateTextfield(focus, false, test); + } else { + DBG_NAV_LOG("test == current"); + } + // Now that the selection has settled down, send it. + updateTextSelection(); + m_shouldPaintCaret = true; +} + +void WebViewCore::scrollFocusedTextInput(float xPercent, int y) +{ + WebCore::Node* focus = currentFocus(); + if (!focus) { + DBG_NAV_LOG("!focus"); + clearTextEntry(); + return; + } + WebCore::RenderObject* renderer = focus->renderer(); + if (!renderer || (!renderer->isTextField() && !renderer->isTextArea())) { + DBG_NAV_LOGD("renderer==%p || not text", renderer); + clearTextEntry(); + return; + } + WebCore::RenderTextControl* renderText = + static_cast<WebCore::RenderTextControl*>(renderer); + int x = (int) (xPercent * (renderText->scrollWidth() - + renderText->clientWidth())); + DBG_NAV_LOGD("x=%d y=%d xPercent=%g scrollW=%d clientW=%d", x, y, + xPercent, renderText->scrollWidth(), renderText->clientWidth()); + renderText->setScrollLeft(x); + renderText->setScrollTop(y); +} + +void WebViewCore::setFocusControllerActive(bool active) +{ + m_mainFrame->page()->focusController()->setActive(active); +} + +void WebViewCore::saveDocumentState(WebCore::Frame* frame) +{ + if (!CacheBuilder::validNode(m_mainFrame, frame, 0)) + frame = m_mainFrame; + WebCore::HistoryItem *item = frame->loader()->history()->currentItem(); + + // item can be null when there is no offical URL for the current page. This happens + // when the content is loaded using with WebCoreFrameBridge::LoadData() and there + // is no failing URL (common case is when content is loaded using data: scheme) + if (item) { + item->setDocumentState(frame->document()->formElementsState()); + } +} + +// Create an array of java Strings. +static jobjectArray makeLabelArray(JNIEnv* env, const uint16_t** labels, size_t count) +{ + jclass stringClass = env->FindClass("java/lang/String"); + LOG_ASSERT(stringClass, "Could not find java/lang/String"); + jobjectArray array = env->NewObjectArray(count, stringClass, 0); + LOG_ASSERT(array, "Could not create new string array"); + + for (size_t i = 0; i < count; i++) { + jobject newString = env->NewString(&labels[i][1], labels[i][0]); + env->SetObjectArrayElement(array, i, newString); + env->DeleteLocalRef(newString); + checkException(env); + } + env->DeleteLocalRef(stringClass); + return array; +} + +void WebViewCore::openFileChooser(PassRefPtr<WebCore::FileChooser> chooser) { + if (!chooser) + return; + JNIEnv* env = JSC::Bindings::getJNIEnv(); + + WTF::String acceptType = chooser->acceptTypes(); + jstring jAcceptType = wtfStringToJstring(env, acceptType, true); + jstring jName = (jstring) env->CallObjectMethod( + m_javaGlue->object(env).get(), m_javaGlue->m_openFileChooser, jAcceptType); + checkException(env); + env->DeleteLocalRef(jAcceptType); + + const UChar* string = static_cast<const UChar*>(env->GetStringChars(jName, NULL)); + + if (!string) + return; + + WTF::String webcoreString = jstringToWtfString(env, jName); + env->ReleaseStringChars(jName, string); + + if (webcoreString.length()) + chooser->chooseFile(webcoreString); +} + +void WebViewCore::listBoxRequest(WebCoreReply* reply, const uint16_t** labels, size_t count, const int enabled[], size_t enabledCount, + bool multiple, const int selected[], size_t selectedCountOrSelection) +{ + // If m_popupReply is not null, then we already have a list showing. + if (m_popupReply != 0) + return; + + LOG_ASSERT(m_javaGlue->m_obj, "No java widget associated with this view!"); + + // Create an array of java Strings for the drop down. + JNIEnv* env = JSC::Bindings::getJNIEnv(); + jobjectArray labelArray = makeLabelArray(env, labels, count); + + // Create an array determining whether each item is enabled. + jintArray enabledArray = env->NewIntArray(enabledCount); + checkException(env); + jint* ptrArray = env->GetIntArrayElements(enabledArray, 0); + checkException(env); + for (size_t i = 0; i < enabledCount; i++) { + ptrArray[i] = enabled[i]; + } + env->ReleaseIntArrayElements(enabledArray, ptrArray, 0); + checkException(env); + + if (multiple) { + // Pass up an array representing which items are selected. + jintArray selectedArray = env->NewIntArray(selectedCountOrSelection); + checkException(env); + jint* selArray = env->GetIntArrayElements(selectedArray, 0); + checkException(env); + for (size_t i = 0; i < selectedCountOrSelection; i++) { + selArray[i] = selected[i]; + } + env->ReleaseIntArrayElements(selectedArray, selArray, 0); + + env->CallVoidMethod(m_javaGlue->object(env).get(), + m_javaGlue->m_requestListBox, labelArray, enabledArray, + selectedArray); + env->DeleteLocalRef(selectedArray); + } else { + // Pass up the single selection. + env->CallVoidMethod(m_javaGlue->object(env).get(), + m_javaGlue->m_requestSingleListBox, labelArray, enabledArray, + selectedCountOrSelection); + } + + env->DeleteLocalRef(labelArray); + env->DeleteLocalRef(enabledArray); + checkException(env); + + Retain(reply); + m_popupReply = reply; +} + +bool WebViewCore::key(const PlatformKeyboardEvent& event) +{ + WebCore::EventHandler* eventHandler; + WebCore::Node* focusNode = currentFocus(); + DBG_NAV_LOGD("keyCode=%s unichar=%d focusNode=%p", + event.keyIdentifier().utf8().data(), event.unichar(), focusNode); + if (focusNode) { + WebCore::Frame* frame = focusNode->document()->frame(); + WebFrame* webFrame = WebFrame::getWebFrame(frame); + eventHandler = frame->eventHandler(); + VisibleSelection old = frame->selection()->selection(); + bool handled = eventHandler->keyEvent(event); + if (isContentEditable(focusNode)) { + // keyEvent will return true even if the contentEditable did not + // change its selection. In the case that it does not, we want to + // return false so that the key will be sent back to our navigation + // system. + handled |= frame->selection()->selection() != old; + } + return handled; + } else { + eventHandler = m_mainFrame->eventHandler(); + } + return eventHandler->keyEvent(event); +} + +// For when the user clicks the trackball, presses dpad center, or types into an +// unfocused textfield. In the latter case, 'fake' will be true +void WebViewCore::click(WebCore::Frame* frame, WebCore::Node* node, bool fake) { + if (!node) { + WebCore::IntPoint pt = m_mousePos; + pt.move(m_scrollOffsetX, m_scrollOffsetY); + WebCore::HitTestResult hitTestResult = m_mainFrame->eventHandler()-> + hitTestResultAtPoint(pt, false); + node = hitTestResult.innerNode(); + frame = node->document()->frame(); + DBG_NAV_LOGD("m_mousePos=(%d,%d) m_scrollOffset=(%d,%d) pt=(%d,%d)" + " node=%p", m_mousePos.x(), m_mousePos.y(), + m_scrollOffsetX, m_scrollOffsetY, pt.x(), pt.y(), node); + } + if (node) { + EditorClientAndroid* client + = static_cast<EditorClientAndroid*>( + m_mainFrame->editor()->client()); + client->setShouldChangeSelectedRange(false); + handleMouseClick(frame, node, fake); + client->setShouldChangeSelectedRange(true); + } +} + +#if USE(ACCELERATED_COMPOSITING) +GraphicsLayerAndroid* WebViewCore::graphicsRootLayer() const +{ + RenderView* contentRenderer = m_mainFrame->contentRenderer(); + if (!contentRenderer) + return 0; + return static_cast<GraphicsLayerAndroid*>( + contentRenderer->compositor()->rootPlatformLayer()); +} +#endif + +bool WebViewCore::handleTouchEvent(int action, Vector<int>& ids, Vector<IntPoint>& points, int actionIndex, int metaState) +{ + bool preventDefault = false; + +#if USE(ACCELERATED_COMPOSITING) + GraphicsLayerAndroid* rootLayer = graphicsRootLayer(); + if (rootLayer) + rootLayer->pauseDisplay(true); +#endif + +#if ENABLE(TOUCH_EVENTS) // Android + #define MOTION_EVENT_ACTION_POINTER_DOWN 5 + #define MOTION_EVENT_ACTION_POINTER_UP 6 + + WebCore::TouchEventType type = WebCore::TouchStart; + WebCore::PlatformTouchPoint::State defaultTouchState; + Vector<WebCore::PlatformTouchPoint::State> touchStates(points.size()); + + switch (action) { + case 0: // MotionEvent.ACTION_DOWN + type = WebCore::TouchStart; + defaultTouchState = WebCore::PlatformTouchPoint::TouchPressed; + break; + case 1: // MotionEvent.ACTION_UP + type = WebCore::TouchEnd; + defaultTouchState = WebCore::PlatformTouchPoint::TouchReleased; + break; + case 2: // MotionEvent.ACTION_MOVE + type = WebCore::TouchMove; + defaultTouchState = WebCore::PlatformTouchPoint::TouchMoved; + break; + case 3: // MotionEvent.ACTION_CANCEL + type = WebCore::TouchCancel; + defaultTouchState = WebCore::PlatformTouchPoint::TouchCancelled; + break; + case 5: // MotionEvent.ACTION_POINTER_DOWN + type = WebCore::TouchStart; + defaultTouchState = WebCore::PlatformTouchPoint::TouchStationary; + break; + case 6: // MotionEvent.ACTION_POINTER_UP + type = WebCore::TouchEnd; + defaultTouchState = WebCore::PlatformTouchPoint::TouchStationary; + break; + case 0x100: // WebViewCore.ACTION_LONGPRESS + type = WebCore::TouchLongPress; + defaultTouchState = WebCore::PlatformTouchPoint::TouchPressed; + break; + case 0x200: // WebViewCore.ACTION_DOUBLETAP + type = WebCore::TouchDoubleTap; + defaultTouchState = WebCore::PlatformTouchPoint::TouchPressed; + break; + default: + // We do not support other kinds of touch event inside WebCore + // at the moment. + LOGW("Java passed a touch event type that we do not support in WebCore: %d", action); + return 0; + } + + for (int c = 0; c < static_cast<int>(points.size()); c++) { + points[c].setX(points[c].x() - m_scrollOffsetX); + points[c].setY(points[c].y() - m_scrollOffsetY); + + // Setting the touch state for each point. + // Note: actionIndex will be 0 for all actions that are not ACTION_POINTER_DOWN/UP. + if (action == MOTION_EVENT_ACTION_POINTER_DOWN && c == actionIndex) { + touchStates[c] = WebCore::PlatformTouchPoint::TouchPressed; + } else if (action == MOTION_EVENT_ACTION_POINTER_UP && c == actionIndex) { + touchStates[c] = WebCore::PlatformTouchPoint::TouchReleased; + } else { + touchStates[c] = defaultTouchState; + }; + } + + WebCore::PlatformTouchEvent te(ids, points, type, touchStates, metaState); + preventDefault = m_mainFrame->eventHandler()->handleTouchEvent(te); +#endif + +#if USE(ACCELERATED_COMPOSITING) + if (rootLayer) + rootLayer->pauseDisplay(false); +#endif + return preventDefault; +} + +void WebViewCore::touchUp(int touchGeneration, + WebCore::Frame* frame, WebCore::Node* node, int x, int y) +{ + if (touchGeneration == 0) { + // m_mousePos should be set in getTouchHighlightRects() + WebCore::HitTestResult hitTestResult = m_mainFrame->eventHandler()->hitTestResultAtPoint(m_mousePos, false); + node = hitTestResult.innerNode(); + if (node) + frame = node->document()->frame(); + else + frame = 0; + DBG_NAV_LOGD("touch up on (%d, %d), scrollOffset is (%d, %d), node:%p, frame:%p", m_mousePos.x() + m_scrollOffsetX, m_mousePos.y() + m_scrollOffsetY, m_scrollOffsetX, m_scrollOffsetY, node, frame); + } else { + if (m_touchGeneration > touchGeneration) { + DBG_NAV_LOGD("m_touchGeneration=%d > touchGeneration=%d" + " x=%d y=%d", m_touchGeneration, touchGeneration, x, y); + return; // short circuit if a newer touch has been generated + } + // This moves m_mousePos to the correct place, and handleMouseClick uses + // m_mousePos to determine where the click happens. + moveMouse(frame, x, y); + m_lastGeneration = touchGeneration; + } + if (frame && CacheBuilder::validNode(m_mainFrame, frame, 0)) { + frame->loader()->resetMultipleFormSubmissionProtection(); + } + DBG_NAV_LOGD("touchGeneration=%d handleMouseClick frame=%p node=%p" + " x=%d y=%d", touchGeneration, frame, node, x, y); + handleMouseClick(frame, node, false); +} + +// Check for the "x-webkit-soft-keyboard" attribute. If it is there and +// set to hidden, do not show the soft keyboard. Node passed as a parameter +// must not be null. +static bool shouldSuppressKeyboard(const WebCore::Node* node) { + LOG_ASSERT(node, "node passed to shouldSuppressKeyboard cannot be null"); + const NamedNodeMap* attributes = node->attributes(); + if (!attributes) return false; + size_t length = attributes->length(); + for (size_t i = 0; i < length; i++) { + const Attribute* a = attributes->attributeItem(i); + if (a->localName() == "x-webkit-soft-keyboard" && a->value() == "hidden") + return true; + } + return false; +} + +// Common code for both clicking with the trackball and touchUp +// Also used when typing into a non-focused textfield to give the textfield focus, +// in which case, 'fake' is set to true +bool WebViewCore::handleMouseClick(WebCore::Frame* framePtr, WebCore::Node* nodePtr, bool fake) +{ + bool valid = !framePtr || CacheBuilder::validNode(m_mainFrame, framePtr, nodePtr); + WebFrame* webFrame = WebFrame::getWebFrame(m_mainFrame); + if (valid && nodePtr) { + // Need to special case area tags because an image map could have an area element in the middle + // so when attempting to get the default, the point chosen would be follow the wrong link. + if (nodePtr->hasTagName(WebCore::HTMLNames::areaTag)) { + webFrame->setUserInitiatedAction(true); + nodePtr->dispatchSimulatedClick(0, true, true); + webFrame->setUserInitiatedAction(false); + DBG_NAV_LOG("area"); + return true; + } + } + if (!valid || !framePtr) + framePtr = m_mainFrame; + webFrame->setUserInitiatedAction(true); + WebCore::PlatformMouseEvent mouseDown(m_mousePos, m_mousePos, WebCore::LeftButton, + WebCore::MouseEventPressed, 1, false, false, false, false, + WTF::currentTime()); + // ignore the return from as it will return true if the hit point can trigger selection change + framePtr->eventHandler()->handleMousePressEvent(mouseDown); + WebCore::PlatformMouseEvent mouseUp(m_mousePos, m_mousePos, WebCore::LeftButton, + WebCore::MouseEventReleased, 1, false, false, false, false, + WTF::currentTime()); + bool handled = framePtr->eventHandler()->handleMouseReleaseEvent(mouseUp); + webFrame->setUserInitiatedAction(false); + + // If the user clicked on a textfield, make the focusController active + // so we show the blinking cursor. + WebCore::Node* focusNode = currentFocus(); + DBG_NAV_LOGD("m_mousePos={%d,%d} focusNode=%p handled=%s", m_mousePos.x(), + m_mousePos.y(), focusNode, handled ? "true" : "false"); + if (focusNode) { + WebCore::RenderObject* renderer = focusNode->renderer(); + if (renderer && (renderer->isTextField() || renderer->isTextArea())) { + bool ime = !shouldSuppressKeyboard(focusNode) + && !(static_cast<WebCore::HTMLInputElement*>(focusNode))->readOnly(); + if (ime) { +#if ENABLE(WEB_AUTOFILL) + if (renderer->isTextField()) { + EditorClientAndroid* editorC = static_cast<EditorClientAndroid*>(framePtr->page()->editorClient()); + WebAutoFill* autoFill = editorC->getAutoFill(); + autoFill->formFieldFocused(static_cast<HTMLFormControlElement*>(focusNode)); + } +#endif + if (!fake) { + RenderTextControl* rtc + = static_cast<RenderTextControl*> (renderer); + requestKeyboardWithSelection(focusNode, rtc->selectionStart(), + rtc->selectionEnd()); + } + } else if (!fake) { + requestKeyboard(false); + } + } else if (!fake){ + // If the selection is contentEditable, show the keyboard so the + // user can type. Otherwise hide the keyboard because no text + // input is needed. + if (isContentEditable(focusNode)) { + requestKeyboard(true); + } else if (!nodeIsPlugin(focusNode)) { + clearTextEntry(); + } + } + } else if (!fake) { + // There is no focusNode, so the keyboard is not needed. + clearTextEntry(); + } + return handled; +} + +void WebViewCore::popupReply(int index) +{ + if (m_popupReply) { + m_popupReply->replyInt(index); + Release(m_popupReply); + m_popupReply = 0; + } +} + +void WebViewCore::popupReply(const int* array, int count) +{ + if (m_popupReply) { + m_popupReply->replyIntArray(array, count); + Release(m_popupReply); + m_popupReply = 0; + } +} + +void WebViewCore::formDidBlur(const WebCore::Node* node) +{ + // If the blur is on a text input, keep track of the node so we can + // hide the soft keyboard when the new focus is set, if it is not a + // text input. + if (isTextInput(node)) + m_blurringNodePointer = reinterpret_cast<int>(node); +} + +void WebViewCore::focusNodeChanged(const WebCore::Node* newFocus) +{ + if (isTextInput(newFocus)) + m_shouldPaintCaret = true; + else if (m_blurringNodePointer) { + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue->object(env).get(), + m_javaGlue->m_formDidBlur, m_blurringNodePointer); + checkException(env); + m_blurringNodePointer = 0; + } +} + +void WebViewCore::addMessageToConsole(const WTF::String& message, unsigned int lineNumber, const WTF::String& sourceID, int msgLevel) { + JNIEnv* env = JSC::Bindings::getJNIEnv(); + jstring jMessageStr = wtfStringToJstring(env, message); + jstring jSourceIDStr = wtfStringToJstring(env, sourceID); + env->CallVoidMethod(m_javaGlue->object(env).get(), + m_javaGlue->m_addMessageToConsole, jMessageStr, lineNumber, + jSourceIDStr, msgLevel); + env->DeleteLocalRef(jMessageStr); + env->DeleteLocalRef(jSourceIDStr); + checkException(env); +} + +void WebViewCore::jsAlert(const WTF::String& url, const WTF::String& text) +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + jstring jInputStr = wtfStringToJstring(env, text); + jstring jUrlStr = wtfStringToJstring(env, url); + env->CallVoidMethod(m_javaGlue->object(env).get(), m_javaGlue->m_jsAlert, jUrlStr, jInputStr); + env->DeleteLocalRef(jInputStr); + env->DeleteLocalRef(jUrlStr); + checkException(env); +} + +void WebViewCore::exceededDatabaseQuota(const WTF::String& url, const WTF::String& databaseIdentifier, const unsigned long long currentQuota, unsigned long long estimatedSize) +{ +#if ENABLE(DATABASE) + JNIEnv* env = JSC::Bindings::getJNIEnv(); + jstring jDatabaseIdentifierStr = wtfStringToJstring(env, databaseIdentifier); + jstring jUrlStr = wtfStringToJstring(env, url); + env->CallVoidMethod(m_javaGlue->object(env).get(), + m_javaGlue->m_exceededDatabaseQuota, jUrlStr, + jDatabaseIdentifierStr, currentQuota, estimatedSize); + env->DeleteLocalRef(jDatabaseIdentifierStr); + env->DeleteLocalRef(jUrlStr); + checkException(env); +#endif +} + +void WebViewCore::reachedMaxAppCacheSize(const unsigned long long spaceNeeded) +{ +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue->object(env).get(), + m_javaGlue->m_reachedMaxAppCacheSize, spaceNeeded); + checkException(env); +#endif +} + +void WebViewCore::populateVisitedLinks(WebCore::PageGroup* group) +{ + m_groupForVisitedLinks = group; + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue->object(env).get(), m_javaGlue->m_populateVisitedLinks); + checkException(env); +} + +void WebViewCore::geolocationPermissionsShowPrompt(const WTF::String& origin) +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + jstring originString = wtfStringToJstring(env, origin); + env->CallVoidMethod(m_javaGlue->object(env).get(), + m_javaGlue->m_geolocationPermissionsShowPrompt, + originString); + env->DeleteLocalRef(originString); + checkException(env); +} + +void WebViewCore::geolocationPermissionsHidePrompt() +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue->object(env).get(), + m_javaGlue->m_geolocationPermissionsHidePrompt); + checkException(env); +} + +jobject WebViewCore::getDeviceMotionService() +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + jobject object = env->CallObjectMethod(m_javaGlue->object(env).get(), + m_javaGlue->m_getDeviceMotionService); + checkException(env); + return object; +} + +jobject WebViewCore::getDeviceOrientationService() +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + jobject object = env->CallObjectMethod(m_javaGlue->object(env).get(), + m_javaGlue->m_getDeviceOrientationService); + checkException(env); + return object; +} + +bool WebViewCore::jsConfirm(const WTF::String& url, const WTF::String& text) +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + jstring jInputStr = wtfStringToJstring(env, text); + jstring jUrlStr = wtfStringToJstring(env, url); + jboolean result = env->CallBooleanMethod(m_javaGlue->object(env).get(), m_javaGlue->m_jsConfirm, jUrlStr, jInputStr); + env->DeleteLocalRef(jInputStr); + env->DeleteLocalRef(jUrlStr); + checkException(env); + return result; +} + +bool WebViewCore::jsPrompt(const WTF::String& url, const WTF::String& text, const WTF::String& defaultValue, WTF::String& result) +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + jstring jUrlStr = wtfStringToJstring(env, url); + jstring jInputStr = wtfStringToJstring(env, text); + jstring jDefaultStr = wtfStringToJstring(env, defaultValue); + jstring returnVal = static_cast<jstring>(env->CallObjectMethod(m_javaGlue->object(env).get(), m_javaGlue->m_jsPrompt, jUrlStr, jInputStr, jDefaultStr)); + env->DeleteLocalRef(jUrlStr); + env->DeleteLocalRef(jInputStr); + env->DeleteLocalRef(jDefaultStr); + checkException(env); + + // If returnVal is null, it means that the user cancelled the dialog. + if (!returnVal) + return false; + + result = jstringToWtfString(env, returnVal); + env->DeleteLocalRef(returnVal); + return true; +} + +bool WebViewCore::jsUnload(const WTF::String& url, const WTF::String& message) +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + jstring jInputStr = wtfStringToJstring(env, message); + jstring jUrlStr = wtfStringToJstring(env, url); + jboolean result = env->CallBooleanMethod(m_javaGlue->object(env).get(), m_javaGlue->m_jsUnload, jUrlStr, jInputStr); + env->DeleteLocalRef(jInputStr); + env->DeleteLocalRef(jUrlStr); + checkException(env); + return result; +} + +bool WebViewCore::jsInterrupt() +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + jboolean result = env->CallBooleanMethod(m_javaGlue->object(env).get(), m_javaGlue->m_jsInterrupt); + checkException(env); + return result; +} + +AutoJObject +WebViewCore::getJavaObject() +{ + return m_javaGlue->object(JSC::Bindings::getJNIEnv()); +} + +jobject +WebViewCore::getWebViewJavaObject() +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + return env->GetObjectField(m_javaGlue->object(env).get(), gWebViewCoreFields.m_webView); +} + +void WebViewCore::updateTextSelection() { + WebCore::Node* focusNode = currentFocus(); + if (!focusNode) + return; + RenderObject* renderer = focusNode->renderer(); + if (!renderer || (!renderer->isTextArea() && !renderer->isTextField())) + return; + RenderTextControl* rtc = static_cast<RenderTextControl*>(renderer); + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue->object(env).get(), + m_javaGlue->m_updateTextSelection, reinterpret_cast<int>(focusNode), + rtc->selectionStart(), rtc->selectionEnd(), m_textGeneration); + checkException(env); +} + +void WebViewCore::updateTextfield(WebCore::Node* ptr, bool changeToPassword, + const WTF::String& text) +{ + if (m_blockTextfieldUpdates) + return; + JNIEnv* env = JSC::Bindings::getJNIEnv(); + if (changeToPassword) { + env->CallVoidMethod(m_javaGlue->object(env).get(), m_javaGlue->m_updateTextfield, + (int) ptr, true, 0, m_textGeneration); + checkException(env); + return; + } + jstring string = wtfStringToJstring(env, text); + env->CallVoidMethod(m_javaGlue->object(env).get(), m_javaGlue->m_updateTextfield, + (int) ptr, false, string, m_textGeneration); + env->DeleteLocalRef(string); + checkException(env); +} + +void WebViewCore::clearTextEntry() +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue->object(env).get(), + m_javaGlue->m_clearTextEntry); +} + +void WebViewCore::setBackgroundColor(SkColor c) +{ + WebCore::FrameView* view = m_mainFrame->view(); + if (!view) + return; + + // need (int) cast to find the right constructor + WebCore::Color bcolor((int)SkColorGetR(c), (int)SkColorGetG(c), + (int)SkColorGetB(c), (int)SkColorGetA(c)); + view->setBaseBackgroundColor(bcolor); + + // Background color of 0 indicates we want a transparent background + if (c == 0) + view->setTransparent(true); +} + +jclass WebViewCore::getPluginClass(const WTF::String& libName, const char* className) +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + + jstring libString = wtfStringToJstring(env, libName); + jstring classString = env->NewStringUTF(className); + jobject pluginClass = env->CallObjectMethod(m_javaGlue->object(env).get(), + m_javaGlue->m_getPluginClass, + libString, classString); + checkException(env); + + // cleanup unneeded local JNI references + env->DeleteLocalRef(libString); + env->DeleteLocalRef(classString); + + if (pluginClass != NULL) { + return static_cast<jclass>(pluginClass); + } else { + return NULL; + } +} + +void WebViewCore::showFullScreenPlugin(jobject childView, NPP npp) +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + AutoJObject obj = m_javaGlue->object(env); + + env->CallVoidMethod(obj.get(), + m_javaGlue->m_showFullScreenPlugin, childView, (int)npp); + checkException(env); +} + +void WebViewCore::hideFullScreenPlugin() +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue->object(env).get(), + m_javaGlue->m_hideFullScreenPlugin); + checkException(env); +} + +jobject WebViewCore::createSurface(jobject view) +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + jobject result = env->CallObjectMethod(m_javaGlue->object(env).get(), + m_javaGlue->m_createSurface, view); + checkException(env); + return result; +} + +jobject WebViewCore::addSurface(jobject view, int x, int y, int width, int height) +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + jobject result = env->CallObjectMethod(m_javaGlue->object(env).get(), + m_javaGlue->m_addSurface, + view, x, y, width, height); + checkException(env); + return result; +} + +void WebViewCore::updateSurface(jobject childView, int x, int y, int width, int height) +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue->object(env).get(), + m_javaGlue->m_updateSurface, childView, + x, y, width, height); + checkException(env); +} + +void WebViewCore::destroySurface(jobject childView) +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue->object(env).get(), m_javaGlue->m_destroySurface, childView); + checkException(env); +} + +jobject WebViewCore::getContext() +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + AutoJObject obj = m_javaGlue->object(env); + + jobject result = env->CallObjectMethod(obj.get(), m_javaGlue->m_getContext); + checkException(env); + return result; +} + +void WebViewCore::keepScreenOn(bool screenOn) { + if ((screenOn && m_screenOnCounter == 0) || (!screenOn && m_screenOnCounter == 1)) { + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue->object(env).get(), m_javaGlue->m_keepScreenOn, screenOn); + checkException(env); + } + + // update the counter + if (screenOn) + m_screenOnCounter++; + else if (m_screenOnCounter > 0) + m_screenOnCounter--; +} + +bool WebViewCore::validNodeAndBounds(Frame* frame, Node* node, + const IntRect& originalAbsoluteBounds) +{ + bool valid = CacheBuilder::validNode(m_mainFrame, frame, node); + if (!valid) + return false; + RenderObject* renderer = node->renderer(); + if (!renderer) + return false; + IntRect absBounds = node->hasTagName(HTMLNames::areaTag) + ? CacheBuilder::getAreaRect(static_cast<HTMLAreaElement*>(node)) + : renderer->absoluteBoundingBoxRect(); + return absBounds == originalAbsoluteBounds; +} + +void WebViewCore::showRect(int left, int top, int width, int height, + int contentWidth, int contentHeight, float xPercentInDoc, + float xPercentInView, float yPercentInDoc, float yPercentInView) +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue->object(env).get(), m_javaGlue->m_showRect, + left, top, width, height, contentWidth, contentHeight, + xPercentInDoc, xPercentInView, yPercentInDoc, yPercentInView); + checkException(env); +} + +void WebViewCore::centerFitRect(int x, int y, int width, int height) +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue->object(env).get(), + m_javaGlue->m_centerFitRect, x, y, width, height); + checkException(env); +} + + +void WebViewCore::setScrollbarModes(ScrollbarMode horizontalMode, ScrollbarMode verticalMode) +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue->object(env).get(), m_javaGlue->m_setScrollbarModes, + horizontalMode, verticalMode); + checkException(env); +} + +void WebViewCore::notifyWebAppCanBeInstalled() +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + env->CallVoidMethod(m_javaGlue->object(env).get(), m_javaGlue->m_setInstallableWebApp); + checkException(env); +} + +#if ENABLE(VIDEO) +void WebViewCore::enterFullscreenForVideoLayer(int layerId, const WTF::String& url) +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + jstring jUrlStr = wtfStringToJstring(env, url); + env->CallVoidMethod(m_javaGlue->object(env).get(), + m_javaGlue->m_enterFullscreenForVideoLayer, layerId, jUrlStr); + checkException(env); +} +#endif + +void WebViewCore::setWebTextViewAutoFillable(int queryId, const string16& previewSummary) +{ +#if ENABLE(WEB_AUTOFILL) + JNIEnv* env = JSC::Bindings::getJNIEnv(); + jstring preview = env->NewString(previewSummary.data(), previewSummary.length()); + env->CallVoidMethod(m_javaGlue->object(env).get(), m_javaGlue->m_setWebTextViewAutoFillable, queryId, preview); + env->DeleteLocalRef(preview); +#endif +} + +bool WebViewCore::drawIsPaused() const +{ + JNIEnv* env = JSC::Bindings::getJNIEnv(); + return env->GetBooleanField(m_javaGlue->object(env).get(), + gWebViewCoreFields.m_drawIsPaused); +} + +#if USE(CHROME_NETWORK_STACK) +void WebViewCore::setWebRequestContextUserAgent() +{ + // We cannot create a WebRequestContext, because we might not know it this is a private tab or not yet + if (m_webRequestContext) + m_webRequestContext->setUserAgent(WebFrame::getWebFrame(m_mainFrame)->userAgentForURL(0)); // URL not used +} + +void WebViewCore::setWebRequestContextCacheMode(int cacheMode) +{ + m_cacheMode = cacheMode; + // We cannot create a WebRequestContext, because we might not know it this is a private tab or not yet + if (!m_webRequestContext) + return; + + m_webRequestContext->setCacheMode(cacheMode); +} + +WebRequestContext* WebViewCore::webRequestContext() +{ + if (!m_webRequestContext) { + Settings* settings = mainFrame()->settings(); + m_webRequestContext = new WebRequestContext(settings && settings->privateBrowsingEnabled()); + setWebRequestContextUserAgent(); + setWebRequestContextCacheMode(m_cacheMode); + } + return m_webRequestContext.get(); +} +#endif + +void WebViewCore::scrollRenderLayer(int layer, const SkRect& rect) +{ +#if USE(ACCELERATED_COMPOSITING) + GraphicsLayerAndroid* root = graphicsRootLayer(); + if (!root) + return; + + LayerAndroid* layerAndroid = root->platformLayer(); + if (!layerAndroid) + return; + + LayerAndroid* target = layerAndroid->findById(layer); + if (!target) + return; + + RenderLayer* owner = target->owningLayer(); + if (!owner) + return; + + if (owner->stackingContext()) + owner->scrollToOffset(rect.fLeft, rect.fTop, true, false); +#endif +} + +//---------------------------------------------------------------------- +// Native JNI methods +//---------------------------------------------------------------------- +static void RevealSelection(JNIEnv *env, jobject obj) +{ + GET_NATIVE_VIEW(env, obj)->revealSelection(); +} + +static jstring RequestLabel(JNIEnv *env, jobject obj, int framePointer, + int nodePointer) +{ + return wtfStringToJstring(env, GET_NATIVE_VIEW(env, obj)->requestLabel( + (WebCore::Frame*) framePointer, (WebCore::Node*) nodePointer)); +} + +static void ClearContent(JNIEnv *env, jobject obj) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + viewImpl->clearContent(); +} + +static void UpdateFrameCacheIfLoading(JNIEnv *env, jobject obj) +{ + GET_NATIVE_VIEW(env, obj)->updateFrameCacheIfLoading(); +} + +static void SetSize(JNIEnv *env, jobject obj, jint width, jint height, + jint textWrapWidth, jfloat scale, jint screenWidth, jint screenHeight, + jint anchorX, jint anchorY, jboolean ignoreHeight) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOGV("webviewcore::nativeSetSize(%u %u)\n viewImpl: %p", (unsigned)width, (unsigned)height, viewImpl); + LOG_ASSERT(viewImpl, "viewImpl not set in nativeSetSize"); + viewImpl->setSizeScreenWidthAndScale(width, height, textWrapWidth, scale, + screenWidth, screenHeight, anchorX, anchorY, ignoreHeight); +} + +static void SetScrollOffset(JNIEnv *env, jobject obj, jint gen, jboolean sendScrollEvent, jint x, jint y) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "need viewImpl"); + + viewImpl->setScrollOffset(gen, sendScrollEvent, x, y); +} + +static void SetGlobalBounds(JNIEnv *env, jobject obj, jint x, jint y, jint h, + jint v) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "need viewImpl"); + + viewImpl->setGlobalBounds(x, y, h, v); +} + +static jboolean Key(JNIEnv *env, jobject obj, jint keyCode, jint unichar, + jint repeatCount, jboolean isShift, jboolean isAlt, jboolean isSym, + jboolean isDown) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + return GET_NATIVE_VIEW(env, obj)->key(PlatformKeyboardEvent(keyCode, + unichar, repeatCount, isDown, isShift, isAlt, isSym)); +} + +static void Click(JNIEnv *env, jobject obj, int framePtr, int nodePtr, jboolean fake) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in Click"); + + viewImpl->click(reinterpret_cast<WebCore::Frame*>(framePtr), + reinterpret_cast<WebCore::Node*>(nodePtr), fake); +} + +static void ContentInvalidateAll(JNIEnv *env, jobject obj) +{ + GET_NATIVE_VIEW(env, obj)->contentInvalidateAll(); +} + +static void DeleteSelection(JNIEnv *env, jobject obj, jint start, jint end, + jint textGeneration) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + viewImpl->deleteSelection(start, end, textGeneration); +} + +static void SetSelection(JNIEnv *env, jobject obj, jint start, jint end) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + viewImpl->setSelection(start, end); +} + +static jstring ModifySelection(JNIEnv *env, jobject obj, jint direction, jint granularity) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + String selectionString = viewImpl->modifySelection(direction, granularity); + return wtfStringToJstring(env, selectionString); +} + +static void ReplaceTextfieldText(JNIEnv *env, jobject obj, + jint oldStart, jint oldEnd, jstring replace, jint start, jint end, + jint textGeneration) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + WTF::String webcoreString = jstringToWtfString(env, replace); + viewImpl->replaceTextfieldText(oldStart, + oldEnd, webcoreString, start, end, textGeneration); +} + +static void PassToJs(JNIEnv *env, jobject obj, + jint generation, jstring currentText, jint keyCode, + jint keyValue, jboolean down, jboolean cap, jboolean fn, jboolean sym) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WTF::String current = jstringToWtfString(env, currentText); + GET_NATIVE_VIEW(env, obj)->passToJs(generation, current, + PlatformKeyboardEvent(keyCode, keyValue, 0, down, cap, fn, sym)); +} + +static void ScrollFocusedTextInput(JNIEnv *env, jobject obj, jfloat xPercent, + jint y) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + viewImpl->scrollFocusedTextInput(xPercent, y); +} + +static void SetFocusControllerActive(JNIEnv *env, jobject obj, jboolean active) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + LOGV("webviewcore::nativeSetFocusControllerActive()\n"); + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in nativeSetFocusControllerActive"); + viewImpl->setFocusControllerActive(active); +} + +static void SaveDocumentState(JNIEnv *env, jobject obj, jint frame) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + LOGV("webviewcore::nativeSaveDocumentState()\n"); + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in nativeSaveDocumentState"); + viewImpl->saveDocumentState((WebCore::Frame*) frame); +} + +void WebViewCore::addVisitedLink(const UChar* string, int length) +{ + if (m_groupForVisitedLinks) + m_groupForVisitedLinks->addVisitedLink(string, length); +} + +static jint UpdateLayers(JNIEnv *env, jobject obj, jobject region) +{ + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + BaseLayerAndroid* result = viewImpl->createBaseLayer(); + SkRegion* nativeRegion = GraphicsJNI::getNativeRegion(env, region); + if (result) { + SkIRect bounds; + LayerAndroid* root = static_cast<LayerAndroid*>(result->getChild(0)); + if (root) { + root->bounds().roundOut(&bounds); + nativeRegion->setRect(bounds); + } + } + return reinterpret_cast<jint>(result); +} + +static jint RecordContent(JNIEnv *env, jobject obj, jobject region, jobject pt) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + SkRegion* nativeRegion = GraphicsJNI::getNativeRegion(env, region); + SkIPoint nativePt; + BaseLayerAndroid* result = viewImpl->recordContent(nativeRegion, &nativePt); + GraphicsJNI::ipoint_to_jpoint(nativePt, env, pt); + return reinterpret_cast<jint>(result); +} + +static void SplitContent(JNIEnv *env, jobject obj, jint content) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + viewImpl->splitContent(reinterpret_cast<PictureSet*>(content)); +} + +static void SendListBoxChoice(JNIEnv* env, jobject obj, jint choice) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in nativeSendListBoxChoice"); + viewImpl->popupReply(choice); +} + +// Set aside a predetermined amount of space in which to place the listbox +// choices, to avoid unnecessary allocations. +// The size here is arbitrary. We want the size to be at least as great as the +// number of items in the average multiple-select listbox. +#define PREPARED_LISTBOX_STORAGE 10 + +static void SendListBoxChoices(JNIEnv* env, jobject obj, jbooleanArray jArray, + jint size) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in nativeSendListBoxChoices"); + jboolean* ptrArray = env->GetBooleanArrayElements(jArray, 0); + SkAutoSTMalloc<PREPARED_LISTBOX_STORAGE, int> storage(size); + int* array = storage.get(); + int count = 0; + for (int i = 0; i < size; i++) { + if (ptrArray[i]) { + array[count++] = i; + } + } + env->ReleaseBooleanArrayElements(jArray, ptrArray, JNI_ABORT); + viewImpl->popupReply(array, count); +} + +static jstring FindAddress(JNIEnv *env, jobject obj, jstring addr, + jboolean caseInsensitive) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + if (!addr) + return 0; + int length = env->GetStringLength(addr); + if (!length) + return 0; + const jchar* addrChars = env->GetStringChars(addr, 0); + int start, end; + bool success = CacheBuilder::FindAddress(addrChars, length, + &start, &end, caseInsensitive) == CacheBuilder::FOUND_COMPLETE; + jstring ret = 0; + if (success) + ret = env->NewString(addrChars + start, end - start); + env->ReleaseStringChars(addr, addrChars); + return ret; +} + +static jboolean HandleTouchEvent(JNIEnv *env, jobject obj, jint action, jintArray idArray, + jintArray xArray, jintArray yArray, + jint count, jint actionIndex, jint metaState) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in %s", __FUNCTION__); + jint* ptrIdArray = env->GetIntArrayElements(idArray, 0); + jint* ptrXArray = env->GetIntArrayElements(xArray, 0); + jint* ptrYArray = env->GetIntArrayElements(yArray, 0); + Vector<int> ids(count); + Vector<IntPoint> points(count); + for (int c = 0; c < count; c++) { + ids[c] = ptrIdArray[c]; + points[c].setX(ptrXArray[c]); + points[c].setY(ptrYArray[c]); + } + env->ReleaseIntArrayElements(idArray, ptrIdArray, JNI_ABORT); + env->ReleaseIntArrayElements(xArray, ptrXArray, JNI_ABORT); + env->ReleaseIntArrayElements(yArray, ptrYArray, JNI_ABORT); + + return viewImpl->handleTouchEvent(action, ids, points, actionIndex, metaState); +} + +static void TouchUp(JNIEnv *env, jobject obj, jint touchGeneration, + jint frame, jint node, jint x, jint y) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in %s", __FUNCTION__); + viewImpl->touchUp(touchGeneration, + (WebCore::Frame*) frame, (WebCore::Node*) node, x, y); +} + +static jstring RetrieveHref(JNIEnv *env, jobject obj, jint x, jint y) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in %s", __FUNCTION__); + WTF::String result = viewImpl->retrieveHref(x, y); + if (!result.isEmpty()) + return wtfStringToJstring(env, result); + return 0; +} + +static jstring RetrieveAnchorText(JNIEnv *env, jobject obj, jint x, jint y) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in %s", __FUNCTION__); + WTF::String result = viewImpl->retrieveAnchorText(x, y); + if (!result.isEmpty()) + return wtfStringToJstring(env, result); + return 0; +} + +static jstring RetrieveImageSource(JNIEnv *env, jobject obj, jint x, jint y) +{ + WTF::String result = GET_NATIVE_VIEW(env, obj)->retrieveImageSource(x, y); + return !result.isEmpty() ? wtfStringToJstring(env, result) : 0; +} + +static void StopPaintingCaret(JNIEnv *env, jobject obj) +{ + GET_NATIVE_VIEW(env, obj)->setShouldPaintCaret(false); +} + +static void MoveFocus(JNIEnv *env, jobject obj, jint framePtr, jint nodePtr) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in %s", __FUNCTION__); + viewImpl->moveFocus((WebCore::Frame*) framePtr, (WebCore::Node*) nodePtr); +} + +static void MoveMouse(JNIEnv *env, jobject obj, jint frame, + jint x, jint y) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in %s", __FUNCTION__); + viewImpl->moveMouse((WebCore::Frame*) frame, x, y); +} + +static void MoveMouseIfLatest(JNIEnv *env, jobject obj, jint moveGeneration, + jint frame, jint x, jint y) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in %s", __FUNCTION__); + viewImpl->moveMouseIfLatest(moveGeneration, + (WebCore::Frame*) frame, x, y); +} + +static void UpdateFrameCache(JNIEnv *env, jobject obj) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in %s", __FUNCTION__); + viewImpl->updateFrameCache(); +} + +static jint GetContentMinPrefWidth(JNIEnv *env, jobject obj) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in %s", __FUNCTION__); + + WebCore::Frame* frame = viewImpl->mainFrame(); + if (frame) { + WebCore::Document* document = frame->document(); + if (document) { + WebCore::RenderObject* renderer = document->renderer(); + if (renderer && renderer->isRenderView()) { + return renderer->minPreferredLogicalWidth(); + } + } + } + return 0; +} + +static void SetViewportSettingsFromNative(JNIEnv *env, jobject obj) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in %s", __FUNCTION__); + + WebCore::Settings* s = viewImpl->mainFrame()->page()->settings(); + if (!s) + return; + +#ifdef ANDROID_META_SUPPORT + env->SetIntField(obj, gWebViewCoreFields.m_viewportWidth, s->viewportWidth()); + env->SetIntField(obj, gWebViewCoreFields.m_viewportHeight, s->viewportHeight()); + env->SetIntField(obj, gWebViewCoreFields.m_viewportInitialScale, s->viewportInitialScale()); + env->SetIntField(obj, gWebViewCoreFields.m_viewportMinimumScale, s->viewportMinimumScale()); + env->SetIntField(obj, gWebViewCoreFields.m_viewportMaximumScale, s->viewportMaximumScale()); + env->SetBooleanField(obj, gWebViewCoreFields.m_viewportUserScalable, s->viewportUserScalable()); + env->SetIntField(obj, gWebViewCoreFields.m_viewportDensityDpi, s->viewportTargetDensityDpi()); +#endif +} + +static void SetBackgroundColor(JNIEnv *env, jobject obj, jint color) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in %s", __FUNCTION__); + + viewImpl->setBackgroundColor((SkColor) color); +} + +static void DumpDomTree(JNIEnv *env, jobject obj, jboolean useFile) +{ + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in %s", __FUNCTION__); + + viewImpl->dumpDomTree(useFile); +} + +static void DumpRenderTree(JNIEnv *env, jobject obj, jboolean useFile) +{ + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in %s", __FUNCTION__); + + viewImpl->dumpRenderTree(useFile); +} + +static void DumpNavTree(JNIEnv *env, jobject obj) +{ + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in %s", __FUNCTION__); + + viewImpl->dumpNavTree(); +} + +static void DumpV8Counters(JNIEnv*, jobject) +{ +#if USE(V8) +#ifdef ANDROID_INSTRUMENT + V8Counters::dumpCounters(); +#endif +#endif +} + +static void SetJsFlags(JNIEnv *env, jobject obj, jstring flags) +{ +#if USE(V8) + WTF::String flagsString = jstringToWtfString(env, flags); + WTF::CString utf8String = flagsString.utf8(); + WebCore::ScriptController::setFlags(utf8String.data(), utf8String.length()); +#endif +} + + +// Called from the Java side to set a new quota for the origin or new appcache +// max size in response to a notification that the original quota was exceeded or +// that the appcache has reached its maximum size. +static void SetNewStorageLimit(JNIEnv* env, jobject obj, jlong quota) { +#if ENABLE(DATABASE) || ENABLE(OFFLINE_WEB_APPLICATIONS) + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + Frame* frame = viewImpl->mainFrame(); + + // The main thread is blocked awaiting this response, so now we can wake it + // up. + ChromeClientAndroid* chromeC = static_cast<ChromeClientAndroid*>(frame->page()->chrome()->client()); + chromeC->wakeUpMainThreadWithNewQuota(quota); +#endif +} + +// Called from Java to provide a Geolocation permission state for the specified origin. +static void GeolocationPermissionsProvide(JNIEnv* env, jobject obj, jstring origin, jboolean allow, jboolean remember) { + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + Frame* frame = viewImpl->mainFrame(); + + ChromeClientAndroid* chromeClient = static_cast<ChromeClientAndroid*>(frame->page()->chrome()->client()); + chromeClient->provideGeolocationPermissions(jstringToWtfString(env, origin), allow, remember); +} + +static void RegisterURLSchemeAsLocal(JNIEnv* env, jobject obj, jstring scheme) { +#ifdef ANDROID_INSTRUMENT + TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); +#endif + WebCore::SchemeRegistry::registerURLSchemeAsLocal(jstringToWtfString(env, scheme)); +} + +static bool FocusBoundsChanged(JNIEnv* env, jobject obj) +{ + return GET_NATIVE_VIEW(env, obj)->focusBoundsChanged(); +} + +static void Pause(JNIEnv* env, jobject obj) +{ + // This is called for the foreground tab when the browser is put to the + // background (and also for any tab when it is put to the background of the + // browser). The browser can only be killed by the system when it is in the + // background, so saving the Geolocation permission state now ensures that + // is maintained when the browser is killed. + ChromeClient* chromeClient = GET_NATIVE_VIEW(env, obj)->mainFrame()->page()->chrome()->client(); + ChromeClientAndroid* chromeClientAndroid = static_cast<ChromeClientAndroid*>(chromeClient); + chromeClientAndroid->storeGeolocationPermissions(); + + Frame* mainFrame = GET_NATIVE_VIEW(env, obj)->mainFrame(); + for (Frame* frame = mainFrame; frame; frame = frame->tree()->traverseNext()) { + Geolocation* geolocation = frame->domWindow()->navigator()->optionalGeolocation(); + if (geolocation) + geolocation->suspend(); + } + + GET_NATIVE_VIEW(env, obj)->deviceMotionAndOrientationManager()->maybeSuspendClients(); + + ANPEvent event; + SkANP::InitEvent(&event, kLifecycle_ANPEventType); + event.data.lifecycle.action = kPause_ANPLifecycleAction; + GET_NATIVE_VIEW(env, obj)->sendPluginEvent(event); + + GET_NATIVE_VIEW(env, obj)->setIsPaused(true); +} + +static void Resume(JNIEnv* env, jobject obj) +{ + Frame* mainFrame = GET_NATIVE_VIEW(env, obj)->mainFrame(); + for (Frame* frame = mainFrame; frame; frame = frame->tree()->traverseNext()) { + Geolocation* geolocation = frame->domWindow()->navigator()->optionalGeolocation(); + if (geolocation) + geolocation->resume(); + } + + GET_NATIVE_VIEW(env, obj)->deviceMotionAndOrientationManager()->maybeResumeClients(); + + ANPEvent event; + SkANP::InitEvent(&event, kLifecycle_ANPEventType); + event.data.lifecycle.action = kResume_ANPLifecycleAction; + GET_NATIVE_VIEW(env, obj)->sendPluginEvent(event); + + GET_NATIVE_VIEW(env, obj)->setIsPaused(false); +} + +static void FreeMemory(JNIEnv* env, jobject obj) +{ + ANPEvent event; + SkANP::InitEvent(&event, kLifecycle_ANPEventType); + event.data.lifecycle.action = kFreeMemory_ANPLifecycleAction; + GET_NATIVE_VIEW(env, obj)->sendPluginEvent(event); +} + +static void ProvideVisitedHistory(JNIEnv *env, jobject obj, jobject hist) +{ + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in %s", __FUNCTION__); + + jobjectArray array = static_cast<jobjectArray>(hist); + + jsize len = env->GetArrayLength(array); + for (jsize i = 0; i < len; i++) { + jstring item = static_cast<jstring>(env->GetObjectArrayElement(array, i)); + const UChar* str = static_cast<const UChar*>(env->GetStringChars(item, 0)); + jsize len = env->GetStringLength(item); + viewImpl->addVisitedLink(str, len); + env->ReleaseStringChars(item, str); + env->DeleteLocalRef(item); + } +} + +// Notification from the UI thread that the plugin's full-screen surface has been discarded +static void FullScreenPluginHidden(JNIEnv* env, jobject obj, jint npp) +{ + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + PluginWidgetAndroid* plugin = viewImpl->getPluginWidget((NPP)npp); + if (plugin) + plugin->exitFullScreen(false); +} + +static WebCore::IntRect jrect_to_webrect(JNIEnv* env, jobject obj) +{ + int L, T, R, B; + GraphicsJNI::get_jrect(env, obj, &L, &T, &R, &B); + return WebCore::IntRect(L, T, R - L, B - T); +} + +static bool ValidNodeAndBounds(JNIEnv *env, jobject obj, int frame, int node, + jobject rect) +{ + IntRect nativeRect = jrect_to_webrect(env, rect); + return GET_NATIVE_VIEW(env, obj)->validNodeAndBounds( + reinterpret_cast<Frame*>(frame), + reinterpret_cast<Node*>(node), nativeRect); +} + +static jobject GetTouchHighlightRects(JNIEnv* env, jobject obj, jint x, jint y, jint slop) +{ + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + if (!viewImpl) + return 0; + Vector<IntRect> rects = viewImpl->getTouchHighlightRects(x, y, slop); + if (rects.isEmpty()) + return 0; + + jclass arrayClass = env->FindClass("java/util/ArrayList"); + LOG_ASSERT(arrayClass, "Could not find java/util/ArrayList"); + jmethodID init = env->GetMethodID(arrayClass, "<init>", "(I)V"); + LOG_ASSERT(init, "Could not find constructor for ArrayList"); + jobject array = env->NewObject(arrayClass, init, rects.size()); + LOG_ASSERT(array, "Could not create a new ArrayList"); + jmethodID add = env->GetMethodID(arrayClass, "add", "(Ljava/lang/Object;)Z"); + LOG_ASSERT(add, "Could not find add method on ArrayList"); + jclass rectClass = env->FindClass("android/graphics/Rect"); + LOG_ASSERT(rectClass, "Could not find android/graphics/Rect"); + jmethodID rectinit = env->GetMethodID(rectClass, "<init>", "(IIII)V"); + LOG_ASSERT(rectinit, "Could not find init method on Rect"); + + for (size_t i = 0; i < rects.size(); i++) { + jobject rect = env->NewObject(rectClass, rectinit, rects[i].x(), + rects[i].y(), rects[i].right(), rects[i].bottom()); + if (rect) { + env->CallBooleanMethod(array, add, rect); + env->DeleteLocalRef(rect); + } + } + + env->DeleteLocalRef(rectClass); + env->DeleteLocalRef(arrayClass); + return array; +} + +static void AutoFillForm(JNIEnv* env, jobject obj, jint queryId) +{ +#if ENABLE(WEB_AUTOFILL) + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + if (!viewImpl) + return; + + WebCore::Frame* frame = viewImpl->mainFrame(); + if (frame) { + EditorClientAndroid* editorC = static_cast<EditorClientAndroid*>(frame->page()->editorClient()); + WebAutoFill* autoFill = editorC->getAutoFill(); + autoFill->fillFormFields(queryId); + } +#endif +} + +static void ScrollRenderLayer(JNIEnv* env, jobject obj, jint layer, jobject jRect) +{ + SkRect rect; + GraphicsJNI::jrect_to_rect(env, jRect, &rect); + GET_NATIVE_VIEW(env, obj)->scrollRenderLayer(layer, rect); +} + +// ---------------------------------------------------------------------------- + +/* + * JNI registration. + */ +static JNINativeMethod gJavaWebViewCoreMethods[] = { + { "nativeClearContent", "()V", + (void*) ClearContent }, + { "nativeFocusBoundsChanged", "()Z", + (void*) FocusBoundsChanged } , + { "nativeKey", "(IIIZZZZ)Z", + (void*) Key }, + { "nativeClick", "(IIZ)V", + (void*) Click }, + { "nativeContentInvalidateAll", "()V", + (void*) ContentInvalidateAll }, + { "nativeSendListBoxChoices", "([ZI)V", + (void*) SendListBoxChoices }, + { "nativeSendListBoxChoice", "(I)V", + (void*) SendListBoxChoice }, + { "nativeSetSize", "(IIIFIIIIZ)V", + (void*) SetSize }, + { "nativeSetScrollOffset", "(IZII)V", + (void*) SetScrollOffset }, + { "nativeSetGlobalBounds", "(IIII)V", + (void*) SetGlobalBounds }, + { "nativeSetSelection", "(II)V", + (void*) SetSelection } , + { "nativeModifySelection", "(II)Ljava/lang/String;", + (void*) ModifySelection }, + { "nativeDeleteSelection", "(III)V", + (void*) DeleteSelection } , + { "nativeReplaceTextfieldText", "(IILjava/lang/String;III)V", + (void*) ReplaceTextfieldText } , + { "nativeMoveFocus", "(II)V", + (void*) MoveFocus }, + { "nativeMoveMouse", "(III)V", + (void*) MoveMouse }, + { "nativeMoveMouseIfLatest", "(IIII)V", + (void*) MoveMouseIfLatest }, + { "passToJs", "(ILjava/lang/String;IIZZZZ)V", + (void*) PassToJs }, + { "nativeScrollFocusedTextInput", "(FI)V", + (void*) ScrollFocusedTextInput }, + { "nativeSetFocusControllerActive", "(Z)V", + (void*) SetFocusControllerActive }, + { "nativeSaveDocumentState", "(I)V", + (void*) SaveDocumentState }, + { "nativeFindAddress", "(Ljava/lang/String;Z)Ljava/lang/String;", + (void*) FindAddress }, + { "nativeHandleTouchEvent", "(I[I[I[IIII)Z", + (void*) HandleTouchEvent }, + { "nativeTouchUp", "(IIIII)V", + (void*) TouchUp }, + { "nativeRetrieveHref", "(II)Ljava/lang/String;", + (void*) RetrieveHref }, + { "nativeRetrieveAnchorText", "(II)Ljava/lang/String;", + (void*) RetrieveAnchorText }, + { "nativeRetrieveImageSource", "(II)Ljava/lang/String;", + (void*) RetrieveImageSource }, + { "nativeStopPaintingCaret", "()V", + (void*) StopPaintingCaret }, + { "nativeUpdateFrameCache", "()V", + (void*) UpdateFrameCache }, + { "nativeGetContentMinPrefWidth", "()I", + (void*) GetContentMinPrefWidth }, + { "nativeUpdateLayers", "(Landroid/graphics/Region;)I", + (void*) UpdateLayers }, + { "nativeRecordContent", "(Landroid/graphics/Region;Landroid/graphics/Point;)I", + (void*) RecordContent }, + { "setViewportSettingsFromNative", "()V", + (void*) SetViewportSettingsFromNative }, + { "nativeSplitContent", "(I)V", + (void*) SplitContent }, + { "nativeSetBackgroundColor", "(I)V", + (void*) SetBackgroundColor }, + { "nativeRegisterURLSchemeAsLocal", "(Ljava/lang/String;)V", + (void*) RegisterURLSchemeAsLocal }, + { "nativeDumpDomTree", "(Z)V", + (void*) DumpDomTree }, + { "nativeDumpRenderTree", "(Z)V", + (void*) DumpRenderTree }, + { "nativeDumpNavTree", "()V", + (void*) DumpNavTree }, + { "nativeDumpV8Counters", "()V", + (void*) DumpV8Counters }, + { "nativeSetNewStorageLimit", "(J)V", + (void*) SetNewStorageLimit }, + { "nativeGeolocationPermissionsProvide", "(Ljava/lang/String;ZZ)V", + (void*) GeolocationPermissionsProvide }, + { "nativePause", "()V", (void*) Pause }, + { "nativeResume", "()V", (void*) Resume }, + { "nativeFreeMemory", "()V", (void*) FreeMemory }, + { "nativeSetJsFlags", "(Ljava/lang/String;)V", (void*) SetJsFlags }, + { "nativeRequestLabel", "(II)Ljava/lang/String;", + (void*) RequestLabel }, + { "nativeRevealSelection", "()V", (void*) RevealSelection }, + { "nativeUpdateFrameCacheIfLoading", "()V", + (void*) UpdateFrameCacheIfLoading }, + { "nativeProvideVisitedHistory", "([Ljava/lang/String;)V", + (void*) ProvideVisitedHistory }, + { "nativeFullScreenPluginHidden", "(I)V", + (void*) FullScreenPluginHidden }, + { "nativeValidNodeAndBounds", "(IILandroid/graphics/Rect;)Z", + (void*) ValidNodeAndBounds }, + { "nativeGetTouchHighlightRects", "(III)Ljava/util/ArrayList;", + (void*) GetTouchHighlightRects }, + { "nativeAutoFillForm", "(I)V", + (void*) AutoFillForm }, + { "nativeScrollLayer", "(ILandroid/graphics/Rect;)V", + (void*) ScrollRenderLayer }, +}; + +int registerWebViewCore(JNIEnv* env) +{ + jclass widget = env->FindClass("android/webkit/WebViewCore"); + LOG_ASSERT(widget, + "Unable to find class android/webkit/WebViewCore"); + gWebViewCoreFields.m_nativeClass = env->GetFieldID(widget, "mNativeClass", + "I"); + LOG_ASSERT(gWebViewCoreFields.m_nativeClass, + "Unable to find android/webkit/WebViewCore.mNativeClass"); + gWebViewCoreFields.m_viewportWidth = env->GetFieldID(widget, + "mViewportWidth", "I"); + LOG_ASSERT(gWebViewCoreFields.m_viewportWidth, + "Unable to find android/webkit/WebViewCore.mViewportWidth"); + gWebViewCoreFields.m_viewportHeight = env->GetFieldID(widget, + "mViewportHeight", "I"); + LOG_ASSERT(gWebViewCoreFields.m_viewportHeight, + "Unable to find android/webkit/WebViewCore.mViewportHeight"); + gWebViewCoreFields.m_viewportInitialScale = env->GetFieldID(widget, + "mViewportInitialScale", "I"); + LOG_ASSERT(gWebViewCoreFields.m_viewportInitialScale, + "Unable to find android/webkit/WebViewCore.mViewportInitialScale"); + gWebViewCoreFields.m_viewportMinimumScale = env->GetFieldID(widget, + "mViewportMinimumScale", "I"); + LOG_ASSERT(gWebViewCoreFields.m_viewportMinimumScale, + "Unable to find android/webkit/WebViewCore.mViewportMinimumScale"); + gWebViewCoreFields.m_viewportMaximumScale = env->GetFieldID(widget, + "mViewportMaximumScale", "I"); + LOG_ASSERT(gWebViewCoreFields.m_viewportMaximumScale, + "Unable to find android/webkit/WebViewCore.mViewportMaximumScale"); + gWebViewCoreFields.m_viewportUserScalable = env->GetFieldID(widget, + "mViewportUserScalable", "Z"); + LOG_ASSERT(gWebViewCoreFields.m_viewportUserScalable, + "Unable to find android/webkit/WebViewCore.mViewportUserScalable"); + gWebViewCoreFields.m_viewportDensityDpi = env->GetFieldID(widget, + "mViewportDensityDpi", "I"); + LOG_ASSERT(gWebViewCoreFields.m_viewportDensityDpi, + "Unable to find android/webkit/WebViewCore.mViewportDensityDpi"); + gWebViewCoreFields.m_webView = env->GetFieldID(widget, + "mWebView", "Landroid/webkit/WebView;"); + LOG_ASSERT(gWebViewCoreFields.m_webView, + "Unable to find android/webkit/WebViewCore.mWebView"); + gWebViewCoreFields.m_drawIsPaused = env->GetFieldID(widget, + "mDrawIsPaused", "Z"); + LOG_ASSERT(gWebViewCoreFields.m_drawIsPaused, + "Unable to find android/webkit/WebViewCore.mDrawIsPaused"); + gWebViewCoreFields.m_lowMemoryUsageMb = env->GetFieldID(widget, "mLowMemoryUsageThresholdMb", "I"); + gWebViewCoreFields.m_highMemoryUsageMb = env->GetFieldID(widget, "mHighMemoryUsageThresholdMb", "I"); + gWebViewCoreFields.m_highUsageDeltaMb = env->GetFieldID(widget, "mHighUsageDeltaMb", "I"); + + gWebViewCoreStaticMethods.m_isSupportedMediaMimeType = + env->GetStaticMethodID(widget, "isSupportedMediaMimeType", "(Ljava/lang/String;)Z"); + LOG_FATAL_IF(!gWebViewCoreStaticMethods.m_isSupportedMediaMimeType, + "Could not find static method isSupportedMediaMimeType from WebViewCore"); + + env->DeleteLocalRef(widget); + + return jniRegisterNativeMethods(env, "android/webkit/WebViewCore", + gJavaWebViewCoreMethods, NELEM(gJavaWebViewCoreMethods)); +} + +} /* namespace android */ diff --git a/Source/WebKit/android/jni/WebViewCore.h b/Source/WebKit/android/jni/WebViewCore.h new file mode 100644 index 0000000..8d8f303 --- /dev/null +++ b/Source/WebKit/android/jni/WebViewCore.h @@ -0,0 +1,714 @@ +/* + * Copyright 2006, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef WEBVIEWCORE_H +#define WEBVIEWCORE_H + +#include "CacheBuilder.h" +#include "CachedHistory.h" +#include "DeviceMotionAndOrientationManager.h" +#include "DOMSelection.h" +#include "FileChooser.h" +#include "PictureSet.h" +#include "PlatformGraphicsContext.h" +#include "SkColor.h" +#include "SkTDArray.h" +#include "SkRegion.h" +#include "Timer.h" +#include "WebCoreRefObject.h" +#include "WebCoreJni.h" +#include "WebRequestContext.h" +#include "android_npapi.h" + +#include <jni.h> +#include <ui/KeycodeLabels.h> +#include <ui/PixelFormat.h> + +namespace WebCore { + class Color; + class FrameView; + class HTMLAnchorElement; + class HTMLElement; + class HTMLImageElement; + class HTMLSelectElement; + class RenderPart; + class RenderText; + class Node; + class PlatformKeyboardEvent; + class QualifiedName; + class RenderTextControl; + class ScrollView; + class TimerBase; + class PageGroup; +} + +#if USE(ACCELERATED_COMPOSITING) +namespace WebCore { + class GraphicsLayerAndroid; +} +#endif + +namespace WebCore { + class BaseLayerAndroid; +} + +struct PluginWidgetAndroid; +class SkPicture; +class SkIRect; + +namespace android { + + enum Direction { + DIRECTION_BACKWARD = 0, + DIRECTION_FORWARD = 1 + }; + + enum NavigationAxis { + AXIS_CHARACTER = 0, + AXIS_WORD = 1, + AXIS_SENTENCE = 2, + AXIS_HEADING = 3, + AXIS_SIBLING = 4, + AXIS_PARENT_FIRST_CHILD = 5, + AXIS_DOCUMENT = 6 + }; + + class CachedFrame; + class CachedNode; + class CachedRoot; + class ListBoxReply; + + class WebCoreReply : public WebCoreRefObject { + public: + virtual ~WebCoreReply() {} + + virtual void replyInt(int value) { + SkDEBUGF(("WebCoreReply::replyInt(%d) not handled\n", value)); + } + + virtual void replyIntArray(const int* array, int count) { + SkDEBUGF(("WebCoreReply::replyIntArray() not handled\n")); + } + // add more replyFoo signatures as needed + }; + + // one instance of WebViewCore per page for calling into Java's WebViewCore + class WebViewCore : public WebCoreRefObject { + public: + /** + * Initialize the native WebViewCore with a JNI environment, a Java + * WebViewCore object and the main frame. + */ + WebViewCore(JNIEnv* env, jobject javaView, WebCore::Frame* mainframe); + ~WebViewCore(); + + // helper function + static WebViewCore* getWebViewCore(const WebCore::FrameView* view); + static WebViewCore* getWebViewCore(const WebCore::ScrollView* view); + + // Followings are called from native WebCore to Java + + /** + * Notification that a form was blurred. Pass a message to hide the + * keyboard if it was showing for that Node. + * @param Node The Node that blurred. + */ + void formDidBlur(const WebCore::Node*); + void focusNodeChanged(const WebCore::Node*); + + /** + * Scroll to an absolute position. + * @param x The x coordinate. + * @param y The y coordinate. + * @param animate If it is true, animate to the new scroll position + * + * This method calls Java to trigger a gradual scroll event. + */ + void scrollTo(int x, int y, bool animate = false); + + /** + * Record the invalid rectangle + */ + void contentInvalidate(const WebCore::IntRect &rect); + void contentInvalidateAll(); + + /** + * Satisfy any outstanding invalidates, so that the current state + * of the DOM is drawn. + */ + void contentDraw(); + + /** + * copy the layers to the UI side + */ + void layersDraw(); + +#if USE(ACCELERATED_COMPOSITING) + GraphicsLayerAndroid* graphicsRootLayer() const; +#endif + + /** Invalidate the view/screen, NOT the content/DOM, but expressed in + * content/DOM coordinates (i.e. they need to eventually be scaled, + * by webview into view.java coordinates + */ + void viewInvalidate(const WebCore::IntRect& rect); + + /** + * Invalidate part of the content that may be offscreen at the moment + */ + void offInvalidate(const WebCore::IntRect &rect); + + /** + * Called by webcore when the progress indicator is done + * used to rebuild and display any changes in focus + */ + void notifyProgressFinished(); + + /** + * Notify the view that WebCore did its first layout. + */ + void didFirstLayout(); + + /** + * Notify the view to update the viewport. + */ + void updateViewport(); + + /** + * Notify the view to restore the screen width, which in turn restores + * the scale. Also restore the scale for the text wrap. + */ + void restoreScale(float scale, float textWrapScale); + + /** + * Tell the java side to update the focused textfield + * @param pointer Pointer to the node for the input field. + * @param changeToPassword If true, we are changing the textfield to + * a password field, and ignore the String + * @param text If changeToPassword is false, this is the new text that + * should go into the textfield. + */ + void updateTextfield(WebCore::Node* pointer, + bool changeToPassword, const WTF::String& text); + + /** + * Tell the java side to update the current selection in the focused + * textfield to the WebTextView. This function finds the currently + * focused textinput, and passes its selection to java. + * If there is no focus, or it is not a text input, this does nothing. + */ + void updateTextSelection(); + + void clearTextEntry(); + // JavaScript support + void jsAlert(const WTF::String& url, const WTF::String& text); + bool jsConfirm(const WTF::String& url, const WTF::String& text); + bool jsPrompt(const WTF::String& url, const WTF::String& message, + const WTF::String& defaultValue, WTF::String& result); + bool jsUnload(const WTF::String& url, const WTF::String& message); + bool jsInterrupt(); + + /** + * Tell the Java side that the origin has exceeded its database quota. + * @param url The URL of the page that caused the quota overflow + * @param databaseIdentifier the id of the database that caused the + * quota overflow. + * @param currentQuota The current quota for the origin + * @param estimatedSize The estimated size of the database + */ + void exceededDatabaseQuota(const WTF::String& url, + const WTF::String& databaseIdentifier, + const unsigned long long currentQuota, + const unsigned long long estimatedSize); + + /** + * Tell the Java side that the appcache has exceeded its max size. + * @param spaceNeeded is the amount of disk space that would be needed + * in order for the last appcache operation to succeed. + */ + void reachedMaxAppCacheSize(const unsigned long long spaceNeeded); + + /** + * Set up the PageGroup's idea of which links have been visited, + * with the browser history. + * @param group the object to deliver the links to. + */ + void populateVisitedLinks(WebCore::PageGroup*); + + /** + * Instruct the browser to show a Geolocation permission prompt for the + * specified origin. + * @param origin The origin of the frame requesting Geolocation + * permissions. + */ + void geolocationPermissionsShowPrompt(const WTF::String& origin); + /** + * Instruct the browser to hide the Geolocation permission prompt. + */ + void geolocationPermissionsHidePrompt(); + + jobject getDeviceMotionService(); + jobject getDeviceOrientationService(); + + void addMessageToConsole(const String& message, unsigned int lineNumber, const String& sourceID, int msgLevel); + + /** + * Tell the Java side of the scrollbar mode + */ + void setScrollbarModes(ScrollbarMode horizontalMode, ScrollbarMode verticalMode); + + // + // Followings support calls from Java to native WebCore + // + + WTF::String retrieveHref(int x, int y); + WTF::String retrieveAnchorText(int x, int y); + WTF::String retrieveImageSource(int x, int y); + WTF::String requestLabel(WebCore::Frame* , WebCore::Node* ); + + // If the focus is a textfield (<input>), textarea, or contentEditable, + // scroll the selection on screen (if necessary). + void revealSelection(); + // Create a single picture to represent the drawn DOM (used by navcache) + void recordPicture(SkPicture* picture); + + void moveFocus(WebCore::Frame* frame, WebCore::Node* node); + void moveMouse(WebCore::Frame* frame, int x, int y); + void moveMouseIfLatest(int moveGeneration, + WebCore::Frame* frame, int x, int y); + + // set the scroll amount that webview.java is currently showing + void setScrollOffset(int moveGeneration, bool sendScrollEvent, int dx, int dy); + + void setGlobalBounds(int x, int y, int h, int v); + + void setSizeScreenWidthAndScale(int width, int height, int screenWidth, + float scale, int realScreenWidth, int screenHeight, int anchorX, + int anchorY, bool ignoreHeight); + + /** + * Handle key events from Java. + * @return Whether keyCode was handled by this class. + */ + bool key(const WebCore::PlatformKeyboardEvent& event); + + /** + * Handle (trackball) click event / dpad center press from Java. + * Also used when typing into an unfocused textfield, in which case 'fake' + * will be true. + */ + void click(WebCore::Frame* frame, WebCore::Node* node, bool fake); + + /** + * Handle touch event + */ + bool handleTouchEvent(int action, Vector<int>& ids, Vector<IntPoint>& points, int actionIndex, int metaState); + + /** + * Handle motionUp event from the UI thread (called touchUp in the + * WebCore thread). + * @param touchGeneration Generation number for touches so we can ignore + * touches when a newer one has been generated. + * @param frame Pointer to Frame containing the node that was touched. + * @param node Pointer to Node that was touched. + * @param x x-position of the touch. + * @param y y-position of the touch. + */ + void touchUp(int touchGeneration, WebCore::Frame* frame, + WebCore::Node* node, int x, int y); + + /** + * Sets the index of the label from a popup + */ + void popupReply(int index); + void popupReply(const int* array, int count); + + /** + * Delete text from start to end in the focused textfield. + * If start == end, set the selection, but perform no deletion. + * If there is no focus, silently fail. + * If start and end are out of order, swap them. + */ + void deleteSelection(int start, int end, int textGeneration); + + /** + * Set the selection of the currently focused textfield to (start, end). + * If start and end are out of order, swap them. + */ + void setSelection(int start, int end); + + /** + * Modifies the current selection. + * + * Note: Accessibility support. + * + * direction - The direction in which to alter the selection. + * granularity - The granularity of the selection modification. + * + * returns - The selected HTML as a string. This is not a well formed + * HTML, rather the selection annotated with the tags of all + * intermediary elements it crosses. + */ + String modifySelection(const int direction, const int granularity); + + /** + * Moves the selection to the given node in a given frame i.e. selects that node. + * + * Note: Accessibility support. + * + * frame - The frame in which to select is the node to be selected. + * node - The node to be selected. + * + * returns - The selected HTML as a string. This is not a well formed + * HTML, rather the selection annotated with the tags of all + * intermediary elements it crosses. + */ + String moveSelection(WebCore::Frame* frame, WebCore::Node* node); + + /** + * In the currently focused textfield, replace the characters from oldStart to oldEnd + * (if oldStart == oldEnd, this will be an insert at that position) with replace, + * and set the selection to (start, end). + */ + void replaceTextfieldText(int oldStart, + int oldEnd, const WTF::String& replace, int start, int end, + int textGeneration); + void passToJs(int generation, + const WTF::String& , const WebCore::PlatformKeyboardEvent& ); + /** + * Scroll the focused textfield to (x, y) in document space + */ + void scrollFocusedTextInput(float x, int y); + /** + * Set the FocusController's active and focused states, so that + * the caret will draw (true) or not. + */ + void setFocusControllerActive(bool active); + + void saveDocumentState(WebCore::Frame* frame); + + void addVisitedLink(const UChar*, int); + + // TODO: I don't like this hack but I need to access the java object in + // order to send it as a parameter to java + AutoJObject getJavaObject(); + + // Return the parent WebView Java object associated with this + // WebViewCore. + jobject getWebViewJavaObject(); + + void setBackgroundColor(SkColor c); + void updateFrameCache(); + void updateCacheOnNodeChange(); + void dumpDomTree(bool); + void dumpRenderTree(bool); + void dumpNavTree(); + + /* We maintain a list of active plugins. The list is edited by the + pluginview itself. The list is used to service invals to the plugin + pageflipping bitmap. + */ + void addPlugin(PluginWidgetAndroid*); + void removePlugin(PluginWidgetAndroid*); + // returns true if the pluginwidgit is in our active list + bool isPlugin(PluginWidgetAndroid*) const; + void invalPlugin(PluginWidgetAndroid*); + void drawPlugins(); + + // send the current screen size/zoom to all of the plugins in our list + void sendPluginVisibleScreen(); + + // send onLoad event to plugins who are descendents of the given frame + void notifyPluginsOnFrameLoad(const Frame*); + + // gets a rect representing the current on-screen portion of the document + void getVisibleScreen(ANPRectI&); + + // send this event to all of the plugins in our list + void sendPluginEvent(const ANPEvent&); + + // lookup the plugin widget struct given an NPP + PluginWidgetAndroid* getPluginWidget(NPP npp); + + // return the cursorNode if it is a plugin + Node* cursorNodeIsPlugin(); + + // Notify the Java side whether it needs to pass down the touch events + void needTouchEvents(bool); + + void requestKeyboardWithSelection(const WebCore::Node*, int selStart, int selEnd); + // Notify the Java side that webkit is requesting a keyboard + void requestKeyboard(bool showKeyboard); + + // Generates a class loader that contains classes from the plugin's apk + jclass getPluginClass(const WTF::String& libName, const char* className); + + // Creates a full screen surface for a plugin + void showFullScreenPlugin(jobject webkitPlugin, NPP npp); + + // Instructs the UI thread to discard the plugin's full-screen surface + void hideFullScreenPlugin(); + + // Creates a childView for the plugin but does not attach to the view hierarchy + jobject createSurface(jobject view); + + // Adds the plugin's view (aka surface) to the view hierarchy + jobject addSurface(jobject view, int x, int y, int width, int height); + + // Updates a Surface coordinates and dimensions for a plugin + void updateSurface(jobject childView, int x, int y, int width, int height); + + // Destroys a SurfaceView for a plugin + void destroySurface(jobject childView); + + // Returns the context (android.content.Context) of the WebView + jobject getContext(); + + // Manages requests to keep the screen on while the WebView is visible + void keepScreenOn(bool screenOn); + + bool validNodeAndBounds(Frame* , Node* , const IntRect& ); + + // Make the rect (left, top, width, height) visible. If it can be fully + // fit, center it on the screen. Otherwise make sure the point specified + // by (left + xPercentInDoc * width, top + yPercentInDoc * height) + // pinned at the screen position (xPercentInView, yPercentInView). + void showRect(int left, int top, int width, int height, int contentWidth, + int contentHeight, float xPercentInDoc, float xPercentInView, + float yPercentInDoc, float yPercentInView); + + // Scale the rect (x, y, width, height) to make it just fit and centered + // in the current view. + void centerFitRect(int x, int y, int width, int height); + + // return a list of rects matching the touch point (x, y) with the slop + Vector<IntRect> getTouchHighlightRects(int x, int y, int slop); + + // Open a file chooser for selecting a file to upload + void openFileChooser(PassRefPtr<WebCore::FileChooser> ); + + // reset the picture set to empty + void clearContent(); + + bool focusBoundsChanged(); + + // record the inval area, and the picture size + BaseLayerAndroid* recordContent(SkRegion* , SkIPoint* ); + + // This creates a new BaseLayerAndroid by copying the current m_content + // and doing a copy of the layers. The layers' content may be updated + // as we are calling layersSync(). + BaseLayerAndroid* createBaseLayer(); + + int textWrapWidth() const { return m_textWrapWidth; } + float scale() const { return m_scale; } + float textWrapScale() const { return m_screenWidth * m_scale / m_textWrapWidth; } + WebCore::Frame* mainFrame() const { return m_mainFrame; } + void updateCursorBounds(const CachedRoot* root, + const CachedFrame* cachedFrame, const CachedNode* cachedNode); + void updateFrameCacheIfLoading(); + + // utility to split slow parts of the picture set + void splitContent(PictureSet*); + + void notifyWebAppCanBeInstalled(); + +#if ENABLE(VIDEO) + void enterFullscreenForVideoLayer(int layerId, const WTF::String& url); +#endif + + void setWebTextViewAutoFillable(int queryId, const string16& previewSummary); + + DeviceMotionAndOrientationManager* deviceMotionAndOrientationManager() { return &m_deviceMotionAndOrientationManager; } + + void listBoxRequest(WebCoreReply* reply, const uint16_t** labels, + size_t count, const int enabled[], size_t enabledCount, + bool multiple, const int selected[], size_t selectedCountOrSelection); + bool shouldPaintCaret() { return m_shouldPaintCaret; } + void setShouldPaintCaret(bool should) { m_shouldPaintCaret = should; } + bool isPaused() const { return m_isPaused; } + void setIsPaused(bool isPaused) { m_isPaused = isPaused; } + bool drawIsPaused() const; + // The actual content (without title bar) size in doc coordinate + int screenWidth() const { return m_screenWidth; } + int screenHeight() const { return m_screenHeight; } +#if USE(CHROME_NETWORK_STACK) + void setWebRequestContextUserAgent(); + void setWebRequestContextCacheMode(int mode); + WebRequestContext* webRequestContext(); +#endif + // Attempts to scroll the layer to the x,y coordinates of rect. The + // layer is the id of the LayerAndroid. + void scrollRenderLayer(int layer, const SkRect& rect); + // call only from webkit thread (like add/remove), return true if inst + // is still alive + static bool isInstance(WebViewCore*); + // if there exists at least one WebViewCore instance then we return the + // application context, otherwise NULL is returned. + static jobject getApplicationContext(); + // Check whether a media mimeType is supported in Android media framework. + static bool isSupportedMediaMimeType(const WTF::String& mimeType); + + // these members are shared with webview.cpp + static Mutex gFrameCacheMutex; + CachedRoot* m_frameCacheKit; // nav data being built by webcore + SkPicture* m_navPictureKit; + int m_moveGeneration; // copy of state in WebViewNative triggered by move + int m_touchGeneration; // copy of state in WebViewNative triggered by touch + int m_lastGeneration; // last action using up to date cache + bool m_updatedFrameCache; + bool m_findIsUp; + bool m_hasCursorBounds; + WebCore::IntRect m_cursorBounds; + WebCore::IntRect m_cursorHitBounds; + void* m_cursorFrame; + IntPoint m_cursorLocation; + void* m_cursorNode; + static Mutex gCursorBoundsMutex; + // These two fields go together: we use the mutex to protect access to + // m_buttons, so that we, and webview.cpp can look/modify the m_buttons + // field safely from our respective threads + static Mutex gButtonMutex; + WTF::Vector<Container> m_buttons; + // end of shared members + + // internal functions + private: + CacheBuilder& cacheBuilder(); + WebCore::Node* currentFocus(); + // Compare the new set of buttons to the old one. All of the new + // buttons either replace our old ones or should be added to our list. + // Then check the old buttons to see if any are no longer needed. + void updateButtonList(WTF::Vector<Container>* buttons); + void reset(bool fromConstructor); + // Create a set of pictures to represent the drawn DOM, driven by + // the invalidated region and the time required to draw (used to draw) + void recordPictureSet(PictureSet* master); + + friend class ListBoxReply; + struct JavaGlue; + struct JavaGlue* m_javaGlue; + WebCore::Frame* m_mainFrame; + WebCoreReply* m_popupReply; + WebCore::Node* m_lastFocused; + WebCore::IntRect m_lastFocusedBounds; + int m_blurringNodePointer; + int m_lastFocusedSelStart; + int m_lastFocusedSelEnd; + PictureSet m_content; // the set of pictures to draw + SkRegion m_addInval; // the accumulated inval region (not yet drawn) + SkRegion m_rebuildInval; // the accumulated region for rebuilt pictures + // Used in passToJS to avoid updating the UI text field until after the + // key event has been processed. + bool m_blockTextfieldUpdates; + bool m_focusBoundsChanged; + bool m_skipContentDraw; + // Passed in with key events to know when they were generated. Store it + // with the cache so that we can ignore stale text changes. + int m_textGeneration; + CachedRoot* m_temp; + SkPicture* m_tempPict; + int m_maxXScroll; + int m_maxYScroll; + int m_scrollOffsetX; // webview.java's current scroll in X + int m_scrollOffsetY; // webview.java's current scroll in Y + WebCore::IntPoint m_mousePos; + bool m_frameCacheOutOfDate; + bool m_progressDone; + int m_lastPassed; + int m_lastVelocity; + CachedHistory m_history; + int m_screenWidth; // width of the visible rect in document coordinates + int m_screenHeight;// height of the visible rect in document coordinates + int m_textWrapWidth; + float m_scale; + unsigned m_domtree_version; + bool m_check_domtree_version; + PageGroup* m_groupForVisitedLinks; + bool m_isPaused; + int m_cacheMode; + bool m_shouldPaintCaret; + + SkTDArray<PluginWidgetAndroid*> m_plugins; + WebCore::Timer<WebViewCore> m_pluginInvalTimer; + void pluginInvalTimerFired(WebCore::Timer<WebViewCore>*) { + this->drawPlugins(); + } + int m_screenOnCounter; + + void doMaxScroll(CacheBuilder::Direction dir); + SkPicture* rebuildPicture(const SkIRect& inval); + void rebuildPictureSet(PictureSet* ); + void sendNotifyProgressFinished(); + /* + * Handle a mouse click, either from a touch or trackball press. + * @param frame Pointer to the Frame containing the node that was clicked on. + * @param node Pointer to the Node that was clicked on. + * @param fake This is a fake mouse click, used to put a textfield into focus. Do not + * open the IME. + */ + bool handleMouseClick(WebCore::Frame*, WebCore::Node*, bool fake); + WebCore::HTMLAnchorElement* retrieveAnchorElement(int x, int y); + WebCore::HTMLElement* retrieveElement(int x, int y, + const WebCore::QualifiedName& ); + WebCore::HTMLImageElement* retrieveImageElement(int x, int y); + // below are members responsible for accessibility support + String modifySelectionTextNavigationAxis(DOMSelection* selection, int direction, int granularity); + String modifySelectionDomNavigationAxis(DOMSelection* selection, int direction, int granularity); + Text* traverseNextContentTextNode(Node* fromNode, Node* toNode ,int direction); + bool isVisible(Node* node); + bool isHeading(Node* node); + String formatMarkup(DOMSelection* selection); + void selectAt(int x, int y); + Node* m_currentNodeDomNavigationAxis; + void scrollNodeIntoView(Frame* frame, Node* node); + bool isContentTextNode(Node* node); + Node* getIntermediaryInputElement(Node* fromNode, Node* toNode, int direction); + bool isContentInputElement(Node* node); + bool isDescendantOf(Node* parent, Node* node); + void advanceAnchorNode(DOMSelection* selection, int direction, String& markup, bool ignoreFirstNode, ExceptionCode& ec); + Node* getNextAnchorNode(Node* anchorNode, bool skipFirstHack, int direction); + Node* getImplicitBoundaryNode(Node* node, unsigned offset, int direction); + +#if ENABLE(TOUCH_EVENTS) + bool m_forwardingTouchEvents; +#endif +#if DEBUG_NAV_UI + uint32_t m_now; +#endif + DeviceMotionAndOrientationManager m_deviceMotionAndOrientationManager; +#if USE(CHROME_NETWORK_STACK) + scoped_refptr<WebRequestContext> m_webRequestContext; +#endif + + // called from constructor, to add this to a global list + static void addInstance(WebViewCore*); + // called from destructor, to remove this from a global list + static void removeInstance(WebViewCore*); + }; + +} // namespace android + +#endif // WEBVIEWCORE_H |