/* * Copyright 2006, The Android Open Source Project * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #define LOG_TAG "webcoreglue" #include #include #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 #include #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 "wds/DebugServer.h" #include #include #include #include "jni.h" #include "jni_instance.h" #include #include #include #include #include #include #ifdef ANDROID_INSTRUMENT #include "TimeCounter.h" #endif namespace android { // ---------------------------------------------------------------------------- #define WEBCORE_MEMORY_CAP 15 * 1024 * 1024 // ---------------------------------------------------------------------------- struct WebFrame::JavaBrowserFrame { 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->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 = JSC::Bindings::getJNIEnv(); env->DeleteGlobalRef(mJavaFrame->mObj); env->DeleteGlobalRef(mJavaFrame->mHistoryList); mJavaFrame->mObj = 0; } delete mJavaFrame; } WebFrame* WebFrame::getWebFrame(const WebCore::Frame* frame) { FrameLoaderClientAndroid* client = static_cast (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, "", "(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 TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); #endif LOGV("::WebCore:: startLoadingResource(%p, %s)", loader, request.url().string().ascii().data()); WebCore::String method = request.httpMethod(); WebCore::HTTPHeaderMap headers = request.httpHeaderFields(); JNIEnv* env = JSC::Bindings::getJNIEnv(); 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& 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 TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); #endif LOGV("::WebCore:: reportError(%d, %s)", errorCode, description.ascii().data()); JNIEnv* env = JSC::Bindings::getJNIEnv(); 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 TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); #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 = JSC::Bindings::getJNIEnv(); 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 TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); #endif JNIEnv* env = JSC::Bindings::getJNIEnv(); WebCore::FrameLoadType loadType = frame->loader()->loadType(); bool isMainFrame = (!frame->tree() || !frame->tree()->parent()); env->CallVoidMethod(mJavaFrame->frame(env).get(), mJavaFrame->mTransitionToCommitted, (int)loadType, isMainFrame); checkException(env); } void WebFrame::didFinishLoad(WebCore::Frame* frame) { #ifdef ANDROID_INSTRUMENT TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); #endif JNIEnv* env = JSC::Bindings::getJNIEnv(); 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 TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); #endif LOGV("::WebCore:: addHistoryItem"); JNIEnv* env = JSC::Bindings::getJNIEnv(); WebHistory::AddItem(mJavaFrame->history(env), item); } void WebFrame::removeHistoryItem(int index) { #ifdef ANDROID_INSTRUMENT TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); #endif LOGV("::WebCore:: removeHistoryItem at %d", index); JNIEnv* env = JSC::Bindings::getJNIEnv(); WebHistory::RemoveItem(mJavaFrame->history(env), index); } void WebFrame::updateHistoryIndex(int newIndex) { #ifdef ANDROID_INSTRUMENT TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); #endif LOGV("::WebCore:: updateHistoryIndex to %d", newIndex); JNIEnv* env = JSC::Bindings::getJNIEnv(); WebHistory::UpdateHistoryIndex(mJavaFrame->history(env), newIndex); } void WebFrame::setTitle(const WebCore::String& title) { #ifdef ANDROID_INSTRUMENT TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); #endif #ifndef NDEBUG LOGV("setTitle(%s)", title.ascii().data()); #endif JNIEnv* env = JSC::Bindings::getJNIEnv(); 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 TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); #endif LOGV("::WebCore:: windowObjectCleared"); JNIEnv* env = JSC::Bindings::getJNIEnv(); env->CallVoidMethod(mJavaFrame->frame(env).get(), mJavaFrame->mWindowObjectCleared, (int)frame); checkException(env); } void WebFrame::setProgress(float newProgress) { #ifdef ANDROID_INSTRUMENT TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); #endif JNIEnv* env = JSC::Bindings::getJNIEnv(); 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 TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); #endif LOG_ASSERT(icon, "DidReceiveIcon called without an image!"); JNIEnv* env = JSC::Bindings::getJNIEnv(); jobject bitmap = webcoreImageToJavaBitmap(env, icon); if (!bitmap) return; env->CallVoidMethod(mJavaFrame->frame(env).get(), mJavaFrame->mDidReceiveIcon, bitmap); env->DeleteLocalRef(bitmap); checkException(env); } void WebFrame::updateVisitedHistory(const WebCore::KURL& url, bool reload) { #ifdef ANDROID_INSTRUMENT TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); #endif WebCore::String urlStr(url.string()); JNIEnv* env = JSC::Bindings::getJNIEnv(); 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 TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); #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 // always handle "POST" in place if (equalIgnoringCase(request.httpMethod(), "POST")) return true; 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 = JSC::Bindings::getJNIEnv(); 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 TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); #endif JNIEnv* env = JSC::Bindings::getJNIEnv(); jobject obj = env->CallObjectMethod(mJavaFrame->frame(env).get(), mJavaFrame->mCreateWindow, dialog, userGesture); if (obj) { WebCore::Frame* frame = GET_NATIVE_FRAME(env, obj); return frame; } return NULL; } void WebFrame::requestFocus() const { #ifdef ANDROID_INSTRUMENT TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); #endif JNIEnv* env = JSC::Bindings::getJNIEnv(); env->CallVoidMethod(mJavaFrame->frame(env).get(), mJavaFrame->mRequestFocus); checkException(env); } void WebFrame::closeWindow(WebViewCore* webViewCore) { #ifdef ANDROID_INSTRUMENT TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); #endif assert(webViewCore); JNIEnv* env = JSC::Bindings::getJNIEnv(); env->CallVoidMethod(mJavaFrame->frame(env).get(), mJavaFrame->mCloseWindow, webViewCore->getJavaObject().get()); } struct PolicyFunctionWrapper { WebCore::FramePolicyFunction func; }; void WebFrame::decidePolicyForFormResubmission(WebCore::FramePolicyFunction func) { #ifdef ANDROID_INSTRUMENT TimeCounterAuto counter(TimeCounter::JavaCallbackTimeCounter); #endif JNIEnv* env = JSC::Bindings::getJNIEnv(); 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 = JSC::Bindings::getJNIEnv(); 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 TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); #endif WebCore::Frame* pFrame = GET_NATIVE_FRAME(env, obj); LOG_ASSERT(pFrame, "nativeCallPolicyFunction must take a valid frame pointer!"); PolicyFunctionWrapper* pFunc = (PolicyFunctionWrapper*)func; LOG_ASSERT(pFunc, "nativeCallPolicyFunction must take a valid function pointer!"); (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 TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); #endif ChromeClientAndroid* chromeC = new ChromeClientAndroid; EditorClientAndroid* editorC = new EditorClientAndroid; WebCore::ContextMenuClient* contextMenuC = new ContextMenuClientAndroid; WebCore::DragClient* dragC = new DragClientAndroid; InspectorClientAndroid* inspectorC = new InspectorClientAndroid; // Create a new page WebCore::Page* page = new WebCore::Page(chromeC, contextMenuC, editorC, dragC, inspectorC); // css files without explicit MIMETYPE is treated as generic text files in // the Java side. So we can't enforce CSS MIMETYPE. page->settings()->setEnforceCSSMIMETypeInStrictMode(false); /* 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); FrameLoaderClientAndroid* loaderC = new FrameLoaderClientAndroid(webFrame); // Create a Frame and the page holds its reference WebCore::Frame* frame = WebCore::Frame::create(page, NULL, loaderC).get(); loaderC->setFrame(frame); #if ENABLE(WDS) WDS::server()->addFrame(frame); #endif // Create a WebViewCore to access the Java WebViewCore associated with this page WebViewCore* webViewCore = new WebViewCore(env, javaview, frame); // 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 TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); #endif WebCore::Frame* pFrame = GET_NATIVE_FRAME(env, obj); LOG_ASSERT(pFrame, "nativeDestroyFrame must take a valid frame pointer!"); LOGV("::WebCore:: deleting frame %p", pFrame); WebCore::FrameView* view = pFrame->view(); view->ref(); // detachFromParent will cause the page to be closed. WebCore::FrameLoader* fl = pFrame->loader(); // retain a pointer because detachFromParent will set the page to null. WebCore::Page* page = pFrame->page(); if (fl) fl->detachFromParent(); delete page; view->deref(); SET_NATIVE_FRAME(env, obj, 0); #if ENABLE(WDS) WDS::server()->removeFrame(pFrame); #endif } static void LoadUrl(JNIEnv *env, jobject obj, jstring url) { #ifdef ANDROID_INSTRUMENT TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); #endif WebCore::Frame* pFrame = GET_NATIVE_FRAME(env, obj); LOG_ASSERT(pFrame, "nativeLoadUrl must take a valid frame pointer!"); 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 TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); #endif WebCore::Frame* pFrame = GET_NATIVE_FRAME(env, obj); LOG_ASSERT(pFrame, "nativeLoadData must take a valid frame pointer!"); // Setup the resource request WebCore::ResourceRequest request(to_string(env, baseUrl)); // Setup the substituteData const char* dataStr = env->GetStringUTFChars(data, NULL); WTF::PassRefPtr 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 TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); #endif WebCore::Frame* pFrame = GET_NATIVE_FRAME(env, obj); LOG_ASSERT(pFrame, "nativeStopLoading must take a valid frame pointer!"); LOGV("::WebCore:: stopLoading %p", pFrame); // Stop loading the page and do not send an unload event pFrame->loader()->stopForUserCancel(); } static jstring ExternalRepresentation(JNIEnv *env, jobject obj) { #ifdef ANDROID_INSTRUMENT TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); #endif WebCore::Frame* pFrame = GET_NATIVE_FRAME(env, obj); LOG_ASSERT(pFrame, "android_webcore_nativeExternalRepresentation must take a valid frame pointer!"); // Request external representation of the render tree 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 TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); #endif WebCore::Frame* pFrame = GET_NATIVE_FRAME(env, obj); LOG_ASSERT(pFrame, "android_webcore_nativeDocumentAsText must take a valid frame pointer!"); 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 TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); #endif WebCore::Frame* pFrame = GET_NATIVE_FRAME(env, obj); LOG_ASSERT(pFrame, "nativeReload must take a valid frame pointer!"); WebCore::FrameLoader* loader = pFrame->loader(); if (allowStale) loader->reloadAllowingStaleData(loader->documentLoader()->overrideEncoding()); else loader->reload(); } static void GoBackOrForward(JNIEnv *env, jobject obj, jint pos) { #ifdef ANDROID_INSTRUMENT TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); #endif WebCore::Frame* pFrame = GET_NATIVE_FRAME(env, obj); LOG_ASSERT(pFrame, "nativeGoBackOrForward must take a valid frame pointer!"); if (pos == 1) pFrame->page()->goForward(); else if (pos == -1) pFrame->page()->goBack(); else pFrame->loader()->goBackOrForward(pos); } static jobject StringByEvaluatingJavaScriptFromString(JNIEnv *env, jobject obj, jstring script) { #ifdef ANDROID_INSTRUMENT TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); #endif WebCore::Frame* pFrame = GET_NATIVE_FRAME(env, obj); LOG_ASSERT(pFrame, "stringByEvaluatingJavaScriptFromString must take a valid frame pointer!"); 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 TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); #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 TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); #endif WebCore::cache()->setDisabled(disabled); } static jboolean CacheDisabled(JNIEnv *env, jobject obj) { #ifdef ANDROID_INSTRUMENT TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); #endif return WebCore::cache()->disabled(); } static void ClearCache(JNIEnv *env, jobject obj) { #ifdef ANDROID_INSTRUMENT TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); #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 TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); #endif WebCore::Frame* pFrame = GET_NATIVE_FRAME(env, obj); LOG_ASSERT(pFrame, "DocumentHasImages must take a valid frame pointer!"); return pFrame->document()->images()->length() > 0; } static jboolean HasPasswordField(JNIEnv *env, jobject obj) { #ifdef ANDROID_INSTRUMENT TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); #endif WebCore::Frame* pFrame = GET_NATIVE_FRAME(env, obj); LOG_ASSERT(pFrame, "HasPasswordField must take a valid frame pointer!"); bool found = false; WTF::PassRefPtr form = pFrame->document()->forms(); WebCore::Node* node = form->firstItem(); while (node && !found) { WTF::Vector 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 TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); #endif WebCore::Frame* pFrame = GET_NATIVE_FRAME(env, obj); LOG_ASSERT(pFrame, "GetUsernamePassword must take a valid frame pointer!"); jobjectArray strArray = NULL; WebCore::String username, password; bool found = false; WTF::PassRefPtr form = pFrame->document()->forms(); WebCore::Node* node = form->firstItem(); while (node && !found) { WTF::Vector 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 TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); #endif WebCore::Frame* pFrame = GET_NATIVE_FRAME(env, obj); LOG_ASSERT(pFrame, "SetUsernamePassword must take a valid frame pointer!"); WebCore::HTMLInputElement* usernameEle = NULL; WebCore::HTMLInputElement* passwordEle = NULL; bool found = false; WTF::PassRefPtr form = pFrame->document()->forms(); WebCore::Node* node = form->firstItem(); while (node && !found) { WTF::Vector 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 TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter); #endif WebCore::Frame* pFrame = GET_NATIVE_FRAME(env, obj); LOG_ASSERT(pFrame, "GetFormTextData must take a valid frame pointer!"); jobject hashMap = NULL; WTF::PassRefPtr 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, "", "(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(node); if (form->autoComplete()) { WTF::Vector 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 */