diff options
Diffstat (limited to 'WebKit/android/jni')
22 files changed, 8047 insertions, 0 deletions
diff --git a/WebKit/android/jni/JavaBridge.cpp b/WebKit/android/jni/JavaBridge.cpp new file mode 100644 index 0000000..bd73b0a --- /dev/null +++ b/WebKit/android/jni/JavaBridge.cpp @@ -0,0 +1,331 @@ +/* +** Copyright 2006-2008, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +#define LOG_TAG "webcoreglue" + +#include <config.h> +#include <wtf/Platform.h> + +#include "Cache.h" +#include "CookieClient.h" +#include "JavaSharedClient.h" +#include "KURL.h" +#include "NetworkStateNotifier.h" +#include "Timer.h" +#include "TimerClient.h" +#include "jni_utility.h" +#include "WebCoreJni.h" + +#ifdef ANDROID_INSTRUMENT +#include "Frame.h" +#include "SystemTime.h" +#endif + +#include <jni.h> +#include <JNIHelp.h> +#include <SkImageRef_GlobalPool.h> +#include <SkUtils.h> +#include <utils/misc.h> + +// maximum bytes used to cache decoded images +// (not including big images using ashmem) +#define IMAGE_POOL_BUDGET (512 * 1024) + +#ifdef ANDROID_INSTRUMENT +static uint32_t sTotalTimeUsed = 0; + +namespace WebCore { +void Frame::resetSharedTimerTimeCounter() +{ + sTotalTimeUsed = 0; +} + +void Frame::reportSharedTimerTimeCounter() +{ + LOG(LOG_DEBUG, "WebCore", "*-* Total native 2 (shared timer) time: %d ms\n", + sTotalTimeUsed); +} +} +#endif + +namespace android { + +// ---------------------------------------------------------------------------- + +static jfieldID gJavaBridge_ObjectID; + +// ---------------------------------------------------------------------------- + +class JavaBridge : public WebCore::TimerClient, public WebCore::CookieClient +{ +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, WebCore::KURL const& docURL, WebCore::String const& value); + virtual WebCore::String cookies(WebCore::KURL const& url); + virtual bool cookiesEnabled(); + + //////////////////////////////////////////// + + virtual void setSharedTimerCallback(void (*f)()); + + //////////////////////////////////////////// + + 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 SetDeferringTimers(JNIEnv* env, jobject obj, jboolean defer); + static void ServiceFuncPtrQueue(JNIEnv*); + +private: + JavaVM* mJvm; + jobject mJavaObject; + jmethodID mSetSharedTimer; + jmethodID mStopSharedTimer; + jmethodID mSetCookies; + jmethodID mCookies; + jmethodID mCookiesEnabled; + jmethodID mSignalFuncPtrQueue; +}; + +static void (*sSharedTimerFiredCallback)(); +static JavaBridge* gJavaBridge; + +JavaBridge::JavaBridge(JNIEnv* env, jobject obj) +{ + mJvm = jnienv_to_javavm(env); + mJavaObject = adoptGlobalRef(env, 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;Ljava/lang/String;)V"); + mCookies = env->GetMethodID(clazz, "cookies", "(Ljava/lang/String;)Ljava/lang/String;"); + mCookiesEnabled = env->GetMethodID(clazz, "cookiesEnabled", "()Z"); + mSignalFuncPtrQueue = env->GetMethodID(clazz, "signalServiceFuncPtrQueue", "()V"); + + 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"); + + WebCore::JavaSharedClient::SetTimerClient(this); + WebCore::JavaSharedClient::SetCookieClient(this); + gJavaBridge = this; +} + +JavaBridge::~JavaBridge() +{ + if (mJavaObject) { + JNIEnv* env = javavm_to_jnienv(mJvm); + env->DeleteGlobalRef(mJavaObject); + mJavaObject = 0; + } + + WebCore::JavaSharedClient::SetTimerClient(NULL); + WebCore::JavaSharedClient::SetCookieClient(NULL); +} + +void +JavaBridge::setSharedTimer(long long timemillis) +{ + JNIEnv* env = javavm_to_jnienv(mJvm); + AutoJObject obj = getRealObject(env, mJavaObject); + env->CallVoidMethod(obj.get(), mSetSharedTimer, timemillis); +} + +void +JavaBridge::stopSharedTimer() +{ + JNIEnv* env = javavm_to_jnienv(mJvm); + AutoJObject obj = getRealObject(env, mJavaObject); + env->CallVoidMethod(obj.get(), mStopSharedTimer); +} + +void +JavaBridge::setCookies(WebCore::KURL const& url, WebCore::KURL const& docUrl, WebCore::String const& value) +{ + JNIEnv* env = javavm_to_jnienv(mJvm); + const WebCore::String& urlStr = url.string(); + jstring jUrlStr = env->NewString(urlStr.characters(), urlStr.length()); + const WebCore::String& docUrlStr = docUrl.string(); + jstring jDocUrlStr = env->NewString(docUrlStr.characters(), docUrlStr.length()); + jstring jValueStr = env->NewString(value.characters(), value.length()); + + AutoJObject obj = getRealObject(env, mJavaObject); + env->CallVoidMethod(obj.get(), mSetCookies, jUrlStr, jDocUrlStr, jValueStr); + env->DeleteLocalRef(jUrlStr); + env->DeleteLocalRef(jDocUrlStr); + env->DeleteLocalRef(jValueStr); +} + +WebCore::String +JavaBridge::cookies(WebCore::KURL const& url) +{ + JNIEnv* env = javavm_to_jnienv(mJvm); + const WebCore::String& urlStr = url.string(); + jstring jUrlStr = env->NewString(urlStr.characters(), urlStr.length()); + + AutoJObject obj = getRealObject(env, mJavaObject); + jstring string = (jstring)(env->CallObjectMethod(obj.get(), mCookies, jUrlStr)); + + WebCore::String ret = to_string(env, string); + env->DeleteLocalRef(jUrlStr); + env->DeleteLocalRef(string); + return ret; +} + +bool +JavaBridge::cookiesEnabled() +{ + JNIEnv* env = javavm_to_jnienv(mJvm); + AutoJObject obj = getRealObject(env, mJavaObject); + jboolean ret = env->CallBooleanMethod(obj.get(), mCookiesEnabled); + return (ret != 0); +} + +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. + JSC::Bindings::getJNIEnv(); + JNIEnv* env = javavm_to_jnienv(mJvm); + AutoJObject obj = getRealObject(env, mJavaObject); + env->CallVoidMethod(obj.get(), mSignalFuncPtrQueue); +} + +// ---------------------------------------------------------------------------- + +// visible to Shared +void AndroidSignalServiceFuncPtrQueue() +{ + gJavaBridge->signalServiceFuncPtrQueue(); +} + +// ---------------------------------------------------------------------------- + +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 + uint32_t startTime = WebCore::get_thread_msec(); +#endif + SkAutoMemoryUsageProbe mup("JavaBridge::sharedTimerFired"); + sSharedTimerFiredCallback(); +#ifdef ANDROID_INSTRUMENT + sTotalTimeUsed += WebCore::get_thread_msec() - startTime; +#endif + } +} + +void JavaBridge::SetCacheSize(JNIEnv* env, jobject obj, jint bytes) +{ + WebCore::cache()->setCapacities(0, bytes/2, bytes); + SkImageRef_GlobalPool::SetRAMBudget(IMAGE_POOL_BUDGET); + LOGV("--- set ImageRef budget %d\n", SkImageRef_GlobalPool::GetRAMBudget()); +} + +void JavaBridge::SetNetworkOnLine(JNIEnv* env, jobject obj, jboolean online) +{ + WebCore::networkStateNotifier().networkStateChange(online); +} + +void JavaBridge::SetDeferringTimers(JNIEnv* env, jobject obj, jboolean defer) +{ + WebCore::setDeferringTimers(defer); +} + +void JavaBridge::ServiceFuncPtrQueue(JNIEnv*) +{ + WebCore::JavaSharedClient::ServiceFunctionPtrQueue(); +} + +// ---------------------------------------------------------------------------- + +/* + * 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 }, + { "setDeferringTimers", "(Z)V", + (void*) JavaBridge::SetDeferringTimers }, + { "nativeServiceFuncPtrQueue", "()V", + (void*) JavaBridge::ServiceFuncPtrQueue }, +}; + +int register_javabridge(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"); + + return jniRegisterNativeMethods(env, "android/webkit/JWebCoreJavaBridge", + gWebCoreJavaBridgeMethods, NELEM(gWebCoreJavaBridgeMethods)); +} + +} /* namespace android */ diff --git a/WebKit/android/jni/JavaSharedClient.cpp b/WebKit/android/jni/JavaSharedClient.cpp new file mode 100644 index 0000000..aaefd72 --- /dev/null +++ b/WebKit/android/jni/JavaSharedClient.cpp @@ -0,0 +1,106 @@ +/* +** +** Copyright 2007, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +#include "config.h" +#include "JavaSharedClient.h" +#define LOG_TAG "JavaSharedClient" +#include "utils/Log.h" +#include "SkDeque.h" +#include "SkThread.h" + +namespace android { + void AndroidSignalServiceFuncPtrQueue(); +} + +namespace WebCore { + + TimerClient* JavaSharedClient::GetTimerClient() + { + //LOG_ASSERT(gTimerClient != NULL, "gTimerClient not initialized!!!"); + return gTimerClient; + } + + CookieClient* JavaSharedClient::GetCookieClient() + { + //LOG_ASSERT(gCookieClient != NULL, "gCookieClient not initialized!!!"); + return gCookieClient; + } + + void JavaSharedClient::SetTimerClient(TimerClient* client) + { + //LOG_ASSERT(gTimerClient == NULL || client == NULL, "gTimerClient already set, aborting..."); + gTimerClient = client; + } + + void JavaSharedClient::SetCookieClient(CookieClient* client) + { + //LOG_ASSERT(gCookieClient == NULL || client == NULL, "gCookieClient already set, aborting..."); + gCookieClient = client; + } + + TimerClient* JavaSharedClient::gTimerClient = NULL; + CookieClient* JavaSharedClient::gCookieClient = 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(); + + android::AndroidSignalServiceFuncPtrQueue(); + } + + void JavaSharedClient::ServiceFunctionPtrQueue() + { + for (;;) { + void (*proc)(void*); + void* payload; + 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 (NULL != rec) { + proc = rec->fProc; + payload = rec->fPayload; + gFuncPtrQ.pop_front(); + } + gFuncPtrQMutex.release(); + + if (NULL == rec) { + break; + } + proc(payload); + } + } +} diff --git a/WebKit/android/jni/JavaSharedClient.h b/WebKit/android/jni/JavaSharedClient.h new file mode 100644 index 0000000..470c4dd --- /dev/null +++ b/WebKit/android/jni/JavaSharedClient.h @@ -0,0 +1,45 @@ +/* +** +** Copyright 2007, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +#ifndef JAVA_SHARED_CLIENT_H +#define JAVA_SHARED_CLIENT_H + +namespace WebCore { + + class TimerClient; + class CookieClient; + + class JavaSharedClient + { + public: + static TimerClient* GetTimerClient(); + static CookieClient* GetCookieClient(); + + static void SetTimerClient(TimerClient* client); + static void SetCookieClient(CookieClient* 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; + }; +} +#endif diff --git a/WebKit/android/jni/PictureSet.cpp b/WebKit/android/jni/PictureSet.cpp new file mode 100644 index 0000000..e02e96a --- /dev/null +++ b/WebKit/android/jni/PictureSet.cpp @@ -0,0 +1,682 @@ +/* + * Copyright 2008, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#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 "SystemTime.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; + pictureAndBounds.mPicture->safeRef(); + pictureAndBounds.mWroteElapsed = false; + mPictures.append(pictureAndBounds); +} + +void PictureSet::add(const SkRegion& area, SkPicture* picture, + uint32_t elapsed, bool split) +{ + DBG_SET_LOGD("%p {%d,%d,r=%d,b=%d} elapsed=%d split=%d", this, + area.getBounds().fLeft, area.getBounds().fTop, + area.getBounds().fRight, area.getBounds().fBottom, + elapsed, split); + picture->safeRef(); + /* 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}; + 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) { + working->mPicture->safeUnref(); + 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() +{ + // dump(__FUNCTION__); + Pictures* last = mPictures.end(); + for (Pictures* working = mPictures.begin(); working != last; working++) { + working->mArea.setEmpty(); + working->mPicture->safeUnref(); + } + mPictures.clear(); + mWidth = mHeight = 0; +} + +bool PictureSet::draw(SkCanvas* canvas) +{ + DBG_SET_LOG(""); + 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("first=%d last=%d", 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); + pathClip.computeBounds(&pathBounds, SkPath::kFast_BoundsType); + } else { + pathBounds.set(area.getBounds()); + canvas->clipRect(pathBounds); + } + canvas->translate(pathBounds.fLeft, pathBounds.fTop); + canvas->save(); + uint32_t startTime = WebCore::get_thread_msec(); + canvas->drawPicture(*working->mPicture); + size_t elapsed = working->mElapsed = WebCore::get_thread_msec() - 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)", this, label, mPictures.size()); + const Pictures* last = mPictures.end(); + for (const Pictures* working = mPictures.begin(); working != last; working++) { + const SkIRect& bounds = working->mArea.getBounds(); + MeasureStream measure; + if (working->mPicture != NULL) + working->mPicture->serialize(&measure); + LOGD(" [%d] {%d,%d,r=%d,b=%d} elapsed=%d split=%s" + " wroteElapsed=%s base=%s pictSize=%d", + working - mPictures.begin(), + bounds.fLeft, bounds.fTop, bounds.fRight, bounds.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 void commonDrawBitmap(const SkBitmap& bitmap, + 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 + } + + 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 (emptyPicture(working->mPicture) == false) + 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; + working->mPicture->safeUnref(); + working->mPicture = NULL; + } + return true; +} + +void PictureSet::set(const PictureSet& src) +{ + DBG_SET_LOG("start"); + 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) +{ + mPictures[i].mPicture->safeUnref(); + mPictures[i].mPicture = p; +} + +void PictureSet::split(PictureSet* out) const +{ + dump(__FUNCTION__); + SkIRect totalBounds; + out->mWidth = mWidth; + out->mHeight = mHeight; + totalBounds.set(0, 0, mWidth, mHeight); + SkRegion* total = new SkRegion(totalBounds); + const Pictures* last = mPictures.end(); + uint32_t balance = 0; + bool firstTime = true; + const Pictures* singleton = NULL; + int singleOut = -1; + for (const Pictures* working = mPictures.begin(); working != last; working++) { + uint32_t elapsed = working->mElapsed; + if (elapsed < MAX_DRAW_TIME) { + if (working->mSplit) { + total->op(working->mArea, SkRegion::kDifference_Op); + DBG_SET_LOGD("%p total->getBounds()={%d,%d,r=%d,b=%d", this, + total->getBounds().fLeft, total->getBounds().fTop, + total->getBounds().fRight, total->getBounds().fBottom); + singleOut = out->mPictures.end() - out->mPictures.begin(); + out->add(working->mArea, working->mPicture, elapsed, true); + continue; + } + if (firstTime) { + singleton = working; + DBG_SET_LOGD("%p firstTime working=%p working->mArea=" + "{%d,%d,r=%d,b=%d}", this, working, + working->mArea.getBounds().fLeft, + working->mArea.getBounds().fTop, + working->mArea.getBounds().fRight, + working->mArea.getBounds().fBottom); + out->add(working->mArea, working->mPicture, elapsed, false); + firstTime = false; + } else { + if (singleOut >= 0) { + Pictures& outWork = out->mPictures[singleOut]; + DBG_SET_LOGD("%p clear singleton outWork=%p outWork->mArea=" + "{%d,%d,r=%d,b=%d}", this, &outWork, + outWork.mArea.getBounds().fLeft, + outWork.mArea.getBounds().fTop, + outWork.mArea.getBounds().fRight, + outWork.mArea.getBounds().fBottom); + outWork.mArea.setEmpty(); + outWork.mPicture->safeUnref(); + outWork.mPicture = NULL; + singleOut = -1; + } + singleton = NULL; + } + 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); + left = right; + } + top = bottom; + } + } + DBG_SET_LOGD("%p w=%d h=%d total->isEmpty()=%s singleton=%p", + this, mWidth, mHeight, total->isEmpty() ? "true" : "false", singleton); + if (total->isEmpty() == false && singleton == NULL) + out->add(*total, NULL, balance, false); + delete total; + validate(__FUNCTION__); + out->dump("split-out"); +} + +void PictureSet::toPicture(SkPicture* result) const +{ + DBG_SET_LOGD("%p", this); + SkPicture tempPict; + SkAutoPictureRecord arp(&tempPict, mWidth, mHeight); + SkCanvas* recorder = arp.getRecordingCanvas(); + const Pictures* last = mPictures.end(); + for (const Pictures* working = mPictures.begin(); working != last; working++) { + int saved = recorder->save(); + SkPath pathBounds; + working->mArea.getBoundaryPath(&pathBounds); + recorder->clipPath(pathBounds); + recorder->drawPicture(*working->mPicture); + recorder->restoreToCount(saved); + } + result->swap(tempPict); +} + +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/WebKit/android/jni/PictureSet.h b/WebKit/android/jni/PictureSet.h new file mode 100644 index 0000000..5ff003f --- /dev/null +++ b/WebKit/android/jni/PictureSet.h @@ -0,0 +1,90 @@ +/* + * Copyright 2008, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#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); + const SkIRect& bounds(size_t i) { + 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() { return mPictures.size(); } + void split(PictureSet* result) const; + void toPicture(SkPicture* ) const; + bool upToDate(size_t i) { 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 + }; + void add(const Pictures* temp); + WTF::Vector<Pictures> mPictures; + int mHeight; + int mWidth; + }; +} + +#endif diff --git a/WebKit/android/jni/WebCoreFrameBridge.cpp b/WebKit/android/jni/WebCoreFrameBridge.cpp new file mode 100644 index 0000000..9f55134 --- /dev/null +++ b/WebKit/android/jni/WebCoreFrameBridge.cpp @@ -0,0 +1,1253 @@ +/* +** Copyright 2006-2008, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +#define LOG_TAG "webcoreglue" + +#include <config.h> +#include <wtf/Platform.h> + +#include "android_graphics.h" +#include "Arena.h" +#include "AtomicString.h" +#include "Cache.h" +#include "ChromeClientAndroid.h" +#include "ContextMenuClientAndroid.h" +#include "CString.h" +#include "Document.h" +#include "DocumentLoader.h" +#include "DragClientAndroid.h" +#include "EditorClientAndroid.h" +#include "Element.h" +#include "Font.h" +#include "Frame.h" +#include "FrameLoader.h" +#include "FrameLoaderClientAndroid.h" +#include "FrameTree.h" +#include "FrameView.h" +#include "GCController.h" +#include "GraphicsContext.h" +#include "HistoryItem.h" +#include "HTMLElement.h" +#include "HTMLFormElement.h" +#include "HTMLInputElement.h" +#include "HTMLNames.h" +#include "IconDatabase.h" +#include "Image.h" +#include "InspectorClientAndroid.h" +#include "JSDOMWindow.h" +#include <runtime/InitializeThreading.h> +#include <runtime/JSLock.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 "ScriptController.h" +#include "SelectionController.h" +#include "Settings.h" +#include "SubstituteData.h" +#include "WebCoreFrameBridge.h" +#include "WebCoreJni.h" +#include "WebCoreResourceLoader.h" +#include "WebHistory.h" +#include "WebIconDatabase.h" +#include "WebFrameView.h" +#include "WebViewCore.h" + +#include <runtime_root.h> +#include <runtime_object.h> +#include <jni_utility.h> +#include "jni.h" +#include "jni_instance.h" + +#include <JNIHelp.h> +#include <SkGraphics.h> +#include <SkImageRef_GlobalPool.h> +#include <utils/misc.h> +#include <utils/AssetManager.h> +#include <android_runtime/android_util_AssetManager.h> + +#ifdef ANDROID_INSTRUMENT +#include "SystemTime.h" + +static uint32_t sTotalJavaTimeUsed = 0; +static uint32_t sTotalNativeTimeUsed = 0; + +namespace WebCore { +void Frame::resetFramebridgeTimeCounter() +{ + sTotalJavaTimeUsed = 0; + sTotalNativeTimeUsed = 0; +} + +void Frame::reportFramebridgeTimeCounter() +{ + LOG(LOG_DEBUG, "WebCore", "*-* Total Java callback (frame bridge) time: %d ms\n", + sTotalJavaTimeUsed); + LOG(LOG_DEBUG, "WebCore", "*-* Total native 1 (frame bridge) time: %d ms\n", + sTotalNativeTimeUsed); +} +} +#endif + +namespace android { + +#ifdef ANDROID_INSTRUMENT +class TimeCounterFB { +public: + TimeCounterFB(bool native = false) { + mNative = native; + mStartTime = WebCore::get_thread_msec(); + } + + ~TimeCounterFB() { + if (mNative) + sTotalNativeTimeUsed += WebCore::get_thread_msec() - mStartTime; + else + sTotalJavaTimeUsed += WebCore::get_thread_msec() - mStartTime; + } +private: + bool mNative; + uint32_t mStartTime; +}; +#endif + +// ---------------------------------------------------------------------------- + +#define WEBCORE_MEMORY_CAP 15 * 1024 * 1024 + +// ---------------------------------------------------------------------------- + +struct WebFrame::JavaBrowserFrame +{ + JavaVM* mJVM; + jobject mObj; + jobject mHistoryList; // WebBackForwardList object + jmethodID mStartLoadingResource; + jmethodID mLoadStarted; + jmethodID mTransitionToCommitted; + jmethodID mLoadFinished; + jmethodID mReportError; + jmethodID mSetTitle; + jmethodID mWindowObjectCleared; + jmethodID mSetProgress; + jmethodID mDidReceiveIcon; + jmethodID mUpdateVisitedHistory; + jmethodID mHandleUrl; + jmethodID mCreateWindow; + jmethodID mCloseWindow; + jmethodID mDecidePolicyForFormResubmission; + jmethodID mRequestFocus; + jmethodID mGetRawResFilename; + 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->mJVM = jnienv_to_javavm(env); + mJavaFrame->mObj = adoptGlobalRef(env, obj); + mJavaFrame->mHistoryList = adoptGlobalRef(env, historyList); + mJavaFrame->mStartLoadingResource = env->GetMethodID(clazz, "startLoadingResource", + "(ILjava/lang/String;Ljava/lang/String;Ljava/util/HashMap;[BIZZ)Landroid/webkit/LoadListener;"); + 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->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;"); + + LOG_ASSERT(mJavaFrame->mStartLoadingResource, "Could not find method startLoadingResource"); + 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->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"); + + mUserAgent = WebCore::String(); + mUserInitiatedClick = false; +} + +WebFrame::~WebFrame() +{ + if (mJavaFrame->mObj) { + JNIEnv* env = javavm_to_jnienv(mJavaFrame->mJVM); + env->DeleteGlobalRef(mJavaFrame->mObj); + env->DeleteGlobalRef(mJavaFrame->mHistoryList); + mJavaFrame->mObj = 0; + } + delete mJavaFrame; +} + +WebFrame* WebFrame::getWebFrame(const WebCore::Frame* frame) +{ + WebCore::FrameLoaderClientAndroid* client = + static_cast<WebCore::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 = env->NewString((unsigned short *)i->first.characters(), i->first.length()); + jstring val = env->NewString((unsigned short *)i->second.characters(), i->second.length()); + if (key && val) { + env->CallObjectMethod(hashMap, put, key, val); + env->DeleteLocalRef(key); + env->DeleteLocalRef(val); + } + } + env->DeleteLocalRef(mapClass); + + return hashMap; +} + +WebCoreResourceLoader* +WebFrame::startLoadingResource(WebCore::ResourceHandle* loader, + const WebCore::ResourceRequest& request, + bool isHighPriority, bool synchronous) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterFB counter; +#endif + LOGV("::WebCore:: startLoadingResource(%p, %s)", + loader, request.url().string().ascii().data()); + + WebCore::String method = request.httpMethod(); + WebCore::HTTPHeaderMap headers = request.httpHeaderFields(); + + JNIEnv* env = javavm_to_jnienv(mJavaFrame->mJVM); + WebCore::String urlStr = request.url().string(); + jstring jUrlStr = env->NewString(urlStr.characters(), urlStr.length()); + jstring jMethodStr = NULL; + if (!method.isEmpty()) + jMethodStr = env->NewString(method.characters(), method.length()); + jbyteArray jPostDataStr = NULL; + WebCore::FormData* formdata = request.httpBody(); + if (formdata) { + // 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(); + for (size_t i = 0; i < n; ++i) { + const WebCore::FormDataElement& e = elements[i]; + if (e.m_type == WebCore::FormDataElement::data) { + size += e.m_data.size(); + } + } + + // 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; + } + } + env->ReleaseByteArrayElements(jPostDataStr, bytes, 0); + } + } + } + + jobject jHeaderMap = createJavaMapFromHTTPHeaders(env, headers); + + // Convert the WebCore Cache Policy to a WebView Cache Policy. + int cacheMode = 0; // WebView.LOAD_NORMAL + switch (request.cachePolicy()) { + case WebCore::ReloadIgnoringCacheData: + cacheMode = 2; // WebView.LOAD_NO_CACHE + break; + case WebCore::ReturnCacheDataDontLoad: + cacheMode = 3; // WebView.LOAD_CACHE_ONLY + break; + case WebCore::ReturnCacheDataElseLoad: + cacheMode = 1; // WebView.LOAD_CACHE_ELSE_NETWORK + break; + case WebCore::UseProtocolCachePolicy: + default: + break; + } + + LOGV("::WebCore:: startLoadingResource %s with cacheMode %d", urlStr.ascii().data(), cacheMode); + + + jobject jLoadListener = + env->CallObjectMethod(mJavaFrame->frame(env).get(), mJavaFrame->mStartLoadingResource, + (int)loader, jUrlStr, jMethodStr, jHeaderMap, + jPostDataStr, cacheMode, isHighPriority, synchronous); + + env->DeleteLocalRef(jUrlStr); + env->DeleteLocalRef(jMethodStr); + env->DeleteLocalRef(jPostDataStr); + env->DeleteLocalRef(jHeaderMap); + if (checkException(env)) + return NULL; + + WebCoreResourceLoader* h = NULL; + if (jLoadListener) + h = new WebCoreResourceLoader(env, jLoadListener); + env->DeleteLocalRef(jLoadListener); + return h; +} + +void +WebFrame::reportError(int errorCode, const WebCore::String& description, + const WebCore::String& failingUrl) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterFB counter; +#endif + LOGV("::WebCore:: reportError(%d, %s)", errorCode, description.ascii().data()); + JNIEnv* env = javavm_to_jnienv(mJavaFrame->mJVM); + + jstring descStr = env->NewString((unsigned short*)description.characters(), description.length()); + jstring failUrl = env->NewString((unsigned short*)failingUrl.characters(), failingUrl.length()); + 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 + TimeCounterFB counter; +#endif + const WebCore::KURL& url = frame->loader()->activeDocumentLoader()->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::FrameLoadTypeSame || + (loadType == WebCore::FrameLoadTypeRedirectWithLockedHistory && + !isMainFrame)) + return; + + JNIEnv* env = javavm_to_jnienv(mJavaFrame->mJVM); + WebCore::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 = env->NewString((unsigned short*)urlString.characters(), urlString.length()); + + env->CallVoidMethod(mJavaFrame->frame(env).get(), mJavaFrame->mLoadStarted, urlStr, favicon, + (int)loadType, isMainFrame); + checkException(env); + env->DeleteLocalRef(urlStr); + if (favicon) + env->DeleteLocalRef(favicon); +} + +void +WebFrame::transitionToCommitted(WebCore::Frame* frame) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterFB counter; +#endif + JNIEnv* env = javavm_to_jnienv(mJavaFrame->mJVM); + 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 + TimeCounterFB counter; +#endif + JNIEnv* env = javavm_to_jnienv(mJavaFrame->mJVM); + WebCore::FrameLoader* loader = frame->loader(); + const WebCore::KURL& url = loader->activeDocumentLoader()->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(); + WebCore::String urlString(url.string()); + jstring urlStr = env->NewString((unsigned short*)urlString.characters(), urlString.length()); + 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 + TimeCounterFB counter; +#endif + LOGV("::WebCore:: addHistoryItem"); + JNIEnv* env = javavm_to_jnienv(mJavaFrame->mJVM); + WebHistory::AddItem(mJavaFrame->history(env), item); +} + +void +WebFrame::removeHistoryItem(int index) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterFB counter; +#endif + LOGV("::WebCore:: removeHistoryItem at %d", index); + JNIEnv* env = javavm_to_jnienv(mJavaFrame->mJVM); + WebHistory::RemoveItem(mJavaFrame->history(env), index); +} + +void +WebFrame::updateHistoryIndex(int newIndex) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterFB counter; +#endif + LOGV("::WebCore:: updateHistoryIndex to %d", newIndex); + JNIEnv* env = javavm_to_jnienv(mJavaFrame->mJVM); + WebHistory::UpdateHistoryIndex(mJavaFrame->history(env), newIndex); +} + +void +WebFrame::setTitle(const WebCore::String& title) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterFB counter; +#endif +#ifndef NDEBUG + LOGV("setTitle(%s)", title.ascii().data()); +#endif + JNIEnv* env = javavm_to_jnienv(mJavaFrame->mJVM); + jstring jTitleStr = env->NewString((unsigned short *)title.characters(), title.length()); + + env->CallVoidMethod(mJavaFrame->frame(env).get(), mJavaFrame->mSetTitle, + jTitleStr); + checkException(env); + env->DeleteLocalRef(jTitleStr); +} + +void +WebFrame::windowObjectCleared(WebCore::Frame* frame) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterFB counter; +#endif + LOGV("::WebCore:: windowObjectCleared"); + JNIEnv* env = javavm_to_jnienv(mJavaFrame->mJVM); + + env->CallVoidMethod(mJavaFrame->frame(env).get(), mJavaFrame->mWindowObjectCleared, (int)frame); + checkException(env); +} + +void +WebFrame::setProgress(float newProgress) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterFB counter; +#endif + JNIEnv* env = javavm_to_jnienv(mJavaFrame->mJVM); + int progress = (int) (100 * newProgress); + env->CallVoidMethod(mJavaFrame->frame(env).get(), mJavaFrame->mSetProgress, progress); + checkException(env); +} + +const WebCore::String +WebFrame::userAgentForURL(const WebCore::KURL* url) +{ + return mUserAgent; +} + +void +WebFrame::didReceiveIcon(WebCore::Image* icon) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterFB counter; +#endif + LOG_ASSERT(icon, "DidReceiveIcon called without an image!"); + JNIEnv* env = javavm_to_jnienv(mJavaFrame->mJVM); + jobject bitmap = webcoreImageToJavaBitmap(env, icon); + if (!bitmap) + return; + + env->CallVoidMethod(mJavaFrame->frame(env).get(), mJavaFrame->mDidReceiveIcon, bitmap); + env->DeleteLocalRef(bitmap); + checkException(env); +} + +void +WebFrame::updateVisitedHistory(const WebCore::KURL& url, bool reload) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterFB counter; +#endif + WebCore::String urlStr(url.string()); + JNIEnv* env = javavm_to_jnienv(mJavaFrame->mJVM); + jstring jUrlStr = env->NewString((unsigned short*)urlStr.characters(), urlStr.length()); + + env->CallVoidMethod(mJavaFrame->frame(env).get(), mJavaFrame->mUpdateVisitedHistory, jUrlStr, reload); + checkException(env); +} + +bool +WebFrame::canHandleRequest(const WebCore::ResourceRequest& request) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterFB counter; +#endif + // Internal loads are ok but any request that is due to a user hitting a key + // should be checked. + bool userGesture = false; +#ifdef ANDROID_USER_GESTURE + userGesture = request.userGesture(); +#endif + WebCore::KURL requestUrl = request.url(); + if (!mUserInitiatedClick && !userGesture && + (requestUrl.protocolIs("http") || requestUrl.protocolIs("https") || + requestUrl.protocolIs("file") || requestUrl.protocolIs("about") || + requestUrl.protocolIs("javascript"))) + return true; + WebCore::String url(request.url().string()); + // Empty urls should not be sent to java + if (url.isEmpty()) + return true; + JNIEnv* env = javavm_to_jnienv(mJavaFrame->mJVM); + jstring jUrlStr = env->NewString((unsigned short *)url.characters(), url.length()); + + // 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); + return (ret == 0); +} + +WebCore::Frame* +WebFrame::createWindow(bool dialog, bool userGesture) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterFB counter; +#endif + JNIEnv* env = javavm_to_jnienv(mJavaFrame->mJVM); + 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 + TimeCounterFB counter; +#endif + JNIEnv* env = javavm_to_jnienv(mJavaFrame->mJVM); + env->CallVoidMethod(mJavaFrame->frame(env).get(), mJavaFrame->mRequestFocus); + checkException(env); +} + +void +WebFrame::closeWindow(WebViewCore* webViewCore) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterFB counter; +#endif + assert(webViewCore); + JNIEnv* env = javavm_to_jnienv(mJavaFrame->mJVM); + 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 + TimeCounterFB counter; +#endif + JNIEnv* env = javavm_to_jnienv(mJavaFrame->mJVM); + PolicyFunctionWrapper* p = new PolicyFunctionWrapper; + p->func = func; + env->CallVoidMethod(mJavaFrame->frame(env).get(), mJavaFrame->mDecidePolicyForFormResubmission, p); +} + +WebCore::String +WebFrame::getRawResourceFilename(RAW_RES_ID id) const +{ + JNIEnv* env = javavm_to_jnienv(mJavaFrame->mJVM); + jstring ret = (jstring) env->CallObjectMethod(mJavaFrame->frame(env).get(), + mJavaFrame->mGetRawResFilename, (int)id); + + return to_string(env, ret); +} + +// ---------------------------------------------------------------------------- +static void CallPolicyFunction(JNIEnv* env, jobject obj, jint func, jint decision) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterFB counter(true); +#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!"); + + (pFrame->loader()->*(pFunc->func))((WebCore::PolicyAction)decision); +} + +static void CreateFrame(JNIEnv* env, jobject obj, jobject javaview, jobject jAssetManager, jobject historyList) +{ + JSC::initializeThreading(); +#ifdef ANDROID_INSTRUMENT + TimeCounterFB counter(true); +#endif + WebCore::ChromeClientAndroid* chromeC = new WebCore::ChromeClientAndroid; + WebCore::EditorClientAndroid* editorC = new WebCore::EditorClientAndroid; + WebCore::ContextMenuClient* contextMenuC = new WebCore::ContextMenuClientAndroid; + WebCore::DragClient* dragC = new WebCore::DragClientAndroid; + WebCore::InspectorClientAndroid* inspectorC = new WebCore::InspectorClientAndroid; + // Create a new page + WebCore::Page* page = new WebCore::Page(chromeC, contextMenuC, editorC, dragC, inspectorC); + /* TODO: Don't turn on PageCache until we can restore the ScrollView State. + * This caused bug http://b/issue?id=1202983 + page->settings()->setUsesPageCache(true); + // 10 is a random number chosen because it is small enough to give the user + // a good back/forward page cache without allowing the page cache to get too + // big. + WebCore::pageCache()->setCapacity(10); + */ + 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 chromeC and release our ownership + chromeC->setWebFrame(webFrame); + Release(webFrame); + + WebCore::FrameLoaderClientAndroid* loaderC = new WebCore::FrameLoaderClientAndroid(webFrame); + // Create a Frame and the page holds its reference + WebCore::Frame* frame = WebCore::Frame::create(page, NULL, loaderC).get(); + loaderC->setFrame(frame); + + // Create a WebViewCore to access the Java WebViewCore associated with this page + WebViewCore* webViewCore = new WebViewCore(env, javaview, frame); + + // Create a FrameView + WebCore::FrameView* frameView = new WebCore::FrameView(frame); + // Create a WebFrameView + WebFrameView* webFrameView = new WebFrameView(frameView, 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); + frameView->deref(); + + // Set the frame to active to turn on keyboard focus. + frame->init(); + frame->selection()->setFocused(true); + + // Allow local access to file:/// and substitute data + WebCore::FrameLoader::setLocalLoadPolicy( + WebCore::FrameLoader::AllowLocalLoadsForLocalAndSubstituteData); + + LOGV("::WebCore:: createFrame %p", frame); + + // Set the mNativeFrame field in Frame + SET_NATIVE_FRAME(env, obj, (int)frame); + + // Setup the asset manager. + AssetManager* am = assetManagerForJavaObject(env, jAssetManager); + // Initialize our skinning classes + WebCore::RenderSkinAndroid::Init(am); +} + +static void DestroyFrame(JNIEnv* env, jobject obj) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterFB counter(true); +#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); +} + +static void LoadUrl(JNIEnv *env, jobject obj, jstring url) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterFB counter(true); +#endif + WebCore::Frame* pFrame = GET_NATIVE_FRAME(env, obj); + LOG_ASSERT(pFrame, "nativeLoadUrl must take a valid frame pointer!"); + + WebCore::String webcoreUrl = to_string(env, url); + WebCore::ResourceRequest request(webcoreUrl); + LOGV("LoadUrl %s", webcoreUrl.latin1().data()); + pFrame->loader()->load(request); +} + + +static void LoadData(JNIEnv *env, jobject obj, jstring baseUrl, jstring data, + jstring mimeType, jstring encoding, jstring failUrl) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterFB counter(true); +#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(to_string(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, + to_string(env, mimeType), to_string(env, encoding), + WebCore::KURL(to_string(env, failUrl))); + + // Perform the load + pFrame->loader()->load(request, substituteData); +} + +static void StopLoading(JNIEnv *env, jobject obj) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterFB counter(true); +#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(); +} + +static jstring ExternalRepresentation(JNIEnv *env, jobject obj) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterFB counter(true); +#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 + WebCore::String renderDump = WebCore::externalRepresentation(pFrame->contentRenderer()); + unsigned len = renderDump.length(); + if (!len) + return NULL; + return env->NewString(renderDump.characters(), len); +} + +static jstring DocumentAsText(JNIEnv *env, jobject obj) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterFB counter(true); +#endif + WebCore::Frame* pFrame = GET_NATIVE_FRAME(env, obj); + LOG_ASSERT(pFrame, "android_webcore_nativeDocumentAsText must take a valid frame pointer!"); + + WebCore::Element *documentElement = pFrame->document()->documentElement(); + if (!documentElement) + return NULL; + WebCore::String renderDump = ((WebCore::HTMLElement*)documentElement)->innerText(); + renderDump.append("\n"); + unsigned len = renderDump.length(); + if (!len) + return NULL; + return env->NewString((unsigned short*)renderDump.characters(), len); +} + +static void Reload(JNIEnv *env, jobject obj, jboolean allowStale) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterFB counter(true); +#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) + loader->reloadAllowingStaleData(loader->documentLoader()->overrideEncoding()); + else + loader->reload(); +} + +static void GoBackOrForward(JNIEnv *env, jobject obj, jint pos) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterFB counter(true); +#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->loader()->goBackOrForward(pos); +} + +static jobject StringByEvaluatingJavaScriptFromString(JNIEnv *env, jobject obj, jstring script) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterFB counter(true); +#endif + WebCore::Frame* pFrame = GET_NATIVE_FRAME(env, obj); + LOG_ASSERT(pFrame, "stringByEvaluatingJavaScriptFromString must take a valid frame pointer!"); + + JSC::JSValue* r = + pFrame->loader()->executeScript(to_string(env, script), true); + WebCore::String result = WebCore::String(); + if (r) { + // note: r->getString() returns a UString. + result = WebCore::String(r->isString() ? r->getString() : + r->toString(pFrame->script()->globalObject()->globalExec())); + } + + unsigned len = result.length(); + if (len == 0) + return NULL; + return env->NewString((unsigned short*)result.characters(), len); +} + +static void AddJavascriptInterface(JNIEnv *env, jobject obj, jint nativeFramePointer, + jobject javascriptObj, jstring interfaceName) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterFB counter(true); +#endif + WebCore::Frame* pFrame = (WebCore::Frame*)nativeFramePointer; + LOG_ASSERT(pFrame, "nativeAddJavascriptInterface must take a valid frame pointer!"); + + JavaVM* vm; + env->GetJavaVM(&vm); + LOGV("::WebCore:: addJSInterface: %p", pFrame); + + // Copied from qwebframe.cpp + JSC::JSLock lock(false); + WebCore::JSDOMWindow *window = WebCore::toJSDOMWindow(pFrame); + JSC::Bindings::RootObject *root = pFrame->script()->bindingRootObject(); + if (window) { + JSC::Bindings::setJavaVM(vm); + // Add the binding to JS environment + JSC::ExecState* exec = window->globalExec(); + JSC::JSObject *addedObject = JSC::Bindings::Instance::createRuntimeObject( + exec, JSC::Bindings::JavaInstance::create(javascriptObj, root)); + // Add the binding name to the window's table of child objects. + JSC::PutPropertySlot slot; + window->put(exec, + JSC::Identifier(exec, to_string(env, interfaceName)), + addedObject, slot); + } +} + +static void SetCacheDisabled(JNIEnv *env, jobject obj, jboolean disabled) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterFB counter(true); +#endif + WebCore::cache()->setDisabled(disabled); +} + +static jboolean CacheDisabled(JNIEnv *env, jobject obj) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterFB counter(true); +#endif + return WebCore::cache()->disabled(); +} + +static void ClearCache(JNIEnv *env, jobject obj) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterFB counter(true); +#endif + 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); + } + // force JavaScript to GC when clear cache + WebCore::gcController().garbageCollectSoon(); + // clear image cache + SkImageRef_GlobalPool::SetRAMUsed(0); +} + +static jboolean DocumentHasImages(JNIEnv *env, jobject obj) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterFB counter(true); +#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 + TimeCounterFB counter(true); +#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(); + while (node && !found) { + WTF::Vector<WebCore::HTMLFormControlElement*> elements = + ((WebCore::HTMLFormElement*)node)->formElements; + size_t size = elements.size(); + for (size_t i = 0; i< size && !found; i++) { + WebCore::HTMLFormControlElement* e = elements[i]; + if (e->hasLocalName(WebCore::HTMLNames::inputTag)) { + if (((WebCore::HTMLInputElement*)e)->inputType() == + WebCore::HTMLInputElement::PASSWORD) + found = true; + } + } + node = form->nextItem(); + } + return found; +} + +static jobjectArray GetUsernamePassword(JNIEnv *env, jobject obj) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterFB counter(true); +#endif + WebCore::Frame* pFrame = GET_NATIVE_FRAME(env, obj); + LOG_ASSERT(pFrame, "GetUsernamePassword must take a valid frame pointer!"); + jobjectArray strArray = NULL; + + WebCore::String username, password; + bool found = false; + WTF::PassRefPtr<WebCore::HTMLCollection> form = pFrame->document()->forms(); + WebCore::Node* node = form->firstItem(); + while (node && !found) { + WTF::Vector<WebCore::HTMLFormControlElement*> elements = + ((WebCore::HTMLFormElement*)node)->formElements; + size_t size = elements.size(); + for (size_t i = 0; i< size && !found; i++) { + WebCore::HTMLFormControlElement* e = elements[i]; + if (e->hasLocalName(WebCore::HTMLNames::inputTag)) { + WebCore::HTMLInputElement* input = (WebCore::HTMLInputElement*)e; + if (input->autoComplete() == false) + continue; + if (input->inputType() == WebCore::HTMLInputElement::PASSWORD) + password = input->value(); + else if (input->inputType() == WebCore::HTMLInputElement::TEXT) + username = input->value(); + if (!username.isNull() && !password.isNull()) + found = true; + } + } + node = form->nextItem(); + } + if (found) { + jclass stringClass = env->FindClass("java/lang/String"); + strArray = env->NewObjectArray(2, stringClass, NULL); + env->SetObjectArrayElement(strArray, 0, env->NewString((unsigned short *) + username.characters(), username.length())); + env->SetObjectArrayElement(strArray, 1, env->NewString((unsigned short *) + password.characters(), password.length())); + } + return strArray; +} + +static void SetUsernamePassword(JNIEnv *env, jobject obj, + jstring username, jstring password) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterFB counter(true); +#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) { + WTF::Vector<WebCore::HTMLFormControlElement*> elements = + ((WebCore::HTMLFormElement*)node)->formElements; + size_t size = elements.size(); + for (size_t i = 0; i< size && !found; i++) { + WebCore::HTMLFormControlElement* e = elements[i]; + if (e->hasLocalName(WebCore::HTMLNames::inputTag)) { + WebCore::HTMLInputElement* input = (WebCore::HTMLInputElement*)e; + if (input->autoComplete() == false) + continue; + if (input->inputType() == WebCore::HTMLInputElement::PASSWORD) + passwordEle = input; + else if (input->inputType() == WebCore::HTMLInputElement::TEXT) + usernameEle = input; + if (usernameEle != NULL && passwordEle != NULL) + found = true; + } + } + node = form->nextItem(); + } + if (found) { + usernameEle->setValue(to_string(env, username)); + passwordEle->setValue(to_string(env, password)); + } +} + +static jobject GetFormTextData(JNIEnv *env, jobject obj) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterFB counter(true); +#endif + WebCore::Frame* pFrame = GET_NATIVE_FRAME(env, obj); + LOG_ASSERT(pFrame, "GetFormTextData must take a valid frame pointer!"); + jobject hashMap = NULL; + + WTF::PassRefPtr<WebCore::HTMLCollection> collection = pFrame->document()->forms(); + if (collection->length() > 0) { + 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"); + 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"); + + static const WebCore::AtomicString text("text"); + static const WebCore::AtomicString off("off"); + + WebCore::HTMLFormElement* form; + WebCore::HTMLInputElement* input; + for (WebCore::Node* node = collection->firstItem(); node; node = collection->nextItem()) { + form = static_cast<WebCore::HTMLFormElement*>(node); + if (form->autoComplete()) { + WTF::Vector<WebCore::HTMLFormControlElement*> elements = form->formElements; + size_t size = elements.size(); + for (size_t i = 0; i < size; i++) { + WebCore::HTMLFormControlElement* e = elements[i]; + if (e->type() == text) { + if (e->hasAttribute(WebCore::HTMLNames::autocompleteAttr)) { + const WebCore::AtomicString& attr = e->getAttribute(WebCore::HTMLNames::autocompleteAttr); + if (attr == off) + continue; + } + input = (WebCore::HTMLInputElement*) e; + WebCore::String value = input->value(); + int len = value.length(); + if (len) { + const WebCore::AtomicString& name = input->name(); + jstring key = env->NewString((jchar *)name.characters(), name.length()); + jstring val = env->NewString((jchar *)value.characters(), len); + LOG_ASSERT(key && val, "name or value not set"); + env->CallObjectMethod(hashMap, put, key, val); + env->DeleteLocalRef(key); + env->DeleteLocalRef(val); + } + } + } + } + } + env->DeleteLocalRef(mapClass); + + } + return hashMap; +} + +// ---------------------------------------------------------------------------- + +/* + * 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 }, + { "stopLoading", "()V", + (void*) StopLoading }, + { "nativeLoadUrl", "(Ljava/lang/String;)V", + (void*) LoadUrl }, + { "nativeLoadData", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V", + (void*) LoadData }, + { "externalRepresentation", "()Ljava/lang/String;", + (void*) ExternalRepresentation }, + { "documentAsText", "()Ljava/lang/String;", + (void*) DocumentAsText }, + { "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 }, + { "getFormTextData", "()Ljava/util/HashMap;", + (void*) GetFormTextData } +}; + +int register_webframe(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"); + + return jniRegisterNativeMethods(env, "android/webkit/BrowserFrame", + gBrowserFrameNativeMethods, NELEM(gBrowserFrameNativeMethods)); +} + +} /* namespace android */ + diff --git a/WebKit/android/jni/WebCoreFrameBridge.h b/WebKit/android/jni/WebCoreFrameBridge.h new file mode 100644 index 0000000..6dcae0f --- /dev/null +++ b/WebKit/android/jni/WebCoreFrameBridge.h @@ -0,0 +1,125 @@ +/* +** Copyright 2006-2008, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +// TODO: change name to WebFrame.h + +#ifndef WEBFRAME_H +#define WEBFRAME_H + +#include "FrameLoaderClient.h" +#include "PlatformString.h" +#include "WebCoreRefObject.h" +#include <jni.h> + +namespace WebCore { + class HistoryItem; + class Image; + class Page; + class RenderPart; + class ResourceHandle; + class ResourceRequest; +} + +namespace android { + +class WebCoreResourceLoader; +class WebViewCore; + +// one instance of WebFrame per Page for calling into Java's BrowserFrame +class WebFrame : public WebCoreRefObject { + public: + // these ids need to be in sync with the constants in BrowserFrame.java + enum RAW_RES_ID { + NODOMAIN = 1, + LOADERROR, + }; + WebFrame(JNIEnv* env, jobject obj, jobject historyList, WebCore::Page* page); + ~WebFrame(); + + // helper function + static WebFrame* getWebFrame(const WebCore::Frame* frame); + + WebCoreResourceLoader* startLoadingResource(WebCore::ResourceHandle*, + const WebCore::ResourceRequest& request, + bool isHighPriority, + bool synchronous); + + void reportError(int errorCode, const WebCore::String& description, + const WebCore::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 WebCore::String& title); + + void windowObjectCleared(WebCore::Frame* frame); + + void setProgress(float newProgress); + + const WebCore::String userAgentForURL(const WebCore::KURL* url); + + void didReceiveIcon(WebCore::Image* icon); + + void updateVisitedHistory(const WebCore::KURL& url, bool reload); + + 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(WebCore::String userAgent) { mUserAgent = userAgent; } + + WebCore::String getRawResourceFilename(RAW_RES_ID) const; + + /** + * When the user initiates a click (via trackball, enter-press, or touch), + * we set mUserInitiatedClick 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. + * We also check it to determine whether or not to allow webkit to request + * a scroll. If it was user initated, the scroll is allowed. + */ + void setUserInitiatedClick(bool userInitiatedClick) { mUserInitiatedClick = userInitiatedClick; } + + bool userInitiatedClick() { return mUserInitiatedClick; } + + WebCore::Page* page() const { return mPage; } + + private: + struct JavaBrowserFrame; + JavaBrowserFrame* mJavaFrame; + WebCore::Page* mPage; + WebCore::String mUserAgent; + bool mUserInitiatedClick; +}; + +} // namespace android + +#endif // WEBFRAME_H diff --git a/WebKit/android/jni/WebCoreJni.cpp b/WebKit/android/jni/WebCoreJni.cpp new file mode 100644 index 0000000..85fa720 --- /dev/null +++ b/WebKit/android/jni/WebCoreJni.cpp @@ -0,0 +1,159 @@ +/* +** +** Copyright 2007, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +#define LOG_TAG "webcoreglue" + +#include "config.h" + +#include "WebCoreJni.h" +#include "jni_utility.h" +#include <jni.h> +#include <utils/Log.h> + +namespace android { + +extern int register_webframe(JNIEnv*); +extern int register_javabridge(JNIEnv*); +extern int register_resource_loader(JNIEnv*); +extern int register_webviewcore(JNIEnv*); +extern int register_webhistory(JNIEnv*); +extern int register_webicondatabase(JNIEnv*); +extern int register_websettings(JNIEnv*); +extern int register_webview(JNIEnv*); + +// Class, constructor, and get method on WeakReference +jclass gWeakRefClass; +jmethodID gWeakRefInit; +jmethodID gWeakRefGet; + +jobject adoptGlobalRef(JNIEnv* env, jobject obj) +{ + // Create a WeakReference object + jobject ref = env->NewObject(gWeakRefClass, gWeakRefInit, obj); + // Increment the ref count of the WeakReference + ref = env->NewGlobalRef(ref); + return ref; +} + +AutoJObject getRealObject(JNIEnv* env, jobject obj) +{ + jobject real = env->CallObjectMethod(obj, gWeakRefGet); + if (!real) + LOGE("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. +WebCore::String to_string(JNIEnv* env, jstring str) +{ + if (!str || !env) + return WebCore::String(); + const jchar* s = env->GetStringChars(str, NULL); + if (!s) + return WebCore::String(); + WebCore::String ret(s, env->GetStringLength(str)); + env->ReleaseStringChars(str, s); + checkException(env); + return ret; +} + +JavaVM* jnienv_to_javavm(JNIEnv* env) +{ + JavaVM* vm; + return env->GetJavaVM(&vm) >= 0 ? vm : NULL; +} + +JNIEnv* javavm_to_jnienv(JavaVM* vm) +{ + JNIEnv* env; + return vm->GetEnv((void **)&env, JNI_VERSION_1_4) >= 0 ? env : NULL; +} + +} + +struct RegistrationMethod { + const char* name; + int (*func)(JNIEnv*); +}; + +static RegistrationMethod gWebCoreRegMethods[] = { + { "JavaBridge", android::register_javabridge }, + { "WebFrame", android::register_webframe }, + { "WebCoreResourceLoader", android::register_resource_loader }, + { "WebViewCore", android::register_webviewcore }, + { "WebHistory", android::register_webhistory }, + { "WebIconDatabase", android::register_webicondatabase }, + { "WebSettings", android::register_websettings }, + { "WebView", android::register_webview } +}; + +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!"); + + // Instantiate the WeakReference fields. + android::gWeakRefClass = env->FindClass("java/lang/ref/WeakReference"); + LOG_ASSERT(android::gWeakRefClass, "Could not find WeakReference"); + android::gWeakRefInit = env->GetMethodID(android::gWeakRefClass, + "<init>", "(Ljava/lang/Object;)V"); + LOG_ASSERT(android::gWeakRefInit, + "Could not find constructor for WeakReference"); + android::gWeakRefGet = env->GetMethodID(android::gWeakRefClass, "get", + "()Ljava/lang/Object;"); + LOG_ASSERT(android::gWeakRefInit, + "Could not find get method for WeakReference"); + + 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; +} diff --git a/WebKit/android/jni/WebCoreJni.h b/WebKit/android/jni/WebCoreJni.h new file mode 100644 index 0000000..9acc2f4 --- /dev/null +++ b/WebKit/android/jni/WebCoreJni.h @@ -0,0 +1,71 @@ +/* + * Copyright 2008, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_WEBKIT_WEBCOREJNI_H +#define ANDROID_WEBKIT_WEBCOREJNI_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() { + if (m_obj) + m_env->DeleteLocalRef(m_obj); + } + jobject get() const { + return m_obj; + } + JNIEnv* env() const { + return m_env; + } +private: + 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 WeakReference returned as an +// AutoJObject. +AutoJObject getRealObject(JNIEnv*, jobject); + +// Convert the given jobject to a WeakReference and create a new global +// reference to that WeakReference. +jobject adoptGlobalRef(JNIEnv*, jobject); + +// Helper method for check java exceptions. Returns true if an exception +// occurred and logs the exception. +bool checkException(JNIEnv* env); + +// Get the JavaVM pointer for the given JNIEnv pointer +JavaVM* jnienv_to_javavm(JNIEnv* env); + +// Get the JNIEnv pointer for the given JavaVM pointer +JNIEnv* javavm_to_jnienv(JavaVM* vm); + +// Create a WebCore::String object from a jstring object. +WebCore::String to_string(JNIEnv* env, jstring str); + +} + +#endif diff --git a/WebKit/android/jni/WebCoreRefObject.h b/WebKit/android/jni/WebCoreRefObject.h new file mode 100644 index 0000000..7e96191 --- /dev/null +++ b/WebKit/android/jni/WebCoreRefObject.h @@ -0,0 +1,38 @@ +/* +** +** Copyright 2007, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +#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/WebKit/android/jni/WebCoreResourceLoader.cpp b/WebKit/android/jni/WebCoreResourceLoader.cpp new file mode 100644 index 0000000..2e349e8 --- /dev/null +++ b/WebKit/android/jni/WebCoreResourceLoader.cpp @@ -0,0 +1,359 @@ +/* +** Copyright 2006, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +#define LOG_TAG "webcoreglue" + +#include <config.h> +#include <wtf/Platform.h> + +#include "jni_utility.h" +#include "WebCoreResourceLoader.h" +#include "SkUtils.h" + +#include "CString.h" +#include "ResourceError.h" +#include "ResourceHandle.h" +#include "ResourceHandleClient.h" +#include "ResourceHandleInternal.h" +#include "ResourceResponse.h" +#include "WebCoreJni.h" + +#ifdef ANDROID_INSTRUMENT +#include "Frame.h" +#include "SystemTime.h" +#endif + +#include <utils/misc.h> +#include <JNIHelp.h> +#include <SkTypes.h> +#include <stdlib.h> + +#ifdef ANDROID_INSTRUMENT +static uint32_t sTotalTimeUsed = 0; + +namespace WebCore { +void Frame::resetResourceLoadTimeCounter() +{ + sTotalTimeUsed = 0; +} + +void Frame::reportResourceLoadTimeCounter() +{ + LOG(LOG_DEBUG, "WebCore", "*-* Total native 3 (resource load) time: %d ms\n", + sTotalTimeUsed); +} +} +#endif + +namespace android { + +#ifdef ANDROID_INSTRUMENT +class TimeCounterRC { +public: + TimeCounterRC() { + mStartTime = WebCore::get_thread_msec(); + } + + ~TimeCounterRC() { + sTotalTimeUsed += WebCore::get_thread_msec() - mStartTime; + } + +private: + uint32_t mStartTime; +}; +#endif + +// ---------------------------------------------------------------------------- + +static struct resourceloader_t { + jfieldID mObject; + jmethodID mCancelMethodID; + jmethodID mDownloadFileMethodID; + jmethodID mWillLoadFromCacheMethodID; +} 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 + +WebCoreResourceLoader::WebCoreResourceLoader(JNIEnv *env, jobject jLoadListener) +{ + mJvm = jnienv_to_javavm(env); + mJLoader = env->NewGlobalRef(jLoadListener); +} + +WebCoreResourceLoader::~WebCoreResourceLoader() +{ + JNIEnv* env = javavm_to_jnienv(mJvm); + SET_NATIVE_HANDLE(env, mJLoader, 0); + env->DeleteGlobalRef(mJLoader); + mJLoader = 0; +} + +void WebCoreResourceLoader::cancel() +{ + JNIEnv* env = javavm_to_jnienv(mJvm); + env->CallVoidMethod(mJLoader, gResourceLoader.mCancelMethodID); + checkException(env); +} + +void WebCoreResourceLoader::downloadFile() +{ + JNIEnv* env = javavm_to_jnienv(mJvm); + env->CallVoidMethod(mJLoader, gResourceLoader.mDownloadFileMethodID); + 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) +{ + JNIEnv* env = javavm_to_jnienv(JSC::Bindings::getJavaVM()); + WebCore::String urlStr = url.string(); + jstring jUrlStr = env->NewString(urlStr.characters(), urlStr.length()); + jclass resourceLoader = env->FindClass("android/webkit/LoadListener"); + bool val = env->CallStaticBooleanMethod(resourceLoader, + gResourceLoader.mWillLoadFromCacheMethodID, jUrlStr); + checkException(env); + env->DeleteLocalRef(jUrlStr); + + return val; +} + +// ---------------------------------------------------------------------------- +void WebCoreResourceLoader::SetResponseHeader(JNIEnv* env, jobject obj, jint nativeResponse, jstring key, jstring val) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterRC counter; +#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) { + WebCore::String valStr = to_string(env, val); + if (!valStr.isEmpty()) + response->setHTTPHeaderField(to_string(env, key), valStr); + } +} + +jint WebCoreResourceLoader::CreateResponse(JNIEnv* env, jobject obj, jstring url, jint statusCode, + jstring statusText, jstring mimeType, jlong expectedLength, + jstring encoding, jlong expireTime) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterRC counter; +#endif + LOG_ASSERT(url, "Must have a url in the response!"); + WebCore::KURL kurl(to_string(env, url)); + WebCore::String encodingStr; + WebCore::String mimeTypeStr; + if (mimeType) { + mimeTypeStr = to_string(env, mimeType); + LOGV("Response setMIMEType: %s", mimeTypeStr.latin1().data()); + } + if (encoding) { + encodingStr = to_string(env, encoding); + LOGV("Response setTextEncodingName: %s", encodingStr.latin1().data()); + } + WebCore::ResourceResponse* response = new WebCore::ResourceResponse( + kurl, mimeTypeStr, (long long)expectedLength, + encodingStr, WebCore::String()); + response->setHTTPStatusCode(statusCode); + if (statusText) { + WebCore::String status = to_string(env, statusText); + response->setHTTPStatusText(status); + LOGV("Response setStatusText: %s", status.latin1().data()); + } + // FIXME: This assumes that time_t is a long and that long is the same size as int. + if ((unsigned long)expireTime > INT_MAX) + expireTime = INT_MAX; + response->setExpirationDate((time_t)expireTime); + return (int)response; +} + +void WebCoreResourceLoader::ReceivedResponse(JNIEnv* env, jobject obj, jint nativeResponse) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterRC counter; +#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 + TimeCounterRC counter; +#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 + TimeCounterRC counter; +#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); +} + +jstring WebCoreResourceLoader::RedirectedToUrl(JNIEnv* env, jobject obj, + jstring baseUrl, jstring redirectTo, jint nativeResponse) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterRC counter; +#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->request(); + WebCore::KURL url(WebCore::KURL(to_string(env, baseUrl)), + to_string(env, redirectTo)); + r.setURL(url); + if (r.httpMethod() == "POST") { + r.setHTTPMethod("GET"); + r.clearHTTPReferrer(); + r.setHTTPBody(0); + r.setHTTPContentType(""); + } + 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; + } + handle->client()->willSendRequest(handle, r, *response); + delete response; + WebCore::String s = url.string(); + return env->NewString((unsigned short*)s.characters(), s.length()); +} + +void WebCoreResourceLoader::Error(JNIEnv* env, jobject obj, jint id, jstring description, + jstring failingUrl) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterRC counter; +#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, + to_string(env, failingUrl), to_string(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;J)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 register_resource_loader(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.mWillLoadFromCacheMethodID = + env->GetStaticMethodID(resourceLoader, "willLoadFromCache", "(Ljava/lang/String;)Z"); + LOG_FATAL_IF(gResourceLoader.mWillLoadFromCacheMethodID == NULL, + "Could not find static method willLoadFromCache on LoadListener"); + + return jniRegisterNativeMethods(env, "android/webkit/LoadListener", + gResourceloaderMethods, NELEM(gResourceloaderMethods)); +} + +} /* namespace android */ diff --git a/WebKit/android/jni/WebCoreResourceLoader.h b/WebKit/android/jni/WebCoreResourceLoader.h new file mode 100644 index 0000000..e3b3cc7 --- /dev/null +++ b/WebKit/android/jni/WebCoreResourceLoader.h @@ -0,0 +1,67 @@ +/* //device/libs/android_runtime/android_webcore_resource_loader.h +** +** Copyright 2006, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +#ifndef ANDROID_WEBKIT_RESOURCELOADLISTENER_H +#define ANDROID_WEBKIT_RESOURCELOADLISTENER_H + +#include "KURL.h" + +#include "WebCoreRefObject.h" +#include <jni.h> + +namespace android { + +class WebCoreResourceLoader : public WebCoreRefObject +{ +public: + WebCoreResourceLoader(JNIEnv *env, jobject jLoadListener); + virtual ~WebCoreResourceLoader(); + + /** + * Call to java to cancel the current load. + */ + void cancel(); + + /** + * Call to java to download the current load rather than feed it + * back to WebCore + */ + void downloadFile(); + + /** + * Call to java to find out if this URL is in the cache + */ + static bool willLoadFromCache(const WebCore::KURL& url); + + // Native jni functions + static void SetResponseHeader(JNIEnv*, jobject, jint, jstring, jstring); + static jint CreateResponse(JNIEnv*, jobject, jstring, jint, jstring, + jstring, jlong, jstring, jlong); + 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); + +private: + JavaVM* mJvm; + jobject mJLoader; +}; + +} // end namespace android + +#endif diff --git a/WebKit/android/jni/WebCoreViewBridge.h b/WebKit/android/jni/WebCoreViewBridge.h new file mode 100644 index 0000000..361e9c4 --- /dev/null +++ b/WebKit/android/jni/WebCoreViewBridge.h @@ -0,0 +1,82 @@ +/* +** +** Copyright 2007, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +#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() : + mBounds(0,0,0,0), + m_windowBounds(0,0,0,0) + {} + virtual ~WebCoreViewBridge() {} + + virtual void draw(WebCore::GraphicsContext* ctx, + const WebCore::IntRect& rect) = 0; + + const WebCore::IntRect& getBounds() const + { + return mBounds; + } + + const WebCore::IntRect& getWindowBounds() const + { + return m_windowBounds; + } + + void setSize(int w, int h) + { + mBounds.setWidth(w); + mBounds.setHeight(h); + } + + void setLocation(int x, int y) + { + mBounds.setX(x); + mBounds.setY(y); + } + + void setWindowBounds(int x, int y, int h, int v) + { + m_windowBounds = WebCore::IntRect(x, y, h, v); + } + + int width() const { return mBounds.width(); } + int height() const { return mBounds.height(); } + int locX() const { return mBounds.x(); } + int locY() const { return mBounds.y(); } + + virtual bool forFrameView() const { return false; } + virtual bool forPluginView() const { return false; } + +private: + WebCore::IntRect mBounds; + WebCore::IntRect m_windowBounds; +}; + +#endif // WEBCORE_VIEW_BRIDGE_H diff --git a/WebKit/android/jni/WebFrameView.cpp b/WebKit/android/jni/WebFrameView.cpp new file mode 100644 index 0000000..3310c51 --- /dev/null +++ b/WebKit/android/jni/WebFrameView.cpp @@ -0,0 +1,100 @@ +/* + * + * Copyright 2008, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#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. + SkRect r; + android_setrect(&r, transRect); + + canvas->save(); + canvas->translate(SkIntToScalar(bounds.x()), SkIntToScalar(bounds.y())); + canvas->clipRect(r); + } + 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/WebKit/android/jni/WebFrameView.h b/WebKit/android/jni/WebFrameView.h new file mode 100644 index 0000000..350a301 --- /dev/null +++ b/WebKit/android/jni/WebFrameView.h @@ -0,0 +1,53 @@ +/* + * + * Copyright 2008, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#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); + + virtual bool forFrameView() const { return true; } + + private: + WebCore::FrameView* mFrameView; + WebViewCore* mWebViewCore; + }; + +} // namespace android + +#endif // WEB_FRAMEVIEW_H diff --git a/WebKit/android/jni/WebHistory.cpp b/WebKit/android/jni/WebHistory.cpp new file mode 100644 index 0000000..203869e --- /dev/null +++ b/WebKit/android/jni/WebHistory.cpp @@ -0,0 +1,879 @@ +/* +** +** Copyright 2007, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +#define LOG_TAG "webhistory" + +#include <config.h> +#include <wtf/OwnPtr.h> +#include <wtf/Platform.h> + +#include "WebHistory.h" + +#include "BackForwardList.h" +#include "CString.h" +#include "DocumentLoader.h" +#include "Frame.h" +#include "FrameLoader.h" +#include "FrameLoaderClientAndroid.h" +#include "FrameTree.h" +#include "HistoryItem.h" +#include "Page.h" +#include "TextEncoding.h" +#include "WebCoreFrameBridge.h" +#include "WebCoreJni.h" + +#include <JNIHelp.h> +#include <SkUtils.h> +#include <utils/misc.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; + jfieldID mCurrentIndex; +} 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::BackForwardList* list = 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(NULL); + // 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()->setCurrentHistoryItem(current); + 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->childItemWithName(child->tree()->name()); + child->loader()->setCurrentHistoryItem(item); + // Append the first child to the queue if it exists. + if (WebCore::Frame* f = child->tree()->firstChild()) + frameQueue.append(f); + 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()->currentHistoryItem(); + } + } + } +} + +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; + + // Set the current index in the list. + WebCore::BackForwardList* list = pFrame->page()->backForwardList(); + WebCore::HistoryItem* currentItem = list->entries()[index].get(); + list->goToItem(currentItem); + + // Update the current and previous history item. + WebCore::FrameLoader* loader = pFrame->loader(); + loader->setCurrentHistoryItem(currentItem); + loader->setPreviousHistoryItem(list->backItem()); + + // Update the request with the current item's info. + WebCore::ResourceRequest& request = loader->documentLoader()->request(); + request.setURL(currentItem->url()); + request.setMainDocumentURL(currentItem->url()); + // Reload the current page + loader->reloadAllowingStaleData(loader->documentLoader()->overrideEncoding()); +} + +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(); +#ifdef ANDROID_HISTORY_CLIENT + RefPtr<WebHistoryItem> bridge = new WebHistoryItem(env, obj, newItem.get()); + newItem->setBridge(bridge.get()); +#endif + // 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); +#ifdef ANDROID_HISTORY_CLIENT + bridge->setActive(); +#endif + + // Add the new item to the back/forward list. + WebCore::Frame* pFrame = (WebCore::Frame*)frame; + pFrame->page()->backForwardList()->addItem(newItem); + +#ifdef ANDROID_HISTORY_CLIENT + // Update the item. + bridge->updateHistoryItem(newItem.get()); +#endif +} + +// 7 empty strings + no document state + children count = 9 unsigned values +// 1 char for isTargetItem +// ANDROID_HISTORY_CLIENT adds 1 int for scale. +#ifdef ANDROID_HISTORY_CLIENT +#define HISTORY_MIN_SIZE ((int)(sizeof(unsigned) * 10 + sizeof(char))) +#else +#define HISTORY_MIN_SIZE ((int)(sizeof(unsigned) * 9 + sizeof(char))) +#endif + +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. +#ifdef ANDROID_HISTORY_CLIENT + LOG_ASSERT(item->bridge(), "Why don't we have a bridge object here?"); +#endif + 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. + jbyte* bytes = env->GetByteArrayElements(b, NULL); + memcpy(bytes, v.data(), v.size()); + env->ReleaseByteArrayElements(b, bytes, 0); + return b; +} + +WebHistoryItem::WebHistoryItem(JNIEnv* env, jobject obj, + WebCore::HistoryItem* item) { + JavaVM* vm; + mJVM = env->GetJavaVM(&vm) >= 0 ? vm : NULL; + mObject = adoptGlobalRef(env, obj); + mScale = 100; + mActive = false; + mParent = NULL; + mHistoryItem = item; +} + +WebHistoryItem::~WebHistoryItem() { + if (mObject) { + JNIEnv* env; + env = mJVM->GetEnv((void **)&env, JNI_VERSION_1_4) >= 0 ? env : NULL; + if (!env) + return; + env->DeleteGlobalRef(mObject); + } +} + +void WebHistoryItem::updateHistoryItem(WebCore::HistoryItem* item) { +#ifdef ANDROID_HISTORY_CLIENT + // Do not want to update during inflation. + if (!mActive) + return; + WebHistoryItem* webItem = this; + // Now we need to update the top-most WebHistoryItem based on the top-most + // HistoryItem. + if (mParent) { + webItem = mParent.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(); + } + JNIEnv* env; + env = webItem->mJVM->GetEnv((void **)&env, JNI_VERSION_1_4) >= 0 ? env : NULL; + if (!env) + return; + + // Don't do anything if the item has been gc'd already + AutoJObject realItem = getRealObject(env, webItem->mObject); + if (!realItem.get()) + return; + + const WebCore::String& urlString = item->urlString(); + jstring urlStr = NULL; + if (!urlString.isNull()) + urlStr = env->NewString((unsigned short*)urlString.characters(), urlString.length()); + const WebCore::String& originalUrlString = item->originalURLString(); + jstring originalUrlStr = NULL; + if (!originalUrlString.isNull()) { + originalUrlStr = env->NewString( + (unsigned short*) originalUrlString.characters(), + originalUrlString.length()); + } + const WebCore::String& titleString = item->title(); + jstring titleStr = NULL; + if (!titleString.isNull()) + titleStr = env->NewString((unsigned short*)titleString.characters(), titleString.length()); + + // 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; + WebCore::String url = item->urlString(); + if (item->url().hasRef()) { + 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); +#endif +} + +static void historyItemChanged(WebCore::HistoryItem* item) { +#ifdef ANDROID_HISTORY_CLIENT + LOG_ASSERT(item, + "historyItemChanged called with a null item"); + if (item->bridge()) + item->bridge()->updateHistoryItem(item); +#endif +} + +void WebHistory::AddItem(const AutoJObject& list, WebCore::HistoryItem* item) +{ +#ifdef ANDROID_HISTORY_CLIENT + 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); + + // 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); +#endif +} + +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()->SetIntField(list.get(), gWebBackForwardList.mCurrentIndex, newIndex); +} + +static void write_string(WTF::Vector<char>& v, const WebCore::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 referrer + write_string(v, item->formReferrer()); + + // Form data + const WebCore::FormData* formData = item->formData(); + if (formData) + write_string(v, formData->flattenToString()); + else + write_string(v, WebCore::String()); // Empty constructor does not allocate a buffer. + + // Target + write_string(v, item->target()); + +#ifdef ANDROID_HISTORY_CLIENT + WebHistoryItem* bridge = item->bridge(); + LOG_ASSERT(bridge, "We should have a bridge here!"); + // Screen scale + int scale = bridge->scale(); + LOGV("Writing scale %d", scale); + v.append((char*)&scale, sizeof(int)); +#endif + + // Document state + const WTF::Vector<WebCore::String>& docState = item->documentState(); + WTF::Vector<WebCore::String>::const_iterator end = docState.end(); + unsigned stateSize = docState.size(); + LOGV("Writing docState %d", stateSize); + v.append((char*)&stateSize, sizeof(unsigned)); + for (WTF::Vector<WebCore::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(); +#ifdef ANDROID_HISTORY_CLIENT + LOG_ASSERT(parent->bridge(), + "The parent item should have a bridge object!"); + if (!item->bridge()) { + WebHistoryItem* bridge = new 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. + LOG_ASSERT(parent->bridge()->parent() == NULL || + item->bridge()->parent() == parent->bridge(), + "Somehow this item has an incorrect parent"); + item->bridge()->setParent(parent->bridge()); + } +#endif + 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. + WebCore::String formContentType; + WebCore::String formReferrer; + 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 referrer + memcpy(&l, data, sizeofUnsigned); + data += sizeofUnsigned; + if (l) { + LOGV("Referrer %d %.*s", l, l, data); + if (data + l < end) + formReferrer = 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; + } + if (end - data < sizeofUnsigned) + return false; + + // Set up the form info + if (formData != NULL) { + WebCore::ResourceRequest r; + r.setHTTPMethod("POST"); + r.setHTTPContentType(formContentType); + r.setHTTPReferrer(formReferrer); + 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; + +#ifdef ANDROID_HISTORY_CLIENT + WebHistoryItem* bridge = newItem->bridge(); + LOG_ASSERT(bridge, "There should be a bridge object during inflate"); + // Read the screen scale + memcpy(&l, data, sizeofUnsigned); + LOGV("Screen scale %d", l); + bridge->setScale(l); + data += sizeofUnsigned; + if (end - data < sizeofUnsigned) + return false; +#endif + + // 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<WebCore::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(); +#ifdef ANDROID_HISTORY_CLIENT + // Set a bridge that will not call into java. + child->setBridge(new WebHistoryItem(bridge)); +#endif + // Read the child item. + if (!read_item_recursive(child.get(), pData, end - data)) { + child.clear(); + return false; + } +#ifdef ANDROID_HISTORY_CLIENT + child->bridge()->setActive(); +#endif + 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(); +#ifdef ANDROID_HISTORY_CLIENT + testItem->setBridge(new WebHistoryItem(NULL)); +#endif + 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 referrer + 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 referrer 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!"); +#ifdef ANDROID_HISTORY_CLIENT + offset += 4; // Scale +#endif + // 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!"); + +#ifdef ANDROID_HISTORY_CLIENT + offset = 36; +#else + offset = 28; +#endif + // 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 register_webhistory(JNIEnv* env) +{ +#ifdef ANDROID_HISTORY_CLIENT + // Get notified of all changes to history items. + WebCore::notifyHistoryItemChanged = historyItemChanged; +#endif +#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"); + + // Find the WebBackForwardList object, the addHistoryItem and + // removeHistoryItem methods and the mCurrentIndex field. + 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.mCurrentIndex = env->GetFieldID(clazz, "mCurrentIndex", "I"); + LOG_ASSERT(gWebBackForwardList.mCurrentIndex, "Could not find field mCurrentIndex"); + + 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/WebKit/android/jni/WebHistory.h b/WebKit/android/jni/WebHistory.h new file mode 100644 index 0000000..7d9ff2a --- /dev/null +++ b/WebKit/android/jni/WebHistory.h @@ -0,0 +1,69 @@ +/* + * Copyright 2006, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_WEBKIT_WEBHISTORY_H +#define ANDROID_WEBKIT_WEBHISTORY_H + +#include <jni.h> +#include <wtf/RefCounted.h> +#include <wtf/Vector.h> + +namespace WebCore { + class HistoryItem; +} + +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); +}; + +class WebHistoryItem : public WTF::RefCounted<WebHistoryItem> { +public: + WebHistoryItem(WebHistoryItem* parent) + : mParent(parent) + , mObject(NULL) + , mJVM(NULL) + , mScale(100) + , mActive(false) + , mHistoryItem(NULL) {} + WebHistoryItem(JNIEnv*, jobject, WebCore::HistoryItem*); + ~WebHistoryItem(); + void updateHistoryItem(WebCore::HistoryItem* item); + void setScale(int s) { mScale = s; } + void setActive() { mActive = true; } + void setParent(WebHistoryItem* parent) { mParent = parent; } + WebHistoryItem* parent() { return mParent.get(); } + int scale() { return mScale; } + WebCore::HistoryItem* historyItem() { return mHistoryItem; } +private: + RefPtr<WebHistoryItem> mParent; + jobject mObject; + JavaVM* mJVM; + int mScale; + bool mActive; + WebCore::HistoryItem* mHistoryItem; +}; + +}; + +#endif diff --git a/WebKit/android/jni/WebIconDatabase.cpp b/WebKit/android/jni/WebIconDatabase.cpp new file mode 100644 index 0000000..7bc0ae5 --- /dev/null +++ b/WebKit/android/jni/WebIconDatabase.cpp @@ -0,0 +1,227 @@ +/* +** Copyright 2006, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +#define LOG_TAG "favicons" + +#include <config.h> +#include <wtf/Platform.h> + +#include "WebIconDatabase.h" + +#include "IconDatabase.h" +#include "Image.h" +#include "IntRect.h" +#include "JavaSharedClient.h" +#include "jni_utility.h" +#include "KURL.h" +#include "WebCoreJni.h" + +#include <pthread.h> +#include "GraphicsJNI.h" +#include <SkBitmap.h> +#include <SkImageDecoder.h> +#include <SkTemplates.h> +#include <utils/misc.h> +#include <JNIHelp.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 WebCore::String& pageURL) +{ + // If there are no clients currently, drop this message. + if (mClients.size() == 0) + return; + + // Attempt to attach to the current vm. + JavaVM* vm = JSC::Bindings::getJavaVM(); + JavaVMAttachArgs args; + + args.version = JNI_VERSION_1_4; + args.name = "IconDatabase"; + args.group = NULL; + + JNIEnv* env; + bool threadIsAttached = true; + if (vm->AttachCurrentThread(&env, (void*) &args) != JNI_OK) { + LOGE("Could not attach IconDatabase thread to the VM"); + threadIsAttached = false; + } + + mNotificationsMutex.lock(); + mNotifications.append(pageURL); + if (!mDeliveryRequested) { + if (threadIsAttached) { + mDeliveryRequested = true; + WebCore::JavaSharedClient::EnqueueFunctionPtr(DeliverNotifications, this); + } + } + mNotificationsMutex.unlock(); +} + +// Called in the WebCore thread +void WebIconDatabase::RegisterForIconNotification(WebIconDatabaseClient* client) +{ + gIconDatabaseClient->mClientsMutex.lock(); + gIconDatabaseClient->mClients.append(client); + gIconDatabaseClient->mClientsMutex.unlock(); +} + +// Called in the WebCore thread +void WebIconDatabase::UnregisterForIconNotification(WebIconDatabaseClient* client) +{ + WebIconDatabase* db = gIconDatabaseClient; + db->mClientsMutex.lock(); + for (unsigned i = 0; i < db->mClients.size(); ++i) { + if (db->mClients[i] == client) { + db->mClients.remove(i); + break; + } + } + db->mClientsMutex.unlock(); +} + +// 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<WebCore::String> queue; + mNotificationsMutex.lock(); + queue.swap(mNotifications); + mDeliveryRequested = false; + mNotificationsMutex.unlock(); + + // Swap the clients queue + Vector<WebIconDatabaseClient*> clients; + mClientsMutex.lock(); + clients.swap(mClients); + mClientsMutex.unlock(); + + 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"); + WebCore::String pathStr = to_string(env, path); + LOGV("Opening WebIconDatabase file '%s'", pathStr.latin1().data()); + bool res = iconDb->open(pathStr); + if (!res) + LOGE("Open failed!"); +} + +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"); + WebCore::String urlStr = to_string(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"); + WebCore::String urlStr = to_string(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"); + WebCore::String urlStr = to_string(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 register_webicondatabase(JNIEnv* env) +{ + jclass webIconDB = env->FindClass("android/webkit/WebIconDatabase"); + LOG_ASSERT(webIconDB, "Unable to find class android.webkit.WebIconDatabase"); + + return jniRegisterNativeMethods(env, "android/webkit/WebIconDatabase", + gWebIconDatabaseMethods, NELEM(gWebIconDatabaseMethods)); +} + +} diff --git a/WebKit/android/jni/WebIconDatabase.h b/WebKit/android/jni/WebIconDatabase.h new file mode 100644 index 0000000..eefe1f6 --- /dev/null +++ b/WebKit/android/jni/WebIconDatabase.h @@ -0,0 +1,69 @@ +/* //device/libs/WebKitLib/WebKit/WebCore/platform/android/jni/android_webkit_webicondatabase.h +** +** Copyright 2006, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +#ifndef ANDROID_WEBKIT_WEBICONDATABASE_H +#define ANDROID_WEBKIT_WEBICONDATABASE_H + +#include "IconDatabaseClient.h" +#include "utils/threads.h" +#include "wtf/Vector.h" + +#include <jni.h> + +namespace WebCore { + class Image; + class String; +} + +namespace android { + + class WebIconDatabaseClient { + public: + virtual ~WebIconDatabaseClient() {} + virtual void didAddIconForPageUrl(const WebCore::String& pageUrl) = 0; + }; + + class WebIconDatabase : public WebCore::IconDatabaseClient { + public: + WebIconDatabase() : mDeliveryRequested(false) {} + // IconDatabaseClient method + virtual void dispatchDidAddIconForPageURL(const WebCore::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 and a mutex to protect it. + Vector<WebIconDatabaseClient*> mClients; + android::Mutex mClientsMutex; + + // Queue of page urls that have received an icon. + Vector<WebCore::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/WebKit/android/jni/WebSettings.cpp b/WebKit/android/jni/WebSettings.cpp new file mode 100644 index 0000000..eb34fb8 --- /dev/null +++ b/WebKit/android/jni/WebSettings.cpp @@ -0,0 +1,325 @@ +/* +** +** Copyright 2007, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +#define LOG_TAG "websettings" + +#include <config.h> +#include <wtf/Platform.h> + +#include "Document.h" +#include "Frame.h" +#include "FrameLoader.h" +#include "FrameView.h" +#include "DocLoader.h" +#include "Page.h" +#include "RenderTable.h" +#ifdef ANDROID_PLUGINS +#include "PlatformString.h" +#include "PluginDatabase.h" +#endif +#include "Settings.h" +#include "WebCoreFrameBridge.h" +#include "WebCoreJni.h" + +#include <JNIHelp.h> +#include <utils/misc.h> + +namespace WebCore { +// Defined in FileSystemAndroid.cpp +extern String sPluginPath; +} + +namespace android { + +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;"); + 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 + mJavaScriptEnabled = env->GetFieldID(clazz, "mJavaScriptEnabled", "Z"); + mPluginsEnabled = env->GetFieldID(clazz, "mPluginsEnabled", "Z"); +#ifdef ANDROID_PLUGINS + mPluginsPath = env->GetFieldID(clazz, "mPluginsPath", "Ljava/lang/String;"); +#endif + mJavaScriptCanOpenWindowsAutomatically = env->GetFieldID(clazz, + "mJavaScriptCanOpenWindowsAutomatically", "Z"); + mUseWideViewport = env->GetFieldID(clazz, "mUseWideViewport", "Z"); + mSupportMultipleWindows = env->GetFieldID(clazz, "mSupportMultipleWindows", "Z"); + mShrinksStandaloneImagesToFit = env->GetFieldID(clazz, "mShrinksStandaloneImagesToFit", "Z"); + mUseDoubleTree = env->GetFieldID(clazz, "mUseDoubleTree", "Z"); + + 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(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(mJavaScriptEnabled, "Could not find field mJavaScriptEnabled"); + LOG_ASSERT(mPluginsEnabled, "Could not find field mPluginsEnabled"); +#ifdef ANDROID_PLUGINS + LOG_ASSERT(mPluginsPath, "Could not find field mPluginsPath"); +#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(mUseDoubleTree, "Could not find field mUseDoubleTree"); + + jclass c = env->FindClass("java/lang/Enum"); + LOG_ASSERT(c, "Could not find Enum class!"); + mOrdinal = env->GetMethodID(c, "ordinal", "()I"); + LOG_ASSERT(mOrdinal, "Could not find method ordinal"); + c = env->FindClass("android/webkit/WebSettings$TextSize"); + LOG_ASSERT(c, "Could not find TextSize enum"); + mTextSizeValue = env->GetFieldID(c, "value", "I"); + } + + // Field ids + jfieldID mLayoutAlgorithm; + jfieldID mTextSize; + jfieldID mStandardFontFamily; + jfieldID mFixedFontFamily; + jfieldID mSansSerifFontFamily; + jfieldID mSerifFontFamily; + jfieldID mCursiveFontFamily; + jfieldID mFantasyFontFamily; + jfieldID mDefaultTextEncoding; + jfieldID mUserAgent; + jfieldID mMinimumFontSize; + jfieldID mMinimumLogicalFontSize; + jfieldID mDefaultFontSize; + jfieldID mDefaultFixedFontSize; + jfieldID mLoadsImagesAutomatically; +#ifdef ANDROID_BLOCK_NETWORK_IMAGE + jfieldID mBlockNetworkImage; +#endif + jfieldID mJavaScriptEnabled; + jfieldID mPluginsEnabled; +#ifdef ANDROID_PLUGINS + jfieldID mPluginsPath; +#endif + jfieldID mJavaScriptCanOpenWindowsAutomatically; + jfieldID mUseWideViewport; + jfieldID mSupportMultipleWindows; + jfieldID mShrinksStandaloneImagesToFit; + jfieldID mUseDoubleTree; + + // Ordinal() method and value field for enums + jmethodID mOrdinal; + jfieldID mTextSizeValue; +}; + +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); +} + +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::DocLoader* docLoader = pFrame->document()->docLoader(); + +#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()->updateStyleSelector(); + 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->zoomFactor() != zoomFactor) + pFrame->setZoomFactor(zoomFactor, /*isTextOnly*/true); + + jstring str = (jstring)env->GetObjectField(obj, gFieldIds->mStandardFontFamily); + s->setStandardFontFamily(to_string(env, str)); + + str = (jstring)env->GetObjectField(obj, gFieldIds->mFixedFontFamily); + s->setFixedFontFamily(to_string(env, str)); + + str = (jstring)env->GetObjectField(obj, gFieldIds->mSansSerifFontFamily); + s->setSansSerifFontFamily(to_string(env, str)); + + str = (jstring)env->GetObjectField(obj, gFieldIds->mSerifFontFamily); + s->setSerifFontFamily(to_string(env, str)); + + str = (jstring)env->GetObjectField(obj, gFieldIds->mCursiveFontFamily); + s->setCursiveFontFamily(to_string(env, str)); + + str = (jstring)env->GetObjectField(obj, gFieldIds->mFantasyFontFamily); + s->setFantasyFontFamily(to_string(env, str)); + + str = (jstring)env->GetObjectField(obj, gFieldIds->mDefaultTextEncoding); + s->setDefaultTextEncodingName(to_string(env, str)); + + str = (jstring)env->GetObjectField(obj, gFieldIds->mUserAgent); + WebFrame::getWebFrame(pFrame)->setUserAgent(to_string(env, str)); + + 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) + docLoader->setAutoLoadImages(true); + +#ifdef ANDROID_BLOCK_NETWORK_IMAGE + flag = env->GetBooleanField(obj, gFieldIds->mBlockNetworkImage); + s->setBlockNetworkImage(flag); + if(!flag) + docLoader->setBlockNetworkImage(false); +#endif + + flag = env->GetBooleanField(obj, gFieldIds->mJavaScriptEnabled); + s->setJavaScriptEnabled(flag); + + flag = env->GetBooleanField(obj, gFieldIds->mPluginsEnabled); + s->setPluginsEnabled(flag); + +#ifdef ANDROID_PLUGINS + ::WebCore::PluginDatabase *pluginDatabase = + ::WebCore::PluginDatabase::installedPlugins(); + str = (jstring)env->GetObjectField(obj, gFieldIds->mPluginsPath); + if (str) { + WebCore::String pluginsPath = to_string(env, str); + s->setPluginsPath(pluginsPath); + // Set the plugin directories to this single entry. + Vector< ::WebCore::String > paths(1); + paths[0] = pluginsPath; + pluginDatabase->setPluginDirectories(paths); + // Set the home directory for plugin temporary files + WebCore::sPluginPath = paths[0]; + // Reload plugins. + pluginDatabase->refresh(); + } +#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); +#if USE(LOW_BANDWIDTH_DISPLAY) + flag = env->GetBooleanField(obj, gFieldIds->mUseDoubleTree); + pFrame->loader()->setUseLowBandwidthDisplay(flag); +#endif + } +}; + +//------------------------------------------------------------- +// JNI registration +//------------------------------------------------------------- + +static JNINativeMethod gWebSettingsMethods[] = { + { "nativeSync", "(I)V", + (void*) WebSettings::Sync } +}; + +int register_websettings(JNIEnv* env) +{ + jclass clazz = env->FindClass("android/webkit/WebSettings"); + LOG_ASSERT(clazz, "Unable to find class WebSettings!"); + gFieldIds = new FieldIds(env, clazz); + return jniRegisterNativeMethods(env, "android/webkit/WebSettings", + gWebSettingsMethods, NELEM(gWebSettingsMethods)); +} + +} diff --git a/WebKit/android/jni/WebViewCore.cpp b/WebKit/android/jni/WebViewCore.cpp new file mode 100644 index 0000000..363f2be --- /dev/null +++ b/WebKit/android/jni/WebViewCore.cpp @@ -0,0 +1,2502 @@ +/* + * Copyright 2006, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "webcoreglue" + +#include <config.h> +#include "WebViewCore.h" + +#include "AtomicString.h" +#include "CachedNode.h" +#include "CachedRoot.h" +#include "Color.h" +#include "Document.h" +#include "Element.h" +#include "Editor.h" +#include "EditorClientAndroid.h" +#include "EventHandler.h" +#include "EventNames.h" +#include "Font.h" +#include "FrameLoader.h" +#include "FrameTree.h" +#include "FrameView.h" +#include "GraphicsContext.h" +#include "GraphicsJNI.h" +#include "HitTestResult.h" +#include "HTMLAnchorElement.h" +#include "HTMLAreaElement.h" +#include "HTMLElement.h" +#include "HTMLImageElement.h" +#include "HTMLInputElement.h" +#include "HTMLMapElement.h" +#include "HTMLNames.h" +#include "HTMLOptGroupElement.h" +#include "HTMLOptionElement.h" +#include "HTMLSelectElement.h" +#include "HTMLTextAreaElement.h" +#include "InlineTextBox.h" +#include <JNIHelp.h> +#include "KeyboardCodes.h" +#include "Node.h" +#include "Page.h" +#include "PlatformKeyboardEvent.h" +#include "PlatformString.h" +#include "PluginInfoStore.h" +#include "PluginWidgetAndroid.h" +#include "Position.h" +#include "ProgressTracker.h" +#include "RenderLayer.h" +#include "RenderText.h" +#include "RenderTextControl.h" +#include "RenderThemeAndroid.h" +#include "RenderView.h" +#include "ResourceRequest.h" +#include "SelectionController.h" +#include "Settings.h" +#include "SkTemplates.h" +#include "SkTypes.h" +#include "SkCanvas.h" +#include "SkPicture.h" +#include "SkUtils.h" +#include "StringImpl.h" +#include "SystemTime.h" +#include "Text.h" +#include "TypingCommand.h" +#include "WebCoreFrameBridge.h" +#include "WebFrameView.h" +#include "HistoryItem.h" +#include "android_graphics.h" +#include <ui/KeycodeLabels.h> + +#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 "CString.h" + +FILE* gDomTreeFile = 0; +FILE* gRenderTreeFile = 0; +#endif + +#ifdef ANDROID_INSTRUMENT +static uint32_t sTotalTimeUsed = 0; +static uint32_t sTotalPaintTimeUsed = 0; +static uint32_t sCounter = 0; + +namespace WebCore { +void Frame::resetWebViewCoreTimeCounter() +{ + sTotalTimeUsed = 0; +} + +void Frame::reportWebViewCoreTimeCounter() +{ + LOG(LOG_DEBUG, "WebCore", "*-* Total native 4 (webview core) time: %d ms\n", + sTotalTimeUsed); +} +// This should be in Frame.cpp, but android LOG is conflict with webcore LOG +void Frame::resetPaintTimeCounter() +{ + sTotalPaintTimeUsed = 0; + sCounter = 0; +} + +void Frame::reportPaintTimeCounter() +{ + LOG(LOG_DEBUG, "WebCore", "*-* Total draw time: %d ms called %d times\n", + sTotalPaintTimeUsed, sCounter); +} +} +#endif + +//////////////////////////////////////////////////////////////////////////////////////////////// + +namespace android { + +#ifdef ANDROID_INSTRUMENT +class TimeCounterWV { +public: + TimeCounterWV() { + m_startTime = WebCore::get_thread_msec(); + } + + ~TimeCounterWV() { + sTotalTimeUsed += WebCore::get_thread_msec() - m_startTime; + } + +private: + uint32_t m_startTime; +}; +#endif + +// ---------------------------------------------------------------------------- + +#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_webView; +} gWebViewCoreFields; + +// ---------------------------------------------------------------------------- + +struct WebViewCore::JavaGlue { + JavaVM* m_JVM; + jobject m_obj; + jmethodID m_spawnScrollTo; + jmethodID m_scrollTo; + jmethodID m_scrollBy; + jmethodID m_contentDraw; + jmethodID m_requestListBox; + jmethodID m_requestSingleListBox; + jmethodID m_jsAlert; + jmethodID m_jsConfirm; + jmethodID m_jsPrompt; + jmethodID m_jsUnload; + jmethodID m_didFirstLayout; + jmethodID m_sendMarkNodeInvalid; + jmethodID m_sendNotifyFocusSet; + jmethodID m_sendNotifyProgressFinished; + jmethodID m_sendRecomputeFocus; + jmethodID m_sendViewInvalidate; + jmethodID m_updateTextfield; + jmethodID m_restoreScale; + jmethodID m_needTouchEvents; + 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::gFrameGenerationMutex; +Mutex WebViewCore::gRecomputeFocusMutex; +Mutex WebViewCore::gButtonMutex; +Mutex WebViewCore::m_contentMutex; + +WebViewCore::WebViewCore(JNIEnv* env, jobject javaWebViewCore, WebCore::Frame* mainframe) + : m_pluginInvalTimer(this, &WebViewCore::pluginInvalTimerFired) +{ + m_mainFrame = mainframe; + + m_popupReply = 0; + m_buildGeneration = 0; + m_moveGeneration = 0; + m_generation = 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_scale = 1; + + 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_JVM = jnienv_to_javavm(env); + m_javaGlue->m_obj = adoptGlobalRef(env, javaWebViewCore); + m_javaGlue->m_spawnScrollTo = GetJMethod(env, clazz, "contentSpawnScrollTo", "(II)V"); + m_javaGlue->m_scrollTo = GetJMethod(env, clazz, "contentScrollTo", "(II)V"); + m_javaGlue->m_scrollBy = GetJMethod(env, clazz, "contentScrollBy", "(II)V"); + m_javaGlue->m_contentDraw = GetJMethod(env, clazz, "contentDraw", "()V"); + m_javaGlue->m_requestListBox = GetJMethod(env, clazz, "requestListBox", "([Ljava/lang/String;[Z[I)V"); + m_javaGlue->m_requestSingleListBox = GetJMethod(env, clazz, "requestListBox", "([Ljava/lang/String;[ZI)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_didFirstLayout = GetJMethod(env, clazz, "didFirstLayout", "()V"); + m_javaGlue->m_sendMarkNodeInvalid = GetJMethod(env, clazz, "sendMarkNodeInvalid", "(I)V"); + m_javaGlue->m_sendNotifyFocusSet = GetJMethod(env, clazz, "sendNotifyFocusSet", "()V"); + m_javaGlue->m_sendNotifyProgressFinished = GetJMethod(env, clazz, "sendNotifyProgressFinished", "()V"); + m_javaGlue->m_sendRecomputeFocus = GetJMethod(env, clazz, "sendRecomputeFocus", "()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_restoreScale = GetJMethod(env, clazz, "restoreScale", "(I)V"); + m_javaGlue->m_needTouchEvents = GetJMethod(env, clazz, "needTouchEvents", "(Z)V"); + + env->SetIntField(javaWebViewCore, gWebViewCoreFields.m_nativeClass, (jint)this); + + m_scrollOffsetX = m_scrollOffsetY = 0; + + reset(true); +} + +WebViewCore::~WebViewCore() +{ + // Release the focused view + Release(m_popupReply); + + if (m_javaGlue->m_obj) { + JNIEnv* env = javavm_to_jnienv(m_javaGlue->m_JVM); + env->DeleteGlobalRef(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) +{ + WebFrameView* webFrameView = static_cast<WebFrameView*>(view->platformWidget()); + 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_lastFocusedBounds = WebCore::IntRect(0,0,0,0); + clearContent(); + m_updatedFrameCache = true; + m_frameCacheOutOfDate = true; + m_blockFocusChange = false; + m_snapAnchorNode = 0; + m_useReplay = false; + m_skipContentDraw = false; + m_findIsUp = false; +} + +static bool layoutIfNeededRecursive(WebCore::Frame* f) +{ + if (!f) + return true; + + WebCore::FrameView* v = f->view(); + if (!v) + return true; + + if (v->needsLayout()) + v->layout(); + + WebCore::Frame* child = f->tree()->firstChild(); + bool success = true; + while (child) { + success &= layoutIfNeededRecursive(child); + child = child->tree()->nextSibling(); + } + + return success && !v->needsLayout(); +} + +void WebViewCore::recordPicture(SkPicture* picture) +{ + // if there is no document yet, just return + if (!m_mainFrame->document()) + return; + // Call layout to ensure that the contentWidth and contentHeight are correct + if (!layoutIfNeededRecursive(m_mainFrame)) + return; + // draw into the picture's recording canvas + WebCore::FrameView* view = m_mainFrame->view(); + SkAutoPictureRecord arp(picture, view->contentsWidth(), view->contentsHeight()); + 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, INT_MAX, INT_MAX)); + + 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 (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; + // 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(); + 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); + picture->safeUnref(); + } + // 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); + WebCore::CacheBuilder& builder = m_mainFrame->getCacheBuilder(); + WebCore::Node* oldFocusNode = builder.currentFocus(); + m_frameCacheOutOfDate = true; + WebCore::IntRect oldBounds = oldFocusNode ? + oldFocusNode->getRect() : WebCore::IntRect(0,0,0,0); + DBG_NAV_LOGD_THROTTLE("m_lastFocused=%p oldFocusNode=%p" + " m_lastFocusedBounds={%d,%d,%d,%d} oldBounds={%d,%d,%d,%d}", + m_lastFocused, oldFocusNode, + m_lastFocusedBounds.x(), m_lastFocusedBounds.y(), m_lastFocusedBounds.width(), m_lastFocusedBounds.height(), + oldBounds.x(), oldBounds.y(), oldBounds.width(), oldBounds.height()); + if (m_lastFocused != oldFocusNode || m_lastFocusedBounds != oldBounds + || m_findIsUp) { + m_lastFocused = oldFocusNode; + m_lastFocusedBounds = oldBounds; + DBG_NAV_LOG("call updateFrameCache"); + updateFrameCache(); + } +} + +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++; + } + } +} + +void WebViewCore::clearContent() +{ + DBG_SET_LOG(""); + m_contentMutex.lock(); + m_content.clear(); + m_contentMutex.unlock(); + m_addInval.setEmpty(); +} + +void WebViewCore::copyContentToPicture(SkPicture* picture) +{ + DBG_SET_LOG("start"); + m_contentMutex.lock(); + PictureSet copyContent = PictureSet(m_content); + m_contentMutex.unlock(); + copyContent.toPicture(picture); + DBG_SET_LOG("end"); +} + +bool WebViewCore::drawContent(SkCanvas* canvas, SkColor color) +{ + DBG_SET_LOG("start"); + m_contentMutex.lock(); + PictureSet copyContent = PictureSet(m_content); + m_contentMutex.unlock(); + int sc = canvas->save(SkCanvas::kClip_SaveFlag); + SkRect clip; + clip.set(0, 0, copyContent.width(), copyContent.height()); + canvas->clipRect(clip, SkRegion::kDifference_Op); + canvas->drawColor(color); + canvas->restoreToCount(sc); + bool tookTooLong = copyContent.draw(canvas); + m_contentMutex.lock(); + m_content.setDrawTimes(copyContent); + m_contentMutex.unlock(); + DBG_SET_LOG("end"); + return tookTooLong; +} + +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); + 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())); + + 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("draw [%d] {%d,%d,w=%d,h=%d}", index, inval.fLeft, + inval.fTop, inval.width(), inval.height()); + pictureSet->setPicture(index, rebuildPicture(inval)); + } + pictureSet->validate(__FUNCTION__); +} + +bool WebViewCore::recordContent(SkRegion* region, SkIPoint* point) +{ + DBG_SET_LOG("start"); + m_contentMutex.lock(); + PictureSet contentCopy(m_content); + m_contentMutex.unlock(); + recordPictureSet(&contentCopy); + float progress = (float) m_mainFrame->page()->progress()->estimatedProgress(); + if (progress > 0.0f && progress < 1.0f && contentCopy.isEmpty()) { + DBG_SET_LOGD("empty (progress=%g)", progress); + return false; + } + region->set(m_addInval); + m_addInval.setEmpty(); + m_contentMutex.lock(); + contentCopy.setDrawTimes(m_content); + m_content.set(contentCopy); + point->fX = m_content.width(); + point->fY = m_content.height(); + m_contentMutex.unlock(); + 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 true; +} + +void WebViewCore::splitContent() +{ + bool layoutSuceeded = layoutIfNeededRecursive(m_mainFrame); + LOG_ASSERT(layoutSuceeded, "Can never be called recursively"); + PictureSet tempPictureSet; + m_contentMutex.lock(); + m_content.split(&tempPictureSet); + m_contentMutex.unlock(); + rebuildPictureSet(&tempPictureSet); + m_contentMutex.lock(); + m_content.set(tempPictureSet); + m_contentMutex.unlock(); +} + +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 = javavm_to_jnienv(m_javaGlue->m_JVM); + env->CallVoidMethod(m_javaGlue->object(env).get(), animate ? m_javaGlue->m_spawnScrollTo : m_javaGlue->m_scrollTo, x, y); + checkException(env); +} + +void WebViewCore::sendMarkNodeInvalid(WebCore::Node* node) +{ + LOG_ASSERT(m_javaGlue->m_obj, "A Java widget was not associated with this view bridge!"); + JNIEnv* env = javavm_to_jnienv(m_javaGlue->m_JVM); + env->CallVoidMethod(m_javaGlue->object(env).get(), m_javaGlue->m_sendMarkNodeInvalid, (int) node); + checkException(env); +} + +void WebViewCore::sendNotifyFocusSet() +{ + LOG_ASSERT(m_javaGlue->m_obj, "A Java widget was not associated with this view bridge!"); + JNIEnv* env = javavm_to_jnienv(m_javaGlue->m_JVM); + env->CallVoidMethod(m_javaGlue->object(env).get(), m_javaGlue->m_sendNotifyFocusSet); + checkException(env); +} + +void WebViewCore::sendNotifyProgressFinished() +{ + LOG_ASSERT(m_javaGlue->m_obj, "A Java widget was not associated with this view bridge!"); + JNIEnv* env = javavm_to_jnienv(m_javaGlue->m_JVM); + env->CallVoidMethod(m_javaGlue->object(env).get(), m_javaGlue->m_sendNotifyProgressFinished); + checkException(env); +} + +void WebViewCore::sendRecomputeFocus() +{ + LOG_ASSERT(m_javaGlue->m_obj, "A Java widget was not associated with this view bridge!"); + JNIEnv* env = javavm_to_jnienv(m_javaGlue->m_JVM); + env->CallVoidMethod(m_javaGlue->object(env).get(), m_javaGlue->m_sendRecomputeFocus); + checkException(env); +} + +void WebViewCore::viewInvalidate(const SkIRect& rect) +{ + LOG_ASSERT(m_javaGlue->m_obj, "A Java widget was not associated with this view bridge!"); + JNIEnv* env = javavm_to_jnienv(m_javaGlue->m_JVM); + env->CallVoidMethod(m_javaGlue->object(env).get(), m_javaGlue->m_sendViewInvalidate, + rect.fLeft, rect.fTop, rect.fRight, rect.fBottom); + 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 = javavm_to_jnienv(m_javaGlue->m_JVM); + env->CallVoidMethod(m_javaGlue->object(env).get(), m_javaGlue->m_sendViewInvalidate, + rect.x(), rect.y(), rect.right(), rect.bottom()); + checkException(env); +} + +void WebViewCore::scrollBy(int dx, int dy) +{ + if (!(dx | dy)) + return; + JNIEnv* env = javavm_to_jnienv(m_javaGlue->m_JVM); + env->CallVoidMethod(m_javaGlue->object(env).get(), m_javaGlue->m_scrollBy, dx, dy); + checkException(env); +} + +void WebViewCore::contentDraw() +{ + JNIEnv* env = javavm_to_jnienv(m_javaGlue->m_JVM); + env->CallVoidMethod(m_javaGlue->object(env).get(), m_javaGlue->m_contentDraw); + 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, max; + android_setrect(&rect, r); + max.set(0, 0, INT_MAX, INT_MAX); + if (!rect.intersect(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::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!"); + + const WebCore::KURL& url = m_mainFrame->loader()->url(); + if (url.isEmpty()) + return; + LOGV("::WebCore:: didFirstLayout %s", url.string().ascii().data()); + + JNIEnv* env = javavm_to_jnienv(m_javaGlue->m_JVM); + env->CallVoidMethod(m_javaGlue->object(env).get(), m_javaGlue->m_didFirstLayout); + checkException(env); + + DBG_NAV_LOG("call updateFrameCache"); + updateFrameCache(); + m_history.setDidFirstLayout(true); +} + +void WebViewCore::restoreScale(int scale) +{ + DEBUG_NAV_UI_LOGD("%s", __FUNCTION__); + LOG_ASSERT(m_javaGlue->m_obj, "A Java widget was not associated with this view bridge!"); + + JNIEnv* env = javavm_to_jnienv(m_javaGlue->m_JVM); + env->CallVoidMethod(m_javaGlue->object(env).get(), m_javaGlue->m_restoreScale, scale); + 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) // Android + JNIEnv* env = javavm_to_jnienv(m_javaGlue->m_JVM); + env->CallVoidMethod(m_javaGlue->object(env).get(), m_javaGlue->m_needTouchEvents, need); + checkException(env); +#endif +} + +void WebViewCore::notifyFocusSet() +{ + sendNotifyFocusSet(); +} + +void WebViewCore::notifyProgressFinished() +{ + DBG_NAV_LOG("call updateFrameCache"); + updateFrameCache(); + sendNotifyProgressFinished(); +} + +void WebViewCore::doMaxScroll(WebCore::CacheBuilder::Direction dir) +{ + int dx = 0, dy = 0; + + switch (dir) { + case WebCore::CacheBuilder::LEFT: + dx = -m_maxXScroll; + break; + case WebCore::CacheBuilder::UP: + dy = -m_maxYScroll; + break; + case WebCore::CacheBuilder::RIGHT: + dx = m_maxXScroll; + break; + case WebCore::CacheBuilder::DOWN: + dy = m_maxYScroll; + break; + case WebCore::CacheBuilder::UNINITIALIZED: + default: + LOG_ASSERT(0, "unexpected focus selector"); + } + this->scrollBy(dx, dy); +} + +void WebViewCore::setScrollOffset(int dx, int dy) +{ + DBG_NAV_LOGD("{%d,%d}", dx, dy); + if (m_scrollOffsetX != dx || m_scrollOffsetY != dy) { + m_scrollOffsetX = dx; + m_scrollOffsetY = dy; + m_mainFrame->sendScrollEvent(); + // 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); + } +} + +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 screenWidth, int scale) +{ + WebCoreViewBridge* window = m_mainFrame->view()->platformWidget(); + int ow = window->width(); + int oh = window->height(); + window->setSize(width, height); + int osw = m_screenWidth; + m_screenWidth = screenWidth; + m_scale = scale; + m_maxXScroll = screenWidth >> 2; + m_maxYScroll = (screenWidth * height / width) >> 2; + DBG_NAV_LOGD("old:(w=%d,h=%d,s=%d) new:(w=%d,h=%d,s=%d)", + ow, oh, osw, width, height, screenWidth); + if (ow != width || oh != height || osw != screenWidth) { + WebCore::RenderObject *r = m_mainFrame->contentRenderer(); + DBG_NAV_LOGD("renderer=%p", r); + if (r) { + r->setNeedsLayoutAndPrefWidthsRecalc(); + m_mainFrame->forceLayout(true); + } + } +} + +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 + if (useFile) + gRenderTreeFile = fopen(RENDER_TREE_LOG_FILE, "w"); + WebCore::CString renderDump = WebCore::externalRepresentation(m_mainFrame->contentRenderer()).utf8(); + const char* data = renderDump.data(); + int length = renderDump.length(); + int last = 0; + for (int i = 0; i < length; i++) { + if (data[i] == '\n') { + if (i != last) { + char* chunk = new char[i - last + 1]; + strncpy(chunk, (data + last), i - last); + chunk[i - last] = '\0'; + DUMP_RENDER_LOGD("%s", chunk); + } + last = i + 1; + } + } + if (gRenderTreeFile) { + fclose(gRenderTreeFile); + gRenderTreeFile = 0; + } +#endif +} + +void WebViewCore::dumpNavTree() +{ +#if DUMP_NAV_CACHE + m_mainFrame->getCacheBuilder().mDebug.print(); +#endif +} + +WebCore::String WebViewCore::retrieveHref(WebCore::Frame* frame, WebCore::Node* node) +{ + WebCore::CacheBuilder& builder = m_mainFrame->getCacheBuilder(); + if (!builder.validNode(frame, node)) + return WebCore::String(); + if (!node->hasTagName(WebCore::HTMLNames::aTag)) + return WebCore::String(); + WebCore::HTMLAnchorElement* anchor = static_cast<WebCore::HTMLAnchorElement*>(node); + return anchor->href(); +} + +bool WebViewCore::prepareFrameCache() +{ + if (!m_frameCacheOutOfDate) { + DBG_NAV_LOG("!m_frameCacheOutOfDate"); + return false; + } + m_frameCacheOutOfDate = false; +#if DEBUG_NAV_UI + DBG_NAV_LOG("m_frameCacheOutOfDate was true"); + m_now = SkTime::GetMSecs(); +#endif + m_temp = new CachedRoot(); + m_temp->init(m_mainFrame, &m_history); + m_temp->setGeneration(++m_buildGeneration); + WebCore::CacheBuilder& builder = m_mainFrame->getCacheBuilder(); + 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); + return true; +} + +void WebViewCore::releaseFrameCache(bool newCache) +{ + if (!newCache) { + DBG_NAV_LOG("!newCache"); + return; + } + 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(); + notifyFocusSet(); + // it's tempting to send an invalidate here, but it's a bad idea + // the cache is now up to date, but the focus is not -- the event + // may need to be recomputed from the prior history. An invalidate + // will draw the stale location causing the ring to flash at the wrong place. +} + +void WebViewCore::updateFrameCache() +{ + m_useReplay = false; + releaseFrameCache(prepareFrameCache()); +} + +void WebViewCore::removeFrameGeneration(WebCore::Frame* frame) +{ + DBG_NAV_LOGD("frame=%p m_generation=%d", frame, m_generation); + gFrameGenerationMutex.lock(); + int last = m_frameGenerations.size() - 1; + for (int index = 0; index <= last; index++) { + if (m_frameGenerations[index].m_frame == frame) { + DBG_NAV_LOGD("index=%d last=%d", index, last); + if (index != last) + m_frameGenerations[index] = m_frameGenerations[last]; + m_frameGenerations.removeLast(); + break; + } + } + gFrameGenerationMutex.unlock(); +} + +void WebViewCore::updateFrameGeneration(WebCore::Frame* frame) +{ + DBG_NAV_LOGD("frame=%p m_generation=%d", frame, m_generation); + gFrameGenerationMutex.lock(); + ++m_buildGeneration; + for (size_t index = 0; index < m_frameGenerations.size(); index++) { + if (m_frameGenerations[index].m_frame == frame) { + DBG_NAV_LOG("replace"); + m_frameGenerations[index].m_generation = m_buildGeneration; + goto done; + } + } + { + FrameGen frameGen = {frame, m_buildGeneration}; + m_frameGenerations.append(frameGen); + DBG_NAV_LOG("append"); + } +done: + gFrameGenerationMutex.unlock(); +} + +int WebViewCore::retrieveFrameGeneration(WebCore::Frame* frame) +{ + int result = INT_MAX; + gFrameGenerationMutex.lock(); + for (size_t index = 0; index < m_frameGenerations.size(); index++) { + if (m_frameGenerations[index].m_frame == frame) { + result = m_frameGenerations[index].m_generation; + break; + } + } + gFrameGenerationMutex.unlock(); + DBG_NAV_LOGD("frame=%p m_generation=%d result=%d", frame, m_generation, result); + return result; +} + +/////////////////////////////////////////////////////////////////////////////// + +void WebViewCore::addPlugin(PluginWidgetAndroid* w) +{ + SkDebugf("----------- addPlugin %p", w); + *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); + } +} + +void WebViewCore::invalPlugin(PluginWidgetAndroid* w) +{ + const double PLUGIN_INVAL_DELAY = 0; // should this be non-zero? + + 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(); + w->localToPageCoords(&dirty); + inval.op(dirty, SkRegion::kUnion_Op); + } + } + + if (!inval.isEmpty()) { + // inval.getBounds() is our rectangle + this->viewInvalidate(inval.getBounds()); + } +} + +/////////////////////////////////////////////////////////////////////////////// + +void WebViewCore::setFinalFocus(WebCore::Frame* frame, WebCore::Node* node, + int x, int y, bool block) +{ + DBG_NAV_LOGD("frame=%p node=%p x=%d y=%d", frame, node, x, y); + bool result = finalKitFocus(frame, node, x, y); + if (block) { + m_blockFocusChange = true; + if (!result && node) + touchUp(m_touchGeneration, 0, 0, 0, x, y, 0, true, true); + } +} + +void WebViewCore::setKitFocus(int moveGeneration, int buildGeneration, + WebCore::Frame* frame, WebCore::Node* node, int x, int y, + bool ignoreNullFocus) +{ + DBG_NAV_LOGD("m_moveGeneration=%d moveGeneration=%d" + " buildGeneration=%d frame=%p node=%p x=%d y=%d", + m_moveGeneration, moveGeneration, buildGeneration, frame, node, x, y); + if (m_blockFocusChange) { + DBG_NAV_LOG("m_blockFocusChange"); + return; + } + 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 + } + if (!commonKitFocus(moveGeneration, buildGeneration, frame, node, x, y, + ignoreNullFocus)) + return; + m_lastGeneration = moveGeneration; +} + +bool WebViewCore::commonKitFocus(int generation, int buildGeneration, + WebCore::Frame* frame, WebCore::Node* node, int x, int y, + bool ignoreNullFocus) +{ + DBG_NAV_LOGD("generation=%d buildGeneration=%d frame=%p" + " node=%p x=%d y=%d", generation, buildGeneration, frame, node, x, y); + m_useReplay = true; + bool newCache = prepareFrameCache(); // must wait for possible recompute before using + if (m_moveGeneration > generation) { + DBG_NAV_LOGD("m_moveGeneration=%d > generation=%d", + m_moveGeneration, generation); + releaseFrameCache(newCache); + return false; // short-circuit if a newer move has already been generated + } + // if the nav cache has been rebuilt since this focus request was generated, + // send a request back to the UI side to recompute the kit-side focus + if (m_buildGeneration > buildGeneration || (node && !m_mainFrame->getCacheBuilder().validNode(frame, node))) { + DBG_NAV_LOGD("m_buildGeneration=%d > buildGeneration=%d", + m_buildGeneration, buildGeneration); + gRecomputeFocusMutex.lock(); + bool first = !m_recomputeEvents.size(); + m_recomputeEvents.append(generation); + gRecomputeFocusMutex.unlock(); + releaseFrameCache(newCache); + if (first) + sendRecomputeFocus(); + return false; + } + releaseFrameCache(newCache); + if (!node && ignoreNullFocus) + return true; + finalKitFocus(frame, node, x, y); + return true; +} + +bool WebViewCore::finalKitFocus(WebCore::Frame* frame, WebCore::Node* node, + int x, int y) +{ + if (!frame) + frame = m_mainFrame; + WebCore::CacheBuilder& builder = m_mainFrame->getCacheBuilder(); + WebCore::Node* oldFocusNode = builder.currentFocus(); + // 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. + bool valid = builder.validNode(frame, node); + if (valid) { + WebCore::PlatformMouseEvent mouseEvent(m_mousePos, m_mousePos, WebCore::NoButton, + WebCore::MouseEventMoved, 1, false, false, false, false, WebCore::currentTime()); + frame->eventHandler()->handleMouseMoveEvent(mouseEvent); + } + WebCore::Document* oldDoc = oldFocusNode ? oldFocusNode->document() : 0; + if (!node) { + if (oldFocusNode) + oldDoc->setFocusedNode(0); + return false; + } else if (!valid) { + DBG_NAV_LOGD("sendMarkNodeInvalid node=%p", node); + sendMarkNodeInvalid(node); + if (oldFocusNode) + oldDoc->setFocusedNode(0); + return false; + } + // If we jump frames (docs), kill the focus on the old doc + builder.setLastFocus(node); + if (oldFocusNode && node->document() != oldDoc) { + oldDoc->setFocusedNode(0); + } + if (!node->isTextNode()) + static_cast<WebCore::Element*>(node)->focus(false); + //setFocus on things that WebCore doesn't recognize as supporting focus + //for instance, if there is an onclick element that does not support focus + DBG_NAV_LOGD("setFocusedNode node=%p", node); + node->document()->setFocusedNode(node); + m_lastFocused = node; + m_lastFocusedBounds = node->getRect(); + return true; +} + +// helper function to find the frame that has focus +static WebCore::Frame* FocusedFrame(WebCore::Frame* frame) +{ + if (!frame) + return 0; + WebCore::Node* focusNode = frame->getCacheBuilder().currentFocus(); + if (!focusNode) + return 0; + WebCore::Document* doc = focusNode->document(); + if (!doc) + return 0; + return doc->frame(); +} + +static WebCore::RenderTextControl* FocusedTextControl(WebCore::Frame* frame) +{ + WebCore::Node* focusNode = frame->getCacheBuilder().currentFocus(); + WebCore::RenderObject* renderer = focusNode->renderer(); + if (renderer && (renderer->isTextField() || renderer->isTextArea())) { + return static_cast<WebCore::RenderTextControl*>(renderer); + } + return 0; +} + +WebCore::Frame* WebViewCore::changedKitFocus(WebCore::Frame* frame, + WebCore::Node* node, int x, int y) +{ + if (!frame || !node) + return m_mainFrame; + WebCore::Node* current = m_mainFrame->getCacheBuilder().currentFocus(); + if (current == node) + return frame; + return finalKitFocus(frame, node, x, y) ? frame : m_mainFrame; +} + +static int findTextBoxIndex(WebCore::Node* node, const WebCore::IntPoint& pt) +{ + if (!node->isTextNode()) { + DBG_NAV_LOGD("node=%p pt=(%d,%d) isText=false", node, pt.x(), pt.y()); + return -2; // error + } + WebCore::RenderText* renderText = (WebCore::RenderText*) node->renderer(); + if (!renderText) { + DBG_NAV_LOGD("node=%p pt=(%d,%d) renderText=0", node, pt.x(), pt.y()); + return -3; // error + } + int renderX, renderY; + renderText->absolutePosition(renderX, renderY); + WebCore::InlineTextBox *textBox = renderText->firstTextBox(); + int globalX, globalY; + WebCore::CacheBuilder::GetGlobalOffset(node, &globalX, &globalY); + int x = pt.x() - globalX; + int y = pt.y() - globalY; + do { + int textBoxStart = textBox->start(); + int textBoxEnd = textBoxStart + textBox->len(); + if (textBoxEnd <= textBoxStart) + continue; + WebCore::IntRect bounds = textBox->selectionRect(renderX, renderY, + textBoxStart, textBoxEnd); + if (!bounds.contains(x, y)) + continue; + int offset = textBox->offsetForPosition(x - renderX); +#if DEBUG_NAV_UI + int prior = offset > 0 ? textBox->positionForOffset(offset - 1) : -1; + int current = textBox->positionForOffset(offset); + int next = textBox->positionForOffset(offset + 1); + DBG_NAV_LOGD( + "offset=%d pt.x=%d globalX=%d renderX=%d x=%d " + "textBox->x()=%d textBox->start()=%d prior=%d current=%d next=%d", + offset, pt.x(), globalX, renderX, x, + textBox->xPos(), textBox->start(), prior, current, next + ); +#endif + return textBox->start() + offset; + } while ((textBox = textBox->nextTextBox())); + return -1; // couldn't find point, may have walked off end +} + +static inline bool isPunctuation(UChar c) +{ + return WTF::Unicode::category(c) & (0 + | WTF::Unicode::Punctuation_Dash + | WTF::Unicode::Punctuation_Open + | WTF::Unicode::Punctuation_Close + | WTF::Unicode::Punctuation_Connector + | WTF::Unicode::Punctuation_Other + | WTF::Unicode::Punctuation_InitialQuote + | WTF::Unicode::Punctuation_FinalQuote + ); +} + +static int centerX(const SkIRect& rect) +{ + return (rect.fLeft + rect.fRight) >> 1; +} + +static int centerY(const SkIRect& rect) +{ + return (rect.fTop + rect.fBottom) >> 1; +} + +WebCore::String WebViewCore::getSelection(SkRegion* selRgn) +{ + SkRegion::Iterator iter(*selRgn); + // FIXME: switch this to use StringBuilder instead + WebCore::String result; + WebCore::Node* lastStartNode = 0; + int lastStartEnd = -1; + UChar lastChar = 0xffff; + for (; !iter.done(); iter.next()) { + const SkIRect& rect = iter.rect(); + DBG_NAV_LOGD("rect=(%d, %d, %d, %d)", rect.fLeft, rect.fTop, + rect.fRight, rect.fBottom); + int cy = centerY(rect); + WebCore::IntPoint startPt = WebCore::IntPoint(rect.fLeft + 1, cy); + WebCore::HitTestResult hitTestResult = m_mainFrame->eventHandler()-> + hitTestResultAtPoint(startPt, false); + WebCore::Node* node = hitTestResult.innerNode(); + if (!node) { + DBG_NAV_LOG("!node"); + return result; + } + WebCore::IntPoint endPt = WebCore::IntPoint(rect.fRight - 2, cy); + hitTestResult = m_mainFrame->eventHandler()->hitTestResultAtPoint(endPt, false); + WebCore::Node* endNode = hitTestResult.innerNode(); + if (!endNode) { + DBG_NAV_LOG("!endNode"); + return result; + } + int start = findTextBoxIndex(node, startPt); + if (start < 0) + continue; + int end = findTextBoxIndex(endNode, endPt); + if (end < -1) // use node if endNode is not valid + endNode = node; + if (end <= 0) + end = static_cast<WebCore::Text*>(endNode)->string()->length(); + DBG_NAV_LOGD("node=%p start=%d endNode=%p end=%d", node, start, endNode, end); + WebCore::Node* startNode = node; + do { + if (!node->isTextNode()) + continue; + if (node->getRect().isEmpty()) + continue; + WebCore::Text* textNode = static_cast<WebCore::Text*>(node); + WebCore::StringImpl* string = textNode->string(); + if (!string->length()) + continue; + const UChar* chars = string->characters(); + int newLength = node == endNode ? end : string->length(); + if (node == startNode) { + #if DEBUG_NAV_UI + if (node == lastStartNode) + DBG_NAV_LOGD("start=%d last=%d", start, lastStartEnd); + #endif + if (node == lastStartNode && start < lastStartEnd) + break; // skip rect if text overlaps already written text + lastStartNode = node; + lastStartEnd = newLength - start; + } + if (newLength < start) { + DBG_NAV_LOGD("newLen=%d < start=%d", newLength, start); + break; + } + if (!isPunctuation(chars[start])) + result.append(' '); + result.append(chars + start, newLength - start); + start = 0; + } while (node != endNode && (node = node->traverseNextNode())); + } + result = result.simplifyWhiteSpace().stripWhiteSpace(); +#if DUMP_NAV_CACHE + { + char buffer[256]; + WebCore::CacheBuilder::Debug debug; + debug.init(buffer, sizeof(buffer)); + debug.print("copy: "); + debug.wideString(result); + DUMP_NAV_LOGD("%s", buffer); + } +#endif + return result; +} + +static void selectInFrame(WebCore::Frame* frame, int start, int end) +{ + WebCore::Document* doc = frame->document(); + if (!doc) + return; + + WebCore::Node* focus = doc->focusedNode(); + if (!focus) + return; + + WebCore::RenderObject* renderer = focus->renderer(); + if (renderer && (renderer->isTextField() || renderer->isTextArea())) { + WebCore::RenderTextControl* rtc = static_cast<WebCore::RenderTextControl*>(renderer); + if (start > end) { + int temp = start; + start = end; + end = temp; + } + rtc->setSelectionRange(start, end); + frame->revealSelection(); + } +} + +WebCore::Frame* WebViewCore::setSelection(WebCore::Frame* frame, WebCore::Node* node, + int x, int y, int start, int end) +{ + // FIXME: Consider using a generation number to avoid doing this many more times than necessary. + frame = changedKitFocus(frame, node, x, y); + if (!frame) + return 0; + selectInFrame(frame, start, end); + return frame; +} + +// Shortcut for no modifier keys +#define NO_MODIFIER_KEYS (static_cast<WebCore::PlatformKeyboardEvent::ModifierKey>(0)) + +WebCore::Frame* WebViewCore::deleteSelection(WebCore::Frame* frame, WebCore::Node* node, + int x, int y, int start, int end) +{ + frame = setSelection(frame, node, x, y, start, end); + if (start != end) { + WebCore::PlatformKeyboardEvent downEvent(kKeyCodeDel, WebCore::VK_BACK, + WebCore::PlatformKeyboardEvent::KeyDown, 0, NO_MODIFIER_KEYS); + frame->eventHandler()->keyEvent(downEvent); + WebCore::PlatformKeyboardEvent upEvent(kKeyCodeDel, WebCore::VK_BACK, + WebCore::PlatformKeyboardEvent::KeyUp, 0, NO_MODIFIER_KEYS); + frame->eventHandler()->keyEvent(upEvent); + } + return frame; +} + +void WebViewCore::replaceTextfieldText(WebCore::Frame* frame, WebCore::Node* node, int x, int y, + int oldStart, int oldEnd, jstring replace, int start, int end) +{ + JNIEnv* env = javavm_to_jnienv(m_javaGlue->m_JVM); + + WebCore::String webcoreString = to_string(env, replace); + frame = setSelection(frame, node, x, y, oldStart, oldEnd); + WebCore::TypingCommand::insertText(frame->document(), webcoreString, false); + selectInFrame(frame, start, end); +} + +void WebViewCore::passToJs(WebCore::Frame* frame, WebCore::Node* node, int x, int y, int generation, + jstring currentText, int keyCode, int keyValue, bool down, bool cap, bool fn, bool sym) +{ + frame = changedKitFocus(frame, node, x, y); + // Construct the ModifierKey value + int mods = 0; + if (cap) { + mods |= WebCore::PlatformKeyboardEvent::ShiftKey; + } + if (fn) { + mods |= WebCore::PlatformKeyboardEvent::AltKey; + } + if (sym) { + mods |= WebCore::PlatformKeyboardEvent::CtrlKey; + } + WebCore::PlatformKeyboardEvent event(keyCode, keyValue, + down ? WebCore::PlatformKeyboardEvent::KeyDown : + WebCore::PlatformKeyboardEvent::KeyUp, + 0, static_cast<WebCore::PlatformKeyboardEvent::ModifierKey>(mods)); + // Block text field updates during a key press. + m_blockTextfieldUpdates = true; + frame->eventHandler()->keyEvent(event); + m_blockTextfieldUpdates = false; + m_textGeneration = generation; + + WebCore::Node* currentFocus = m_mainFrame->getCacheBuilder().currentFocus(); + // Make sure we have the same focus and it is a text field. + if (node == currentFocus && currentFocus) { + WebCore::RenderObject* renderer = currentFocus->renderer(); + if (renderer && (renderer->isTextField() || renderer->isTextArea())) { + WebCore::RenderTextControl* renderText = static_cast<WebCore::RenderTextControl*>(renderer); + JNIEnv* env = javavm_to_jnienv(m_javaGlue->m_JVM); + WebCore::String current = to_string(env, currentText); + WebCore::String test = renderText->text(); + // If the text changed during the key event, update the UI text field. + if (test != current) + updateTextfield(currentFocus, false, test); + } + } +} + +void WebViewCore::saveDocumentState(WebCore::Frame* frame, WebCore::Node* node, int x, int y) +{ + frame = changedKitFocus(frame, node, x, y); + WebCore::HistoryItem *item = frame->loader()->currentHistoryItem(); + + // 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()); + } +} + +// Convert a WebCore::String into an array of characters where the first +// character represents the length, for easy conversion to java. +static uint16_t* stringConverter(const WebCore::String& text) +{ + size_t length = text.length(); + uint16_t* itemName = new uint16_t[length+1]; + itemName[0] = (uint16_t)length; + uint16_t* firstChar = &(itemName[1]); + memcpy((void*)firstChar, text.characters(), sizeof(UChar)*length); + return itemName; +} + +// Response to dropdown created for a listbox. +class ListBoxReply : public WebCoreReply { +public: + ListBoxReply(WebCore::HTMLSelectElement* select, WebCore::Frame* frame, WebViewCore* view) + : m_select(select) + , m_frame(frame) + , m_viewImpl(view) + {} + + // Response used if the listbox only allows single selection. + // index is listIndex of the selected item, or -1 if nothing is selected. + virtual void replyInt(int index) + { + // If the select element no longer exists, do to a page change, etc, silently return. + if (!m_select || !m_viewImpl->m_mainFrame->getCacheBuilder().validNode(m_frame, m_select)) + return; + if (-1 == index) { + if (m_select->selectedIndex() != -1) { +#ifdef ANDROID_DESELECT_SELECT + m_select->deselectItems(); +#endif + m_select->onChange(); + m_viewImpl->contentInvalidate(m_select->getRect()); + } + return; + } + WebCore::HTMLOptionElement* option = static_cast<WebCore::HTMLOptionElement*>( + m_select->item(m_select->listToOptionIndex(index))); + if (!option->selected()) { + option->setSelected(true); + m_select->onChange(); + m_viewImpl->contentInvalidate(m_select->getRect()); + } + } + + // Response if the listbox allows multiple selection. array stores the listIndices + // of selected positions. + virtual void replyIntArray(const int* array, int count) + { + // If the select element no longer exists, do to a page change, etc, silently return. + if (!m_select || !m_viewImpl->m_mainFrame->getCacheBuilder().validNode(m_frame, m_select)) + return; +#ifdef ANDROID_DESELECT_SELECT + m_select->deselectItems(); +#endif + WebCore::HTMLOptionElement* option; + for (int i = 0; i < count; i++) { + option = static_cast<WebCore::HTMLOptionElement*>( + m_select->item(array[m_select->listToOptionIndex(i)])); + option->setSelected(true); + } + m_viewImpl->contentInvalidate(m_select->getRect()); + } +private: + // The select element associated with this listbox. + WebCore::HTMLSelectElement* m_select; + // The frame of this select element, to verify that it is valid. + WebCore::Frame* m_frame; + // For calling invalidate and checking the select element's validity + WebViewCore* m_viewImpl; +}; + +// 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::listBoxRequest(WebCoreReply* reply, const uint16_t** labels, size_t count, const int enabled[], size_t enabledCount, + bool multiple, const int selected[], size_t selectedCountOrSelection) +{ + // Reuse m_popupReply + Release(m_popupReply); + m_popupReply = 0; + + 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 = javavm_to_jnienv(m_javaGlue->m_JVM); + jobjectArray labelArray = makeLabelArray(env, labels, count); + + // Create an array determining whether each item is enabled. + jbooleanArray enabledArray = env->NewBooleanArray(enabledCount); + checkException(env); + jboolean* ptrArray = env->GetBooleanArrayElements(enabledArray, 0); + checkException(env); + for (size_t i = 0; i < enabledCount; i++) { + ptrArray[i] = enabled[i]; + } + env->ReleaseBooleanArrayElements(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::keyUp(KeyCode keyCode, int keyVal) +{ + DBG_NAV_LOGD("keyCode=%d", keyCode); + bool keyHandled = false; + WebCore::Node* focusNode = m_mainFrame->getCacheBuilder().currentFocus(); + if (focusNode) { + WebCore::Frame* focusFrame = focusNode->document()->frame(); + switch (keyCode) { + case kKeyCodeNewline: + case kKeyCodeDpadCenter: { + focusFrame->loader()->resetMultipleFormSubmissionProtection(); + WebFrame* webFrame = WebFrame::getWebFrame(m_mainFrame); + webFrame->setUserInitiatedClick(true); + if ((focusNode->hasTagName(WebCore::HTMLNames::inputTag) && + ((WebCore::HTMLInputElement*)focusNode)->isTextField()) || + focusNode->hasTagName(WebCore::HTMLNames::textareaTag)) { + // Create the key down event. + WebCore::PlatformKeyboardEvent keydown(keyCode, keyVal, + WebCore::PlatformKeyboardEvent::KeyDown, 0, + NO_MODIFIER_KEYS); + // Create the key up event. + WebCore::PlatformKeyboardEvent keyup(keyCode, keyVal, + WebCore::PlatformKeyboardEvent::KeyUp, 0, + NO_MODIFIER_KEYS); + // Send both events. + keyHandled = focusFrame->eventHandler()->keyEvent(keydown); + keyHandled |= focusFrame->eventHandler()->keyEvent(keyup); + } else { + keyHandled = handleMouseClick(focusFrame, focusNode); + } + webFrame->setUserInitiatedClick(false); + break; + } + default: + keyHandled = false; + } + } + m_blockFocusChange = false; + return keyHandled; +} + +bool WebViewCore::sendKeyToFocusNode(int keyCode, UChar32 unichar, + int repeatCount, bool isShift, bool isAlt, KeyAction action) { + WebCore::Node* focusNode = m_mainFrame->getCacheBuilder().currentFocus(); + if (focusNode) { + WebCore::Frame* focusFrame = focusNode->document()->frame(); + WebCore::PlatformKeyboardEvent::Type type; + switch (action) { + case DownKeyAction: + type = WebCore::PlatformKeyboardEvent::KeyDown; + break; + case UpKeyAction: + type = WebCore::PlatformKeyboardEvent::KeyUp; + break; + default: + SkDebugf("---- unexpected KeyAction %d\n", action); + return false; + } + + int mods = 0; // PlatformKeyboardEvent::ModifierKey + if (isShift) { + mods |= WebCore::PlatformKeyboardEvent::ShiftKey; + } + if (isAlt) { + mods |= WebCore::PlatformKeyboardEvent::AltKey; + } + + WebCore::PlatformKeyboardEvent evt(keyCode, unichar, type, repeatCount, + static_cast<WebCore::PlatformKeyboardEvent::ModifierKey>(mods)); + return focusFrame->eventHandler()->keyEvent(evt); + } + return false; +} + +bool WebViewCore::handleTouchEvent(int action, int x, int y) +{ + bool preventDefault = false; + +#if ENABLE(TOUCH_EVENTS) // Android + WebCore::TouchEventType type = WebCore::TouchEventCancel; + switch (action) { + case 0: // MotionEvent.ACTION_DOWN + type = WebCore::TouchEventStart; + break; + case 1: // MotionEvent.ACTION_UP + type = WebCore::TouchEventEnd; + break; + case 2: // MotionEvent.ACTION_MOVE + type = WebCore::TouchEventMove; + break; + case 3: // MotionEvent.ACTION_CANCEL + type = WebCore::TouchEventCancel; + break; + } + WebCore::IntPoint pt(x - m_scrollOffsetX, y - m_scrollOffsetY); + WebCore::PlatformTouchEvent te(pt, pt, type); + preventDefault = m_mainFrame->eventHandler()->handleTouchEvent(te); +#endif + + return preventDefault; +} + +void WebViewCore::touchUp(int touchGeneration, int buildGeneration, + WebCore::Frame* frame, WebCore::Node* node, int x, int y, int size, + bool isClick, bool retry) +{ + 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 + } + if (retry) + finalKitFocus(frame, node, x, y); + else if (!commonKitFocus(touchGeneration, buildGeneration, + frame, node, x, y, false)) { + return; + } + m_lastGeneration = touchGeneration; + // If this is just a touch and not a click, we have already done the change in focus, + // so just leave the function now. + if (!isClick) + return; + if (frame) { + frame->loader()->resetMultipleFormSubmissionProtection(); + } + WebCore::EditorClientAndroid* client = static_cast<WebCore::EditorClientAndroid*>(m_mainFrame->editor()->client()); + client->setFromClick(true); + DBG_NAV_LOGD("touchGeneration=%d handleMouseClick frame=%p node=%p" + " x=%d y=%d", touchGeneration, frame, node, x, y); + handleMouseClick(frame, node); + client->setFromClick(false); +} + +bool WebViewCore::handleMouseClick(WebCore::Frame* framePtr, WebCore::Node* nodePtr) +{ + if (framePtr && !m_mainFrame->getCacheBuilder().validNode(framePtr, nodePtr)) + return false; + WebFrame* webFrame = WebFrame::getWebFrame(m_mainFrame); + // 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 && nodePtr->hasTagName(WebCore::HTMLNames::areaTag)) { + webFrame->setUserInitiatedClick(true); + WebCore::EventTargetNodeCast(nodePtr)->dispatchSimulatedClick(0, true, true); + webFrame->setUserInitiatedClick(false); + return true; + } + WebCore::RenderObject* renderer = nodePtr ? nodePtr->renderer() : 0; + if (renderer) { + if (renderer->isMenuList()) { + WebCore::HTMLSelectElement* select = static_cast<WebCore::HTMLSelectElement*>(nodePtr); + const WTF::Vector<WebCore::HTMLElement*>& listItems = select->listItems(); + SkTDArray<const uint16_t*> names; + SkTDArray<int> enabledArray; + SkTDArray<int> selectedArray; + int size = listItems.size(); + bool multiple = select->multiple(); + for (int i = 0; i < size; i++) { + if (listItems[i]->hasTagName(WebCore::HTMLNames::optionTag)) { + WebCore::HTMLOptionElement* option = static_cast<WebCore::HTMLOptionElement*>(listItems[i]); + *names.append() = stringConverter(option->optionText()); + *enabledArray.append() = option->disabled() ? 0 : 1; + if (multiple && option->selected()) + *selectedArray.append() = i; + } else if (listItems[i]->hasTagName(WebCore::HTMLNames::optgroupTag)) { + WebCore::HTMLOptGroupElement* optGroup = static_cast<WebCore::HTMLOptGroupElement*>(listItems[i]); + *names.append() = stringConverter(optGroup->groupLabelText()); + *enabledArray.append() = 0; + if (multiple) + *selectedArray.append() = 0; + } + } + WebCoreReply* reply = new ListBoxReply(select, select->document()->frame(), this); + listBoxRequest(reply, names.begin(), size, enabledArray.begin(), enabledArray.count(), + multiple, selectedArray.begin(), multiple ? selectedArray.count() : + select->optionToListIndex(select->selectedIndex())); + return true; + } + } + if (!framePtr) + framePtr = m_mainFrame; + webFrame->setUserInitiatedClick(true); + DBG_NAV_LOGD("m_mousePos={%d,%d}", m_mousePos.x(), m_mousePos.y()); + WebCore::PlatformMouseEvent mouseDown(m_mousePos, m_mousePos, WebCore::LeftButton, + WebCore::MouseEventPressed, 1, false, false, false, false, + WebCore::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, + WebCore::currentTime()); + bool handled = framePtr->eventHandler()->handleMouseReleaseEvent(mouseUp); + webFrame->setUserInitiatedClick(false); + 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 = NULL; + } +} + +void WebViewCore::jsAlert(const WebCore::String& url, const WebCore::String& text) +{ + JNIEnv* env = javavm_to_jnienv(m_javaGlue->m_JVM); + jstring jInputStr = env->NewString((unsigned short *)text.characters(), text.length()); + jstring jUrlStr = env->NewString((unsigned short *)url.characters(), url.length()); + env->CallVoidMethod(m_javaGlue->object(env).get(), m_javaGlue->m_jsAlert, jUrlStr, jInputStr); + env->DeleteLocalRef(jInputStr); + env->DeleteLocalRef(jUrlStr); + checkException(env); +} + +bool WebViewCore::jsConfirm(const WebCore::String& url, const WebCore::String& text) +{ + JNIEnv* env = javavm_to_jnienv(m_javaGlue->m_JVM); + jstring jInputStr = env->NewString((unsigned short *)text.characters(), text.length()); + jstring jUrlStr = env->NewString((unsigned short *)url.characters(), url.length()); + 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 WebCore::String& url, const WebCore::String& text, const WebCore::String& defaultValue, WebCore::String& result) +{ + JNIEnv* env = javavm_to_jnienv(m_javaGlue->m_JVM); + jstring jInputStr = env->NewString((unsigned short *)text.characters(), text.length()); + jstring jDefaultStr = env->NewString((unsigned short *)defaultValue.characters(), defaultValue.length()); + jstring jUrlStr = env->NewString((unsigned short *)url.characters(), url.length()); + jstring returnVal = (jstring) env->CallObjectMethod(m_javaGlue->object(env).get(), m_javaGlue->m_jsPrompt, jUrlStr, jInputStr, jDefaultStr); + // If returnVal is null, it means that the user cancelled the dialog. + if (!returnVal) + return false; + + result = to_string(env, returnVal); + env->DeleteLocalRef(jInputStr); + env->DeleteLocalRef(jDefaultStr); + env->DeleteLocalRef(jUrlStr); + checkException(env); + return true; +} + +bool WebViewCore::jsUnload(const WebCore::String& url, const WebCore::String& message) +{ + JNIEnv* env = javavm_to_jnienv(m_javaGlue->m_JVM); + jstring jInputStr = env->NewString((unsigned short *)message.characters(), message.length()); + jstring jUrlStr = env->NewString((unsigned short *)url.characters(), url.length()); + 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; +} + +AutoJObject +WebViewCore::getJavaObject() +{ + return getRealObject(javavm_to_jnienv(m_javaGlue->m_JVM), m_javaGlue->m_obj); +} + +jobject +WebViewCore::getWebViewJavaObject() +{ + JNIEnv* env = javavm_to_jnienv(m_javaGlue->m_JVM); + return env->GetObjectField(m_javaGlue->object(env).get(), gWebViewCoreFields.m_webView); +} + +void WebViewCore::updateTextfield(WebCore::Node* ptr, bool changeToPassword, + const WebCore::String& text) +{ + if (m_blockTextfieldUpdates) + return; + JNIEnv* env = javavm_to_jnienv(m_javaGlue->m_JVM); + if (changeToPassword) { + env->CallVoidMethod(m_javaGlue->object(env).get(), m_javaGlue->m_updateTextfield, + (int) ptr, true, 0, m_textGeneration); + checkException(env); + return; + } + int length = text.length(); + jstring string = env->NewString((unsigned short *) text.characters(), length); + env->CallVoidMethod(m_javaGlue->object(env).get(), m_javaGlue->m_updateTextfield, + (int) ptr, false, string, m_textGeneration); + env->DeleteLocalRef(string); + checkException(env); +} + +void WebViewCore::setSnapAnchor(int x, int y) +{ + m_snapAnchorNode = 0; + if (!x && !y) { + return; + } + + WebCore::IntPoint point = WebCore::IntPoint(x, y); + WebCore::Node* node = m_mainFrame->eventHandler()->hitTestResultAtPoint(point, false).innerNode(); + if (node) { +// LOGD("found focus node name: %s, type %d\n", node->nodeName().utf8().data(), node->nodeType()); + while (node) { + if (node->hasTagName(WebCore::HTMLNames::divTag) || + node->hasTagName(WebCore::HTMLNames::tableTag)) { + m_snapAnchorNode = node; + return; + } +// LOGD("parent node name: %s, type %d\n", node->nodeName().utf8().data(), node->nodeType()); + node = node->parentNode(); + } + } +} + +void WebViewCore::snapToAnchor() +{ + if (m_snapAnchorNode) { + if (m_snapAnchorNode->inDocument()) { + int rx, ry; + m_snapAnchorNode->renderer()->absolutePosition(rx, ry); + scrollTo(rx, ry); + } else { + m_snapAnchorNode = 0; + } + } +} + +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); +} + +//---------------------------------------------------------------------- +// Native JNI methods +//---------------------------------------------------------------------- +static jstring WebCoreStringToJString(JNIEnv *env, WebCore::String string) +{ + int length = string.length(); + if (!length) + return 0; + jstring ret = env->NewString((jchar *)string.characters(), length); + env->DeleteLocalRef(ret); + return ret; +} + +static void SetSize(JNIEnv *env, jobject obj, jint width, jint height, + jint screenWidth, jfloat scale) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterWV counter; +#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"); + // convert the scale to an int + int s = (int) (scale * 100); + // a negative value indicates that we should not change the scale + if (scale < 0) + s = viewImpl->scale(); + + viewImpl->setSizeScreenWidthAndScale(width, height, screenWidth, s); +} + +static void SetScrollOffset(JNIEnv *env, jobject obj, jint dx, jint dy) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterWV counter; +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "need viewImpl"); + + viewImpl->setScrollOffset(dx, dy); +} + +static void SetGlobalBounds(JNIEnv *env, jobject obj, jint x, jint y, jint h, + jint v) +{ + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "need viewImpl"); + + viewImpl->setGlobalBounds(x, y, h, v); +} + +static jboolean KeyUp(JNIEnv *env, jobject obj, jint key, jint val) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterWV counter; +#endif +// LOGV("webviewcore::nativeKeyUp(%u)\n", (unsigned)key); + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in nativeKeyUp"); + return viewImpl->keyUp((KeyCode)key, val); +} + +static bool SendKeyToFocusNode(JNIEnv *env, jobject jwebviewcore, + jint keyCode, jint unichar, jint repeatCount, + jboolean isShift, jboolean isAlt, jint action) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterWV counter; +#endif + + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, jwebviewcore); + LOG_ASSERT(viewImpl, "viewImpl not set in SendKeyToFocusNode"); + return viewImpl->sendKeyToFocusNode((KeyCode)keyCode, unichar, repeatCount, + isShift, isAlt, static_cast<WebViewCore::KeyAction>(action)); +} + +static void DeleteSelection(JNIEnv *env, jobject obj, + jint frame, jint node, jint x, jint y, jint start, jint end) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterWV counter; +#endif + LOGV("webviewcore::nativeDeleteSelection()\n"); + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in nativeDeleteSelection"); + viewImpl->deleteSelection((WebCore::Frame*) frame, (WebCore::Node*) node, + x, y, start, end); +} + +static void SetSelection(JNIEnv *env, jobject obj, + jint frame, jint node, jint x, jint y, jint start, jint end) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterWV counter; +#endif + LOGV("webviewcore::nativeSetSelection()\n"); + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in nativeDeleteSelection"); + viewImpl->setSelection((WebCore::Frame*) frame, (WebCore::Node*) node, + x, y, start, end); +} + + +static void ReplaceTextfieldText(JNIEnv *env, jobject obj, + jint framePtr, jint nodePtr, jint x, jint y, jint oldStart, jint oldEnd, + jstring replace, jint start, jint end) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterWV counter; +#endif + LOGV("webviewcore::nativeReplaceTextfieldText()\n"); + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in nativeReplaceTextfieldText"); + viewImpl->replaceTextfieldText((WebCore::Frame*) framePtr, (WebCore::Node*) nodePtr, x, y, oldStart, + oldEnd, replace, start, end); +} + +static void PassToJs(JNIEnv *env, jobject obj, jint frame, jint node, + jint x, jint y, jint generation, jstring currentText, jint keyCode, + jint keyValue, jboolean down, jboolean cap, jboolean fn, jboolean sym) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterWV counter; +#endif + LOGV("webviewcore::nativePassToJs()\n"); + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in nativePassToJs"); + viewImpl->passToJs((WebCore::Frame*) frame, (WebCore::Node*) node, + x, y, generation, currentText, keyCode, keyValue, down, cap, fn, sym); +} + +static void SaveDocumentState(JNIEnv *env, jobject obj, jint frame, jint node, jint x, jint y) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterWV counter; +#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, (WebCore::Node*) node, x, y); +} + +static bool RecordContent(JNIEnv *env, jobject obj, jobject region, jobject pt) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterWV counter; +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + SkRegion* nativeRegion = GraphicsJNI::getNativeRegion(env, region); + SkIPoint nativePt; + bool result = viewImpl->recordContent(nativeRegion, &nativePt); + GraphicsJNI::ipoint_to_jpoint(nativePt, env, pt); + return result; +} + +static void SplitContent(JNIEnv *env, jobject obj) +{ + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + viewImpl->splitContent(); +} + +static void SendListBoxChoice(JNIEnv* env, jobject obj, jint choice) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterWV counter; +#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 + TimeCounterWV counter; +#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) +{ + 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 = WebCore::CacheBuilder::FindAddress(addrChars, length, + &start, &end) == WebCore::CacheBuilder::FOUND_COMPLETE; + jstring ret = 0; + if (success) { + ret = env->NewString((jchar*) addrChars + start, end - start); + env->DeleteLocalRef(ret); + } + env->ReleaseStringChars(addr, addrChars); + return ret; +} + +static jboolean HandleTouchEvent(JNIEnv *env, jobject obj, jint action, jint x, jint y) +{ + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in %s", __FUNCTION__); + return viewImpl->handleTouchEvent(action, x, y); +} + +static void TouchUp(JNIEnv *env, jobject obj, jint touchGeneration, + jint buildGeneration, jint frame, jint node, jint x, jint y, jint size, + jboolean isClick, jboolean retry) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterWV counter; +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in %s", __FUNCTION__); + viewImpl->touchUp(touchGeneration, buildGeneration, + (WebCore::Frame*) frame, (WebCore::Node*) node, x, y, size, isClick, retry); +} + +static jstring RetrieveHref(JNIEnv *env, jobject obj, jint frame, + jint node) +{ + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in %s", __FUNCTION__); + WebCore::String result = viewImpl->retrieveHref((WebCore::Frame*) frame, + (WebCore::Node*) node); + if (!result.isEmpty()) + return WebCoreStringToJString(env, result); + return 0; +} + +static void SetFinalFocus(JNIEnv *env, jobject obj, jint frame, jint node, + jint x, jint y, jboolean block) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterWV counter; +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in %s", __FUNCTION__); + viewImpl->setFinalFocus((WebCore::Frame*) frame, (WebCore::Node*) node, x, + y, block); +} + +static void SetKitFocus(JNIEnv *env, jobject obj, jint moveGeneration, + jint buildGeneration, jint frame, jint node, jint x, jint y, + jboolean ignoreNullFocus) +{ +#ifdef ANDROID_INSTRUMENT + TimeCounterWV counter; +#endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in %s", __FUNCTION__); + viewImpl->setKitFocus(moveGeneration, buildGeneration, + (WebCore::Frame*) frame, (WebCore::Node*) node, x, y, + ignoreNullFocus); +} + +static void UnblockFocus(JNIEnv *env, jobject obj) +{ + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in %s", __FUNCTION__); + viewImpl->unblockFocus(); +} + +static void UpdateFrameCache(JNIEnv *env, jobject obj) +{ + 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) +{ + 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->minPrefWidth(); + } + } + } + return 0; +} + +static void SetViewportSettingsFromNative(JNIEnv *env, jobject obj) +{ + 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()); +#endif +} + +static void SetSnapAnchor(JNIEnv *env, jobject obj, jint x, jint y) +{ + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in %s", __FUNCTION__); + + viewImpl->setSnapAnchor(x, y); +} + +static void SnapToAnchor(JNIEnv *env, jobject obj) +{ + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in %s", __FUNCTION__); + + viewImpl->snapToAnchor(); +} + +static void SetBackgroundColor(JNIEnv *env, jobject obj, jint color) +{ + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in %s", __FUNCTION__); + + viewImpl->setBackgroundColor((SkColor) color); +} + +static jstring GetSelection(JNIEnv *env, jobject obj, jobject selRgn) +{ + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in %s", __FUNCTION__); + SkRegion* selectionRegion = GraphicsJNI::getNativeRegion(env, selRgn); + WebCore::String result = viewImpl->getSelection(selectionRegion); + if (!result.isEmpty()) + return WebCoreStringToJString(env, result); + return 0; +} + +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 RefreshPlugins(JNIEnv *env, + jobject obj, + jboolean reloadOpenPages) +{ + // Refresh the list of plugins, optionally reloading all open + // pages. + WebCore::refreshPlugins(reloadOpenPages); +} + +static void RegisterURLSchemeAsLocal(JNIEnv* env, jobject obj, jstring scheme) { + WebCore::FrameLoader::registerURLSchemeAsLocal(to_string(env, scheme)); +} + +static void ClearContent(JNIEnv *env, jobject obj) +{ + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + viewImpl->clearContent(); +} + +static void CopyContentToPicture(JNIEnv *env, jobject obj, jobject pict) +{ + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + if (!viewImpl) + return; + SkPicture* picture = GraphicsJNI::getNativePicture(env, pict); + viewImpl->copyContentToPicture(picture); +} + +static bool DrawContent(JNIEnv *env, jobject obj, jobject canv, jint color) +{ + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + SkCanvas* canvas = GraphicsJNI::getNativeCanvas(env, canv); + return viewImpl->drawContent(canvas, color); +} + +// ---------------------------------------------------------------------------- + +/* + * JNI registration. + */ +static JNINativeMethod gJavaWebViewCoreMethods[] = { + { "nativeClearContent", "()V", + (void*) ClearContent }, + { "nativeCopyContentToPicture", "(Landroid/graphics/Picture;)V", + (void*) CopyContentToPicture }, + { "nativeDrawContent", "(Landroid/graphics/Canvas;I)Z", + (void*) DrawContent } , + { "nativeKeyUp", "(II)Z", + (void*) KeyUp }, + { "nativeSendKeyToFocusNode", "(IIIZZI)Z", (void*) SendKeyToFocusNode }, + { "nativeSendListBoxChoices", "([ZI)V", + (void*) SendListBoxChoices }, + { "nativeSendListBoxChoice", "(I)V", + (void*) SendListBoxChoice }, + { "nativeSetSize", "(IIIF)V", + (void*) SetSize }, + { "nativeSetScrollOffset", "(II)V", + (void*) SetScrollOffset }, + { "nativeSetGlobalBounds", "(IIII)V", + (void*) SetGlobalBounds }, + { "nativeSetSelection", "(IIIIII)V", + (void*) SetSelection } , + { "nativeDeleteSelection", "(IIIIII)V", + (void*) DeleteSelection } , + { "nativeReplaceTextfieldText", "(IIIIIILjava/lang/String;II)V", + (void*) ReplaceTextfieldText } , + { "passToJs", "(IIIIILjava/lang/String;IIZZZZ)V", + (void*) PassToJs } , + { "nativeSaveDocumentState", "(IIII)V", + (void*) SaveDocumentState }, + { "nativeFindAddress", "(Ljava/lang/String;)Ljava/lang/String;", + (void*) FindAddress }, + { "nativeHandleTouchEvent", "(III)Z", + (void*) HandleTouchEvent }, + { "nativeTouchUp", "(IIIIIIIZZ)V", + (void*) TouchUp }, + { "nativeRetrieveHref", "(II)Ljava/lang/String;", + (void*) RetrieveHref }, + { "nativeSetFinalFocus", "(IIIIZ)V", + (void*) SetFinalFocus }, + { "nativeSetKitFocus", "(IIIIIIZ)V", + (void*) SetKitFocus }, + { "nativeUnblockFocus", "()V", + (void*) UnblockFocus }, + { "nativeUpdateFrameCache", "()V", + (void*) UpdateFrameCache }, + { "nativeGetContentMinPrefWidth", "()I", + (void*) GetContentMinPrefWidth }, + { "nativeRecordContent", "(Landroid/graphics/Region;Landroid/graphics/Point;)Z", + (void*) RecordContent }, + { "setViewportSettingsFromNative", "()V", + (void*) SetViewportSettingsFromNative }, + { "nativeSetSnapAnchor", "(II)V", + (void*) SetSnapAnchor }, + { "nativeSnapToAnchor", "()V", + (void*) SnapToAnchor }, + { "nativeSplitContent", "()V", + (void*) SplitContent }, + { "nativeSetBackgroundColor", "(I)V", + (void*) SetBackgroundColor }, + { "nativeGetSelection", "(Landroid/graphics/Region;)Ljava/lang/String;", + (void*) GetSelection }, + { "nativeRefreshPlugins", "(Z)V", + (void*) RefreshPlugins }, + { "nativeRegisterURLSchemeAsLocal", "(Ljava/lang/String;)V", + (void*) RegisterURLSchemeAsLocal }, + { "nativeDumpDomTree", "(Z)V", + (void*) DumpDomTree }, + { "nativeDumpRenderTree", "(Z)V", + (void*) DumpRenderTree }, + { "nativeDumpNavTree", "()V", + (void*) DumpNavTree } +}; + +int register_webviewcore(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_webView = env->GetFieldID(widget, + "mWebView", "Landroid/webkit/WebView;"); + LOG_ASSERT(gWebViewCoreFields.m_webView, + "Unable to find android/webkit/WebViewCore.mWebView"); + + return jniRegisterNativeMethods(env, "android/webkit/WebViewCore", + gJavaWebViewCoreMethods, NELEM(gJavaWebViewCoreMethods)); +} + +} /* namespace android */ diff --git a/WebKit/android/jni/WebViewCore.h b/WebKit/android/jni/WebViewCore.h new file mode 100644 index 0000000..8a073c2 --- /dev/null +++ b/WebKit/android/jni/WebViewCore.h @@ -0,0 +1,415 @@ +/* + * + * Copyright 2006, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBVIEWCORE_H +#define WEBVIEWCORE_H + +#include "CacheBuilder.h" +#include "CachedHistory.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 <jni.h> +#include <ui/KeycodeLabels.h> + +namespace WebCore { + class AtomicString; + class Color; + class FrameView; + class HTMLSelectElement; + class RenderPart; + class RenderText; + class Node; + class RenderTextControl; + class ScrollView; + class TimerBase; +} + +struct PluginWidgetAndroid; +class SkPicture; +class SkIRect; + +namespace android { + + 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 + + /** + * 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); + + /** + * Scroll to the point x,y relative to the current position. + * @param x The relative x position. + * @param y The relative y position. + */ + void scrollBy(int x, int y); + + /** + * Record the invalid rectangle + */ + void contentInvalidate(const WebCore::IntRect &rect); + + /** + * Satisfy any outstanding invalidates, so that the current state + * of the DOM is drawn. + */ + void contentDraw(); + + // invalidate the view/display, NOT the content/DOM + void viewInvalidate(const WebCore::IntRect& rect); + void viewInvalidate(const SkIRect& rect); + + /** + * Invalidate part of the content that may be offscreen at the moment + */ + void offInvalidate(const WebCore::IntRect &rect); + + /** + * Called by webcore when the focus was set after returning to prior page + * used to rebuild and display any changes in focus + */ + void notifyFocusSet(); + + /** + * 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 restore the screen width, which in turn restores + * the scale. + */ + void restoreScale(int); + + /** + * 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 WebCore::String& text); + + // JavaScript support + void jsAlert(const WebCore::String& url, const WebCore::String& text); + bool jsConfirm(const WebCore::String& url, const WebCore::String& text); + bool jsPrompt(const WebCore::String& url, const WebCore::String& message, + const WebCore::String& defaultValue, WebCore::String& result); + bool jsUnload(const WebCore::String& url, const WebCore::String& message); + + // + // Followings support calls from Java to native WebCore + // + + WebCore::String retrieveHref(WebCore::Frame* frame, WebCore::Node* node); + + WebCore::String getSelection(SkRegion* ); + + // Create a single picture to represent the drawn DOM (used by navcache) + void recordPicture(SkPicture* picture); + + // 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); + void setFinalFocus(WebCore::Frame* frame, WebCore::Node* node, + int x, int y, bool block); + void setKitFocus(int moveGeneration, int buildGeneration, + WebCore::Frame* frame, WebCore::Node* node, int x, int y, + bool ignoreNullFocus); + + // set the scroll amount that webview.java is currently showing + void setScrollOffset(int dx, int dy); + + void setGlobalBounds(int x, int y, int h, int v); + + void setSizeScreenWidthAndScale(int width, int height, int screenWidth, int scale); + + /** + * Handle keyDown events from Java. + * @param keyCode The key pressed. + * @return Whether keyCode was handled by this class. + */ + bool keyUp(KeyCode keyCode, int keyValue); + + // These need to be lock-step with the KEY_ACTION values in + // WebViewCore.java + enum KeyAction { + DownKeyAction = 0, + UpKeyAction = 1 + }; + /** + * Send the key event to the focus node (if there is one). Return true + * if there is a node, and it claims to have handled the event. + */ + bool sendKeyToFocusNode(int code, UChar32 unichar, int repeatCount, + bool isShift, bool isAlt, KeyAction); + + /** + * Handle touch event + */ + bool handleTouchEvent(int action, int x, int y); + + /** + * Handle motionUp event from the UI thread (called touchUp in the + * WebCore thread). + */ + void touchUp(int touchGeneration, int buildGeneration, + WebCore::Frame* frame, WebCore::Node* node, int x, int y, + int size, bool isClick, bool retry); + + /** + * 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 there is no + * focus, or if start == end, silently fail, but set selection to that value. + * If start and end are out of order, swap them. + * Use the frame, node, x, and y to ensure that the correct node is focused. + * Return a frame. Convenience so replaceTextfieldText can use this function. + */ + WebCore::Frame* deleteSelection(WebCore::Frame* frame, WebCore::Node* node, int x, + int y,int start, int end); + + /** + * Set the selection of the currently focused textfield to (start, end). + * If start and end are out of order, swap them. + * Use the frame, node, x, and y to ensure that the correct node is focused. + * Return a frame. Convenience so deleteSelection can use this function. + */ + WebCore::Frame* setSelection(WebCore::Frame* frame, WebCore::Node* node, int x, + int y,int start, int end); + /** + * In the currenlty focused textfield, represented by frame, node, x, and y (which + * are used to ensure it has focus), 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(WebCore::Frame* frame, WebCore::Node* node, int x, int y, + int oldStart, int oldEnd, jstring replace, int start, int end); + void passToJs(WebCore::Frame* frame, WebCore::Node* node, int x, int y, int generation, + jstring currentText, int jKeyCode, int keyVal, bool down, bool cap, bool fn, bool sym); + + void saveDocumentState(WebCore::Frame* frame, WebCore::Node* node, int x, int y); + + // 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 setSnapAnchor(int x, int y); + void snapToAnchor(); + void unblockFocus() { m_blockFocusChange = false; } + void updateFrameCache(); + 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*); + void invalPlugin(PluginWidgetAndroid*); + void drawPlugins(); + + // Notify the Java side whether it needs to pass down the touch events + void needTouchEvents(bool); + + // other public functions + public: + void removeFrameGeneration(WebCore::Frame* ); + void updateFrameGeneration(WebCore::Frame* ); + + // reset the picture set to empty + void clearContent(); + + // flatten the picture set to a picture + void copyContentToPicture(SkPicture* ); + + // draw the picture set with the specified background color + bool drawContent(SkCanvas* , SkColor ); + + // record the inval area, and the picture size + bool recordContent(SkRegion* , SkIPoint* ); + int screenWidth() const { return m_screenWidth; } + int scale() const { return m_scale; } + WebCore::Frame* mainFrame() const { return m_mainFrame; } + + // utility to split slow parts of the picture set + void splitContent(); + + // these members are shared with webview.cpp + int retrieveFrameGeneration(WebCore::Frame* ); + static Mutex gFrameCacheMutex; + CachedRoot* m_frameCacheKit; // nav data being built by webcore + SkPicture* m_navPictureKit; + int m_generation; // copy of the number bumped by WebViewNative + 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_useReplay; + bool m_findIsUp; + static Mutex gRecomputeFocusMutex; + WTF::Vector<int> m_recomputeEvents; + // 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: + // 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); + + 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); + + friend class ListBoxReply; + struct FrameGen { + const WebCore::Frame* m_frame; + int m_generation; + }; + WTF::Vector<FrameGen> m_frameGenerations; + static Mutex gFrameGenerationMutex; + struct JavaGlue; + struct JavaGlue* m_javaGlue; + WebCore::Frame* m_mainFrame; + WebCoreReply* m_popupReply; + WebCore::Node* m_lastFocused; + WebCore::IntRect m_lastFocusedBounds; + static Mutex m_contentMutex; // protects ui/core thread pictureset access + PictureSet m_content; // the set of pictures to draw (accessed by UI too) + SkRegion m_addInval; // the accumulated inval region (not yet drawn) + // Used in passToJS to avoid updating the UI text field until after the + // key event has been processed. + bool m_blockTextfieldUpdates; + 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_buildGeneration; + 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_blockFocusChange; + int m_lastPassed; + int m_lastVelocity; + CachedHistory m_history; + WebCore::Node* m_snapAnchorNode; + int m_screenWidth; + int m_scale; + + SkTDArray<PluginWidgetAndroid*> m_plugins; + WebCore::Timer<WebViewCore> m_pluginInvalTimer; + void pluginInvalTimerFired(WebCore::Timer<WebViewCore>*) { + this->drawPlugins(); + } + + WebCore::Frame* changedKitFocus(WebCore::Frame* frame, + WebCore::Node* node, int x, int y); + bool commonKitFocus(int generation, int buildGeneration, + WebCore::Frame* frame, WebCore::Node* node, int x, int y, + bool ignoreNullFocus); + bool finalKitFocus(WebCore::Frame* frame, WebCore::Node* node, int x, int y); + void doMaxScroll(WebCore::CacheBuilder::Direction dir); + SkPicture* rebuildPicture(const SkIRect& inval); + void rebuildPictureSet(PictureSet* ); + void sendMarkNodeInvalid(WebCore::Node* ); + void sendNotifyFocusSet(); + void sendNotifyProgressFinished(); + void sendRecomputeFocus(); + bool handleMouseClick(WebCore::Frame* framePtr, WebCore::Node* nodePtr); + bool prepareFrameCache(); + void releaseFrameCache(bool newCache); +#if DEBUG_NAV_UI + uint32_t m_now; +#endif + }; + +} // namespace android + +#endif // WEBVIEWCORE_H |
