| /* |
| * Copyright 2006, The Android Open Source Project |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #define LOG_TAG "webcoreglue" |
| |
| #include "config.h" |
| #include "WebViewCore.h" |
| |
| #include "AccessibilityObject.h" |
| #include "AndroidHitTestResult.h" |
| #include "ApplicationCacheStorage.h" |
| #include "Attribute.h" |
| #include "content/address_detector.h" |
| #include "Chrome.h" |
| #include "ChromeClientAndroid.h" |
| #include "ChromiumIncludes.h" |
| #include "ClientRect.h" |
| #include "ClientRectList.h" |
| #include "Color.h" |
| #include "CSSPropertyNames.h" |
| #include "CSSValueKeywords.h" |
| #include "DatabaseTracker.h" |
| #include "Document.h" |
| #include "DocumentMarkerController.h" |
| #include "DOMWindow.h" |
| #include "DOMSelection.h" |
| #include "Element.h" |
| #include "Editor.h" |
| #include "EditorClientAndroid.h" |
| #include "EventHandler.h" |
| #include "EventNames.h" |
| #include "ExceptionCode.h" |
| #include "FocusController.h" |
| #include "Font.h" |
| #include "FontCache.h" |
| #include "Frame.h" |
| #include "FrameLoader.h" |
| #include "FrameLoaderClientAndroid.h" |
| #include "FrameTree.h" |
| #include "FrameView.h" |
| #include "Geolocation.h" |
| #include "GraphicsContext.h" |
| #include "GraphicsJNI.h" |
| #include "HTMLAnchorElement.h" |
| #include "HTMLAreaElement.h" |
| #include "HTMLElement.h" |
| #include "HTMLFormControlElement.h" |
| #include "HTMLImageElement.h" |
| #include "HTMLInputElement.h" |
| #include "HTMLLabelElement.h" |
| #include "HTMLMapElement.h" |
| #include "HTMLNames.h" |
| #include "HTMLOptGroupElement.h" |
| #include "HTMLOptionElement.h" |
| #include "HTMLSelectElement.h" |
| #include "HTMLTextAreaElement.h" |
| #include "HistoryItem.h" |
| #include "HitTestRequest.h" |
| #include "HitTestResult.h" |
| #include "InlineTextBox.h" |
| #include "KeyboardEvent.h" |
| #include "MemoryUsage.h" |
| #include "NamedNodeMap.h" |
| #include "Navigator.h" |
| #include "Node.h" |
| #include "NodeList.h" |
| #include "Page.h" |
| #include "PageGroup.h" |
| #include "PictureLayerContent.h" |
| #include "PicturePileLayerContent.h" |
| #include "PlatformKeyboardEvent.h" |
| #include "PlatformString.h" |
| #include "PluginWidgetAndroid.h" |
| #include "PluginView.h" |
| #include "Position.h" |
| #include "ProgressTracker.h" |
| #include "Range.h" |
| #include "RenderBox.h" |
| #include "RenderImage.h" |
| #include "RenderInline.h" |
| #include "RenderLayer.h" |
| #include "RenderPart.h" |
| #include "RenderText.h" |
| #include "RenderTextControl.h" |
| #include "RenderThemeAndroid.h" |
| #include "RenderView.h" |
| #include "ResourceRequest.h" |
| #include "RuntimeEnabledFeatures.h" |
| #include "SchemeRegistry.h" |
| #include "ScopedLocalRef.h" |
| #include "ScriptController.h" |
| #include "SelectionController.h" |
| #include "SelectText.h" |
| #include "Settings.h" |
| #include "SkANP.h" |
| #include "SkTemplates.h" |
| #include "SkTDArray.h" |
| #include "SkTypes.h" |
| #include "SkCanvas.h" |
| #include "SkGraphics.h" |
| #include "SkPicture.h" |
| #include "SkUtils.h" |
| #include "Text.h" |
| #include "TextIterator.h" |
| #include "TilesManager.h" |
| #include "TypingCommand.h" |
| #include "WebCache.h" |
| #include "WebCoreFrameBridge.h" |
| #include "WebCoreJni.h" |
| #include "WebFrameView.h" |
| #include "WindowsKeyboardCodes.h" |
| #include "autofill/WebAutofill.h" |
| #include "htmlediting.h" |
| #include "markup.h" |
| #include "unicode/uloc.h" |
| #include "visible_units.h" |
| |
| #include <JNIHelp.h> |
| #include <JNIUtility.h> |
| #include <androidfw/KeycodeLabels.h> |
| #include <cutils/properties.h> |
| #include <v8.h> |
| #include <wtf/CurrentTime.h> |
| #include <wtf/text/AtomicString.h> |
| #include <wtf/text/CString.h> |
| #include <wtf/text/StringImpl.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 <wtf/text/CString.h> |
| |
| FILE* gDomTreeFile = 0; |
| FILE* gRenderTreeFile = 0; |
| #endif |
| |
| #include "BaseLayerAndroid.h" |
| |
| #if USE(ACCELERATED_COMPOSITING) |
| #include "GraphicsLayerAndroid.h" |
| #include "RenderLayerCompositor.h" |
| #endif |
| |
| #define FOREGROUND_TIMER_INTERVAL 0.004 // 4ms |
| #define BACKGROUND_TIMER_INTERVAL 1.0 // 1s |
| |
| // How many ms to wait for the scroll to "settle" before we will consider doing |
| // prerenders |
| #define PRERENDER_AFTER_SCROLL_DELAY 750 |
| |
| #define TOUCH_FLAG_HIT_HANDLER 0x1 |
| #define TOUCH_FLAG_PREVENT_DEFAULT 0x2 |
| |
| //////////////////////////////////////////////////////////////////////////////////////////////// |
| |
| namespace android { |
| |
| // Copied from CacheBuilder, not sure if this is needed/correct |
| IntRect getAreaRect(const HTMLAreaElement* area) |
| { |
| Node* node = area->document(); |
| while ((node = node->traverseNextNode()) != NULL) { |
| RenderObject* renderer = node->renderer(); |
| if (renderer && renderer->isRenderImage()) { |
| RenderImage* image = static_cast<RenderImage*>(renderer); |
| HTMLMapElement* map = image->imageMap(); |
| if (map) { |
| Node* n; |
| for (n = map->firstChild(); n; |
| n = n->traverseNextNode(map)) { |
| if (n == area) { |
| if (area->isDefault()) |
| return image->absoluteBoundingBoxRect(); |
| return area->computeRect(image); |
| } |
| } |
| } |
| } |
| } |
| return IntRect(); |
| } |
| |
| // Copied from CacheBuilder, not sure if this is needed/correct |
| // TODO: See if this is even needed (I suspect not), and if not remove it |
| bool validNode(Frame* startFrame, void* matchFrame, |
| void* matchNode) |
| { |
| if (matchFrame == startFrame) { |
| if (matchNode == NULL) |
| return true; |
| Node* node = startFrame->document(); |
| while (node != NULL) { |
| if (node == matchNode) { |
| const IntRect& rect = node->hasTagName(HTMLNames::areaTag) ? |
| getAreaRect(static_cast<HTMLAreaElement*>(node)) : node->getRect(); |
| // Consider nodes with empty rects that are not at the origin |
| // to be valid, since news.google.com has valid nodes like this |
| if (rect.x() == 0 && rect.y() == 0 && rect.isEmpty()) |
| return false; |
| return true; |
| } |
| node = node->traverseNextNode(); |
| } |
| return false; |
| } |
| Frame* child = startFrame->tree()->firstChild(); |
| while (child) { |
| bool result = validNode(child, matchFrame, matchNode); |
| if (result) |
| return result; |
| child = child->tree()->nextSibling(); |
| } |
| return false; |
| } |
| |
| static SkTDArray<WebViewCore*> gInstanceList; |
| |
| void WebViewCore::addInstance(WebViewCore* inst) { |
| *gInstanceList.append() = inst; |
| } |
| |
| void WebViewCore::removeInstance(WebViewCore* inst) { |
| int index = gInstanceList.find(inst); |
| ALOG_ASSERT(index >= 0, "RemoveInstance inst not found"); |
| if (index >= 0) { |
| gInstanceList.removeShuffle(index); |
| } |
| } |
| |
| bool WebViewCore::isInstance(WebViewCore* inst) { |
| return gInstanceList.find(inst) >= 0; |
| } |
| |
| jobject WebViewCore::getApplicationContext() { |
| |
| // check to see if there is a valid webviewcore object |
| if (gInstanceList.isEmpty()) |
| return 0; |
| |
| // get the context from the webview |
| jobject context = gInstanceList[0]->getContext(); |
| |
| if (!context) |
| return 0; |
| |
| // get the application context using JNI |
| JNIEnv* env = JSC::Bindings::getJNIEnv(); |
| jclass contextClass = env->GetObjectClass(context); |
| jmethodID appContextMethod = env->GetMethodID(contextClass, "getApplicationContext", "()Landroid/content/Context;"); |
| env->DeleteLocalRef(contextClass); |
| jobject result = env->CallObjectMethod(context, appContextMethod); |
| checkException(env); |
| return result; |
| } |
| |
| |
| struct WebViewCoreStaticMethods { |
| jmethodID m_isSupportedMediaMimeType; |
| } gWebViewCoreStaticMethods; |
| |
| // Check whether a media mimeType is supported in Android media framework. |
| bool WebViewCore::isSupportedMediaMimeType(const WTF::String& mimeType) { |
| JNIEnv* env = JSC::Bindings::getJNIEnv(); |
| jstring jMimeType = wtfStringToJstring(env, mimeType); |
| jclass webViewCore = env->FindClass("android/webkit/WebViewCore"); |
| bool val = env->CallStaticBooleanMethod(webViewCore, |
| gWebViewCoreStaticMethods.m_isSupportedMediaMimeType, jMimeType); |
| checkException(env); |
| env->DeleteLocalRef(webViewCore); |
| env->DeleteLocalRef(jMimeType); |
| |
| return val; |
| } |
| |
| // ---------------------------------------------------------------------------- |
| |
| // Field ids for WebViewCore |
| struct WebViewCoreFields { |
| jfieldID m_nativeClass; |
| jfieldID m_viewportWidth; |
| jfieldID m_viewportHeight; |
| jfieldID m_viewportInitialScale; |
| jfieldID m_viewportMinimumScale; |
| jfieldID m_viewportMaximumScale; |
| jfieldID m_viewportUserScalable; |
| jfieldID m_viewportDensityDpi; |
| jfieldID m_drawIsPaused; |
| jfieldID m_lowMemoryUsageMb; |
| jfieldID m_highMemoryUsageMb; |
| jfieldID m_highUsageDeltaMb; |
| } gWebViewCoreFields; |
| |
| // ---------------------------------------------------------------------------- |
| |
| struct WebViewCore::JavaGlue { |
| jweak m_obj; |
| jmethodID m_scrollTo; |
| jmethodID m_contentDraw; |
| jmethodID m_requestListBox; |
| jmethodID m_openFileChooser; |
| jmethodID m_requestSingleListBox; |
| jmethodID m_jsAlert; |
| jmethodID m_jsConfirm; |
| jmethodID m_jsPrompt; |
| jmethodID m_jsUnload; |
| jmethodID m_jsInterrupt; |
| jmethodID m_getWebView; |
| jmethodID m_didFirstLayout; |
| jmethodID m_updateViewport; |
| jmethodID m_sendNotifyProgressFinished; |
| jmethodID m_sendViewInvalidate; |
| jmethodID m_updateTextfield; |
| jmethodID m_updateTextSelection; |
| jmethodID m_updateTextSizeAndScroll; |
| jmethodID m_clearTextEntry; |
| jmethodID m_restoreScale; |
| jmethodID m_needTouchEvents; |
| jmethodID m_requestKeyboard; |
| jmethodID m_exceededDatabaseQuota; |
| jmethodID m_reachedMaxAppCacheSize; |
| jmethodID m_populateVisitedLinks; |
| jmethodID m_geolocationPermissionsShowPrompt; |
| jmethodID m_geolocationPermissionsHidePrompt; |
| jmethodID m_getDeviceMotionService; |
| jmethodID m_getDeviceOrientationService; |
| jmethodID m_addMessageToConsole; |
| jmethodID m_focusNodeChanged; |
| jmethodID m_getPluginClass; |
| jmethodID m_showFullScreenPlugin; |
| jmethodID m_hideFullScreenPlugin; |
| jmethodID m_createSurface; |
| jmethodID m_addSurface; |
| jmethodID m_updateSurface; |
| jmethodID m_destroySurface; |
| jmethodID m_getContext; |
| jmethodID m_keepScreenOn; |
| jmethodID m_showRect; |
| jmethodID m_centerFitRect; |
| jmethodID m_setScrollbarModes; |
| jmethodID m_exitFullscreenVideo; |
| jmethodID m_setWebTextViewAutoFillable; |
| jmethodID m_selectAt; |
| jmethodID m_initEditField; |
| jmethodID m_chromeCanTakeFocus; |
| jmethodID m_chromeTakeFocus; |
| AutoJObject object(JNIEnv* env) { |
| // We hold a weak reference to the Java WebViewCore to avoid memeory |
| // leaks due to circular references when WebView.destroy() is not |
| // called manually. The WebView and hence the WebViewCore could become |
| // weakly reachable at any time, after which the GC could null our weak |
| // reference, so we have to check the return value of this method at |
| // every use. Note that our weak reference will be nulled before the |
| // WebViewCore is finalized. |
| return getRealObject(env, m_obj); |
| } |
| }; |
| |
| struct WebViewCore::TextFieldInitDataGlue { |
| jmethodID m_constructor; |
| jfieldID m_fieldPointer; |
| jfieldID m_text; |
| jfieldID m_type; |
| jfieldID m_isSpellCheckEnabled; |
| jfieldID m_isTextFieldNext; |
| jfieldID m_isTextFieldPrev; |
| jfieldID m_isAutoCompleteEnabled; |
| jfieldID m_name; |
| jfieldID m_label; |
| jfieldID m_maxLength; |
| jfieldID m_contentBounds; |
| jfieldID m_nodeLayerId; |
| jfieldID m_clientRect; |
| }; |
| |
| /* |
| * WebViewCore Implementation |
| */ |
| |
| static jmethodID GetJMethod(JNIEnv* env, jclass clazz, const char name[], const char signature[]) |
| { |
| jmethodID m = env->GetMethodID(clazz, name, signature); |
| ALOG_ASSERT(m, "Could not find method %s", name); |
| return m; |
| } |
| |
| WebViewCore::WebViewCore(JNIEnv* env, jobject javaWebViewCore, WebCore::Frame* mainframe) |
| : m_touchGeneration(0) |
| , m_lastGeneration(0) |
| , m_javaGlue(new JavaGlue) |
| , m_textFieldInitDataGlue(new TextFieldInitDataGlue) |
| , m_mainFrame(mainframe) |
| , m_popupReply(0) |
| , m_blockTextfieldUpdates(false) |
| , m_skipContentDraw(false) |
| , m_textGeneration(0) |
| , m_maxXScroll(320/4) |
| , m_maxYScroll(240/4) |
| , m_scrollOffsetX(0) |
| , m_scrollOffsetY(0) |
| , m_scrollSetTime(0) |
| , m_mousePos(WebCore::IntPoint(0,0)) |
| , m_screenWidth(320) |
| , m_screenHeight(240) |
| , m_textWrapWidth(320) |
| , m_scale(1.0f) |
| , m_groupForVisitedLinks(0) |
| , m_cacheMode(0) |
| , m_fullscreenVideoMode(false) |
| , m_matchCount(0) |
| , m_activeMatchIndex(0) |
| , m_activeMatch(0) |
| , m_pluginInvalTimer(this, &WebViewCore::pluginInvalTimerFired) |
| , m_screenOnCounter(0) |
| , m_currentNodeDomNavigationAxis(0) |
| , m_deviceMotionAndOrientationManager(this) |
| , m_geolocationManager(this) |
| #if ENABLE(TOUCH_EVENTS) |
| , m_forwardingTouchEvents(false) |
| #endif |
| , m_webRequestContext(0) |
| , m_prerenderEnabled(false) |
| { |
| ALOG_ASSERT(m_mainFrame, "Uh oh, somehow a frameview was made without an initial frame!"); |
| |
| jclass clazz = env->GetObjectClass(javaWebViewCore); |
| m_javaGlue->m_obj = env->NewWeakGlobalRef(javaWebViewCore); |
| m_javaGlue->m_scrollTo = GetJMethod(env, clazz, "contentScrollTo", "(IIZZ)V"); |
| m_javaGlue->m_contentDraw = GetJMethod(env, clazz, "contentDraw", "()V"); |
| m_javaGlue->m_requestListBox = GetJMethod(env, clazz, "requestListBox", "([Ljava/lang/String;[I[I)V"); |
| m_javaGlue->m_openFileChooser = GetJMethod(env, clazz, "openFileChooser", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;"); |
| m_javaGlue->m_requestSingleListBox = GetJMethod(env, clazz, "requestListBox", "([Ljava/lang/String;[II)V"); |
| m_javaGlue->m_jsAlert = GetJMethod(env, clazz, "jsAlert", "(Ljava/lang/String;Ljava/lang/String;)V"); |
| m_javaGlue->m_jsConfirm = GetJMethod(env, clazz, "jsConfirm", "(Ljava/lang/String;Ljava/lang/String;)Z"); |
| m_javaGlue->m_jsPrompt = GetJMethod(env, clazz, "jsPrompt", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;"); |
| m_javaGlue->m_jsUnload = GetJMethod(env, clazz, "jsUnload", "(Ljava/lang/String;Ljava/lang/String;)Z"); |
| m_javaGlue->m_jsInterrupt = GetJMethod(env, clazz, "jsInterrupt", "()Z"); |
| m_javaGlue->m_getWebView = GetJMethod(env, clazz, "getWebView", "()Landroid/webkit/WebView;"); |
| m_javaGlue->m_didFirstLayout = GetJMethod(env, clazz, "didFirstLayout", "(Z)V"); |
| m_javaGlue->m_updateViewport = GetJMethod(env, clazz, "updateViewport", "()V"); |
| m_javaGlue->m_sendNotifyProgressFinished = GetJMethod(env, clazz, "sendNotifyProgressFinished", "()V"); |
| m_javaGlue->m_sendViewInvalidate = GetJMethod(env, clazz, "sendViewInvalidate", "(IIII)V"); |
| m_javaGlue->m_updateTextfield = GetJMethod(env, clazz, "updateTextfield", "(ILjava/lang/String;I)V"); |
| m_javaGlue->m_updateTextSelection = GetJMethod(env, clazz, "updateTextSelection", "(IIIII)V"); |
| m_javaGlue->m_updateTextSizeAndScroll = GetJMethod(env, clazz, "updateTextSizeAndScroll", "(IIIII)V"); |
| m_javaGlue->m_clearTextEntry = GetJMethod(env, clazz, "clearTextEntry", "()V"); |
| m_javaGlue->m_restoreScale = GetJMethod(env, clazz, "restoreScale", "(FF)V"); |
| m_javaGlue->m_needTouchEvents = GetJMethod(env, clazz, "needTouchEvents", "(Z)V"); |
| m_javaGlue->m_requestKeyboard = GetJMethod(env, clazz, "requestKeyboard", "(Z)V"); |
| m_javaGlue->m_exceededDatabaseQuota = GetJMethod(env, clazz, "exceededDatabaseQuota", "(Ljava/lang/String;Ljava/lang/String;JJ)V"); |
| m_javaGlue->m_reachedMaxAppCacheSize = GetJMethod(env, clazz, "reachedMaxAppCacheSize", "(JJ)V"); |
| m_javaGlue->m_populateVisitedLinks = GetJMethod(env, clazz, "populateVisitedLinks", "()V"); |
| m_javaGlue->m_geolocationPermissionsShowPrompt = GetJMethod(env, clazz, "geolocationPermissionsShowPrompt", "(Ljava/lang/String;)V"); |
| m_javaGlue->m_geolocationPermissionsHidePrompt = GetJMethod(env, clazz, "geolocationPermissionsHidePrompt", "()V"); |
| m_javaGlue->m_getDeviceMotionService = GetJMethod(env, clazz, "getDeviceMotionService", "()Landroid/webkit/DeviceMotionService;"); |
| m_javaGlue->m_getDeviceOrientationService = GetJMethod(env, clazz, "getDeviceOrientationService", "()Landroid/webkit/DeviceOrientationService;"); |
| m_javaGlue->m_addMessageToConsole = GetJMethod(env, clazz, "addMessageToConsole", "(Ljava/lang/String;ILjava/lang/String;I)V"); |
| m_javaGlue->m_focusNodeChanged = GetJMethod(env, clazz, "focusNodeChanged", "(ILandroid/webkit/WebViewCore$WebKitHitTest;)V"); |
| m_javaGlue->m_getPluginClass = GetJMethod(env, clazz, "getPluginClass", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Class;"); |
| m_javaGlue->m_showFullScreenPlugin = GetJMethod(env, clazz, "showFullScreenPlugin", "(Landroid/webkit/ViewManager$ChildView;II)V"); |
| m_javaGlue->m_hideFullScreenPlugin = GetJMethod(env, clazz, "hideFullScreenPlugin", "()V"); |
| m_javaGlue->m_createSurface = GetJMethod(env, clazz, "createSurface", "(Landroid/view/View;)Landroid/webkit/ViewManager$ChildView;"); |
| m_javaGlue->m_addSurface = GetJMethod(env, clazz, "addSurface", "(Landroid/view/View;IIII)Landroid/webkit/ViewManager$ChildView;"); |
| m_javaGlue->m_updateSurface = GetJMethod(env, clazz, "updateSurface", "(Landroid/webkit/ViewManager$ChildView;IIII)V"); |
| m_javaGlue->m_destroySurface = GetJMethod(env, clazz, "destroySurface", "(Landroid/webkit/ViewManager$ChildView;)V"); |
| m_javaGlue->m_getContext = GetJMethod(env, clazz, "getContext", "()Landroid/content/Context;"); |
| m_javaGlue->m_keepScreenOn = GetJMethod(env, clazz, "keepScreenOn", "(Z)V"); |
| m_javaGlue->m_showRect = GetJMethod(env, clazz, "showRect", "(IIIIIIFFFF)V"); |
| m_javaGlue->m_centerFitRect = GetJMethod(env, clazz, "centerFitRect", "(IIII)V"); |
| m_javaGlue->m_setScrollbarModes = GetJMethod(env, clazz, "setScrollbarModes", "(II)V"); |
| #if ENABLE(VIDEO) |
| m_javaGlue->m_exitFullscreenVideo = GetJMethod(env, clazz, "exitFullscreenVideo", "()V"); |
| #endif |
| m_javaGlue->m_setWebTextViewAutoFillable = GetJMethod(env, clazz, "setWebTextViewAutoFillable", "(ILjava/lang/String;)V"); |
| m_javaGlue->m_selectAt = GetJMethod(env, clazz, "selectAt", "(II)V"); |
| m_javaGlue->m_initEditField = GetJMethod(env, clazz, "initEditField", "(IIILandroid/webkit/WebViewCore$TextFieldInitData;)V"); |
| m_javaGlue->m_chromeCanTakeFocus = GetJMethod(env, clazz, "chromeCanTakeFocus", "(I)Z"); |
| m_javaGlue->m_chromeTakeFocus = GetJMethod(env, clazz, "chromeTakeFocus", "(I)V"); |
| env->DeleteLocalRef(clazz); |
| |
| env->SetIntField(javaWebViewCore, gWebViewCoreFields.m_nativeClass, (jint)this); |
| |
| jclass tfidClazz = env->FindClass("android/webkit/WebViewCore$TextFieldInitData"); |
| m_textFieldInitDataGlue->m_fieldPointer = env->GetFieldID(tfidClazz, "mFieldPointer", "I"); |
| m_textFieldInitDataGlue->m_text = env->GetFieldID(tfidClazz, "mText", "Ljava/lang/String;"); |
| m_textFieldInitDataGlue->m_type = env->GetFieldID(tfidClazz, "mType", "I"); |
| m_textFieldInitDataGlue->m_isSpellCheckEnabled = env->GetFieldID(tfidClazz, "mIsSpellCheckEnabled", "Z"); |
| m_textFieldInitDataGlue->m_isTextFieldNext = env->GetFieldID(tfidClazz, "mIsTextFieldNext", "Z"); |
| m_textFieldInitDataGlue->m_isTextFieldPrev = env->GetFieldID(tfidClazz, "mIsTextFieldPrev", "Z"); |
| m_textFieldInitDataGlue->m_isAutoCompleteEnabled = env->GetFieldID(tfidClazz, "mIsAutoCompleteEnabled", "Z"); |
| m_textFieldInitDataGlue->m_name = env->GetFieldID(tfidClazz, "mName", "Ljava/lang/String;"); |
| m_textFieldInitDataGlue->m_label = env->GetFieldID(tfidClazz, "mLabel", "Ljava/lang/String;"); |
| m_textFieldInitDataGlue->m_maxLength = env->GetFieldID(tfidClazz, "mMaxLength", "I"); |
| m_textFieldInitDataGlue->m_contentBounds = env->GetFieldID(tfidClazz, "mContentBounds", "Landroid/graphics/Rect;"); |
| m_textFieldInitDataGlue->m_nodeLayerId = env->GetFieldID(tfidClazz, "mNodeLayerId", "I"); |
| m_textFieldInitDataGlue->m_clientRect = env->GetFieldID(tfidClazz, "mClientRect", "Landroid/graphics/Rect;"); |
| m_textFieldInitDataGlue->m_constructor = GetJMethod(env, tfidClazz, "<init>", "()V"); |
| env->DeleteLocalRef(tfidClazz); |
| |
| PageGroup::setShouldTrackVisitedLinks(true); |
| |
| clearContent(); |
| |
| MemoryUsage::setLowMemoryUsageMb(env->GetIntField(javaWebViewCore, gWebViewCoreFields.m_lowMemoryUsageMb)); |
| MemoryUsage::setHighMemoryUsageMb(env->GetIntField(javaWebViewCore, gWebViewCoreFields.m_highMemoryUsageMb)); |
| MemoryUsage::setHighUsageDeltaMb(env->GetIntField(javaWebViewCore, gWebViewCoreFields.m_highUsageDeltaMb)); |
| |
| WebViewCore::addInstance(this); |
| |
| AndroidNetworkLibraryImpl::InitWithApplicationContext(env, 0); |
| |
| // increase the font cache size beyond the standard system setting |
| SkGraphics::SetFontCacheLimit(1572864); // 1572864 bytes == 1.5 MB |
| |
| // Static initialisation of certain important V8 static data gets performed at system startup when |
| // libwebcore gets loaded. We now need to associate the WebCore thread with V8 to complete |
| // initialisation. |
| v8::V8::Initialize(); |
| |
| // Configure any RuntimeEnabled features that we need to change from their default now. |
| // See WebCore/bindings/generic/RuntimeEnabledFeatures.h |
| |
| // HTML5 History API |
| RuntimeEnabledFeatures::setPushStateEnabled(true); |
| if (m_mainFrame) |
| m_mainFrame->settings()->setMinDOMTimerInterval(FOREGROUND_TIMER_INTERVAL); |
| } |
| |
| WebViewCore::~WebViewCore() |
| { |
| WebViewCore::removeInstance(this); |
| |
| // Release the focused view |
| Release(m_popupReply); |
| |
| if (m_javaGlue->m_obj) { |
| JNIEnv* env = JSC::Bindings::getJNIEnv(); |
| env->DeleteWeakGlobalRef(m_javaGlue->m_obj); |
| m_javaGlue->m_obj = 0; |
| } |
| delete m_javaGlue; |
| } |
| |
| WebViewCore* WebViewCore::getWebViewCore(const WebCore::FrameView* view) |
| { |
| if (!view) |
| return 0; |
| if (view->platformWidget()) |
| return static_cast<WebFrameView*>(view->platformWidget())->webViewCore(); |
| Frame* frame = view->frame(); |
| while (Frame* parent = frame->tree()->parent()) |
| frame = parent; |
| WebFrameView* webFrameView = 0; |
| if (frame && frame->view()) |
| webFrameView = static_cast<WebFrameView*>(frame->view()->platformWidget()); |
| if (!webFrameView) |
| return 0; |
| return webFrameView->webViewCore(); |
| } |
| |
| WebViewCore* WebViewCore::getWebViewCore(const WebCore::ScrollView* view) |
| { |
| if (!view) |
| return 0; |
| if (view->platformWidget()) |
| return static_cast<WebFrameView*>(view->platformWidget())->webViewCore(); |
| const FrameView* frameView = 0; |
| if (view->isFrameView()) |
| frameView = static_cast<const FrameView*>(view); |
| else { |
| frameView = static_cast<const FrameView*>(view->root()); |
| if (!frameView) |
| return 0; |
| } |
| return getWebViewCore(frameView); |
| } |
| |
| static bool layoutIfNeededRecursive(WebCore::Frame* f) |
| { |
| if (!f) |
| return true; |
| |
| WebCore::FrameView* v = f->view(); |
| if (!v) |
| return true; |
| v->updateLayoutAndStyleIfNeededRecursive(); |
| return !v->needsLayout(); |
| } |
| |
| WebCore::Node* WebViewCore::currentFocus() |
| { |
| return focusedFrame()->document()->focusedNode(); |
| } |
| |
| void WebViewCore::layout() |
| { |
| TRACE_METHOD(); |
| |
| // if there is no document yet, just return |
| if (!m_mainFrame->document()) { |
| ALOGV("!m_mainFrame->document()"); |
| 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 |
| bool success = layoutIfNeededRecursive(m_mainFrame); |
| |
| // 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(); |
| |
| // Use the contents width and height as a starting point. |
| SkIRect contentRect; |
| contentRect.set(0, 0, width, height); |
| SkIRect total(contentRect); |
| |
| // Traverse all the frames and add their sizes if they are in the visible |
| // rectangle. |
| for (WebCore::Frame* frame = m_mainFrame->tree()->traverseNext(); frame; |
| frame = frame->tree()->traverseNext()) { |
| // If the frame doesn't have an owner then it is the top frame and the |
| // view size is the frame size. |
| WebCore::RenderPart* owner = frame->ownerRenderer(); |
| if (owner && owner->style()->visibility() == VISIBLE) { |
| int x = owner->x(); |
| int y = owner->y(); |
| |
| // Traverse the tree up to the parent to find the absolute position |
| // of this frame. |
| WebCore::Frame* parent = frame->tree()->parent(); |
| while (parent) { |
| WebCore::RenderPart* parentOwner = parent->ownerRenderer(); |
| if (parentOwner) { |
| x += parentOwner->x(); |
| y += parentOwner->y(); |
| } |
| parent = parent->tree()->parent(); |
| } |
| // Use the owner dimensions so that padding and border are |
| // included. |
| int right = x + owner->width(); |
| int bottom = y + owner->height(); |
| SkIRect frameRect = {x, y, right, bottom}; |
| // Ignore a width or height that is smaller than 1. Some iframes |
| // have small dimensions in order to be hidden. The iframe |
| // expansion code does not expand in that case so we should ignore |
| // them here. |
| if (frameRect.width() > 1 && frameRect.height() > 1 |
| && SkIRect::Intersects(total, frameRect)) |
| total.join(x, y, right, bottom); |
| } |
| } |
| |
| // If the new total is larger than the content, resize the view to include |
| // all the content. |
| if (!contentRect.contains(total)) { |
| // TODO: Does this ever happen? Is this needed now that we don't flatten |
| // frames? |
| // Resize the view to change the overflow clip. |
| view->resize(total.fRight, total.fBottom); |
| |
| // We have to force a layout in order for the clip to change. |
| m_mainFrame->contentRenderer()->setNeedsLayoutAndPrefWidthsRecalc(); |
| view->forceLayout(); |
| |
| // Relayout similar to above |
| layoutIfNeededRecursive(m_mainFrame); |
| } |
| } |
| |
| void WebViewCore::recordPicturePile() |
| { |
| // if the webkit page dimensions changed, discard the pictureset and redraw. |
| WebCore::FrameView* view = m_mainFrame->view(); |
| int width = view ? view->contentsWidth() : 0; |
| int height = view ? view->contentsHeight() : 0; |
| |
| m_content.setSize(IntSize(width, height)); |
| |
| // Rebuild the pictureset (webkit repaint) |
| m_content.updatePicturesIfNeeded(this); |
| } |
| |
| void WebViewCore::clearContent() |
| { |
| m_content.reset(); |
| updateLocale(); |
| } |
| |
| void WebViewCore::paintContents(WebCore::GraphicsContext* gc, WebCore::IntRect& dirty) |
| { |
| WebCore::FrameView* view = m_mainFrame->view(); |
| if (!view) { |
| gc->setFillColor(WebCore::Color::white, WebCore::ColorSpaceDeviceRGB); |
| gc->fillColor(); |
| return; |
| } |
| |
| IntPoint origin = view->minimumScrollPosition(); |
| IntRect drawArea = dirty; |
| gc->translate(-origin.x(), -origin.y()); |
| drawArea.move(origin.x(), origin.y()); |
| view->platformWidget()->draw(gc, drawArea); |
| } |
| |
| void WebViewCore::setPrerenderingEnabled(bool enable) |
| { |
| MutexLocker locker(m_prerenderLock); |
| m_prerenderEnabled = enable; |
| } |
| |
| bool WebViewCore::prerenderingEnabled() |
| { |
| MutexLocker locker(m_prerenderLock); |
| return m_prerenderEnabled; |
| } |
| |
| SkCanvas* WebViewCore::createPrerenderCanvas(PrerenderedInval* prerendered) |
| { |
| // Has WebView disabled prerenders (not attached, etc...)? |
| if (!prerenderingEnabled()) |
| return 0; |
| // Does this WebView have focus? |
| if (!m_mainFrame->page()->focusController()->isActive()) |
| return 0; |
| // Are we scrolling? |
| if (currentTimeMS() - m_scrollSetTime < PRERENDER_AFTER_SCROLL_DELAY) |
| return 0; |
| // Do we have anything to render? |
| if (prerendered->area.isEmpty()) |
| return 0; |
| FloatRect scaleTemp(m_scrollOffsetX, m_scrollOffsetY, m_screenWidth, m_screenHeight); |
| scaleTemp.scale(m_scale); |
| IntRect visibleTileClip = enclosingIntRect(scaleTemp); |
| FloatRect scaledArea = prerendered->area; |
| scaledArea.scale(m_scale); |
| IntRect enclosingScaledArea = enclosingIntRect(scaledArea); |
| if (enclosingScaledArea.isEmpty()) |
| return 0; |
| // "round out" the screen to tile boundaries so that we can clip yet still |
| // cover any visible tiles with the prerender |
| int tw = TilesManager::tileWidth(); |
| int th = TilesManager::tileHeight(); |
| float left = tw * (int) (visibleTileClip.x() / tw); |
| float top = th * (int) (visibleTileClip.y() / th); |
| float right = tw * (int) ceilf(visibleTileClip.maxX() / (float) tw); |
| float bottom = th * (int) ceilf(visibleTileClip.maxY() / (float) th); |
| visibleTileClip = IntRect(left, top, right - left, bottom - top); |
| enclosingScaledArea.intersect(visibleTileClip); |
| if (enclosingScaledArea.isEmpty()) |
| return 0; |
| prerendered->screenArea = enclosingScaledArea; |
| FloatRect enclosingDocArea(enclosingScaledArea); |
| enclosingDocArea.scale(1 / m_scale); |
| prerendered->area = enclosingIntRect(enclosingDocArea); |
| if (prerendered->area.isEmpty()) |
| return 0; |
| prerendered->bitmap.setConfig(SkBitmap::kARGB_8888_Config, |
| enclosingScaledArea.width(), |
| enclosingScaledArea.height()); |
| prerendered->bitmap.allocPixels(); |
| SkCanvas* bitmapCanvas = new SkCanvas(prerendered->bitmap); |
| bitmapCanvas->scale(m_scale, m_scale); |
| bitmapCanvas->translate(-enclosingDocArea.x(), -enclosingDocArea.y()); |
| return bitmapCanvas; |
| } |
| |
| void WebViewCore::notifyAnimationStarted() |
| { |
| // We notify webkit that the animations have begun |
| // TODO: handle case where not all have begun |
| ChromeClientAndroid* chromeC = static_cast<ChromeClientAndroid*>(m_mainFrame->page()->chrome()->client()); |
| GraphicsLayerAndroid* root = static_cast<GraphicsLayerAndroid*>(chromeC->layersSync()); |
| if (root) |
| root->notifyClientAnimationStarted(); |
| |
| } |
| |
| BaseLayerAndroid* WebViewCore::createBaseLayer(GraphicsLayerAndroid* root) |
| { |
| // We set the background color |
| Color background = Color::white; |
| |
| bool bodyHasFixedBackgroundImage = false; |
| bool bodyHasCSSBackground = false; |
| |
| if (m_mainFrame && m_mainFrame->document() |
| && m_mainFrame->document()->body()) { |
| |
| Document* document = m_mainFrame->document(); |
| RefPtr<RenderStyle> style = document->styleForElementIgnoringPendingStylesheets(document->body()); |
| if (style->hasBackground()) { |
| background = style->visitedDependentColor(CSSPropertyBackgroundColor); |
| bodyHasCSSBackground = true; |
| } |
| WebCore::FrameView* view = m_mainFrame->view(); |
| if (view) { |
| Color viewBackground = view->baseBackgroundColor(); |
| background = bodyHasCSSBackground ? viewBackground.blend(background) : viewBackground; |
| } |
| if (style->hasFixedBackgroundImage()) { |
| Image* backgroundImage = FixedBackgroundImageLayerAndroid::GetCachedImage(style); |
| if (backgroundImage && backgroundImage->width() > 1 && backgroundImage->height() > 1) |
| bodyHasFixedBackgroundImage = true; |
| } |
| } |
| |
| PicturePileLayerContent* content = new PicturePileLayerContent(m_content); |
| m_content.clearPrerenders(); |
| |
| BaseLayerAndroid* realBase = 0; |
| LayerAndroid* base = 0; |
| |
| //If we have a fixed background image on the body element, the fixed image |
| // will be contained in the PictureSet (the content object), and the foreground |
| //of the body element will be moved to a layer. |
| //In that case, let's change the hierarchy to obtain: |
| // |
| //BaseLayerAndroid |
| // \- FixedBackgroundBaseLayerAndroid (fixed positioning) |
| // \- ForegroundBaseLayerAndroid |
| // \- root layer (webkit composited tree) |
| |
| if (bodyHasFixedBackgroundImage) { |
| base = new ForegroundBaseLayerAndroid(0); |
| base->setSize(content->width(), content->height()); |
| |
| Document* document = m_mainFrame->document(); |
| RefPtr<RenderStyle> style = document->styleForElementIgnoringPendingStylesheets(document->body()); |
| |
| FixedBackgroundImageLayerAndroid* baseBackground = |
| new FixedBackgroundImageLayerAndroid(style, content->width(), content->height()); |
| |
| realBase = new BaseLayerAndroid(0); |
| realBase->setSize(content->width(), content->height()); |
| realBase->addChild(baseBackground); |
| realBase->addChild(base); |
| baseBackground->unref(); |
| base->unref(); |
| } else { |
| realBase = new BaseLayerAndroid(content); |
| base = realBase; |
| } |
| |
| realBase->setBackgroundColor(background); |
| |
| SkSafeUnref(content); |
| |
| // We update the layers |
| if (root) { |
| LayerAndroid* copyLayer = new LayerAndroid(*root->contentLayer()); |
| base->addChild(copyLayer); |
| copyLayer->unref(); |
| root->contentLayer()->clearDirtyRegion(); |
| } |
| |
| return realBase; |
| } |
| |
| BaseLayerAndroid* WebViewCore::recordContent(SkIPoint* point) |
| { |
| m_skipContentDraw = true; |
| layout(); |
| ChromeClientAndroid* chromeC = static_cast<ChromeClientAndroid*>(m_mainFrame->page()->chrome()->client()); |
| GraphicsLayerAndroid* root = static_cast<GraphicsLayerAndroid*>(chromeC->layersSync()); |
| m_skipContentDraw = false; |
| recordPicturePile(); |
| |
| BaseLayerAndroid* baseLayer = createBaseLayer(root); |
| |
| baseLayer->markAsDirty(m_content.dirtyRegion()); |
| m_content.dirtyRegion().setEmpty(); |
| #if USE(ACCELERATED_COMPOSITING) |
| #else |
| baseLayer->markAsDirty(m_rebuildInval); |
| #endif |
| point->fX = m_content.size().width(); |
| point->fY = m_content.size().height(); |
| |
| return baseLayer; |
| } |
| |
| void WebViewCore::scrollTo(int x, int y, bool animate) |
| { |
| ALOG_ASSERT(m_javaGlue->m_obj, "A Java widget was not associated with this view bridge!"); |
| |
| JNIEnv* env = JSC::Bindings::getJNIEnv(); |
| AutoJObject javaObject = m_javaGlue->object(env); |
| if (!javaObject.get()) |
| return; |
| env->CallVoidMethod(javaObject.get(), m_javaGlue->m_scrollTo, |
| x, y, animate, false); |
| checkException(env); |
| } |
| |
| void WebViewCore::sendNotifyProgressFinished() |
| { |
| ALOG_ASSERT(m_javaGlue->m_obj, "A Java widget was not associated with this view bridge!"); |
| JNIEnv* env = JSC::Bindings::getJNIEnv(); |
| AutoJObject javaObject = m_javaGlue->object(env); |
| if (!javaObject.get()) |
| return; |
| env->CallVoidMethod(javaObject.get(), m_javaGlue->m_sendNotifyProgressFinished); |
| checkException(env); |
| } |
| |
| void WebViewCore::viewInvalidate(const WebCore::IntRect& rect) |
| { |
| ALOG_ASSERT(m_javaGlue->m_obj, "A Java widget was not associated with this view bridge!"); |
| JNIEnv* env = JSC::Bindings::getJNIEnv(); |
| AutoJObject javaObject = m_javaGlue->object(env); |
| if (!javaObject.get()) |
| return; |
| env->CallVoidMethod(javaObject.get(), |
| m_javaGlue->m_sendViewInvalidate, |
| rect.x(), rect.y(), rect.maxX(), rect.maxY()); |
| checkException(env); |
| } |
| |
| void WebViewCore::contentDraw() |
| { |
| JNIEnv* env = JSC::Bindings::getJNIEnv(); |
| AutoJObject javaObject = m_javaGlue->object(env); |
| if (!javaObject.get()) |
| return; |
| env->CallVoidMethod(javaObject.get(), m_javaGlue->m_contentDraw); |
| checkException(env); |
| } |
| |
| void WebViewCore::contentInvalidate(const WebCore::IntRect &r) |
| { |
| IntPoint origin = m_mainFrame->view()->minimumScrollPosition(); |
| IntRect dirty = r; |
| dirty.move(-origin.x(), -origin.y()); |
| m_content.invalidate(dirty); |
| if (!m_skipContentDraw) |
| contentDraw(); |
| } |
| |
| void WebViewCore::contentInvalidateAll() |
| { |
| WebCore::FrameView* view = m_mainFrame->view(); |
| contentInvalidate(WebCore::IntRect(0, 0, |
| view->contentsWidth(), view->contentsHeight())); |
| } |
| |
| void WebViewCore::offInvalidate(const WebCore::IntRect &r) |
| { |
| // FIXME: these invalidates are offscreen, and can be throttled or |
| // deferred until the area is visible. For now, treat them as |
| // regular invals so that drawing happens (inefficiently) for now. |
| contentInvalidate(r); |
| } |
| |
| void WebViewCore::didFirstLayout() |
| { |
| ALOG_ASSERT(m_javaGlue->m_obj, "A Java widget was not associated with this view bridge!"); |
| |
| JNIEnv* env = JSC::Bindings::getJNIEnv(); |
| AutoJObject javaObject = m_javaGlue->object(env); |
| if (!javaObject.get()) |
| return; |
| |
| const WebCore::KURL& url = m_mainFrame->document()->url(); |
| if (url.isEmpty()) |
| return; |
| ALOGV("::WebCore:: didFirstLayout %s", url.string().ascii().data()); |
| |
| WebCore::FrameLoadType loadType = m_mainFrame->loader()->loadType(); |
| |
| env->CallVoidMethod(javaObject.get(), m_javaGlue->m_didFirstLayout, |
| loadType == WebCore::FrameLoadTypeStandard |
| // When redirect with locked history, we would like to reset the |
| // scale factor. This is important for www.yahoo.com as it is |
| // redirected to www.yahoo.com/?rs=1 on load. |
| || loadType == WebCore::FrameLoadTypeRedirectWithLockedBackForwardList |
| // When "request desktop page" is used, we want to treat it as |
| // a newly-loaded page. |
| || loadType == WebCore::FrameLoadTypeSame); |
| checkException(env); |
| } |
| |
| void WebViewCore::updateViewport() |
| { |
| ALOG_ASSERT(m_javaGlue->m_obj, "A Java widget was not associated with this view bridge!"); |
| |
| JNIEnv* env = JSC::Bindings::getJNIEnv(); |
| AutoJObject javaObject = m_javaGlue->object(env); |
| if (!javaObject.get()) |
| return; |
| env->CallVoidMethod(javaObject.get(), m_javaGlue->m_updateViewport); |
| checkException(env); |
| } |
| |
| void WebViewCore::restoreScale(float scale, float textWrapScale) |
| { |
| ALOG_ASSERT(m_javaGlue->m_obj, "A Java widget was not associated with this view bridge!"); |
| |
| JNIEnv* env = JSC::Bindings::getJNIEnv(); |
| AutoJObject javaObject = m_javaGlue->object(env); |
| if (!javaObject.get()) |
| return; |
| env->CallVoidMethod(javaObject.get(), m_javaGlue->m_restoreScale, scale, textWrapScale); |
| checkException(env); |
| } |
| |
| void WebViewCore::needTouchEvents(bool need) |
| { |
| ALOG_ASSERT(m_javaGlue->m_obj, "A Java widget was not associated with this view bridge!"); |
| |
| #if ENABLE(TOUCH_EVENTS) |
| JNIEnv* env = JSC::Bindings::getJNIEnv(); |
| AutoJObject javaObject = m_javaGlue->object(env); |
| if (!javaObject.get()) |
| return; |
| |
| if (m_forwardingTouchEvents == need) |
| return; |
| |
| env->CallVoidMethod(javaObject.get(), m_javaGlue->m_needTouchEvents, need); |
| checkException(env); |
| |
| m_forwardingTouchEvents = need; |
| #endif |
| } |
| |
| void WebViewCore::requestKeyboard(bool showKeyboard) |
| { |
| ALOG_ASSERT(m_javaGlue->m_obj, "A Java widget was not associated with this view bridge!"); |
| |
| JNIEnv* env = JSC::Bindings::getJNIEnv(); |
| AutoJObject javaObject = m_javaGlue->object(env); |
| if (!javaObject.get()) |
| return; |
| env->CallVoidMethod(javaObject.get(), m_javaGlue->m_requestKeyboard, showKeyboard); |
| checkException(env); |
| } |
| |
| void WebViewCore::notifyProgressFinished() |
| { |
| sendNotifyProgressFinished(); |
| } |
| |
| void WebViewCore::setScrollOffset(bool sendScrollEvent, int dx, int dy) |
| { |
| if (m_scrollOffsetX != dx || m_scrollOffsetY != dy) { |
| m_scrollOffsetX = dx; |
| m_scrollOffsetY = dy; |
| m_scrollSetTime = currentTimeMS(); |
| // The visible rect is located within our coordinate space so it |
| // contains the actual scroll position. Setting the location makes hit |
| // testing work correctly. |
| m_mainFrame->view()->platformWidget()->setLocation(m_scrollOffsetX, |
| m_scrollOffsetY); |
| if (sendScrollEvent) { |
| m_mainFrame->eventHandler()->sendScrollEvent(); |
| |
| // Only update history position if it's user scrolled. |
| // Update history item to reflect the new scroll position. |
| // This also helps save the history information when the browser goes to |
| // background, so scroll position will be restored if browser gets |
| // killed while in background. |
| WebCore::HistoryController* history = m_mainFrame->loader()->history(); |
| // Because the history item saving could be heavy for large sites and |
| // scrolling can generate lots of small scroll offset, the following code |
| // reduces the saving frequency. |
| static const int MIN_SCROLL_DIFF = 32; |
| if (history->currentItem()) { |
| WebCore::IntPoint currentPoint = history->currentItem()->scrollPoint(); |
| if (std::abs(currentPoint.x() - dx) >= MIN_SCROLL_DIFF || |
| std::abs(currentPoint.y() - dy) >= MIN_SCROLL_DIFF) { |
| history->saveScrollPositionAndViewStateToItem(history->currentItem()); |
| } |
| } |
| } |
| |
| // update the currently visible screen |
| sendPluginVisibleScreen(); |
| } |
| } |
| |
| void WebViewCore::setGlobalBounds(int x, int y, int h, int v) |
| { |
| m_mainFrame->view()->platformWidget()->setWindowBounds(x, y, h, v); |
| } |
| |
| void WebViewCore::setSizeScreenWidthAndScale(int width, int height, |
| int textWrapWidth, float scale, int screenWidth, int screenHeight, |
| int anchorX, int anchorY, bool ignoreHeight) |
| { |
| // Ignore the initial empty document. |
| const WebCore::KURL& url = m_mainFrame->document()->url(); |
| if (url.isEmpty()) |
| return; |
| |
| WebCoreViewBridge* window = m_mainFrame->view()->platformWidget(); |
| int ow = window->width(); |
| int oh = window->height(); |
| int osw = m_screenWidth; |
| int osh = m_screenHeight; |
| int otw = m_textWrapWidth; |
| m_screenWidth = screenWidth; |
| m_screenHeight = screenHeight; |
| m_textWrapWidth = textWrapWidth; |
| if (scale >= 0) // negative means keep the current scale |
| m_scale = scale; |
| m_maxXScroll = screenWidth >> 2; |
| m_maxYScroll = m_maxXScroll * height / width; |
| // Don't reflow if the diff is small. |
| const bool reflow = otw && textWrapWidth && |
| ((float) abs(otw - textWrapWidth) / textWrapWidth) >= 0.01f; |
| |
| // When the screen size change, fixed positioned element should be updated. |
| // This is supposed to be light weighted operation without a full layout. |
| if (osh != screenHeight || osw != screenWidth) |
| m_mainFrame->view()->updatePositionedObjects(); |
| |
| if (ow != width || (!ignoreHeight && oh != height) || reflow) { |
| WebCore::RenderObject *r = m_mainFrame->contentRenderer(); |
| if (r) { |
| WebCore::IntPoint anchorPoint = WebCore::IntPoint(anchorX, anchorY); |
| RefPtr<WebCore::Node> node; |
| WebCore::IntRect bounds; |
| WebCore::IntPoint offset; |
| // If the text wrap changed, it is probably zoom change or |
| // orientation change. Try to keep the anchor at the same place. |
| if (otw && textWrapWidth && otw != textWrapWidth && |
| (anchorX != 0 || anchorY != 0)) { |
| WebCore::HitTestResult hitTestResult = |
| m_mainFrame->eventHandler()->hitTestResultAtPoint( |
| anchorPoint, false); |
| node = hitTestResult.innerNode(); |
| if (node && !node->isTextNode()) { |
| // If the hitTestResultAtPoint didn't find a suitable node |
| // for anchoring, try again with some slop. |
| static const int HIT_SLOP = 30; |
| anchorPoint.move(HIT_SLOP, HIT_SLOP); |
| hitTestResult = |
| m_mainFrame->eventHandler()->hitTestResultAtPoint( |
| anchorPoint, false); |
| node = hitTestResult.innerNode(); |
| } |
| } |
| if (node) { |
| bounds = node->getRect(); |
| // sites like nytimes.com insert a non-standard tag <nyt_text> |
| // in the html. If it is the HitTestResult, it may have zero |
| // width and height. In this case, use its parent node. |
| if (bounds.width() == 0) { |
| node = node->parentOrHostNode(); |
| if (node) { |
| bounds = node->getRect(); |
| } |
| } |
| } |
| |
| // Set the size after finding the old anchor point as |
| // hitTestResultAtPoint causes a layout. |
| window->setSize(width, height); |
| window->setVisibleSize(screenWidth, screenHeight); |
| if (width != screenWidth) { |
| m_mainFrame->view()->setUseFixedLayout(true); |
| m_mainFrame->view()->setFixedLayoutSize(IntSize(width, height)); |
| } else |
| m_mainFrame->view()->setUseFixedLayout(false); |
| r->setNeedsLayoutAndPrefWidthsRecalc(); |
| if (m_mainFrame->view()->didFirstLayout()) |
| m_mainFrame->view()->forceLayout(); |
| |
| // scroll to restore current screen center |
| if (node && node->inDocument()) { |
| const WebCore::IntRect& newBounds = node->getRect(); |
| if ((osw && osh && bounds.width() && bounds.height()) |
| && (bounds != newBounds)) { |
| WebCore::FrameView* view = m_mainFrame->view(); |
| // force left align if width is not changed while height changed. |
| // the anchorPoint is probably at some white space in the node |
| // which is affected by text wrap around the screen width. |
| const bool leftAlign = (otw != textWrapWidth) |
| && (bounds.width() == newBounds.width()) |
| && (bounds.height() != newBounds.height()); |
| const float xPercentInDoc = |
| leftAlign ? 0.0 : (float) (anchorX - bounds.x()) / bounds.width(); |
| const float xPercentInView = |
| leftAlign ? 0.0 : (float) (anchorX - m_scrollOffsetX) / osw; |
| const float yPercentInDoc = (float) (anchorY - bounds.y()) / bounds.height(); |
| const float yPercentInView = (float) (anchorY - m_scrollOffsetY) / osh; |
| showRect(newBounds.x(), newBounds.y(), newBounds.width(), |
| newBounds.height(), view->contentsWidth(), |
| view->contentsHeight(), |
| xPercentInDoc, xPercentInView, |
| yPercentInDoc, yPercentInView); |
| } |
| } |
| } |
| } else { |
| window->setSize(width, height); |
| window->setVisibleSize(screenWidth, screenHeight); |
| m_mainFrame->view()->resize(width, height); |
| if (width != screenWidth) { |
| m_mainFrame->view()->setUseFixedLayout(true); |
| m_mainFrame->view()->setFixedLayoutSize(IntSize(width, height)); |
| } else |
| m_mainFrame->view()->setUseFixedLayout(false); |
| } |
| |
| // update the currently visible screen as perceived by the plugin |
| sendPluginVisibleScreen(); |
| } |
| |
| void WebViewCore::dumpDomTree(bool useFile) |
| { |
| #ifdef ANDROID_DOM_LOGGING |
| if (useFile) |
| gDomTreeFile = fopen(DOM_TREE_LOG_FILE, "w"); |
| m_mainFrame->document()->showTreeForThis(); |
| if (gDomTreeFile) { |
| fclose(gDomTreeFile); |
| gDomTreeFile = 0; |
| } |
| #endif |
| } |
| |
| void WebViewCore::dumpRenderTree(bool useFile) |
| { |
| #ifdef ANDROID_DOM_LOGGING |
| WTF::CString renderDump = WebCore::externalRepresentation(m_mainFrame).utf8(); |
| const char* data = renderDump.data(); |
| if (useFile) { |
| gRenderTreeFile = fopen(RENDER_TREE_LOG_FILE, "w"); |
| DUMP_RENDER_LOGD("%s", data); |
| fclose(gRenderTreeFile); |
| gRenderTreeFile = 0; |
| } else { |
| // adb log can only output 1024 characters, so write out line by line. |
| // exclude '\n' as adb log adds it for each output. |
| int length = renderDump.length(); |
| for (int i = 0, last = 0; i < length; i++) { |
| if (data[i] == '\n') { |
| if (i != last) |
| DUMP_RENDER_LOGD("%.*s", (i - last), &(data[last])); |
| last = i + 1; |
| } |
| } |
| } |
| #endif |
| } |
| |
| HTMLElement* WebViewCore::retrieveElement(int x, int y, |
| const QualifiedName& tagName) |
| { |
| HitTestResult hitTestResult = m_mainFrame->eventHandler() |
| ->hitTestResultAtPoint(IntPoint(x, y), false, false, |
| DontHitTestScrollbars, HitTestRequest::Active | HitTestRequest::ReadOnly, |
| IntSize(1, 1)); |
| if (!hitTestResult.innerNode() || !hitTestResult.innerNode()->inDocument()) { |
| ALOGE("Should not happen: no in document Node found"); |
| return 0; |
| } |
| const ListHashSet<RefPtr<Node> >& list = hitTestResult.rectBasedTestResult(); |
| if (list.isEmpty()) { |
| ALOGE("Should not happen: no rect-based-test nodes found"); |
| return 0; |
| } |
| Node* node = hitTestResult.innerNode(); |
| Node* element = node; |
| while (element && (!element->isElementNode() |
| || !element->hasTagName(tagName))) { |
| element = element->parentNode(); |
| } |
| return static_cast<WebCore::HTMLElement*>(element); |
| } |
| |
| HTMLAnchorElement* WebViewCore::retrieveAnchorElement(int x, int y) |
| { |
| return static_cast<HTMLAnchorElement*> |
| (retrieveElement(x, y, HTMLNames::aTag)); |
| } |
| |
| HTMLImageElement* WebViewCore::retrieveImageElement(int x, int y) |
| { |
| return static_cast<HTMLImageElement*> |
| (retrieveElement(x, y, HTMLNames::imgTag)); |
| } |
| |
| WTF::String WebViewCore::retrieveHref(int x, int y) |
| { |
| // TODO: This is expensive, cache |
| HitTestResult result = m_mainFrame->eventHandler()->hitTestResultAtPoint(IntPoint(x, y), |
| false, false, DontHitTestScrollbars, HitTestRequest::Active | HitTestRequest::ReadOnly, IntSize(1, 1)); |
| return result.absoluteLinkURL(); |
| } |
| |
| WTF::String WebViewCore::retrieveAnchorText(int x, int y) |
| { |
| WebCore::HTMLAnchorElement* anchor = retrieveAnchorElement(x, y); |
| return anchor ? anchor->text() : WTF::String(); |
| } |
| |
| WTF::String WebViewCore::retrieveImageSource(int x, int y) |
| { |
| // TODO: This is expensive, cache |
| HitTestResult result = m_mainFrame->eventHandler()->hitTestResultAtPoint(IntPoint(x, y), |
| false, false, DontHitTestScrollbars, HitTestRequest::Active | HitTestRequest::ReadOnly, IntSize(1, 1)); |
| return result.absoluteImageURL(); |
| } |
| |
| WTF::String WebViewCore::requestLabel(WebCore::Frame* frame, |
| WebCore::Node* node) |
| { |
| if (node && validNode(m_mainFrame, frame, node)) { |
| RefPtr<WebCore::NodeList> list = node->document()->getElementsByTagName("label"); |
| unsigned length = list->length(); |
| for (unsigned i = 0; i < length; i++) { |
| WebCore::HTMLLabelElement* label = static_cast<WebCore::HTMLLabelElement*>( |
| list->item(i)); |
| if (label->control() == node) { |
| Node* node = label; |
| String result; |
| while ((node = node->traverseNextNode(label))) { |
| if (node->isTextNode()) { |
| Text* textNode = static_cast<Text*>(node); |
| result += textNode->dataImpl(); |
| } |
| } |
| return result; |
| } |
| } |
| } |
| return WTF::String(); |
| } |
| |
| static bool isContentEditable(const WebCore::Node* node) |
| { |
| if (!node) |
| return false; |
| return node->isContentEditable(); |
| } |
| |
| // Returns true if the node is a textfield, textarea, or contentEditable |
| static bool isTextInput(const WebCore::Node* node) |
| { |
| if (!node) |
| return false; |
| if (isContentEditable(node)) |
| return true; |
| WebCore::RenderObject* renderer = node->renderer(); |
| return renderer && (renderer->isTextField() || renderer->isTextArea()); |
| } |
| |
| void WebViewCore::revealSelection() |
| { |
| WebCore::Node* focus = currentFocus(); |
| if (!focus) |
| return; |
| if (!isTextInput(focus)) |
| return; |
| WebCore::Frame* focusedFrame = focus->document()->frame(); |
| if (!focusedFrame->page()->focusController()->isActive()) |
| return; |
| focusedFrame->selection()->revealSelection(ScrollAlignment::alignToEdgeIfNeeded); |
| } |
| |
| struct TouchNodeData { |
| Node* mUrlNode; |
| Node* mInnerNode; |
| IntRect mBounds; |
| }; |
| |
| // get the bounding box of the Node |
| static IntRect getAbsoluteBoundingBox(Node* node) { |
| IntRect rect; |
| RenderObject* render = node->renderer(); |
| if (!render) |
| return rect; |
| if (render->isRenderInline()) |
| rect = toRenderInline(render)->linesVisualOverflowBoundingBox(); |
| else if (render->isBox()) |
| rect = toRenderBox(render)->visualOverflowRect(); |
| else if (render->isText()) |
| rect = toRenderText(render)->linesBoundingBox(); |
| else |
| ALOGE("getAbsoluteBoundingBox failed for node %p, name %s", node, render->renderName()); |
| FloatPoint absPos = render->localToAbsolute(FloatPoint(), false, true); |
| rect.move(absPos.x(), absPos.y()); |
| return rect; |
| } |
| |
| WebCore::Frame* WebViewCore::focusedFrame() const |
| { |
| return m_mainFrame->page()->focusController()->focusedOrMainFrame(); |
| } |
| |
| VisiblePosition WebViewCore::visiblePositionForContentPoint(int x, int y) |
| { |
| return visiblePositionForContentPoint(IntPoint(x, y)); |
| } |
| |
| VisiblePosition WebViewCore::visiblePositionForContentPoint(const IntPoint& point) |
| { |
| // Hit test of this kind required for this to work inside input fields |
| HitTestRequest request(HitTestRequest::Active |
| | HitTestRequest::MouseMove |
| | HitTestRequest::ReadOnly |
| | HitTestRequest::IgnoreClipping); |
| HitTestResult result(point); |
| focusedFrame()->document()->renderView()->layer()->hitTest(request, result); |
| |
| // Matching the logic in MouseEventWithHitTestResults::targetNode() |
| Node* node = result.innerNode(); |
| if (!node) |
| return VisiblePosition(); |
| Element* element = node->parentElement(); |
| if (!node->inDocument() && element && element->inDocument()) |
| node = element; |
| |
| return node->renderer()->positionForPoint(result.localPoint()); |
| } |
| |
| bool WebViewCore::selectWordAt(int x, int y) |
| { |
| HitTestResult hoverResult; |
| moveMouse(x, y, &hoverResult); |
| if (hoverResult.innerNode()) { |
| Node* node = hoverResult.innerNode(); |
| Frame* frame = node->document()->frame(); |
| Page* page = m_mainFrame->document()->page(); |
| page->focusController()->setFocusedFrame(frame); |
| } |
| |
| IntPoint point = convertGlobalContentToFrameContent(IntPoint(x, y)); |
| |
| // Hit test of this kind required for this to work inside input fields |
| HitTestRequest request(HitTestRequest::Active); |
| HitTestResult result(point); |
| |
| focusedFrame()->document()->renderView()->layer()->hitTest(request, result); |
| |
| // Matching the logic in MouseEventWithHitTestResults::targetNode() |
| Node* node = result.innerNode(); |
| if (!node) |
| return false; |
| Element* element = node->parentElement(); |
| if (!node->inDocument() && element && element->inDocument()) |
| node = element; |
| |
| SelectionController* sc = focusedFrame()->selection(); |
| bool wordSelected = false; |
| if (!sc->contains(point) && (node->isContentEditable() || node->isTextNode()) && !result.isLiveLink() |
| && node->dispatchEvent(Event::create(eventNames().selectstartEvent, true, true))) { |
| VisiblePosition pos(node->renderer()->positionForPoint(result.localPoint())); |
| wordSelected = selectWordAroundPosition(node->document()->frame(), pos); |
| } |
| return wordSelected; |
| } |
| |
| bool WebViewCore::selectWordAroundPosition(Frame* frame, VisiblePosition pos) |
| { |
| VisibleSelection selection(pos); |
| selection.expandUsingGranularity(WordGranularity); |
| SelectionController* selectionController = frame->selection(); |
| selection = VisibleSelection(selection.start(), selection.end()); |
| |
| bool wordSelected = false; |
| if (selectionController->shouldChangeSelection(selection)) { |
| bool allWhitespaces = true; |
| RefPtr<Range> firstRange = selection.firstRange(); |
| String text = firstRange.get() ? firstRange->text() : ""; |
| for (size_t i = 0; i < text.length(); ++i) { |
| if (!isSpaceOrNewline(text[i])) { |
| allWhitespaces = false; |
| break; |
| } |
| } |
| if (allWhitespaces) { |
| VisibleSelection emptySelection(pos); |
| selectionController->setSelection(emptySelection); |
| } else { |
| selectionController->setSelection(selection); |
| wordSelected = true; |
| } |
| } |
| return wordSelected; |
| } |
| |
| int WebViewCore::platformLayerIdFromNode(Node* node, LayerAndroid** outLayer) |
| { |
| if (!node || !node->renderer()) |
| return -1; |
| RenderLayer* renderLayer = node->renderer()->enclosingLayer(); |
| while (renderLayer && !renderLayer->isComposited()) |
| renderLayer = renderLayer->parent(); |
| if (!renderLayer || !renderLayer->isComposited()) |
| return -1; |
| GraphicsLayer* graphicsLayer = renderLayer->backing()->graphicsLayer(); |
| if (!graphicsLayer) |
| return -1; |
| GraphicsLayerAndroid* agl = static_cast<GraphicsLayerAndroid*>(graphicsLayer); |
| LayerAndroid* layer = agl->foregroundLayer(); |
| if (!layer) |
| layer = agl->contentLayer(); |
| if (!layer) |
| return -1; |
| if (outLayer) |
| *outLayer = layer; |
| return layer->uniqueId(); |
| } |
| |
| void WebViewCore::layerToAbsoluteOffset(const LayerAndroid* layer, IntPoint& offset) |
| { |
| while (layer) { |
| const SkPoint& pos = layer->getPosition(); |
| offset.move(pos.fX, pos.fY); |
| const IntPoint& scroll = layer->getScrollOffset(); |
| offset.move(-scroll.x(), -scroll.y()); |
| layer = static_cast<LayerAndroid*>(layer->getParent()); |
| } |
| } |
| |
| void WebViewCore::setSelectionCaretInfo(SelectText* selectTextContainer, |
| const WebCore::Position& pos, const IntPoint& frameOffset, |
| SelectText::HandleId handleId, SelectText::HandleType handleType, |
| int caretRectOffset, EAffinity affinity) |
| { |
| Node* node = pos.anchorNode(); |
| LayerAndroid* layer = 0; |
| int layerId = platformLayerIdFromNode(node, &layer); |
| selectTextContainer->setCaretLayerId(handleId, layerId); |
| IntPoint offset = frameOffset; |
| layerToAbsoluteOffset(layer, offset); |
| RenderObject* r = node->renderer(); |
| RenderText* renderText = toRenderText(r); |
| int caretOffset; |
| InlineBox* inlineBox; |
| pos.getInlineBoxAndOffset(affinity, inlineBox, caretOffset); |
| IntRect caretRect = renderText->localCaretRect(inlineBox, caretOffset); |
| FloatPoint absoluteOffset = renderText->localToAbsolute(caretRect.location()); |
| caretRect.setX(absoluteOffset.x() - offset.x() + caretRectOffset); |
| caretRect.setY(absoluteOffset.y() - offset.y()); |
| selectTextContainer->setCaretRect(handleId, caretRect); |
| selectTextContainer->setHandleType(handleId, handleType); |
| selectTextContainer->setTextRect(handleId, |
| positionToTextRect(pos, affinity, offset, caretRect)); |
| } |
| |
| bool WebViewCore::isLtr(const Position& position) |
| { |
| InlineBox* inlineBox = 0; |
| int caretOffset = 0; |
| position.getInlineBoxAndOffset(DOWNSTREAM, inlineBox, caretOffset); |
| bool isLtr; |
| if (inlineBox) |
| isLtr = inlineBox->isLeftToRightDirection(); |
| else |
| isLtr = position.primaryDirection() == LTR; |
| return isLtr; |
| } |
| |
| static Node* findInputParent(Node* node) |
| { |
| Node* testNode = node; |
| while (testNode) { |
| RenderObject* renderer = testNode->renderer(); |
| if (renderer |
| && (renderer->isTextArea() || renderer->isTextControl())) { |
| return testNode; |
| } |
| testNode = testNode->parentOrHostNode(); |
| } |
| return node; |
| } |
| |
| SelectText* WebViewCore::createSelectText(const VisibleSelection& selection) |
| { |
| bool isCaret = selection.isCaret(); |
| Position base = selection.base(); |
| Position extent = selection.extent(); |
| if (selection.isNone() || (!selection.isContentEditable() && isCaret) |
| || !base.anchorNode() || !base.anchorNode()->renderer() |
| || !extent.anchorNode() || !extent.anchorNode()->renderer()) |
| return 0; |
| |
| RefPtr<Range> range = selection.firstRange(); |
| Node* startContainer = range->startContainer(); |
| Node* endContainer = range->endContainer(); |
| |
| if (!startContainer || !endContainer) |
| return 0; |
| if (!isCaret && startContainer == endContainer |
| && range->startOffset() == range->endOffset()) |
| return 0; |
| |
| IntPoint frameOffset = convertGlobalContentToFrameContent(IntPoint()); |
| SelectText* selectTextContainer = new SelectText(); |
| if (isCaret) { |
| setSelectionCaretInfo(selectTextContainer, base, frameOffset, |
| SelectText::BaseHandle, SelectText::CenterHandle, 0, |
| selection.affinity()); |
| setSelectionCaretInfo(selectTextContainer, base, frameOffset, |
| SelectText::ExtentHandle, SelectText::CenterHandle, 0, |
| selection.affinity()); |
| } else { |
| bool isBaseLtr = isLtr(base); |
| bool isBaseStart = comparePositions(base, extent) <= 0; |
| int baseOffset = isBaseLtr ? 0 : -1; |
| SelectText::HandleType baseHandleType = (isBaseLtr == isBaseStart) |
| ? SelectText::LeftHandle : SelectText::RightHandle; |
| EAffinity affinity = selection.affinity(); |
| setSelectionCaretInfo(selectTextContainer, base, frameOffset, |
| SelectText::BaseHandle, baseHandleType, baseOffset, affinity); |
| bool isExtentLtr = isLtr(extent); |
| int extentOffset = isExtentLtr ? 0 : -1; |
| SelectText::HandleType extentHandleType = (isExtentLtr == isBaseStart) |
| ? SelectText::RightHandle : SelectText::LeftHandle; |
| setSelectionCaretInfo(selectTextContainer, extent, frameOffset, |
| SelectText::ExtentHandle, extentHandleType, extentOffset, affinity); |
| IntRect clipRect; |
| if (selection.isContentEditable()) { |
| Node* editable = findInputParent(base.anchorNode()); |
| RenderObject* render = editable->renderer(); |
| if (render && render->isBox() && !render->isBody()) { |
| RenderBox* renderBox = toRenderBox(render); |
| clipRect = renderBox->clientBoxRect(); |
| FloatPoint pos = renderBox->localToAbsolute(clipRect.location()); |
| clipRect.setX(pos.x()); |
| clipRect.setY(pos.y()); |
| } |
| } |
| |
| Node* stopNode = range->pastLastNode(); |
| for (Node* node = range->firstNode(); node != stopNode; node = node->traverseNextNode()) { |
| RenderObject* r = node->renderer(); |
| if (!r || !r->isText() || r->style()->visibility() != VISIBLE) |
| continue; |
| RenderText* renderText = toRenderText(r); |
| int startOffset = node == startContainer ? range->startOffset() : 0; |
| int endOffset = node == endContainer ? range->endOffset() : numeric_limits<int>::max(); |
| LayerAndroid* layer = 0; |
| int layerId = platformLayerIdFromNode(node, &layer); |
| Vector<IntRect> rects; |
| renderText->absoluteRectsForRange(rects, startOffset, endOffset, true); |
| selectTextContainer->addHighlightRegion(layer, rects, frameOffset, |
| clipRect); |
| } |
| } |
| selectTextContainer->setText(range->text()); |
| return selectTextContainer; |
| } |
| |
| IntRect WebViewCore::positionToTextRect(const Position& position, |
| EAffinity affinity, const WebCore::IntPoint& offset, const IntRect& caretRect) |
| { |
| IntRect textRect = caretRect; |
| InlineBox* inlineBox; |
| int offsetIndex; |
| position.getInlineBoxAndOffset(affinity, inlineBox, offsetIndex); |
| if (inlineBox && inlineBox->isInlineTextBox()) { |
| InlineTextBox* box = static_cast<InlineTextBox*>(inlineBox); |
| RootInlineBox* root = box->root(); |
| RenderText* renderText = box->textRenderer(); |
| int left = root->logicalLeft(); |
| int width = root->logicalWidth(); |
| int top = root->selectionTop(); |
| int height = root->selectionHeight(); |
| |
| if (!renderText->style()->isHorizontalWritingMode()) { |
| swap(left, top); |
| swap(width, height); |
| } |
| FloatPoint origin(left, top); |
| FloatPoint absoluteOrigin = renderText->localToAbsolute(origin); |
| |
| textRect.setX(absoluteOrigin.x() - offset.x()); |
| textRect.setWidth(width); |
| textRect.setY(absoluteOrigin.y() - offset.y()); |
| textRect.setHeight(height); |
| } |
| return textRect; |
| } |
| |
| IntPoint WebViewCore::convertGlobalContentToFrameContent(const IntPoint& point, WebCore::Frame* frame) |
| { |
| if (!frame) frame = focusedFrame(); |
| IntPoint frameOffset(-m_scrollOffsetX, -m_scrollOffsetY); |
| frameOffset = frame->view()->windowToContents(frameOffset); |
| return IntPoint(point.x() + frameOffset.x(), point.y() + frameOffset.y()); |
| } |
| |
| VisiblePosition WebViewCore::trimSelectionPosition(const VisiblePosition &start, |
| const VisiblePosition& stop) |
| { |
| int direction = comparePositions(start, stop); |
| if (direction == 0) |
| return start; |
| bool forward = direction < 0; |
| bool move; |
| VisiblePosition pos = start; |
| bool movedTooFar = false; |
| do { |
| move = true; |
| Node* node = pos.deepEquivalent().anchorNode(); |
| if (node && node->isTextNode() && node->renderer()) { |
| RenderText *textRenderer = toRenderText(node->renderer()); |
| move = !textRenderer->textLength(); |
| } |
| if (move) { |
| VisiblePosition nextPos = forward ? pos.next() : pos.previous(); |
| movedTooFar = nextPos.isNull() || pos == nextPos |
| || ((comparePositions(nextPos, stop) < 0) != forward); |
| pos = nextPos; |
| } |
| } while (move && !movedTooFar); |
| if (movedTooFar) |
| pos = stop; |
| return pos; |
| } |
| |
| void WebViewCore::selectText(SelectText::HandleId handleId, int x, int y) |
| { |
| SelectionController* sc = focusedFrame()->selection(); |
| VisibleSelection selection = sc->selection(); |
| Position base = selection.base(); |
| Position extent = selection.extent(); |
| IntPoint dragPoint = convertGlobalContentToFrameContent(IntPoint(x, y)); |
| VisiblePosition dragPosition(visiblePositionForContentPoint(dragPoint)); |
| |
| if (base.isNull() || extent.isNull() || dragPosition.isNull()) |
| return; |
| bool draggingBase = (handleId == SelectText::BaseHandle); |
| if (draggingBase) |
| base = dragPosition.deepEquivalent(); |
| else |
| extent = dragPosition.deepEquivalent(); |
| |
| bool baseIsStart = (comparePositions(base, extent) <= 0); |
| Position& start = baseIsStart ? base : extent; |
| Position& end = baseIsStart ? extent : base; |
| VisiblePosition startPosition(start, selection.affinity()); |
| VisiblePosition endPosition(end, selection.affinity()); |
| bool draggingStart = (baseIsStart == draggingBase); |
| |
| if (draggingStart) { |
| if (selection.isRange()) { |
| startPosition = trimSelectionPosition(startPosition, endPosition); |
| if ((startPosition != endPosition) && isEndOfBlock(startPosition)) { |
| // Ensure startPosition is not at end of block |
| VisiblePosition nextStartPosition(startPosition.next()); |
| if (nextStartPosition.isNotNull()) |
| startPosition = nextStartPosition; |
| } |
| } |
| startPosition = endPosition.honorEditableBoundaryAtOrAfter(startPosition); |
| if (startPosition.isNull()) |
| return; |
| start = startPosition.deepEquivalent(); |
| if (selection.isCaret()) |
| end = start; |
| } else { |
| if (selection.isRange()) { |
| endPosition = trimSelectionPosition(endPosition, startPosition); |
| if ((start != end) && isStartOfBlock(endPosition)) { |
| // Ensure endPosition is not at start of block |
| VisiblePosition prevEndPosition(endPosition.previous()); |
| if (!prevEndPosition.isNull()) |
| endPosition = prevEndPosition; |
| } |
| } |
| endPosition = startPosition.honorEditableBoundaryAtOrAfter(endPosition); |
| if (endPosition.isNull()) |
| return; |
| end = endPosition.deepEquivalent(); |
| if (selection.isCaret()) |
| start = end; |
| } |
| |
| selection = VisibleSelection(base, extent); |
| // Only allow changes between caret positions or to text selection. |
| bool selectChangeAllowed = (!selection.isCaret() || sc->isCaret()); |
| if (selectChangeAllowed && sc->shouldChangeSelection(selection)) |
| sc->setSelection(selection); |
| } |
| |
| bool WebViewCore::nodeIsClickableOrFocusable(Node* node) |
| { |
| if (!node) |
| return false; |
| if (node->disabled()) |
| return false; |
| if (!node->inDocument()) |
| return false; |
| if (!node->renderer() || node->renderer()->style()->visibility() != VISIBLE) |
| return false; |
| return node->supportsFocus() |
| || node->hasEventListeners(eventNames().clickEvent) |
| || node->hasEventListeners(eventNames().mousedownEvent) |
| || node->hasEventListeners(eventNames().mouseupEvent) |
| || node->hasEventListeners(eventNames().mouseoverEvent); |
| } |
| |
| // get the highlight rectangles for the touch point (x, y) with the slop |
| AndroidHitTestResult WebViewCore::hitTestAtPoint(int x, int y, int slop, bool doMoveMouse) |
| { |
| if (doMoveMouse) |
| moveMouse(x, y, 0, true); |
| HitTestResult hitTestResult = m_mainFrame->eventHandler()->hitTestResultAtPoint(IntPoint(x, y), |
| false, false, DontHitTestScrollbars, HitTestRequest::Active | HitTestRequest::ReadOnly, IntSize(slop, slop)); |
| AndroidHitTestResult androidHitResult(this, hitTestResult); |
| if (!hitTestResult.innerNode() || !hitTestResult.innerNode()->inDocument()) { |
| ALOGE("Should not happen: no in document Node found"); |
| return androidHitResult; |
| } |
| const ListHashSet<RefPtr<Node> >& list = hitTestResult.rectBasedTestResult(); |
| if (list.isEmpty()) { |
| ALOGE("Should not happen: no rect-based-test nodes found"); |
| return androidHitResult; |
| } |
| Frame* frame = hitTestResult.innerNode()->document()->frame(); |
| Vector<TouchNodeData> nodeDataList; |
| if (hitTestResult.innerNode() != hitTestResult.innerNonSharedNode() |
| && hitTestResult.innerNode()->hasTagName(WebCore::HTMLNames::areaTag)) { |
| HTMLAreaElement* area = static_cast<HTMLAreaElement*>(hitTestResult.innerNode()); |
| androidHitResult.hitTestResult().setURLElement(area); |
| androidHitResult.highlightRects().append(area->computeRect( |
| hitTestResult.innerNonSharedNode()->renderer())); |
| return androidHitResult; |
| } |
| ListHashSet<RefPtr<Node> >::const_iterator last = list.end(); |
| for (ListHashSet<RefPtr<Node> >::const_iterator it = list.begin(); it != last; ++it) { |
| // TODO: it seems reasonable to not search across the frame. Isn't it? |
| // if the node is not in the same frame as the innerNode, skip it |
| if (it->get()->document()->frame() != frame) |
| continue; |
| // traverse up the tree to find the first node that needs highlight |
| bool found = false; |
| Node* eventNode = it->get(); |
| Node* innerNode = eventNode; |
| while (eventNode) { |
| RenderObject* render = eventNode->renderer(); |
| if (render && (render->isBody() || render->isRenderView())) |
| break; |
| if (nodeIsClickableOrFocusable(eventNode)) { |
| found = true; |
| break; |
| } |
| // the nodes in the rectBasedTestResult() are ordered based on z-index during hit testing. |
| // so do not search for the eventNode across explicit z-index border. |
| // TODO: this is a hard one to call. z-index is quite complicated as its value only |
| // matters when you compare two RenderLayer in the same hierarchy level. e.g. in |
| // the following example, "b" is on the top as its z level is the highest. even "c" |
| // has 100 as z-index, it is still below "d" as its parent has the same z-index as |
| // "d" and logically before "d". Of course "a" is the lowest in the z level. |
| // |
| // z-index:auto "a" |
| // z-index:2 "b" |
| // z-index:1 |
| // z-index:100 "c" |
| // z-index:1 "d" |
| // |
| // If the fat point touches everyone, the order in the list should be "b", "d", "c" |
| // and "a". When we search for the event node for "b", we really don't want "a" as |
| // in the z-order it is behind everything else. |
| if (render && !render->style()->hasAutoZIndex()) |
| break; |
| eventNode = eventNode->parentNode(); |
| } |
| // didn't find any eventNode, skip it |
| if (!found) |
| continue; |
| // first quick check whether it is a duplicated node before computing bounding box |
| Vector<TouchNodeData>::const_iterator nlast = nodeDataList.end(); |
| for (Vector<TouchNodeData>::const_iterator n = nodeDataList.begin(); n != nlast; ++n) { |
| // found the same node, skip it |
| if (eventNode == n->mUrlNode) { |
| found = false; |
| break; |
| } |
| } |
| if (!found) |
| continue; |
| // next check whether the node is fully covered by or fully covering another node. |
| found = false; |
| IntRect rect = getAbsoluteBoundingBox(eventNode); |
| if (rect.isEmpty()) { |
| // if the node's bounds is empty and it is not a ContainerNode, skip it. |
| if (!eventNode->isContainerNode()) |
| continue; |
| // if the node's children are all positioned objects, its bounds can be empty. |
| // Walk through the children to find the bounding box. |
| Node* child = static_cast<const ContainerNode*>(eventNode)->firstChild(); |
| while (child) { |
| IntRect childrect; |
| if (child->renderer()) |
| childrect = getAbsoluteBoundingBox(child); |
| if (!childrect.isEmpty()) { |
| rect.unite(childrect); |
| child = child->traverseNextSibling(eventNode); |
| } else |
| child = child->traverseNextNode(eventNode); |
| } |
| } |
| for (int i = nodeDataList.size() - 1; i >= 0; i--) { |
| TouchNodeData n = nodeDataList.at(i); |
| // the new node is enclosing an existing node, skip it |
| if (rect.contains(n.mBounds)) { |
| found = true; |
| break; |
| } |
| // the new node is fully inside an existing node, remove the existing node |
| if (n.mBounds.contains(rect)) |
| nodeDataList.remove(i); |
| } |
| if (!found) { |
| TouchNodeData newNode; |
| newNode.mUrlNode = eventNode; |
| newNode.mBounds = rect; |
| newNode.mInnerNode = innerNode; |
| nodeDataList.append(newNode); |
| } |
| } |
| if (!nodeDataList.size()) { |
| androidHitResult.searchContentDetectors(); |
| return androidHitResult; |
| } |
| // finally select the node with the largest overlap with the fat point |
| TouchNodeData final; |
| final.mUrlNode = 0; |
| IntPoint docPos = frame->view()->windowToContents(m_mousePos); |
| IntRect testRect(docPos.x() - slop, docPos.y() - slop, 2 * slop + 1, 2 * slop + 1); |
| int area = 0; |
| Vector<TouchNodeData>::const_iterator nlast = nodeDataList.end(); |
| for (Vector<TouchNodeData>::const_iterator n = nodeDataList.begin(); n != nlast; ++n) { |
| IntRect rect = n->mBounds; |
| rect.intersect(testRect); |
| int a = rect.width() * rect.height(); |
| if (a > area || !final.mUrlNode) { |
| final = *n; |
| area = a; |
| } |
| } |
| // now get the node's highlight rectangles in the page coordinate system |
| if (final.mUrlNode) { |
| // Update innerNode and innerNonSharedNode |
| androidHitResult.hitTestResult().setInnerNode(final.mInnerNode); |
| androidHitResult.hitTestResult().setInnerNonSharedNode(final.mInnerNode); |
| if (final.mUrlNode->isElementNode()) { |
| // We found a URL element. Update the hitTestResult |
| androidHitResult.setURLElement(static_cast<Element*>(final.mUrlNode)); |
| } else { |
| androidHitResult.setURLElement(0); |
| } |
| Vector<IntRect>& highlightRects = androidHitResult.highlightRects(); |
| if (doMoveMouse && highlightRects.size() > 0) { |
| // adjust m_mousePos if it is not inside the returned highlight |
| // rectangles |
| IntRect foundIntersection; |
| IntRect inputRect = IntRect(x - slop, y - slop, |
| slop * 2 + 1, slop * 2 + 1); |
| for (size_t i = 0; i < highlightRects.size(); i++) { |
| IntRect& hr = highlightRects[i]; |
| IntRect test = inputRect; |
| test.intersect(hr); |
| if (!test.isEmpty()) { |
| foundIntersection = test; |
| break; |
| } |
| } |
| if (!foundIntersection.isEmpty() && !foundIntersection.contains(x, y)) { |
| IntPoint pt = foundIntersection.center(); |
| moveMouse(pt.x(), pt.y(), 0, true); |
| } |
| } |
| } else { |
| androidHitResult.searchContentDetectors(); |
| } |
| return androidHitResult; |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| void WebViewCore::addPlugin(PluginWidgetAndroid* w) |
| { |
| // SkDebugf("----------- addPlugin %p", w); |
| /* The plugin must be appended to the end of the array. This ensures that if |
| the plugin is added while iterating through the array (e.g. sendEvent(...)) |
| that the iteration process is not corrupted. |
| */ |
| *m_plugins.append() = w; |
| } |
| |
| void WebViewCore::removePlugin(PluginWidgetAndroid* w) |
| { |
| // SkDebugf("----------- removePlugin %p", w); |
| int index = m_plugins.find(w); |
| if (index < 0) { |
| SkDebugf("--------------- pluginwindow not found! %p\n", w); |
| } else { |
| m_plugins.removeShuffle(index); |
| } |
| } |
| |
| bool WebViewCore::isPlugin(PluginWidgetAndroid* w) const |
| { |
| return m_plugins.find(w) >= 0; |
| } |
| |
| void WebViewCore::invalPlugin(PluginWidgetAndroid* w) |
| { |
| const double PLUGIN_INVAL_DELAY = 1.0 / 60; |
| |
| if (!m_pluginInvalTimer.isActive()) { |
| m_pluginInvalTimer.startOneShot(PLUGIN_INVAL_DELAY); |
| } |
| } |
| |
| void WebViewCore::drawPlugins() |
| { |
| SkRegion inval; // accumualte what needs to be redrawn |
| PluginWidgetAndroid** iter = m_plugins.begin(); |
| PluginWidgetAndroid** stop = m_plugins.end(); |
| |
| for (; iter < stop; ++iter) { |
| PluginWidgetAndroid* w = *iter; |
| SkIRect dirty; |
| if (w->isDirty(&dirty)) { |
| w->draw(); |
| inval.op(dirty, SkRegion::kUnion_Op); |
| } |
| } |
| |
| if (!inval.isEmpty()) { |
| // inval.getBounds() is our rectangle |
| const SkIRect& bounds = inval.getBounds(); |
| WebCore::IntRect r(bounds.fLeft, bounds.fTop, |
| bounds.width(), bounds.height()); |
| this->viewInvalidate(r); |
| } |
| } |
| |
| void WebViewCore::notifyPluginsOnFrameLoad(const Frame* frame) { |
| // if frame is the parent then notify all plugins |
| if (!frame->tree()->parent()) { |
| // trigger an event notifying the plugins that the page has loaded |
| ANPEvent event; |
| SkANP::InitEvent(&event, kLifecycle_ANPEventType); |
| event.data.lifecycle.action = kOnLoad_ANPLifecycleAction; |
| sendPluginEvent(event); |
| // trigger the on/off screen notification if the page was reloaded |
| sendPluginVisibleScreen(); |
| } |
| // else if frame's parent has completed |
| else if (!frame->tree()->parent()->loader()->isLoading()) { |
| // send to all plugins who have this frame in their heirarchy |
| PluginWidgetAndroid** iter = m_plugins.begin(); |
| PluginWidgetAndroid** stop = m_plugins.end(); |
| for (; iter < stop; ++iter) { |
| Frame* currentFrame = (*iter)->pluginView()->parentFrame(); |
| while (currentFrame) { |
| if (frame == currentFrame) { |
| ANPEvent event; |
| SkANP::InitEvent(&event, kLifecycle_ANPEventType); |
| event.data.lifecycle.action = kOnLoad_ANPLifecycleAction; |
| (*iter)->sendEvent(event); |
| |
| // trigger the on/off screen notification if the page was reloaded |
| ANPRectI visibleRect; |
| getVisibleScreen(visibleRect); |
| (*iter)->setVisibleScreen(visibleRect, m_scale); |
| |
| break; |
| } |
| currentFrame = currentFrame->tree()->parent(); |
| } |
| } |
| } |
| } |
| |
| void WebViewCore::getVisibleScreen(ANPRectI& visibleRect) |
| { |
| visibleRect.left = m_scrollOffsetX; |
| visibleRect.top = m_scrollOffsetY; |
| visibleRect.right = m_scrollOffsetX + m_screenWidth; |
| visibleRect.bottom = m_scrollOffsetY + m_screenHeight; |
| } |
| |
| void WebViewCore::sendPluginVisibleScreen() |
| { |
| /* We may want to cache the previous values and only send the notification |
| to the plugin in the event that one of the values has changed. |
| */ |
| |
| ANPRectI visibleRect; |
| getVisibleScreen(visibleRect); |
| |
| PluginWidgetAndroid** iter = m_plugins.begin(); |
| PluginWidgetAndroid** stop = m_plugins.end(); |
| for (; iter < stop; ++iter) { |
| (*iter)->setVisibleScreen(visibleRect, m_scale); |
| } |
| } |
| |
| void WebViewCore::sendPluginSurfaceReady() |
| { |
| PluginWidgetAndroid** iter = m_plugins.begin(); |
| PluginWidgetAndroid** stop = m_plugins.end(); |
| for (; iter < stop; ++iter) { |
| (*iter)->checkSurfaceReady(); |
| } |
| } |
| |
| void WebViewCore::sendPluginEvent(const ANPEvent& evt) |
| { |
| /* The list of plugins may be manipulated as we iterate through the list. |
| This implementation allows for the addition of new plugins during an |
| iteration, but may fail if a plugin is removed. Currently, there are not |
| any use cases where a plugin is deleted while processing this loop, but |
| if it does occur we will have to use an alternate data structure and/or |
| iteration mechanism. |
| */ |
| for (int x = 0; x < m_plugins.count(); x++) { |
| m_plugins[x]->sendEvent(evt); |
| } |
| } |
| |
| PluginWidgetAndroid* WebViewCore::getPluginWidget(NPP npp) |
| { |
| PluginWidgetAndroid** iter = m_plugins.begin(); |
| PluginWidgetAndroid** stop = m_plugins.end(); |
| for (; iter < stop; ++iter) { |
| if ((*iter)->pluginView()->instance() == npp) { |
| return (*iter); |
| } |
| } |
| return 0; |
| } |
| |
| static PluginView* nodeIsPlugin(Node* node) { |
| RenderObject* renderer = node->renderer(); |
| if (renderer && renderer->isWidget()) { |
| Widget* widget = static_cast<RenderWidget*>(renderer)->widget(); |
| if (widget && widget->isPluginView()) |
| return static_cast<PluginView*>(widget); |
| } |
| return 0; |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| // Update mouse position |
| void WebViewCore::moveMouse(int x, int y, HitTestResult* hoveredNode, bool isClickCandidate) |
| { |
| // mouse event expects the position in the window coordinate |
| m_mousePos = WebCore::IntPoint(x - m_scrollOffsetX, y - m_scrollOffsetY); |
| if (isClickCandidate) |
| m_mouseClickPos = m_mousePos; |
| // validNode will still return true if the node is null, as long as we have |
| // a valid frame. Do not want to make a call on frame unless it is valid. |
| WebCore::PlatformMouseEvent mouseEvent(m_mousePos, m_mousePos, |
| WebCore::NoButton, WebCore::MouseEventMoved, 1, false, false, false, |
| false, WTF::currentTime()); |
| m_mainFrame->eventHandler()->handleMouseMoveEvent(mouseEvent, hoveredNode); |
| } |
| |
| Position WebViewCore::getPositionForOffset(Node* node, int offset) |
| { |
| Position start = firstPositionInNode(node); |
| Position end = lastPositionInNode(node); |
| Document* document = node->document(); |
| PassRefPtr<Range> range = Range::create(document, start, end); |
| WebCore::CharacterIterator iterator(range.get()); |
| iterator.advance(offset); |
| return iterator.range()->startPosition(); |
| } |
| |
| void WebViewCore::setSelection(Node* node, int start, int end) |
| { |
| RenderTextControl* control = toRenderTextControl(node); |
| if (control) |
| setSelectionRange(node, start, end); |
| else { |
| Position startPosition = getPositionForOffset(node, start); |
| Position endPosition = getPositionForOffset(node, end); |
| VisibleSelection selection(startPosition, endPosition); |
| SelectionController* selector = node->document()->frame()->selection(); |
| selector->setSelection(selection); |
| } |
| } |
| |
| void WebViewCore::setSelection(int start, int end) |
| { |
| WebCore::Node* focus = currentFocus(); |
| if (!focus) |
| return; |
| if (start > end) |
| swap(start, end); |
| |
| // Tell our EditorClient that this change was generated from the UI, so it |
| // does not need to echo it to the UI. |
| EditorClientAndroid* client = static_cast<EditorClientAndroid*>( |
| m_mainFrame->editor()->client()); |
| client->setUiGeneratedSelectionChange(true); |
| setSelection(focus, start, end); |
| RenderTextControl* control = toRenderTextControl(focus); |
| if (start != end && control) { |
| // Fire a select event. No event is sent when the selection reduces to |
| // an insertion point |
| control->selectionChanged(true); |
| } |
| client->setUiGeneratedSelectionChange(false); |
| bool isPasswordField = false; |
| if (focus->isElementNode()) { |
| WebCore::Element* element = static_cast<WebCore::Element*>(focus); |
| if (WebCore::InputElement* inputElement = element->toInputElement()) |
| isPasswordField = static_cast<WebCore::HTMLInputElement*>(inputElement)->isPasswordField(); |
| } |
| // For password fields, this is done in the UI side via |
| // bringPointIntoView, since the UI does the drawing. |
| if ((control && control->isTextArea()) || !isPasswordField) |
| revealSelection(); |
| } |
| |
| String WebViewCore::modifySelection(const int direction, const int axis) |
| { |
| DOMSelection* selection = m_mainFrame->domWindow()->getSelection(); |
| ASSERT(selection); |
| // We've seen crashes where selection is null, but we don't know why |
| // See http://b/5244036 |
| if (!selection) |
| return String(); |
| if (selection->rangeCount() > 1) |
| selection->removeAllRanges(); |
| switch (axis) { |
| case AXIS_CHARACTER: |
| case AXIS_WORD: |
| case AXIS_SENTENCE: |
| return modifySelectionTextNavigationAxis(selection, direction, axis); |
| case AXIS_HEADING: |
| case AXIS_SIBLING: |
| case AXIS_PARENT_FIRST_CHILD: |
| case AXIS_DOCUMENT: |
| return modifySelectionDomNavigationAxis(selection, direction, axis); |
| default: |
| ALOGE("Invalid navigation axis: %d", axis); |
| return String(); |
| } |
| } |
| |
| void WebViewCore::scrollNodeIntoView(Frame* frame, Node* node) |
| { |
| if (!frame || !node) |
| return; |
| |
| Element* elementNode = 0; |
| |
| // If not an Element, find a visible predecessor |
| // Element to scroll into view. |
| if (!node->isElementNode()) { |
| HTMLElement* body = frame->document()->body(); |
| do { |
| if (node == body) |
| return; |
| node = node->parentNode(); |
| } while (node && !node->isElementNode() && !isVisible(node)); |
| } |
| |
| // Couldn't find a visible predecessor. |
| if (!node) |
| return; |
| |
| elementNode = static_cast<Element*>(node); |
| elementNode->scrollIntoViewIfNeeded(true); |
| } |
| |
| String WebViewCore::modifySelectionTextNavigationAxis(DOMSelection* selection, int direction, int axis) |
| { |
| Node* body = m_mainFrame->document()->body(); |
| |
| ExceptionCode ec = 0; |
| String markup; |
| |
| // initialize the selection if necessary |
| if (selection->rangeCount() == 0) { |
| if (m_currentNodeDomNavigationAxis |
| && validNode(m_mainFrame, |
| m_mainFrame, m_currentNodeDomNavigationAxis)) { |
| RefPtr<Range> rangeRef = |
| selection->frame()->document()->createRange(); |
| rangeRef->selectNode(m_currentNodeDomNavigationAxis, ec); |
| m_currentNodeDomNavigationAxis = 0; |
| if (ec) |
| return String(); |
| selection->addRange(rangeRef.get()); |
| } else if (currentFocus()) { |
| selection->setPosition(currentFocus(), 0, ec); |
| } else { |
| selection->setPosition(body, 0, ec); |
| } |
| if (ec) |
| return String(); |
| } |
| |
| // collapse the selection |
| if (direction == DIRECTION_FORWARD) |
| selection->collapseToEnd(ec); |
| else |
| selection->collapseToStart(ec); |
| if (ec) |
| return String(); |
| |
| // Make sure the anchor node is a text node since we are generating |
| // the markup of the selection which includes the anchor, the focus, |
| // and any crossed nodes. Forcing the condition that the selection |
| // starts and ends on text nodes guarantees symmetric selection markup. |
| // Also this way the text content, rather its container, is highlighted. |
| Node* anchorNode = selection->anchorNode(); |
| if (anchorNode->isElementNode()) { |
| // Collapsed selection while moving forward points to the |
| // next unvisited node and while moving backward to the |
| // last visited node. |
| if (direction == DIRECTION_FORWARD) |
| advanceAnchorNode(selection, direction, markup, false, ec); |
| else |
| advanceAnchorNode(selection, direction, markup, true, ec); |
| if (ec) |
| return String(); |
| if (!markup.isEmpty()) |
| return markup; |
| } |
| |
| // If the selection is at the end of a non white space text move |
| // it to the next visible text node with non white space content. |
| // This is a workaround for the selection getting stuck. |
| anchorNode = selection->anchorNode(); |
| if (anchorNode->isTextNode()) { |
| if (direction == DIRECTION_FORWARD) { |
| String suffix = anchorNode->textContent().substring( |
| selection->anchorOffset(), caretMaxOffset(anchorNode)); |
| // If at the end of non white space text we advance the |
| // anchor node to either an input element or non empty text. |
| if (suffix.stripWhiteSpace().isEmpty()) { |
| advanceAnchorNode(selection, direction, markup, true, ec); |
| } |
| } else { |
| String prefix = anchorNode->textContent().substring(0, |
| selection->anchorOffset()); |
| // If at the end of non white space text we advance the |
| // anchor node to either an input element or non empty text. |
| if (prefix.stripWhiteSpace().isEmpty()) { |
| advanceAnchorNode(selection, direction, markup, true, ec); |
| } |
| } |
| if (ec) |
| return String(); |
| if (!markup.isEmpty()) |
| return markup; |
| } |
| |
| // extend the selection |
| String directionStr; |
| if (direction == DIRECTION_FORWARD) |
| directionStr = "forward"; |
| else |
| directionStr = "backward"; |
| |
| String axisStr; |
| if (axis == AXIS_CHARACTER) |
| axisStr = "character"; |
| else if (axis == AXIS_WORD) |
| axisStr = "word"; |
| else |
| axisStr = "sentence"; |
| |
| selection->modify("extend", directionStr, axisStr); |
| |
| // Make sure the focus node is a text node in order to have the |
| // selection generate symmetric markup because the latter |
| // includes all nodes crossed by the selection. Also this way |
| // the text content, rather its container, is highlighted. |
| Node* focusNode = selection->focusNode(); |
| if (focusNode->isElementNode()) { |
| focusNode = getImplicitBoundaryNode(selection->focusNode(), |
| selection->focusOffset(), direction); |
| if (!focusNode) |
| return String(); |
| if (direction == DIRECTION_FORWARD) { |
| focusNode = focusNode->traversePreviousSiblingPostOrder(body); |
| if (focusNode && !isContentTextNode(focusNode)) { |
| Node* textNode = traverseNextContentTextNode(focusNode, |
| anchorNode, DIRECTION_BACKWARD); |
| if (textNode) |
| anchorNode = textNode; |
| } |
| if (focusNode && isContentTextNode(focusNode)) { |
| selection->extend(focusNode, caretMaxOffset(focusNode), ec); |
| if (ec) |
| return String(); |
| } |
| } else { |
| focusNode = focusNode->traverseNextSibling(); |
| if (focusNode && !isContentTextNode(focusNode)) { |
| Node* textNode = traverseNextContentTextNode(focusNode, |
| anchorNode, DIRECTION_FORWARD); |
| if (textNode) |
| anchorNode = textNode; |
| } |
| if (anchorNode && isContentTextNode(anchorNode)) { |
| selection->extend(focusNode, 0, ec); |
| if (ec) |
| return String(); |
| } |
| } |
| } |
| |
| // Enforce that the selection does not cross anchor boundaries. This is |
| // a workaround for the asymmetric behavior of WebKit while crossing |
| // anchors. |
| anchorNode = getImplicitBoundaryNode(selection->anchorNode(), |
| selection->anchorOffset(), direction); |
| focusNode = getImplicitBoundaryNode(selection->focusNode(), |
| selection->focusOffset(), direction); |
| if (anchorNode && focusNode && anchorNode != focusNode) { |
| Node* inputControl = getIntermediaryInputElement(anchorNode, focusNode, |
| direction); |
| if (inputControl) { |
| if (direction == DIRECTION_FORWARD) { |
| if (isDescendantOf(inputControl, anchorNode)) { |
| focusNode = inputControl; |
| } else { |
| focusNode = inputControl->traversePreviousSiblingPostOrder( |
| body); |
| if (!focusNode) |
| focusNode = inputControl; |
| } |
| // We prefer a text node contained in the input element. |
| if (!isContentTextNode(focusNode)) { |
| Node* textNode = traverseNextContentTextNode(focusNode, |
| anchorNode, DIRECTION_BACKWARD); |
| if (textNode) |
| focusNode = textNode; |
| } |
| // If we found text in the input select it. |
| // Otherwise, select the input element itself. |
| if (isContentTextNode(focusNode)) { |
| selection->extend(focusNode, caretMaxOffset(focusNode), ec); |
| } else if (anchorNode != focusNode) { |
| // Note that the focusNode always has parent and that |
| // the offset can be one more that the index of the last |
| // element - this is how WebKit selects such elements. |
| selection->extend(focusNode->parentNode(), |
| focusNode->nodeIndex() + 1, ec); |
| } |
| if (ec) |
| return String(); |
| } else { |
| if (isDescendantOf(inputControl, anchorNode)) { |
| focusNode = inputControl; |
| } else { |
| focusNode = inputControl->traverseNextSibling(); |
| if (!focusNode) |
| focusNode = inputControl; |
| } |
| // We prefer a text node contained in the input element. |
| if (!isContentTextNode(focusNode)) { |
| Node* textNode = traverseNextContentTextNode(focusNode, |
| anchorNode, DIRECTION_FORWARD); |
| if (textNode) |
| focusNode = textNode; |
| } |
| // If we found text in the input select it. |
| // Otherwise, select the input element itself. |
| if (isContentTextNode(focusNode)) { |
| selection->extend(focusNode, caretMinOffset(focusNode), ec); |
| } else if (anchorNode != focusNode) { |
| // Note that the focusNode always has parent and that |
| // the offset can be one more that the index of the last |
| // element - this is how WebKit selects such elements. |
| selection->extend(focusNode->parentNode(), |
| focusNode->nodeIndex() + 1, ec); |
| } |
| if (ec) |
| return String(); |
| } |
| } |
| } |
| |
| // make sure the selection is visible |
| if (direction == DIRECTION_FORWARD) |
| scrollNodeIntoView(m_mainFrame, selection->focusNode()); |
| else |
| scrollNodeIntoView(m_mainFrame, selection->anchorNode()); |
| |
| // format markup for the visible content |
| RefPtr<Range> range = selection->getRangeAt(0, ec); |
| if (ec) |
| return String(); |
| IntRect bounds = range->boundingBox(); |
| selectAt(bounds.center().x(), bounds.center().y()); |
| markup = formatMarkup(selection); |
| ALOGV("Selection markup: %s", markup.utf8().data()); |
| |
| return markup; |
| } |
| |
| Node* WebViewCore::getImplicitBoundaryNode(Node* node, unsigned offset, int direction) |
| { |
| if (node->offsetInCharacters()) |
| return node; |
| if (!node->hasChildNodes()) |
| return node; |
| if (offset < node->childNodeCount()) |
| return node->childNode(offset); |
| else |
| if (direction == DIRECTION_FORWARD) |
| return node->traverseNextSibling(); |
| else |
| return node->traversePreviousNodePostOrder( |
| node->document()->body()); |
| } |
| |
| Node* WebViewCore::getNextAnchorNode(Node* anchorNode, bool ignoreFirstNode, int direction) |
| { |
| Node* body = 0; |
| Node* currentNode = 0; |
| if (direction == DIRECTION_FORWARD) { |
| if (ignoreFirstNode) |
| currentNode = anchorNode->traverseNextNode(body); |
| else |
| currentNode = anchorNode; |
| } else { |
| body = anchorNode->document()->body(); |
| if (ignoreFirstNode) |
| currentNode = anchorNode->traversePreviousSiblingPostOrder(body); |
| else |
| currentNode = anchorNode; |
| } |
| while (currentNode) { |
| if (isContentTextNode(currentNode) |
| || isContentInputElement(currentNode)) |
| return currentNode; |
| if (direction == DIRECTION_FORWARD) |
| currentNode = currentNode->traverseNextNode(); |
| else |
| currentNode = currentNode->traversePreviousNodePostOrder(body); |
| } |
| return 0; |
| } |
| |
| void WebViewCore::advanceAnchorNode(DOMSelection* selection, int direction, |
| String& markup, bool ignoreFirstNode, ExceptionCode& ec) |
| { |
| Node* anchorNode = getImplicitBoundaryNode(selection->anchorNode(), |
| selection->anchorOffset(), direction); |
| if (!anchorNode) { |
| ec = NOT_FOUND_ERR; |
| return; |
| } |
| // If the anchor offset is invalid i.e. the anchor node has no |
| // child with that index getImplicitAnchorNode returns the next |
| // logical node in the current direction. In such a case our |
| // position in the DOM tree was has already been advanced, |
| // therefore we there is no need to do that again. |
| if (selection->anchorNode()->isElementNode()) { |
| unsigned anchorOffset = selection->anchorOffset(); |
| unsigned childNodeCount = selection->anchorNode()->childNodeCount(); |
| if (anchorOffset >= childNodeCount) |
| ignoreFirstNode = false; |
| } |
| // Find the next anchor node given our position in the DOM and |
| // whether we want the current node to be considered as well. |
| Node* nextAnchorNode = getNextAnchorNode(anchorNode, ignoreFirstNode, |
| direction); |
| if (!nextAnchorNode) { |
| ec = NOT_FOUND_ERR; |
| return; |
| } |
| if (nextAnchorNode->isElementNode()) { |
| // If this is an input element tell the WebView thread |
| // to set the cursor to that control. |
| if (isContentInputElement(nextAnchorNode)) { |
| IntRect bounds = nextAnchorNode->getRect(); |
| selectAt(bounds.center().x(), bounds.center().y()); |
| } |
| Node* textNode = 0; |
| // Treat the text content of links as any other text but |
| // for the rest input elements select the control itself. |
| if (nextAnchorNode->hasTagName(WebCore::HTMLNames::aTag)) |
| textNode = traverseNextContentTextNode(nextAnchorNode, |
| nextAnchorNode, direction); |
| // We prefer to select the text content of the link if such, |
| // otherwise just select the element itself. |
| if (textNode) { |
| nextAnchorNode = textNode; |
| } else { |
| if (direction == DIRECTION_FORWARD) { |
| selection->setBaseAndExtent(nextAnchorNode, |
| caretMinOffset(nextAnchorNode), nextAnchorNode, |
| caretMaxOffset(nextAnchorNode), ec); |
| } else { |
| selection->setBaseAndExtent(nextAnchorNode, |
| caretMaxOffset(nextAnchorNode), nextAnchorNode, |
| caretMinOffset(nextAnchorNode), ec); |
| } |
| if (!ec) |
| markup = formatMarkup(selection); |
| // make sure the selection is visible |
| scrollNodeIntoView(selection->frame(), nextAnchorNode); |
| return; |
| } |
| } |
| if (direction == DIRECTION_FORWARD) |
| selection->setPosition(nextAnchorNode, |
| caretMinOffset(nextAnchorNode), ec); |
| else |
| selection->setPosition(nextAnchorNode, |
| caretMaxOffset(nextAnchorNode), ec); |
| } |
| |
| bool WebViewCore::isContentInputElement(Node* node) |
| { |
| return (isVisible(node) |
| && (node->hasTagName(WebCore::HTMLNames::selectTag) |
| || node->hasTagName(WebCore::HTMLNames::aTag) |
| || node->hasTagName(WebCore::HTMLNames::inputTag) |
| || node->hasTagName(WebCore::HTMLNames::buttonTag))); |
| } |
| |
| bool WebViewCore::isContentTextNode(Node* node) |
| { |
| if (!node || !node->isTextNode()) |
| return false; |
| Text* textNode = static_cast<Text*>(node); |
| return (isVisible(textNode) && textNode->length() > 0 |
| && !textNode->containsOnlyWhitespace()); |
| } |
| |
| Text* WebViewCore::traverseNextContentTextNode(Node* fromNode, Node* toNode, int direction) |
| { |
| Node* currentNode = fromNode; |
| do { |
| if (direction == DIRECTION_FORWARD) |
| currentNode = currentNode->traverseNextNode(toNode); |
| else |
| currentNode = currentNode->traversePreviousNodePostOrder(toNode); |
| } while (currentNode && !isContentTextNode(currentNode)); |
| return static_cast<Text*>(currentNode); |
| } |
| |
| Node* WebViewCore::getIntermediaryInputElement(Node* fromNode, Node* toNode, int direction) |
| { |
| if (fromNode == toNode) |
| return 0; |
| if (direction == DIRECTION_FORWARD) { |
| Node* currentNode = fromNode; |
| while (currentNode && currentNode != toNode) { |
| if (isContentInputElement(currentNode)) |
| return currentNode; |
| currentNode = currentNode->traverseNextNodePostOrder(); |
| } |
| currentNode = fromNode; |
| while (currentNode && currentNode != toNode) { |
| if (isContentInputElement(currentNode)) |
| return currentNode; |
| currentNode = currentNode->traverseNextNode(); |
| } |
| } else { |
| Node* currentNode = fromNode->traversePreviousNode(); |
| while (currentNode && currentNode != toNode) { |
| if (isContentInputElement(currentNode)) |
| return currentNode; |
| currentNode = currentNode->traversePreviousNode(); |
| } |
| currentNode = fromNode->traversePreviousNodePostOrder(); |
| while (currentNode && currentNode != toNode) { |
| if (isContentInputElement(currentNode)) |
| return currentNode; |
| currentNode = currentNode->traversePreviousNodePostOrder(); |
| } |
| } |
| return 0; |
| } |
| |
| bool WebViewCore::isDescendantOf(Node* parent, Node* node) |
| { |
| Node* currentNode = node; |
| while (currentNode) { |
| if (currentNode == parent) { |
| return true; |
| } |
| currentNode = currentNode->parentNode(); |
| } |
| return false; |
| } |
| |
| String WebViewCore::modifySelectionDomNavigationAxis(DOMSelection* selection, int direction, int axis) |
| { |
| HTMLElement* body = m_mainFrame->document()->body(); |
| if (!m_currentNodeDomNavigationAxis && selection->focusNode()) { |
| m_currentNodeDomNavigationAxis = selection->focusNode(); |
| selection->empty(); |
| if (m_currentNodeDomNavigationAxis->isTextNode()) |
| m_currentNodeDomNavigationAxis = |
| m_currentNodeDomNavigationAxis->parentNode(); |
| } |
| if (!m_currentNodeDomNavigationAxis) |
| m_currentNodeDomNavigationAxis = currentFocus(); |
| if (!m_currentNodeDomNavigationAxis |
| || !validNode(m_mainFrame, m_mainFrame, |
| m_currentNodeDomNavigationAxis)) |
| m_currentNodeDomNavigationAxis = body; |
| Node* currentNode = m_currentNodeDomNavigationAxis; |
| if (axis == AXIS_HEADING) { |
| if (currentNode == body && direction == DIRECTION_BACKWARD) |
| currentNode = currentNode->lastDescendant(); |
| do { |
| if (direction == DIRECTION_FORWARD) |
| currentNode = currentNode->traverseNextNode(body); |
| else |
| currentNode = currentNode->traversePreviousNode(body); |
| } while (currentNode && (currentNode->isTextNode() |
| || !isVisible(currentNode) || !isHeading(currentNode))); |
| } else if (axis == AXIS_PARENT_FIRST_CHILD) { |
| if (direction == DIRECTION_FORWARD) { |
| currentNode = currentNode->firstChild(); |
| while (currentNode && (currentNode->isTextNode() |
| || !isVisible(currentNode))) |
| currentNode = currentNode->nextSibling(); |
| } else { |
| do { |
| if (currentNode == body) |
| return String(); |
| currentNode = currentNode->parentNode(); |
| } while (currentNode && (currentNode->isTextNode() |
| || !isVisible(currentNode))); |
| } |
| } else if (axis == AXIS_SIBLING) { |
| do { |
| if (direction == DIRECTION_FORWARD) |
| currentNode = currentNode->nextSibling(); |
| else { |
| if (currentNode == body) |
| return String(); |
| currentNode = currentNode->previousSibling(); |
| } |
| } while (currentNode && (currentNode->isTextNode() |
| || !isVisible(currentNode))); |
| } else if (axis == AXIS_DOCUMENT) { |
| currentNode = body; |
| if (direction == DIRECTION_FORWARD) |
| currentNode = currentNode->lastDescendant(); |
| } else { |
| ALOGE("Invalid axis: %d", axis); |
| return String(); |
| } |
| if (currentNode) { |
| m_currentNodeDomNavigationAxis = currentNode; |
| scrollNodeIntoView(m_mainFrame, currentNode); |
| String selectionString = createMarkup(currentNode); |
| ALOGV("Selection markup: %s", selectionString.utf8().data()); |
| return selectionString; |
| } |
| return String(); |
| } |
| |
| bool WebViewCore::isHeading(Node* node) |
| { |
| if (node->hasTagName(WebCore::HTMLNames::h1Tag) |
| || node->hasTagName(WebCore::HTMLNames::h2Tag) |
| || node->hasTagName(WebCore::HTMLNames::h3Tag) |
| || node->hasTagName(WebCore::HTMLNames::h4Tag) |
| || node->hasTagName(WebCore::HTMLNames::h5Tag) |
| || node->hasTagName(WebCore::HTMLNames::h6Tag)) { |
| return true; |
| } |
| |
| if (node->isElementNode()) { |
| Element* element = static_cast<Element*>(node); |
| String roleAttribute = |
| element->getAttribute(WebCore::HTMLNames::roleAttr).string(); |
| if (equalIgnoringCase(roleAttribute, "heading")) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| bool WebViewCore::isVisible(Node* node) |
| { |
| // start off an element |
| Element* element = 0; |
| if (node->isElementNode()) |
| element = static_cast<Element*>(node); |
| else |
| element = node->parentElement(); |
| // check renderer |
| if (!element->renderer()) { |
| return false; |
| } |
| // check size |
| if (element->offsetHeight() == 0 || element->offsetWidth() == 0) { |
| return false; |
| } |
| // check style |
| Node* body = m_mainFrame->document()->body(); |
| Node* currentNode = element; |
| while (currentNode && currentNode != body) { |
| RenderStyle* style = currentNode->computedStyle(); |
| if (style && |
| (style->display() == WebCore::NONE || style->visibility() == WebCore::HIDDEN)) { |
| return false; |
| } |
| currentNode = currentNode->parentNode(); |
| } |
| return true; |
| } |
| |
| String WebViewCore::formatMarkup(DOMSelection* selection) |
| { |
| ExceptionCode ec = 0; |
| String markup = String(); |
| RefPtr<Range> wholeRange = selection->getRangeAt(0, ec); |
| if (ec) |
| return String(); |
| if (!wholeRange->startContainer() || !wholeRange->startContainer()) |
| return String(); |
| // Since formatted markup contains invisible nodes it |
| // is created from the concatenation of the visible fragments. |
| Node* firstNode = wholeRange->firstNode(); |
| Node* pastLastNode = wholeRange->pastLastNode(); |
| Node* currentNode = firstNode; |
| RefPtr<Range> currentRange; |
| |
| while (currentNode != pastLastNode) { |
| Node* nextNode = currentNode->traverseNextNode(); |
| if (!isVisible(currentNode)) { |
| if (currentRange) { |
| markup = markup + currentRange->toHTML().utf8().data(); |
| currentRange = 0; |
| } |
| } else { |
| if (!currentRange) { |
| currentRange = selection->frame()->document()->createRange(); |
| if (ec) |
| break; |
| if (currentNode == firstNode) { |
| currentRange->setStart(wholeRange->startContainer(), |
| wholeRange->startOffset(), ec); |
| if (ec) |
| break; |
| } else { |
| currentRange->setStart(currentNode->parentNode(), |
| currentNode->nodeIndex(), ec); |
| if (ec) |
| break; |
| } |
| } |
| if (nextNode == pastLastNode) { |
| currentRange->setEnd(wholeRange->endContainer(), |
| wholeRange->endOffset(), ec); |
| if (ec) |
| break; |
| markup = markup + currentRange->toHTML().utf8().data(); |
| } else { |
| if (currentNode->offsetInCharacters()) |
| currentRange->setEnd(currentNode, |
| currentNode->maxCharacterOffset(), ec); |
| else |
| currentRange->setEnd(currentNode->parentNode(), |
| currentNode->nodeIndex() + 1, ec); |
| if (ec) |
| break; |
| } |
| } |
| currentNode = nextNode; |
| } |
| return markup.stripWhiteSpace(); |
| } |
| |
| void WebViewCore::selectAt(int x, int y) |
| { |
| JNIEnv* env = JSC::Bindings::getJNIEnv(); |
| AutoJObject javaObject = m_javaGlue->object(env); |
| if (!javaObject.get()) |
| return; |
| env->CallVoidMethod(javaObject.get(), m_javaGlue->m_selectAt, x, y); |
| checkException(env); |
| } |
| |
| void WebViewCore::deleteSelection(int start, int end, int textGeneration) |
| { |
| setSelection(start, end); |
| if (start == end) |
| return; |
| WebCore::Node* focus = currentFocus(); |
| if (!focus) |
| return; |
| // Prevent our editor client from passing a message to change the |
| // selection. |
| EditorClientAndroid* client = static_cast<EditorClientAndroid*>( |
| m_mainFrame->editor()->client()); |
| client->setUiGeneratedSelectionChange(true); |
| PlatformKeyboardEvent down(AKEYCODE_DEL, 0, 0, true, false, false, false); |
| PlatformKeyboardEvent up(AKEYCODE_DEL, 0, 0, false, false, false, false); |
| key(down); |
| key(up); |
| client->setUiGeneratedSelectionChange(false); |
| m_textGeneration = textGeneration; |
| } |
| |
| void WebViewCore::replaceTextfieldText(int oldStart, |
| int oldEnd, const WTF::String& replace, int start, int end, |
| int textGeneration) |
| { |
| WebCore::Node* focus = currentFocus(); |
| if (!focus) |
| return; |
| setSelection(oldStart, oldEnd); |
| // Prevent our editor client from passing a message to change the |
| // selection. |
| EditorClientAndroid* client = static_cast<EditorClientAndroid*>( |
| m_mainFrame->editor()->client()); |
| client->setUiGeneratedSelectionChange(true); |
| if (replace.length()) |
| WebCore::TypingCommand::insertText(focus->document(), replace, |
| false); |
| else |
| WebCore::TypingCommand::deleteSelection(focus->document()); |
| client->setUiGeneratedSelectionChange(false); |
| // setSelection calls revealSelection, so there is no need to do it here. |
| setSelection(start, end); |
| m_textGeneration = textGeneration; |
| } |
| |
| void WebViewCore::passToJs(int generation, const WTF::String& current, |
| const PlatformKeyboardEvent& event) |
| { |
| WebCore::Node* focus = currentFocus(); |
| if (!focus) { |
| clearTextEntry(); |
| return; |
| } |
| // Block text field updates during a key press. |
| m_blockTextfieldUpdates = true; |
| // Also prevent our editor client from passing a message to change the |
| // selection. |
| EditorClientAndroid* client = static_cast<EditorClientAndroid*>( |
| m_mainFrame->editor()->client()); |
| client->setUiGeneratedSelectionChange(true); |
| key(event); |
| client->setUiGeneratedSelectionChange(false); |
| m_blockTextfieldUpdates = false; |
| m_textGeneration = generation; |
| WTF::String test = getInputText(focus); |
| if (test != current) { |
| // If the text changed during the key event, update the UI text field. |
| updateTextfield(focus, test); |
| } |
| // Now that the selection has settled down, send it. |
| updateTextSelection(); |
| } |
| |
| void WebViewCore::scrollFocusedTextInput(float xPercent, int y) |
| { |
| WebCore::Node* focus = currentFocus(); |
| if (!focus) { |
| clearTextEntry(); |
| return; |
| } |
| WebCore::RenderTextControl* renderText = toRenderTextControl(focus); |
| if (!renderText) { |
| clearTextEntry(); |
| return; |
| } |
| |
| int x = (int)round(xPercent * (renderText->scrollWidth() - |
| renderText->contentWidth())); |
| renderText->setScrollLeft(x); |
| renderText->setScrollTop(y); |
| focus->document()->frame()->selection()->recomputeCaretRect(); |
| updateTextSelection(); |
| } |
| |
| void WebViewCore::setFocusControllerActive(bool active) |
| { |
| m_mainFrame->page()->focusController()->setActive(active); |
| } |
| |
| void WebViewCore::saveDocumentState(WebCore::Frame* frame) |
| { |
| if (!validNode(m_mainFrame, frame, 0)) |
| frame = m_mainFrame; |
| WebCore::HistoryItem *item = frame->loader()->history()->currentItem(); |
| |
| // item can be null when there is no offical URL for the current page. This happens |
| // when the content is loaded using with WebCoreFrameBridge::LoadData() and there |
| // is no failing URL (common case is when content is loaded using data: scheme) |
| if (item) { |
| item->setDocumentState(frame->document()->formElementsState()); |
| } |
| } |
| |
| // Create an array of java Strings. |
| static jobjectArray makeLabelArray(JNIEnv* env, const uint16_t** labels, size_t count) |
| { |
| jclass stringClass = env->FindClass("java/lang/String"); |
| ALOG_ASSERT(stringClass, "Could not find java/lang/String"); |
| jobjectArray array = env->NewObjectArray(count, stringClass, 0); |
| ALOG_ASSERT(array, "Could not create new string array"); |
| |
| for (size_t i = 0; i < count; i++) { |
| jobject newString = env->NewString(&labels[i][1], labels[i][0]); |
| env->SetObjectArrayElement(array, i, newString); |
| env->DeleteLocalRef(newString); |
| checkException(env); |
| } |
| env->DeleteLocalRef(stringClass); |
| return array; |
| } |
| |
| void WebViewCore::openFileChooser(PassRefPtr<WebCore::FileChooser> chooser) |
| { |
| JNIEnv* env = JSC::Bindings::getJNIEnv(); |
| AutoJObject javaObject = m_javaGlue->object(env); |
| if (!javaObject.get()) |
| return; |
| |
| if (!chooser) |
| return; |
| |
| WTF::String acceptType = chooser->acceptTypes(); |
| WTF::String capture; |
| |
| #if ENABLE(MEDIA_CAPTURE) |
| capture = chooser->capture(); |
| #endif |
| |
| jstring jAcceptType = wtfStringToJstring(env, acceptType, true); |
| jstring jCapture = wtfStringToJstring(env, capture, true); |
| jstring jName = (jstring) env->CallObjectMethod( |
| javaObject.get(), m_javaGlue->m_openFileChooser, jAcceptType, jCapture); |
| checkException(env); |
| env->DeleteLocalRef(jAcceptType); |
| env->DeleteLocalRef(jCapture); |
| |
| WTF::String wtfString = jstringToWtfString(env, jName); |
| env->DeleteLocalRef(jName); |
| |
| if (!wtfString.isEmpty()) |
| chooser->chooseFile(wtfString); |
| } |
| |
| 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) |
| { |
| ALOG_ASSERT(m_javaGlue->m_obj, "No java widget associated with this view!"); |
| |
| JNIEnv* env = JSC::Bindings::getJNIEnv(); |
| AutoJObject javaObject = m_javaGlue->object(env); |
| if (!javaObject.get()) |
| return; |
| |
| // If m_popupReply is not null, then we already have a list showing. |
| if (m_popupReply != 0) |
| return; |
| |
| // Create an array of java Strings for the drop down. |
| jobjectArray labelArray = makeLabelArray(env, labels, count); |
| |
| // Create an array determining whether each item is enabled. |
| jintArray enabledArray = env->NewIntArray(enabledCount); |
| checkException(env); |
| jint* ptrArray = env->GetIntArrayElements(enabledArray, 0); |
| checkException(env); |
| for (size_t i = 0; i < enabledCount; i++) { |
| ptrArray[i] = enabled[i]; |
| } |
| env->ReleaseIntArrayElements(enabledArray, ptrArray, 0); |
| checkException(env); |
| |
| if (multiple) { |
| // Pass up an array representing which items are selected. |
| jintArray selectedArray = env->NewIntArray(selectedCountOrSelection); |
| checkException(env); |
| jint* selArray = env->GetIntArrayElements(selectedArray, 0); |
| checkException(env); |
| for (size_t i = 0; i < selectedCountOrSelection; i++) { |
| selArray[i] = selected[i]; |
| } |
| env->ReleaseIntArrayElements(selectedArray, selArray, 0); |
| |
| env->CallVoidMethod(javaObject.get(), |
| m_javaGlue->m_requestListBox, labelArray, enabledArray, |
| selectedArray); |
| env->DeleteLocalRef(selectedArray); |
| } else { |
| // Pass up the single selection. |
| env->CallVoidMethod(javaObject.get(), |
| m_javaGlue->m_requestSingleListBox, labelArray, enabledArray, |
| selectedCountOrSelection); |
| } |
| |
| env->DeleteLocalRef(labelArray); |
| env->DeleteLocalRef(enabledArray); |
| checkException(env); |
| |
| Retain(reply); |
| m_popupReply = reply; |
| } |
| |
| bool WebViewCore::key(const PlatformKeyboardEvent& event) |
| { |
| WebCore::EventHandler* eventHandler; |
| WebCore::Node* focusNode = currentFocus(); |
| if (focusNode) { |
| WebCore::Frame* frame = focusNode->document()->frame(); |
| eventHandler = frame->eventHandler(); |
| VisibleSelection old = frame->selection()->selection(); |
| EditorClientAndroid* client = static_cast<EditorClientAndroid*>( |
| m_mainFrame->editor()->client()); |
| client->setUiGeneratedSelectionChange(true); |
| bool handled = eventHandler->keyEvent(event); |
| client->setUiGeneratedSelectionChange(false); |
| if (isContentEditable(focusNode)) { |
| // keyEvent will return true even if the contentEditable did not |
| // change its selection. In the case that it does not, we want to |
| // return false so that the key will be sent back to our navigation |
| // system. |
| handled |= frame->selection()->selection() != old; |
| } |
| return handled; |
| } else { |
| eventHandler = focusedFrame()->eventHandler(); |
| } |
| return eventHandler->keyEvent(event); |
| } |
| |
| bool WebViewCore::chromeCanTakeFocus(FocusDirection direction) |
| { |
| JNIEnv* env = JSC::Bindings::getJNIEnv(); |
| AutoJObject javaObject = m_javaGlue->object(env); |
| if (!javaObject.get()) |
| return false; |
| return env->CallBooleanMethod(javaObject.get(), m_javaGlue->m_chromeCanTakeFocus, direction); |
| } |
| |
| void WebViewCore::chromeTakeFocus(FocusDirection direction) |
| { |
| JNIEnv* env = JSC::Bindings::getJNIEnv(); |
| AutoJObject javaObject = m_javaGlue->object(env); |
| if (!javaObject.get()) |
| return; |
| env->CallVoidMethod(javaObject.get(), m_javaGlue->m_chromeTakeFocus, direction); |
| } |
| |
| void WebViewCore::setInitialFocus(const WebCore::PlatformKeyboardEvent& platformEvent) |
| { |
| Frame* frame = focusedFrame(); |
| Document* document = frame->document(); |
| if (document) |
| document->setFocusedNode(0); |
| FocusDirection direction; |
| switch (platformEvent.nativeVirtualKeyCode()) { |
| case AKEYCODE_DPAD_LEFT: |
| direction = FocusDirectionLeft; |
| break; |
| case AKEYCODE_DPAD_RIGHT: |
| direction = FocusDirectionRight; |
| break; |
| case AKEYCODE_DPAD_UP: |
| direction = FocusDirectionUp; |
| break; |
| default: |
| direction = FocusDirectionDown; |
| break; |
| } |
| RefPtr<KeyboardEvent> webkitEvent = KeyboardEvent::create(platformEvent, 0); |
| m_mainFrame->page()->focusController()->setInitialFocus(direction, |
| webkitEvent.get()); |
| } |
| |
| #if USE(ACCELERATED_COMPOSITING) |
| GraphicsLayerAndroid* WebViewCore::graphicsRootLayer() const |
| { |
| RenderView* contentRenderer = m_mainFrame->contentRenderer(); |
| if (!contentRenderer) |
| return 0; |
| return static_cast<GraphicsLayerAndroid*>( |
| contentRenderer->compositor()->rootPlatformLayer()); |
| } |
| #endif |
| |
| int WebViewCore::handleTouchEvent(int action, Vector<int>& ids, Vector<IntPoint>& points, int actionIndex, int metaState) |
| { |
| int flags = 0; |
| |
| #if USE(ACCELERATED_COMPOSITING) |
| GraphicsLayerAndroid* rootLayer = graphicsRootLayer(); |
| if (rootLayer) |
| rootLayer->pauseDisplay(true); |
| #endif |
| |
| #if ENABLE(TOUCH_EVENTS) // Android |
| #define MOTION_EVENT_ACTION_POINTER_DOWN 5 |
| #define MOTION_EVENT_ACTION_POINTER_UP 6 |
| |
| WebCore::TouchEventType type = WebCore::TouchStart; |
| WebCore::PlatformTouchPoint::State defaultTouchState; |
| Vector<WebCore::PlatformTouchPoint::State> touchStates(points.size()); |
| |
| switch (action) { |
| case 0: // MotionEvent.ACTION_DOWN |
| type = WebCore::TouchStart; |
| defaultTouchState = WebCore::PlatformTouchPoint::TouchPressed; |
| break; |
| case 1: // MotionEvent.ACTION_UP |
| type = WebCore::TouchEnd; |
| defaultTouchState = WebCore::PlatformTouchPoint::TouchReleased; |
| break; |
| case 2: // MotionEvent.ACTION_MOVE |
| type = WebCore::TouchMove; |
| defaultTouchState = WebCore::PlatformTouchPoint::TouchMoved; |
| break; |
| case 3: // MotionEvent.ACTION_CANCEL |
| type = WebCore::TouchCancel; |
| defaultTouchState = WebCore::PlatformTouchPoint::TouchCancelled; |
| break; |
| case 5: // MotionEvent.ACTION_POINTER_DOWN |
| type = WebCore::TouchStart; |
| defaultTouchState = WebCore::PlatformTouchPoint::TouchStationary; |
| break; |
| case 6: // MotionEvent.ACTION_POINTER_UP |
| type = WebCore::TouchEnd; |
| defaultTouchState = WebCore::PlatformTouchPoint::TouchStationary; |
| break; |
| default: |
| // We do not support other kinds of touch event inside WebCore |
| // at the moment. |
| ALOGW("Java passed a touch event type that we do not support in WebCore: %d", action); |
| return 0; |
| } |
| |
| for (int c = 0; c < static_cast<int>(points.size()); c++) { |
| points[c].setX(points[c].x() - m_scrollOffsetX); |
| points[c].setY(points[c].y() - m_scrollOffsetY); |
| |
| // Setting the touch state for each point. |
| // Note: actionIndex will be 0 for all actions that are not ACTION_POINTER_DOWN/UP. |
| if (action == MOTION_EVENT_ACTION_POINTER_DOWN && c == actionIndex) { |
| touchStates[c] = WebCore::PlatformTouchPoint::TouchPressed; |
| } else if (action == MOTION_EVENT_ACTION_POINTER_UP && c == actionIndex) { |
| touchStates[c] = WebCore::PlatformTouchPoint::TouchReleased; |
| } else { |
| touchStates[c] = defaultTouchState; |
| }; |
| } |
| |
| WebCore::PlatformTouchEvent te(ids, points, type, touchStates, metaState); |
| if (m_mainFrame->eventHandler()->handleTouchEvent(te)) |
| flags |= TOUCH_FLAG_PREVENT_DEFAULT; |
| if (te.hitTouchHandler()) |
| flags |= TOUCH_FLAG_HIT_HANDLER; |
| #endif |
| |
| #if USE(ACCELERATED_COMPOSITING) |
| if (rootLayer) |
| rootLayer->pauseDisplay(false); |
| #endif |
| return flags; |
| } |
| |
| bool WebViewCore::performMouseClick() |
| { |
| WebCore::PlatformMouseEvent mouseDown(m_mouseClickPos, m_mouseClickPos, WebCore::LeftButton, |
| WebCore::MouseEventPressed, 1, false, false, false, false, |
| WTF::currentTime()); |
| // ignore the return from as it will return true if the hit point can trigger selection change |
| m_mainFrame->eventHandler()->handleMousePressEvent(mouseDown); |
| WebCore::PlatformMouseEvent mouseUp(m_mouseClickPos, m_mouseClickPos, WebCore::LeftButton, |
| WebCore::MouseEventReleased, 1, false, false, false, false, |
| WTF::currentTime()); |
| bool handled = m_mainFrame->eventHandler()->handleMouseReleaseEvent(mouseUp); |
| |
| WebCore::Node* focusNode = currentFocus(); |
| initializeTextInput(focusNode, false); |
| return handled; |
| } |
| |
| // Check for the "x-webkit-soft-keyboard" attribute. If it is there and |
| // set to hidden, do not show the soft keyboard. Node passed as a parameter |
| // must not be null. |
| static bool shouldSuppressKeyboard(const WebCore::Node* node) { |
| ALOG_ASSERT(node, "node passed to shouldSuppressKeyboard cannot be null"); |
| const NamedNodeMap* attributes = node->attributes(); |
| if (!attributes) return false; |
| size_t length = attributes->length(); |
| for (size_t i = 0; i < length; i++) { |
| const Attribute* a = attributes->attributeItem(i); |
| if (a->localName() == "x-webkit-soft-keyboard" && a->value() == "hidden") |
| return true; |
| } |
| return false; |
| } |
| |
| WebViewCore::InputType WebViewCore::getInputType(Node* node) |
| { |
| WebCore::RenderObject* renderer = node->renderer(); |
| if (!renderer) |
| return WebViewCore::NONE; |
| if (renderer->isTextArea()) |
| return WebViewCore::TEXT_AREA; |
| |
| if (node->hasTagName(WebCore::HTMLNames::inputTag)) { |
| HTMLInputElement* htmlInput = static_cast<HTMLInputElement*>(node); |
| if (htmlInput->isPasswordField()) |
| return WebViewCore::PASSWORD; |
| if (htmlInput->isSearchField()) |
| return WebViewCore::SEARCH; |
| if (htmlInput->isEmailField()) |
| return WebViewCore::EMAIL; |
| if (htmlInput->isNumberField()) |
| return WebViewCore::NUMBER; |
| if (htmlInput->isTelephoneField()) |
| return WebViewCore::TELEPHONE; |
| if (htmlInput->isTextField()) |
| return WebViewCore::NORMAL_TEXT_FIELD; |
| } |
| |
| if (node->isContentEditable()) |
| return WebViewCore::TEXT_AREA; |
| |
| return WebViewCore::NONE; |
| } |
| |
| int WebViewCore::getMaxLength(Node* node) |
| { |
| int maxLength = -1; |
| if (node->hasTagName(WebCore::HTMLNames::inputTag)) { |
| HTMLInputElement* htmlInput = static_cast<HTMLInputElement*>(node); |
| maxLength = htmlInput->maxLength(); |
| } |
| return maxLength; |
| } |
| |
| String WebViewCore::getFieldName(Node* node) |
| { |
| String name; |
| if (node->hasTagName(WebCore::HTMLNames::inputTag)) { |
| HTMLInputElement* htmlInput = static_cast<HTMLInputElement*>(node); |
| name = htmlInput->name(); |
| } |
| return name; |
| } |
| |
| bool WebViewCore::isSpellCheckEnabled(Node* node) |
| { |
| bool isEnabled = true; |
| if (node->isElementNode()) { |
| WebCore::Element* element = static_cast<WebCore::Element*>(node); |
| isEnabled = element->isSpellCheckingEnabled(); |
| } |
| return isEnabled; |
| } |
| |
| bool WebViewCore::isAutoCompleteEnabled(Node* node) |
| { |
| bool isEnabled = false; |
| if (node->hasTagName(WebCore::HTMLNames::inputTag)) { |
| HTMLInputElement* htmlInput = static_cast<HTMLInputElement*>(node); |
| isEnabled = htmlInput->autoComplete(); |
| } |
| return isEnabled; |
| } |
| |
| WebCore::IntRect WebViewCore::absoluteClientRect(WebCore::Node* node, |
| LayerAndroid* layer) |
| { |
| IntRect clientRect; |
| if (node) { |
| RenderObject* render = node->renderer(); |
| if (render && render->isBox() && !render->isBody()) { |
| IntPoint offset = convertGlobalContentToFrameContent(IntPoint(), |
| node->document()->frame()); |
| WebViewCore::layerToAbsoluteOffset(layer, offset); |
| |
| RenderBox* renderBox = toRenderBox(render); |
| clientRect = renderBox->clientBoxRect(); |
| IntRect contentBox = renderBox->contentBoxRect(); |
| clientRect.setX(contentBox.x()); |
| clientRect.setWidth(contentBox.width()); |
| FloatPoint absPos = renderBox->localToAbsolute(FloatPoint()); |
| clientRect.move(absPos.x() - offset.x(), absPos.y() - offset.y()); |
| } |
| } |
| return clientRect; |
| } |
| |
| jobject WebViewCore::createTextFieldInitData(Node* node) |
| { |
| JNIEnv* env = JSC::Bindings::getJNIEnv(); |
| TextFieldInitDataGlue* classDef = m_textFieldInitDataGlue; |
| ScopedLocalRef<jclass> clazz(env, |
| env->FindClass("android/webkit/WebViewCore$TextFieldInitData")); |
| jobject initData = env->NewObject(clazz.get(), classDef->m_constructor); |
| env->SetIntField(initData, classDef->m_fieldPointer, |
| reinterpret_cast<int>(node)); |
| ScopedLocalRef<jstring> inputText(env, |
| wtfStringToJstring(env, getInputText(node), true)); |
| env->SetObjectField(initData, classDef->m_text, inputText.get()); |
| env->SetIntField(initData, classDef->m_type, getInputType(node)); |
| env->SetBooleanField(initData, classDef->m_isSpellCheckEnabled, |
| isSpellCheckEnabled(node)); |
| Document* document = node->document(); |
| PlatformKeyboardEvent tab(AKEYCODE_TAB, 0, 0, false, false, false, false); |
| PassRefPtr<KeyboardEvent> tabEvent = |
| KeyboardEvent::create(tab, document->defaultView()); |
| env->SetBooleanField(initData, classDef->m_isTextFieldNext, |
| isTextInput(document->nextFocusableNode(node, tabEvent.get()))); |
| env->SetBooleanField(initData, classDef->m_isTextFieldPrev, |
| isTextInput(document->previousFocusableNode(node, tabEvent.get()))); |
| env->SetBooleanField(initData, classDef->m_isAutoCompleteEnabled, |
| isAutoCompleteEnabled(node)); |
| ScopedLocalRef<jstring> fieldName(env, |
| wtfStringToJstring(env, getFieldName(node), false)); |
| env->SetObjectField(initData, classDef->m_name, fieldName.get()); |
| ScopedLocalRef<jstring> label(env, |
| wtfStringToJstring(env, requestLabel(document->frame(), node), false)); |
| env->SetObjectField(initData, classDef->m_label, label.get()); |
| env->SetIntField(initData, classDef->m_maxLength, getMaxLength(node)); |
| LayerAndroid* layer = 0; |
| int layerId = platformLayerIdFromNode(node, &layer); |
| IntRect bounds = absoluteClientRect(node, layer); |
| ScopedLocalRef<jobject> jbounds(env, intRectToRect(env, bounds)); |
| env->SetObjectField(initData, classDef->m_contentBounds, jbounds.get()); |
| env->SetIntField(initData, classDef->m_nodeLayerId, layerId); |
| IntRect contentRect; |
| RenderTextControl* rtc = toRenderTextControl(node); |
| if (rtc) { |
| contentRect.setWidth(rtc->scrollWidth()); |
| contentRect.setHeight(rtc->scrollHeight()); |
| contentRect.move(-rtc->scrollLeft(), -rtc->scrollTop()); |
| } |
| ScopedLocalRef<jobject> jcontentRect(env, intRectToRect(env, contentRect)); |
| env->SetObjectField(initData, classDef->m_clientRect, jcontentRect.get()); |
| return initData; |
| } |
| |
| void WebViewCore::initEditField(Node* node) |
| { |
| JNIEnv* env = JSC::Bindings::getJNIEnv(); |
| AutoJObject javaObject = m_javaGlue->object(env); |
| if (!javaObject.get()) |
| return; |
| m_textGeneration = 0; |
| int start = 0; |
| int end = 0; |
| getSelectionOffsets(node, start, end); |
| SelectText* selectText = createSelectText(focusedFrame()->selection()->selection()); |
| ScopedLocalRef<jobject> initData(env, createTextFieldInitData(node)); |
| env->CallVoidMethod(javaObject.get(), m_javaGlue->m_initEditField, |
| start, end, reinterpret_cast<int>(selectText), initData.get()); |
| checkException(env); |
| } |
| |
| void WebViewCore::popupReply(int index) |
| { |
| if (m_popupReply) { |
| m_popupReply->replyInt(index); |
| Release(m_popupReply); |
| m_popupReply = 0; |
| } |
| } |
| |
| void WebViewCore::popupReply(const int* array, int count) |
| { |
| if (m_popupReply) { |
| m_popupReply->replyIntArray(array, count); |
| Release(m_popupReply); |
| m_popupReply = 0; |
| } |
| } |
| |
| // This is a slightly modified Node::nextNodeConsideringAtomicNodes() with the |
| // extra constraint of limiting the search to inside a containing parent |
| WebCore::Node* nextNodeWithinParent(WebCore::Node* parent, WebCore::Node* start) |
| { |
| if (!isAtomicNode(start) && start->firstChild()) |
| return start->firstChild(); |
| if (start->nextSibling()) |
| return start->nextSibling(); |
| const Node *n = start; |
| while (n && !n->nextSibling()) { |
| n = n->parentNode(); |
| if (n == parent) |
| return 0; |
| } |
| if (n) |
| return n->nextSibling(); |
| return 0; |
| } |
| |
| void WebViewCore::initializeTextInput(WebCore::Node* node, bool fake) |
| { |
| if (node) { |
| if (isTextInput(node)) { |
| bool showKeyboard = true; |
| initEditField(node); |
| WebCore::RenderTextControl* rtc = toRenderTextControl(node); |
| if (rtc && node->hasTagName(HTMLNames::inputTag)) { |
| HTMLInputElement* inputElement = static_cast<HTMLInputElement*>(node); |
| bool ime = !shouldSuppressKeyboard(node) && !inputElement->readOnly(); |
| if (ime) { |
| #if ENABLE(WEB_AUTOFILL) |
| if (rtc->isTextField()) { |
| Page* page = node->document()->page(); |
| EditorClient* editorClient = page->editorClient(); |
| EditorClientAndroid* androidEditor = |
| static_cast<EditorClientAndroid*>(editorClient); |
| WebAutofill* autoFill = androidEditor->getAutofill(); |
| autoFill->formFieldFocused(inputElement); |
| } |
| #endif |
| } else |
| showKeyboard = false; |
| } |
| if (!fake) |
| requestKeyboard(showKeyboard); |
| } else if (!fake && !nodeIsPlugin(node)) { |
| // not a text entry field, put away the keyboard. |
| clearTextEntry(); |
| } |
| } else if (!fake) { |
| // There is no focusNode, so the keyboard is not needed. |
| clearTextEntry(); |
| } |
| } |
| |
| void WebViewCore::focusNodeChanged(WebCore::Node* newFocus) |
| { |
| JNIEnv* env = JSC::Bindings::getJNIEnv(); |
| AutoJObject javaObject = m_javaGlue->object(env); |
| if (!javaObject.get()) |
| return; |
| if (isTextInput(newFocus)) |
| initializeTextInput(newFocus, true); |
| HitTestResult focusHitResult; |
| focusHitResult.setInnerNode(newFocus); |
| focusHitResult.setInnerNonSharedNode(newFocus); |
| if (newFocus && newFocus->isLink() && newFocus->isElementNode()) { |
| focusHitResult.setURLElement(static_cast<Element*>(newFocus)); |
| if (newFocus->hasChildNodes() && !newFocus->hasTagName(HTMLNames::imgTag)) { |
| // Check to see if any of the children are images, and if so |
| // set them as the innerNode and innerNonSharedNode |
| // This will stop when it hits the first image. I'm not sure what |
| // should be done in the case of multiple images inside one anchor... |
| Node* nextNode = newFocus->firstChild(); |
| bool found = false; |
| while (nextNode) { |
| if (nextNode->hasTagName(HTMLNames::imgTag)) { |
| found = true; |
| break; |
| } |
| nextNode = nextNodeWithinParent(newFocus, nextNode); |
| } |
| if (found) { |
| focusHitResult.setInnerNode(nextNode); |
| focusHitResult.setInnerNonSharedNode(nextNode); |
| } |
| } |
| } |
| AndroidHitTestResult androidHitTest(this, focusHitResult); |
| jobject jHitTestObj = androidHitTest.createJavaObject(env); |
| env->CallVoidMethod(javaObject.get(), m_javaGlue->m_focusNodeChanged, |
| reinterpret_cast<int>(newFocus), jHitTestObj); |
| env->DeleteLocalRef(jHitTestObj); |
| } |
| |
| void WebViewCore::addMessageToConsole(const WTF::String& message, unsigned int lineNumber, const WTF::String& sourceID, int msgLevel) { |
| JNIEnv* env = JSC::Bindings::getJNIEnv(); |
| AutoJObject javaObject = m_javaGlue->object(env); |
| if (!javaObject.get()) |
| return; |
| jstring jMessageStr = wtfStringToJstring(env, message); |
| jstring jSourceIDStr = wtfStringToJstring(env, sourceID); |
| env->CallVoidMethod(javaObject.get(), |
| m_javaGlue->m_addMessageToConsole, jMessageStr, lineNumber, |
| jSourceIDStr, msgLevel); |
| env->DeleteLocalRef(jMessageStr); |
| env->DeleteLocalRef(jSourceIDStr); |
| checkException(env); |
| } |
| |
| void WebViewCore::jsAlert(const WTF::String& url, const WTF::String& text) |
| { |
| JNIEnv* env = JSC::Bindings::getJNIEnv(); |
| AutoJObject javaObject = m_javaGlue->object(env); |
| if (!javaObject.get()) |
| return; |
| jstring jInputStr = wtfStringToJstring(env, text); |
| jstring jUrlStr = wtfStringToJstring(env, url); |
| env->CallVoidMethod(javaObject.get(), m_javaGlue->m_jsAlert, jUrlStr, jInputStr); |
| env->DeleteLocalRef(jInputStr); |
| env->DeleteLocalRef(jUrlStr); |
| checkException(env); |
| } |
| |
| bool WebViewCore::exceededDatabaseQuota(const WTF::String& url, const WTF::String& databaseIdentifier, const unsigned long long currentQuota, unsigned long long estimatedSize) |
| { |
| #if ENABLE(DATABASE) |
| JNIEnv* env = JSC::Bindings::getJNIEnv(); |
| AutoJObject javaObject = m_javaGlue->object(env); |
| if (!javaObject.get()) |
| return false; |
| jstring jDatabaseIdentifierStr = wtfStringToJstring(env, databaseIdentifier); |
| jstring jUrlStr = wtfStringToJstring(env, url); |
| env->CallVoidMethod(javaObject.get(), |
| m_javaGlue->m_exceededDatabaseQuota, jUrlStr, |
| jDatabaseIdentifierStr, currentQuota, estimatedSize); |
| env->DeleteLocalRef(jDatabaseIdentifierStr); |
| env->DeleteLocalRef(jUrlStr); |
| checkException(env); |
| return true; |
| #endif |
| } |
| |
| /* |
| * TODO Note that this logic still needs to be cleaned up. Normally the |
| * information provided in this callback is used to resize the appcache. |
| * so we need to provide the current database size. However, webkit provides no |
| * way to reach this information. It can be calculated by fetching all the |
| * origins and their usage, however this is too expensize (requires one inner |
| * join operation for each origin). Rather, we simply return the maximum cache size, |
| * which should be equivalent to the current cache size. This is generally safe. |
| * However, setting the maximum database size to less than the current database size |
| * may cause a problem. In this case, ApplicationCacheStorage ("the owner of database") |
| * uses the updated value, while database internally ignores it and uses the current size |
| * as quota. This means the value we returned here won't reflect the actual database size. |
| */ |
| bool WebViewCore::reachedMaxAppCacheSize(const unsigned long long spaceNeeded) |
| { |
| #if ENABLE(OFFLINE_WEB_APPLICATIONS) |
| JNIEnv* env = JSC::Bindings::getJNIEnv(); |
| AutoJObject javaObject = m_javaGlue->object(env); |
| if (!javaObject.get()) |
| return false; |
| env->CallVoidMethod(javaObject.get(), m_javaGlue->m_reachedMaxAppCacheSize, spaceNeeded, WebCore::cacheStorage().maximumSize()); |
| checkException(env); |
| return true; |
| #endif |
| } |
| |
| void WebViewCore::populateVisitedLinks(WebCore::PageGroup* group) |
| { |
| JNIEnv* env = JSC::Bindings::getJNIEnv(); |
| AutoJObject javaObject = m_javaGlue->object(env); |
| if (!javaObject.get()) |
| return; |
| m_groupForVisitedLinks = group; |
| env->CallVoidMethod(javaObject.get(), m_javaGlue->m_populateVisitedLinks); |
| checkException(env); |
| } |
| |
| void WebViewCore::geolocationPermissionsShowPrompt(const WTF::String& origin) |
| { |
| JNIEnv* env = JSC::Bindings::getJNIEnv(); |
| AutoJObject javaObject = m_javaGlue->object(env); |
| if (!javaObject.get()) |
| return; |
| jstring originString = wtfStringToJstring(env, origin); |
| env->CallVoidMethod(javaObject.get(), |
| m_javaGlue->m_geolocationPermissionsShowPrompt, |
| originString); |
| env->DeleteLocalRef(originString); |
| checkException(env); |
| } |
| |
| void WebViewCore::geolocationPermissionsHidePrompt() |
| { |
| JNIEnv* env = JSC::Bindings::getJNIEnv(); |
| AutoJObject javaObject = m_javaGlue->object(env); |
| if (!javaObject.get()) |
| return; |
| env->CallVoidMethod(javaObject.get(), m_javaGlue->m_geolocationPermissionsHidePrompt); |
| checkException(env); |
| } |
| |
| jobject WebViewCore::getDeviceMotionService() |
| { |
| JNIEnv* env = JSC::Bindings::getJNIEnv(); |
| AutoJObject javaObject = m_javaGlue->object(env); |
| if (!javaObject.get()) |
| return 0; |
| jobject object = env->CallObjectMethod(javaObject.get(), m_javaGlue->m_getDeviceMotionService); |
| checkException(env); |
| return object; |
| } |
| |
| jobject WebViewCore::getDeviceOrientationService() |
| { |
| JNIEnv* env = JSC::Bindings::getJNIEnv(); |
| AutoJObject javaObject = m_javaGlue->object(env); |
| if (!javaObject.get()) |
| return 0; |
| jobject object = env->CallObjectMethod(javaObject.get(), m_javaGlue->m_getDeviceOrientationService); |
| checkException(env); |
| return object; |
| } |
| |
| bool WebViewCore::jsConfirm(const WTF::String& url, const WTF::String& text) |
| { |
| JNIEnv* env = JSC::Bindings::getJNIEnv(); |
| AutoJObject javaObject = m_javaGlue->object(env); |
| if (!javaObject.get()) |
| return false; |
| jstring jInputStr = wtfStringToJstring(env, text); |
| jstring jUrlStr = wtfStringToJstring(env, url); |
| jboolean result = env->CallBooleanMethod(javaObject.get(), m_javaGlue->m_jsConfirm, jUrlStr, jInputStr); |
| env->DeleteLocalRef(jInputStr); |
| env->DeleteLocalRef(jUrlStr); |
| checkException(env); |
| return result; |
| } |
| |
| bool WebViewCore::jsPrompt(const WTF::String& url, const WTF::String& text, const WTF::String& defaultValue, WTF::String& result) |
| { |
| JNIEnv* env = JSC::Bindings::getJNIEnv(); |
| AutoJObject javaObject = m_javaGlue->object(env); |
| if (!javaObject.get()) |
| return false; |
| jstring jUrlStr = wtfStringToJstring(env, url); |
| jstring jInputStr = wtfStringToJstring(env, text); |
| jstring jDefaultStr = wtfStringToJstring(env, defaultValue); |
| jstring returnVal = static_cast<jstring>(env->CallObjectMethod(javaObject.get(), m_javaGlue->m_jsPrompt, jUrlStr, jInputStr, jDefaultStr)); |
| env->DeleteLocalRef(jUrlStr); |
| env->DeleteLocalRef(jInputStr); |
| env->DeleteLocalRef(jDefaultStr); |
| checkException(env); |
| |
| // If returnVal is null, it means that the user cancelled the dialog. |
| if (!returnVal) |
| return false; |
| |
| result = jstringToWtfString(env, returnVal); |
| env->DeleteLocalRef(returnVal); |
| return true; |
| } |
| |
| bool WebViewCore::jsUnload(const WTF::String& url, const WTF::String& message) |
| { |
| JNIEnv* env = JSC::Bindings::getJNIEnv(); |
| AutoJObject javaObject = m_javaGlue->object(env); |
| if (!javaObject.get()) |
| return false; |
| jstring jInputStr = wtfStringToJstring(env, message); |
| jstring jUrlStr = wtfStringToJstring(env, url); |
| jboolean result = env->CallBooleanMethod(javaObject.get(), m_javaGlue->m_jsUnload, jUrlStr, jInputStr); |
| env->DeleteLocalRef(jInputStr); |
| env->DeleteLocalRef(jUrlStr); |
| checkException(env); |
| return result; |
| } |
| |
| bool WebViewCore::jsInterrupt() |
| { |
| JNIEnv* env = JSC::Bindings::getJNIEnv(); |
| AutoJObject javaObject = m_javaGlue->object(env); |
| if (!javaObject.get()) |
| return false; |
| jboolean result = env->CallBooleanMethod(javaObject.get(), m_javaGlue->m_jsInterrupt); |
| checkException(env); |
| return result; |
| } |
| |
| AutoJObject |
| WebViewCore::getJavaObject() |
| { |
| return m_javaGlue->object(JSC::Bindings::getJNIEnv()); |
| } |
| |
| jobject |
| WebViewCore::getWebViewJavaObject() |
| { |
| JNIEnv* env = JSC::Bindings::getJNIEnv(); |
| AutoJObject javaObject = m_javaGlue->object(env); |
| if (!javaObject.get()) |
| return 0; |
| return env->CallObjectMethod(javaObject.get(), m_javaGlue->m_getWebView); |
| } |
| |
| RenderTextControl* WebViewCore::toRenderTextControl(Node* node) |
| { |
| RenderTextControl* rtc = 0; |
| RenderObject* renderer = node->renderer(); |
| if (renderer && renderer->isTextControl()) { |
| rtc = WebCore::toRenderTextControl(renderer); |
| } |
| return rtc; |
| } |
| |
| void WebViewCore::getSelectionOffsets(Node* node, int& start, int& end) |
| { |
| RenderTextControl* rtc = toRenderTextControl(node); |
| if (rtc) { |
| start = rtc->selectionStart(); |
| end = rtc->selectionEnd(); |
| } else { |
| // It must be content editable field. |
| Document* document = node->document(); |
| Frame* frame = document->frame(); |
| SelectionController* selector = frame->selection(); |
| Position selectionStart = selector->start(); |
| Position selectionEnd = selector->end(); |
| Position startOfNode = firstPositionInNode(node); |
| RefPtr<Range> startRange = Range::create(document, startOfNode, |
| selectionStart); |
| start = TextIterator::rangeLength(startRange.get(), true); |
| RefPtr<Range> endRange = Range::create(document, startOfNode, |
| selectionEnd); |
| end = TextIterator::rangeLength(endRange.get(), true); |
| } |
| } |
| |
| String WebViewCore::getInputText(Node* node) |
| { |
| String text; |
| WebCore::RenderTextControl* renderText = toRenderTextControl(node); |
| if (renderText) |
| text = renderText->text(); |
| else { |
| // It must be content editable field. |
| Position start = firstPositionInNode(node); |
| Position end = lastPositionInNode(node); |
| VisibleSelection allEditableText(start, end); |
| if (allEditableText.isRange()) |
| text = allEditableText.firstRange()->text(); |
| } |
| return text; |
| } |
| |
| void WebViewCore::updateTextSelection() |
| { |
| JNIEnv* env = JSC::Bindings::getJNIEnv(); |
| AutoJObject javaObject = m_javaGlue->object(env); |
| if (!javaObject.get()) |
| return; |
| VisibleSelection selection = focusedFrame()->selection()->selection(); |
| int start = 0; |
| int end = 0; |
| if (selection.isCaretOrRange()) |
| getSelectionOffsets(selection.start().anchorNode(), start, end); |
| SelectText* selectText = createSelectText(selection); |
| env->CallVoidMethod(javaObject.get(), |
| m_javaGlue->m_updateTextSelection, reinterpret_cast<int>(currentFocus()), |
| start, end, m_textGeneration, reinterpret_cast<int>(selectText)); |
| checkException(env); |
| } |
| |
| void WebViewCore::updateTextSizeAndScroll(WebCore::Node* node) |
| { |
| JNIEnv* env = JSC::Bindings::getJNIEnv(); |
| AutoJObject javaObject = m_javaGlue->object(env); |
| if (!javaObject.get()) |
| return; |
| RenderTextControl* rtc = toRenderTextControl(node); |
| if (!rtc) |
| return; |
| int width = rtc->scrollWidth(); |
| int height = rtc->contentHeight(); |
| int scrollX = rtc->scrollLeft(); |
| int scrollY = rtc->scrollTop(); |
| env->CallVoidMethod(javaObject.get(), m_javaGlue->m_updateTextSizeAndScroll, |
| reinterpret_cast<int>(node), width, height, scrollX, scrollY); |
| checkException(env); |
| } |
| |
| void WebViewCore::updateTextfield(WebCore::Node* ptr, const WTF::String& text) |
| { |
| JNIEnv* env = JSC::Bindings::getJNIEnv(); |
| AutoJObject javaObject = m_javaGlue->object(env); |
| if (!javaObject.get()) |
| return; |
| if (m_blockTextfieldUpdates) |
| return; |
| jstring string = wtfStringToJstring(env, text); |
| env->CallVoidMethod(javaObject.get(), m_javaGlue->m_updateTextfield, |
| (int) ptr, string, m_textGeneration); |
| env->DeleteLocalRef(string); |
| checkException(env); |
| } |
| |
| void WebViewCore::clearTextEntry() |
| { |
| JNIEnv* env = JSC::Bindings::getJNIEnv(); |
| AutoJObject javaObject = m_javaGlue->object(env); |
| if (!javaObject.get()) |
| return; |
| env->CallVoidMethod(javaObject.get(), m_javaGlue->m_clearTextEntry); |
| } |
| |
| void WebViewCore::setBackgroundColor(SkColor c) |
| { |
| WebCore::FrameView* view = m_mainFrame->view(); |
| if (!view) |
| return; |
| |
| // need (int) cast to find the right constructor |
| WebCore::Color bcolor((int)SkColorGetR(c), (int)SkColorGetG(c), |
| (int)SkColorGetB(c), (int)SkColorGetA(c)); |
| |
| if (view->baseBackgroundColor() == bcolor) |
| return; |
| |
| view->setBaseBackgroundColor(bcolor); |
| |
| // Background color of 0 indicates we want a transparent background |
| if (c == 0) |
| view->setTransparent(true); |
| |
| //invalidate so the new color is shown |
| contentInvalidateAll(); |
| } |
| |
| jclass WebViewCore::getPluginClass(const WTF::String& libName, const char* className) |
| { |
| JNIEnv* env = JSC::Bindings::getJNIEnv(); |
| AutoJObject javaObject = m_javaGlue->object(env); |
| if (!javaObject.get()) |
| return 0; |
| |
| jstring libString = wtfStringToJstring(env, libName); |
| jstring classString = env->NewStringUTF(className); |
| jobject pluginClass = env->CallObjectMethod(javaObject.get(), |
| m_javaGlue->m_getPluginClass, |
| libString, classString); |
| checkException(env); |
| |
| // cleanup unneeded local JNI references |
| env->DeleteLocalRef(libString); |
| env->DeleteLocalRef(classString); |
| |
| if (pluginClass != 0) { |
| return static_cast<jclass>(pluginClass); |
| } else { |
| return 0; |
| } |
| } |
| |
| void WebViewCore::showFullScreenPlugin(jobject childView, int32_t orientation, NPP npp) |
| { |
| JNIEnv* env = JSC::Bindings::getJNIEnv(); |
| AutoJObject javaObject = m_javaGlue->object(env); |
| if (!javaObject.get()) |
| return; |
| |
| env->CallVoidMethod(javaObject.get(), |
| m_javaGlue->m_showFullScreenPlugin, |
| childView, orientation, reinterpret_cast<int>(npp)); |
| checkException(env); |
| } |
| |
| void WebViewCore::hideFullScreenPlugin() |
| { |
| JNIEnv* env = JSC::Bindings::getJNIEnv(); |
| AutoJObject javaObject = m_javaGlue->object(env); |
| if (!javaObject.get()) |
| return; |
| env->CallVoidMethod(javaObject.get(), m_javaGlue->m_hideFullScreenPlugin); |
| checkException(env); |
| } |
| |
| jobject WebViewCore::createSurface(jobject view) |
| { |
| JNIEnv* env = JSC::Bindings::getJNIEnv(); |
| AutoJObject javaObject = m_javaGlue->object(env); |
| if (!javaObject.get()) |
| return 0; |
| jobject result = env->CallObjectMethod(javaObject.get(), m_javaGlue->m_createSurface, view); |
| checkException(env); |
| return result; |
| } |
| |
| jobject WebViewCore::addSurface(jobject view, int x, int y, int width, int height) |
| { |
| JNIEnv* env = JSC::Bindings::getJNIEnv(); |
| AutoJObject javaObject = m_javaGlue->object(env); |
| if (!javaObject.get()) |
| return 0; |
| jobject result = env->CallObjectMethod(javaObject.get(), |
| m_javaGlue->m_addSurface, |
| view, x, y, width, height); |
| checkException(env); |
| return result; |
| } |
| |
| void WebViewCore::updateSurface(jobject childView, int x, int y, int width, int height) |
| { |
| JNIEnv* env = JSC::Bindings::getJNIEnv(); |
| AutoJObject javaObject = m_javaGlue->object(env); |
| if (!javaObject.get()) |
| return; |
| env->CallVoidMethod(javaObject.get(), |
| m_javaGlue->m_updateSurface, childView, |
| x, y, width, height); |
| checkException(env); |
| } |
| |
| void WebViewCore::destroySurface(jobject childView) |
| { |
| JNIEnv* env = JSC::Bindings::getJNIEnv(); |
| AutoJObject javaObject = m_javaGlue->object(env); |
| if (!javaObject.get()) |
| return; |
| env->CallVoidMethod(javaObject.get(), m_javaGlue->m_destroySurface, childView); |
| checkException(env); |
| } |
| |
| jobject WebViewCore::getContext() |
| { |
| JNIEnv* env = JSC::Bindings::getJNIEnv(); |
| AutoJObject javaObject = m_javaGlue->object(env); |
| if (!javaObject.get()) |
| return 0; |
| |
| jobject result = env->CallObjectMethod(javaObject.get(), m_javaGlue->m_getContext); |
| checkException(env); |
| return result; |
| } |
| |
| void WebViewCore::keepScreenOn(bool screenOn) { |
| if ((screenOn && m_screenOnCounter == 0) || (!screenOn && m_screenOnCounter == 1)) { |
| JNIEnv* env = JSC::Bindings::getJNIEnv(); |
| AutoJObject javaObject = m_javaGlue->object(env); |
| if (!javaObject.get()) |
| return; |
| env->CallVoidMethod(javaObject.get(), m_javaGlue->m_keepScreenOn, screenOn); |
| checkException(env); |
| } |
| |
| // update the counter |
| if (screenOn) |
| m_screenOnCounter++; |
| else if (m_screenOnCounter > 0) |
| m_screenOnCounter--; |
| } |
| |
| void WebViewCore::showRect(int left, int top, int width, int height, |
| int contentWidth, int contentHeight, float xPercentInDoc, |
| float xPercentInView, float yPercentInDoc, float yPercentInView) |
| { |
| JNIEnv* env = JSC::Bindings::getJNIEnv(); |
| AutoJObject javaObject = m_javaGlue->object(env); |
| if (!javaObject.get()) |
| return; |
| env->CallVoidMethod(javaObject.get(), m_javaGlue->m_showRect, |
| left, top, width, height, contentWidth, contentHeight, |
| xPercentInDoc, xPercentInView, yPercentInDoc, yPercentInView); |
| checkException(env); |
| } |
| |
| void WebViewCore::centerFitRect(int x, int y, int width, int height) |
| { |
| JNIEnv* env = JSC::Bindings::getJNIEnv(); |
| AutoJObject javaObject = m_javaGlue->object(env); |
| if (!javaObject.get()) |
| return; |
| env->CallVoidMethod(javaObject.get(), m_javaGlue->m_centerFitRect, x, y, width, height); |
| checkException(env); |
| } |
| |
| void WebViewCore::setScrollbarModes(ScrollbarMode horizontalMode, ScrollbarMode verticalMode) |
| { |
| JNIEnv* env = JSC::Bindings::getJNIEnv(); |
| AutoJObject javaObject = m_javaGlue->object(env); |
| if (!javaObject.get()) |
| return; |
| env->CallVoidMethod(javaObject.get(), m_javaGlue->m_setScrollbarModes, horizontalMode, verticalMode); |
| checkException(env); |
| } |
| |
| #if ENABLE(VIDEO) |
| void WebViewCore::enterFullscreenForVideoLayer() |
| { |
| // Just need to update the video mode, to avoid multiple exit full screen. |
| m_fullscreenVideoMode = true; |
| } |
| |
| void WebViewCore::exitFullscreenVideo() |
| { |
| JNIEnv* env = JSC::Bindings::getJNIEnv(); |
| AutoJObject javaObject = m_javaGlue->object(env); |
| if (!javaObject.get()) |
| return; |
| if (m_fullscreenVideoMode) { |
| env->CallVoidMethod(javaObject.get(), m_javaGlue->m_exitFullscreenVideo); |
| m_fullscreenVideoMode = false; |
| } |
| checkException(env); |
| } |
| #endif |
| |
| void WebViewCore::setWebTextViewAutoFillable(int queryId, const string16& previewSummary) |
| { |
| #if ENABLE(WEB_AUTOFILL) |
| JNIEnv* env = JSC::Bindings::getJNIEnv(); |
| AutoJObject javaObject = m_javaGlue->object(env); |
| if (!javaObject.get()) |
| return; |
| jstring preview = env->NewString(previewSummary.data(), previewSummary.length()); |
| env->CallVoidMethod(javaObject.get(), m_javaGlue->m_setWebTextViewAutoFillable, queryId, preview); |
| env->DeleteLocalRef(preview); |
| #endif |
| } |
| |
| bool WebViewCore::drawIsPaused() const |
| { |
| // returning true says scrollview should be offscreen, which pauses |
| // gifs. because this is not again queried when we stop scrolling, we don't |
| // use the stopping currently. |
| return false; |
| } |
| |
| void WebViewCore::setWebRequestContextUserAgent() |
| { |
| // We cannot create a WebRequestContext, because we might not know it this is a private tab or not yet |
| if (m_webRequestContext) |
| m_webRequestContext->setUserAgent(WebFrame::getWebFrame(m_mainFrame)->userAgentForURL(0)); // URL not used |
| } |
| |
| void WebViewCore::setWebRequestContextCacheMode(int cacheMode) |
| { |
| m_cacheMode = cacheMode; |
| // We cannot create a WebRequestContext, because we might not know it this is a private tab or not yet |
| if (!m_webRequestContext) |
| return; |
| |
| m_webRequestContext->setCacheMode(cacheMode); |
| } |
| |
| WebRequestContext* WebViewCore::webRequestContext() |
| { |
| if (!m_webRequestContext) { |
| Settings* settings = mainFrame()->settings(); |
| m_webRequestContext = new WebRequestContext(settings && settings->privateBrowsingEnabled()); |
| setWebRequestContextUserAgent(); |
| setWebRequestContextCacheMode(m_cacheMode); |
| } |
| return m_webRequestContext.get(); |
| } |
| |
| void WebViewCore::scrollRenderLayer(int layer, const SkRect& rect) |
| { |
| #if USE(ACCELERATED_COMPOSITING) |
| GraphicsLayerAndroid* root = graphicsRootLayer(); |
| if (!root) |
| return; |
| |
| LayerAndroid* layerAndroid = root->platformLayer(); |
| if (!layerAndroid) |
| return; |
| |
| LayerAndroid* target = layerAndroid->findById(layer); |
| if (!target) |
| return; |
| |
| RenderLayer* owner = target->owningLayer(); |
| if (!owner) |
| return; |
| |
| if (owner->isRootLayer()) { |
| FrameView* view = owner->renderer()->frame()->view(); |
| IntPoint pt(rect.fLeft, rect.fTop); |
| view->setScrollPosition(pt); |
| } else |
| owner->scrollToOffset(rect.fLeft, rect.fTop); |
| #endif |
| } |
| |
| Vector<VisibleSelection> WebViewCore::getTextRanges( |
| int startX, int startY, int endX, int endY) |
| { |
| // These are the positions of the selection handles, |
| // which reside below the line that they are selecting. |
| // Use the vertical position higher, which will include |
| // the selected text. |
| startY--; |
| endY--; |
| VisiblePosition startSelect = visiblePositionForContentPoint(startX, startY); |
| VisiblePosition endSelect = visiblePositionForContentPoint(endX, endY); |
| Position start = startSelect.deepEquivalent(); |
| Position end = endSelect.deepEquivalent(); |
| Vector<VisibleSelection> ranges; |
| if (!start.isNull() && !end.isNull()) { |
| if (comparePositions(start, end) > 0) { |
| swap(start, end); // RTL start/end positions may be swapped |
| } |
| Position nextRangeStart = start; |
| Position previousRangeEnd; |
| do { |
| VisibleSelection selection(nextRangeStart, end); |
| ranges.append(selection); |
| previousRangeEnd = selection.end(); |
| nextRangeStart = nextCandidate(previousRangeEnd); |
| } while (comparePositions(previousRangeEnd, end) < 0); |
| } |
| return ranges; |
| } |
| |
| void WebViewCore::deleteText(int startX, int startY, int endX, int endY) |
| { |
| Vector<VisibleSelection> ranges = |
| getTextRanges(startX, startY, endX, endY); |
| |
| EditorClientAndroid* client = static_cast<EditorClientAndroid*>( |
| m_mainFrame->editor()->client()); |
| client->setUiGeneratedSelectionChange(true); |
| |
| SelectionController* selector = m_mainFrame->selection(); |
| for (size_t i = 0; i < ranges.size(); i++) { |
| const VisibleSelection& selection = ranges[i]; |
| if (selection.isContentEditable()) { |
| selector->setSelection(selection, CharacterGranularity); |
| Document* document = selection.start().anchorNode()->document(); |
| WebCore::TypingCommand::deleteSelection(document, 0); |
| } |
| } |
| client->setUiGeneratedSelectionChange(false); |
| } |
| |
| void WebViewCore::insertText(const WTF::String &text) |
| { |
| WebCore::Node* focus = currentFocus(); |
| if (!focus || !isTextInput(focus)) |
| return; |
| |
| Document* document = focus->document(); |
| |
| EditorClientAndroid* client = static_cast<EditorClientAndroid*>( |
| m_mainFrame->editor()->client()); |
| if (!client) |
| return; |
| client->setUiGeneratedSelectionChange(true); |
| WebCore::TypingCommand::insertText(document, text, |
| TypingCommand::PreventSpellChecking); |
| client->setUiGeneratedSelectionChange(false); |
| } |
| |
| void WebViewCore::resetFindOnPage() |
| { |
| m_searchText.truncate(0); |
| m_matchCount = 0; |
| m_activeMatchIndex = 0; |
| m_activeMatch = 0; |
| } |
| |
| int WebViewCore::findTextOnPage(const WTF::String &text) |
| { |
| resetFindOnPage(); // reset even if parameters are bad |
| |
| WebCore::Frame* frame = m_mainFrame; |
| if (!frame) |
| return 0; |
| |
| m_searchText = text; |
| FindOptions findOptions = WebCore::CaseInsensitive; |
| |
| do { |
| frame->document()->markers()->removeMarkers(DocumentMarker::TextMatch); |
| m_matchCount += frame->editor()->countMatchesForText(text, findOptions, |
| 0, true); |
| frame->editor()->setMarkedTextMatchesAreHighlighted(true); |
| frame = frame->tree()->traverseNextWithWrap(false); |
| } while (frame); |
| m_activeMatchIndex = m_matchCount - 1; // prime first findNext |
| return m_matchCount; |
| } |
| |
| int WebViewCore::findNextOnPage(bool forward) |
| { |
| if (!m_mainFrame) |
| return -1; |
| if (!m_matchCount) |
| return -1; |
| |
| EditorClientAndroid* client = static_cast<EditorClientAndroid*>( |
| m_mainFrame->editor()->client()); |
| client->setUiGeneratedSelectionChange(true); |
| |
| // Clear previous active match. |
| if (m_activeMatch) { |
| m_mainFrame->document()->markers()->setMarkersActive( |
| m_activeMatch.get(), false); |
| } |
| |
| FindOptions findOptions = WebCore::CaseInsensitive |
| | WebCore::StartInSelection | WebCore::WrapAround; |
| if (!forward) |
| findOptions |= WebCore::Backwards; |
| |
| // Start from the previous active match. |
| if (m_activeMatch) { |
| m_mainFrame->selection()->setSelection(m_activeMatch.get()); |
| } |
| |
| bool found = m_mainFrame->editor()->findString(m_searchText, findOptions); |
| if (found) { |
| VisibleSelection selection(m_mainFrame->selection()->selection()); |
| if (selection.isNone() || selection.start() == selection.end()) { |
| // Temporary workaround for findString() refusing to select text |
| // marked "-webkit-user-select: none". |
| m_activeMatchIndex = 0; |
| m_activeMatch = 0; |
| } else { |
| // Mark current match "active". |
| if (forward) { |
| ++m_activeMatchIndex; |
| if (m_activeMatchIndex == m_matchCount) |
| m_activeMatchIndex = 0; |
| } else { |
| if (m_activeMatchIndex == 0) |
| m_activeMatchIndex = m_matchCount; |
| --m_activeMatchIndex; |
| } |
| m_activeMatch = selection.firstRange(); |
| m_mainFrame->document()->markers()->setMarkersActive( |
| m_activeMatch.get(), true); |
| m_mainFrame->selection()->revealSelection( |
| ScrollAlignment::alignCenterIfNeeded, true); |
| } |
| } |
| |
| // Clear selection so it doesn't display. |
| m_mainFrame->selection()->clear(); |
| client->setUiGeneratedSelectionChange(false); |
| return m_activeMatchIndex; |
| } |
| |
| String WebViewCore::getText(int startX, int startY, int endX, int endY) |
| { |
| String text; |
| |
| Vector<VisibleSelection> ranges = |
| getTextRanges(startX, startY, endX, endY); |
| |
| for (size_t i = 0; i < ranges.size(); i++) { |
| const VisibleSelection& selection = ranges[i]; |
| if (selection.isRange()) { |
| PassRefPtr<Range> range = selection.firstRange(); |
| String textInRange = range->text(); |
| if (textInRange.length() > 0) { |
| if (text.length() > 0) |
| text.append('\n'); |
| text.append(textInRange); |
| } |
| } |
| } |
| |
| return text; |
| } |
| |
| /** |
| * Read the persistent locale. |
| */ |
| void WebViewCore::getLocale(String& language, String& region) |
| { |
| char propLang[PROPERTY_VALUE_MAX], propRegn[PROPERTY_VALUE_MAX]; |
| |
| property_get("persist.sys.language", propLang, ""); |
| property_get("persist.sys.country", propRegn, ""); |
| if (*propLang == 0 && *propRegn == 0) { |
| /* Set to ro properties, default is en_US */ |
| property_get("ro.product.locale.language", propLang, "en"); |
| property_get("ro.product.locale.region", propRegn, "US"); |
| } |
| language = String(propLang, 2); |
| region = String(propRegn, 2); |
| } |
| |
| // generate bcp47 identifier for the supplied language/region |
| static void toLanguageTag(char* output, size_t outSize, const String& language, |
| const String& region) { |
| if (output == NULL || outSize <= 0) |
| return; |
| String locale = language; |
| locale.append('_'); |
| locale.append(region); |
| char canonicalChars[ULOC_FULLNAME_CAPACITY]; |
| UErrorCode uErr = U_ZERO_ERROR; |
| uloc_canonicalize(locale.ascii().data(), canonicalChars, |
| ULOC_FULLNAME_CAPACITY, &uErr); |
| if (U_SUCCESS(uErr)) { |
| char likelyChars[ULOC_FULLNAME_CAPACITY]; |
| uErr = U_ZERO_ERROR; |
| uloc_addLikelySubtags(canonicalChars, likelyChars, |
| ULOC_FULLNAME_CAPACITY, &uErr); |
| if (U_SUCCESS(uErr)) { |
| uErr = U_ZERO_ERROR; |
| uloc_toLanguageTag(likelyChars, output, outSize, FALSE, &uErr); |
| if (U_SUCCESS(uErr)) { |
| return; |
| } else { |
| ALOGD("uloc_toLanguageTag(\"%s\") failed: %s", likelyChars, |
| u_errorName(uErr)); |
| } |
| } else { |
| ALOGD("uloc_addLikelySubtags(\"%s\") failed: %s", canonicalChars, |
| u_errorName(uErr)); |
| } |
| } else { |
| ALOGD("uloc_canonicalize(\"%s\") failed: %s", locale.ascii().data(), |
| u_errorName(uErr)); |
| } |
| // unable to build a proper language identifier |
| output[0] = '\0'; |
| } |
| |
| void WebViewCore::updateLocale() |
| { |
| static String prevLang; |
| static String prevRegn; |
| String language; |
| String region; |
| |
| getLocale(language, region); |
| |
| if ((language != prevLang) || (region != prevRegn)) { |
| prevLang = language; |
| prevRegn = region; |
| GlyphPageTreeNode::resetRoots(); |
| fontCache()->invalidate(); |
| char langTag[ULOC_FULLNAME_CAPACITY]; |
| toLanguageTag(langTag, ULOC_FULLNAME_CAPACITY, language, region); |
| FontPlatformData::setDefaultLanguage(langTag); |
| } |
| } |
| |
| //---------------------------------------------------------------------- |
| // Native JNI methods |
| //---------------------------------------------------------------------- |
| static void RevealSelection(JNIEnv* env, jobject obj, jint nativeClass) |
| { |
| reinterpret_cast<WebViewCore*>(nativeClass)->revealSelection(); |
| } |
| |
| static jstring RequestLabel(JNIEnv* env, jobject obj, jint nativeClass, |
| int framePointer, int nodePointer) |
| { |
| WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); |
| return wtfStringToJstring(env, viewImpl->requestLabel( |
| (WebCore::Frame*) framePointer, (WebCore::Node*) nodePointer)); |
| } |
| |
| static void ClearContent(JNIEnv* env, jobject obj, jint nativeClass) |
| { |
| WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); |
| viewImpl->clearContent(); |
| } |
| |
| static void SetSize(JNIEnv* env, jobject obj, jint nativeClass, jint width, |
| jint height, jint textWrapWidth, jfloat scale, jint screenWidth, |
| jint screenHeight, jint anchorX, jint anchorY, jboolean ignoreHeight) |
| { |
| WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); |
| ALOGV("webviewcore::nativeSetSize(%u %u)\n viewImpl: %p", (unsigned)width, (unsigned)height, viewImpl); |
| ALOG_ASSERT(viewImpl, "viewImpl not set in nativeSetSize"); |
| viewImpl->setSizeScreenWidthAndScale(width, height, textWrapWidth, scale, |
| screenWidth, screenHeight, anchorX, anchorY, ignoreHeight); |
| } |
| |
| static void SetScrollOffset(JNIEnv* env, jobject obj, jint nativeClass, |
| jboolean sendScrollEvent, jint x, jint y) |
| { |
| WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); |
| ALOG_ASSERT(viewImpl, "need viewImpl"); |
| |
| viewImpl->setScrollOffset(sendScrollEvent, x, y); |
| } |
| |
| static void SetGlobalBounds(JNIEnv* env, jobject obj, jint nativeClass, |
| jint x, jint y, jint h, jint v) |
| { |
| WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); |
| ALOG_ASSERT(viewImpl, "need viewImpl"); |
| |
| viewImpl->setGlobalBounds(x, y, h, v); |
| } |
| |
| static jboolean Key(JNIEnv* env, jobject obj, jint nativeClass, jint keyCode, |
| jint unichar, jint repeatCount, jboolean isShift, jboolean isAlt, |
| jboolean isSym, jboolean isDown) |
| { |
| WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); |
| return viewImpl->key(PlatformKeyboardEvent(keyCode, |
| unichar, repeatCount, isDown, isShift, isAlt, isSym)); |
| } |
| |
| static void SetInitialFocus(JNIEnv* env, jobject obj, jint nativeClass, |
| jint keyDirection) |
| { |
| WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); |
| viewImpl->setInitialFocus(PlatformKeyboardEvent(keyDirection, |
| 0, 0, false, false, false, false)); |
| } |
| |
| static void ContentInvalidateAll(JNIEnv* env, jobject obj, jint nativeClass) |
| { |
| reinterpret_cast<WebViewCore*>(nativeClass)->contentInvalidateAll(); |
| } |
| |
| static void DeleteSelection(JNIEnv* env, jobject obj, jint nativeClass, |
| jint start, jint end, jint textGeneration) |
| { |
| WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); |
| viewImpl->deleteSelection(start, end, textGeneration); |
| } |
| |
| static void SetSelection(JNIEnv* env, jobject obj, jint nativeClass, |
| jint start, jint end) |
| { |
| WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); |
| viewImpl->setSelection(start, end); |
| } |
| |
| static jstring ModifySelection(JNIEnv* env, jobject obj, jint nativeClass, |
| jint direction, jint granularity) |
| { |
| WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); |
| String selectionString = viewImpl->modifySelection(direction, granularity); |
| return wtfStringToJstring(env, selectionString); |
| } |
| |
| static void ReplaceTextfieldText(JNIEnv* env, jobject obj, jint nativeClass, |
| jint oldStart, jint oldEnd, jstring replace, jint start, jint end, |
| jint textGeneration) |
| { |
| WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); |
| WTF::String webcoreString = jstringToWtfString(env, replace); |
| viewImpl->replaceTextfieldText(oldStart, |
| oldEnd, webcoreString, start, end, textGeneration); |
| } |
| |
| static void PassToJs(JNIEnv* env, jobject obj, jint nativeClass, |
| jint generation, jstring currentText, jint keyCode, |
| jint keyValue, jboolean down, jboolean cap, jboolean fn, jboolean sym) |
| { |
| WTF::String current = jstringToWtfString(env, currentText); |
| reinterpret_cast<WebViewCore*>(nativeClass)->passToJs(generation, current, |
| PlatformKeyboardEvent(keyCode, keyValue, 0, down, cap, fn, sym)); |
| } |
| |
| static void ScrollFocusedTextInput(JNIEnv* env, jobject obj, jint nativeClass, |
| jfloat xPercent, jint y) |
| { |
| WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); |
| viewImpl->scrollFocusedTextInput(xPercent, y); |
| } |
| |
| static void SetFocusControllerActive(JNIEnv* env, jobject obj, jint nativeClass, |
| jboolean active) |
| { |
| ALOGV("webviewcore::nativeSetFocusControllerActive()\n"); |
| WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); |
| ALOG_ASSERT(viewImpl, "viewImpl not set in nativeSetFocusControllerActive"); |
| viewImpl->setFocusControllerActive(active); |
| } |
| |
| static void SaveDocumentState(JNIEnv* env, jobject obj, jint nativeClass) |
| { |
| ALOGV("webviewcore::nativeSaveDocumentState()\n"); |
| WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); |
| ALOG_ASSERT(viewImpl, "viewImpl not set in nativeSaveDocumentState"); |
| viewImpl->saveDocumentState(viewImpl->focusedFrame()); |
| } |
| |
| void WebViewCore::addVisitedLink(const UChar* string, int length) |
| { |
| if (m_groupForVisitedLinks) |
| m_groupForVisitedLinks->addVisitedLink(string, length); |
| } |
| |
| static void NotifyAnimationStarted(JNIEnv* env, jobject obj, jint nativeClass) |
| { |
| WebViewCore* viewImpl = (WebViewCore*) nativeClass; |
| viewImpl->notifyAnimationStarted(); |
| } |
| |
| static jint RecordContent(JNIEnv* env, jobject obj, jint nativeClass, jobject pt) |
| { |
| WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); |
| SkIPoint nativePt; |
| BaseLayerAndroid* result = viewImpl->recordContent(&nativePt); |
| GraphicsJNI::ipoint_to_jpoint(nativePt, env, pt); |
| return reinterpret_cast<jint>(result); |
| } |
| |
| static void SendListBoxChoice(JNIEnv* env, jobject obj, jint nativeClass, |
| jint choice) |
| { |
| WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); |
| ALOG_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, jint nativeClass, |
| jbooleanArray jArray, jint size) |
| { |
| WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); |
| ALOG_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); |
| } |
| |
| // TODO: Move this to WebView.cpp since it is only needed there |
| static jstring FindAddress(JNIEnv* env, jobject obj, jstring addr, |
| jboolean caseInsensitive) |
| { |
| if (!addr) |
| return 0; |
| int length = env->GetStringLength(addr); |
| if (!length) |
| return 0; |
| const jchar* addrChars = env->GetStringChars(addr, 0); |
| size_t start, end; |
| AddressDetector detector; |
| bool success = detector.FindContent(addrChars, addrChars + length, &start, &end); |
| jstring ret = 0; |
| if (success) |
| ret = env->NewString(addrChars + start, end - start); |
| env->ReleaseStringChars(addr, addrChars); |
| return ret; |
| } |
| |
| static jint HandleTouchEvent(JNIEnv* env, jobject obj, jint nativeClass, |
| jint action, jintArray idArray, jintArray xArray, jintArray yArray, |
| jint count, jint actionIndex, jint metaState) |
| { |
| WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); |
| ALOG_ASSERT(viewImpl, "viewImpl not set in %s", __FUNCTION__); |
| jint* ptrIdArray = env->GetIntArrayElements(idArray, 0); |
| jint* ptrXArray = env->GetIntArrayElements(xArray, 0); |
| jint* ptrYArray = env->GetIntArrayElements(yArray, 0); |
| Vector<int> ids(count); |
| Vector<IntPoint> points(count); |
| for (int c = 0; c < count; c++) { |
| ids[c] = ptrIdArray[c]; |
| points[c].setX(ptrXArray[c]); |
| points[c].setY(ptrYArray[c]); |
| } |
| env->ReleaseIntArrayElements(idArray, ptrIdArray, JNI_ABORT); |
| env->ReleaseIntArrayElements(xArray, ptrXArray, JNI_ABORT); |
| env->ReleaseIntArrayElements(yArray, ptrYArray, JNI_ABORT); |
| |
| return viewImpl->handleTouchEvent(action, ids, points, actionIndex, metaState); |
| } |
| |
| static bool MouseClick(JNIEnv* env, jobject obj, jint nativeClass) |
| { |
| WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); |
| return viewImpl->performMouseClick(); |
| } |
| |
| static jstring RetrieveHref(JNIEnv* env, jobject obj, jint nativeClass, |
| jint x, jint y) |
| { |
| WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); |
| ALOG_ASSERT(viewImpl, "viewImpl not set in %s", __FUNCTION__); |
| WTF::String result = viewImpl->retrieveHref(x, y); |
| if (!result.isEmpty()) |
| return wtfStringToJstring(env, result); |
| return 0; |
| } |
| |
| static jstring RetrieveAnchorText(JNIEnv* env, jobject obj, jint nativeClass, |
| jint x, jint y) |
| { |
| WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); |
| ALOG_ASSERT(viewImpl, "viewImpl not set in %s", __FUNCTION__); |
| WTF::String result = viewImpl->retrieveAnchorText(x, y); |
| if (!result.isEmpty()) |
| return wtfStringToJstring(env, result); |
| return 0; |
| } |
| |
| static jstring RetrieveImageSource(JNIEnv* env, jobject obj, jint nativeClass, |
| jint x, jint y) |
| { |
| WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); |
| WTF::String result = viewImpl->retrieveImageSource(x, y); |
| return !result.isEmpty() ? wtfStringToJstring(env, result) : 0; |
| } |
| |
| static void MoveMouse(JNIEnv* env, jobject obj, jint nativeClass, jint x, jint y) |
| { |
| WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); |
| ALOG_ASSERT(viewImpl, "viewImpl not set in %s", __FUNCTION__); |
| viewImpl->moveMouse(x, y); |
| } |
| |
| static jint GetContentMinPrefWidth(JNIEnv* env, jobject obj, jint nativeClass) |
| { |
| WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); |
| ALOG_ASSERT(viewImpl, "viewImpl not set in %s", __FUNCTION__); |
| |
| WebCore::Frame* frame = viewImpl->mainFrame(); |
| if (frame) { |
| WebCore::Document* document = frame->document(); |
| if (document) { |
| WebCore::RenderObject* renderer = document->renderer(); |
| if (renderer && renderer->isRenderView()) { |
| return renderer->minPreferredLogicalWidth(); |
| } |
| } |
| } |
| return 0; |
| } |
| |
| static void SetViewportSettingsFromNative(JNIEnv* env, jobject obj, |
| jint nativeClass) |
| { |
| WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); |
| ALOG_ASSERT(viewImpl, "viewImpl not set in %s", __FUNCTION__); |
| |
| WebCore::Settings* s = viewImpl->mainFrame()->page()->settings(); |
| if (!s) |
| return; |
| |
| #ifdef ANDROID_META_SUPPORT |
| env->SetIntField(obj, gWebViewCoreFields.m_viewportWidth, s->viewportWidth()); |
| env->SetIntField(obj, gWebViewCoreFields.m_viewportHeight, s->viewportHeight()); |
| env->SetIntField(obj, gWebViewCoreFields.m_viewportInitialScale, s->viewportInitialScale()); |
| env->SetIntField(obj, gWebViewCoreFields.m_viewportMinimumScale, s->viewportMinimumScale()); |
| env->SetIntField(obj, gWebViewCoreFields.m_viewportMaximumScale, s->viewportMaximumScale()); |
| env->SetBooleanField(obj, gWebViewCoreFields.m_viewportUserScalable, s->viewportUserScalable()); |
| env->SetIntField(obj, gWebViewCoreFields.m_viewportDensityDpi, s->viewportTargetDensityDpi()); |
| #endif |
| } |
| |
| static void SetBackgroundColor(JNIEnv* env, jobject obj, jint nativeClass, |
| jint color) |
| { |
| WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); |
| ALOG_ASSERT(viewImpl, "viewImpl not set in %s", __FUNCTION__); |
| |
| viewImpl->setBackgroundColor((SkColor) color); |
| } |
| |
| static void DumpDomTree(JNIEnv* env, jobject obj, jint nativeClass, |
| jboolean useFile) |
| { |
| WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); |
| ALOG_ASSERT(viewImpl, "viewImpl not set in %s", __FUNCTION__); |
| |
| viewImpl->dumpDomTree(useFile); |
| } |
| |
| static void DumpRenderTree(JNIEnv* env, jobject obj, jint nativeClass, |
| jboolean useFile) |
| { |
| WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); |
| ALOG_ASSERT(viewImpl, "viewImpl not set in %s", __FUNCTION__); |
| |
| viewImpl->dumpRenderTree(useFile); |
| } |
| |
| static void SetJsFlags(JNIEnv* env, jobject obj, jint nativeClass, jstring flags) |
| { |
| WTF::String flagsString = jstringToWtfString(env, flags); |
| WTF::CString utf8String = flagsString.utf8(); |
| WebCore::ScriptController::setFlags(utf8String.data(), utf8String.length()); |
| } |
| |
| |
| // Called from the Java side to set a new quota for the origin or new appcache |
| // max size in response to a notification that the original quota was exceeded or |
| // that the appcache has reached its maximum size. |
| static void SetNewStorageLimit(JNIEnv* env, jobject obj, jint nativeClass, |
| jlong quota) |
| { |
| #if ENABLE(DATABASE) || ENABLE(OFFLINE_WEB_APPLICATIONS) |
| WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); |
| Frame* frame = viewImpl->mainFrame(); |
| |
| // The main thread is blocked awaiting this response, so now we can wake it |
| // up. |
| ChromeClientAndroid* chromeC = static_cast<ChromeClientAndroid*>(frame->page()->chrome()->client()); |
| chromeC->wakeUpMainThreadWithNewQuota(quota); |
| #endif |
| } |
| |
| // Called from Java to provide a Geolocation permission state for the specified origin. |
| static void GeolocationPermissionsProvide(JNIEnv* env, jobject obj, |
| jint nativeClass, jstring origin, jboolean allow, jboolean remember) |
| { |
| WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); |
| viewImpl->geolocationManager()->provideRealClientPermissionState(jstringToWtfString(env, origin), allow, remember); |
| } |
| |
| static void RegisterURLSchemeAsLocal(JNIEnv* env, jobject obj, jint nativeClass, |
| jstring scheme) |
| { |
| WebCore::SchemeRegistry::registerURLSchemeAsLocal(jstringToWtfString(env, scheme)); |
| } |
| |
| static void Pause(JNIEnv* env, jobject obj, jint nativeClass) |
| { |
| // This is called for the foreground tab when the browser is put to the |
| // background (and also for any tab when it is put to the background of the |
| // browser). The browser can only be killed by the system when it is in the |
| // background, so saving the Geolocation permission state now ensures that |
| // is maintained when the browser is killed. |
| GeolocationPermissions::maybeStorePermanentPermissions(); |
| |
| WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); |
| Frame* mainFrame = viewImpl->mainFrame(); |
| if (mainFrame) |
| mainFrame->settings()->setMinDOMTimerInterval(BACKGROUND_TIMER_INTERVAL); |
| |
| viewImpl->deviceMotionAndOrientationManager()->maybeSuspendClients(); |
| viewImpl->geolocationManager()->suspendRealClient(); |
| |
| ANPEvent event; |
| SkANP::InitEvent(&event, kLifecycle_ANPEventType); |
| event.data.lifecycle.action = kPause_ANPLifecycleAction; |
| viewImpl->sendPluginEvent(event); |
| } |
| |
| static void Resume(JNIEnv* env, jobject obj, jint nativeClass) |
| { |
| WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); |
| Frame* mainFrame = viewImpl->mainFrame(); |
| if (mainFrame) |
| mainFrame->settings()->setMinDOMTimerInterval(FOREGROUND_TIMER_INTERVAL); |
| |
| viewImpl->deviceMotionAndOrientationManager()->maybeResumeClients(); |
| viewImpl->geolocationManager()->resumeRealClient(); |
| |
| ANPEvent event; |
| SkANP::InitEvent(&event, kLifecycle_ANPEventType); |
| event.data.lifecycle.action = kResume_ANPLifecycleAction; |
| viewImpl->sendPluginEvent(event); |
| } |
| |
| static void FreeMemory(JNIEnv* env, jobject obj, jint nativeClass) |
| { |
| ANPEvent event; |
| SkANP::InitEvent(&event, kLifecycle_ANPEventType); |
| event.data.lifecycle.action = kFreeMemory_ANPLifecycleAction; |
| reinterpret_cast<WebViewCore*>(nativeClass)->sendPluginEvent(event); |
| } |
| |
| static void ProvideVisitedHistory(JNIEnv* env, jobject obj, jint nativeClass, |
| jobject hist) |
| { |
| WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); |
| ALOG_ASSERT(viewImpl, "viewImpl not set in %s", __FUNCTION__); |
| |
| jobjectArray array = static_cast<jobjectArray>(hist); |
| |
| jsize len = env->GetArrayLength(array); |
| for (jsize i = 0; i < len; i++) { |
| jstring item = static_cast<jstring>(env->GetObjectArrayElement(array, i)); |
| const UChar* str = static_cast<const UChar*>(env->GetStringChars(item, 0)); |
| jsize len = env->GetStringLength(item); |
| viewImpl->addVisitedLink(str, len); |
| env->ReleaseStringChars(item, str); |
| env->DeleteLocalRef(item); |
| } |
| } |
| |
| static void PluginSurfaceReady(JNIEnv* env, jobject obj, jint nativeClass) |
| { |
| WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); |
| if (viewImpl) |
| viewImpl->sendPluginSurfaceReady(); |
| } |
| |
| // Notification from the UI thread that the plugin's full-screen surface has been discarded |
| static void FullScreenPluginHidden(JNIEnv* env, jobject obj, jint nativeClass, |
| jint npp) |
| { |
| WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); |
| PluginWidgetAndroid* plugin = viewImpl->getPluginWidget((NPP)npp); |
| if (plugin) |
| plugin->exitFullScreen(false); |
| } |
| |
| static jobject HitTest(JNIEnv* env, jobject obj, jint nativeClass, jint x, |
| jint y, jint slop, jboolean doMoveMouse) |
| { |
| WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); |
| if (!viewImpl) |
| return 0; |
| AndroidHitTestResult result = viewImpl->hitTestAtPoint(x, y, slop, doMoveMouse); |
| return result.createJavaObject(env); |
| } |
| |
| static void AutoFillForm(JNIEnv* env, jobject obj, jint nativeClass, |
| jint queryId) |
| { |
| #if ENABLE(WEB_AUTOFILL) |
| WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); |
| if (!viewImpl) |
| return; |
| |
| WebCore::Frame* frame = viewImpl->mainFrame(); |
| if (frame) { |
| EditorClientAndroid* editorC = static_cast<EditorClientAndroid*>(frame->page()->editorClient()); |
| WebAutofill* autoFill = editorC->getAutofill(); |
| autoFill->fillFormFields(queryId); |
| } |
| #endif |
| } |
| |
| static void CloseIdleConnections(JNIEnv* env, jobject obj, jint nativeClass) |
| { |
| WebCache::get(true)->closeIdleConnections(); |
| WebCache::get(false)->closeIdleConnections(); |
| } |
| |
| static void nativeCertTrustChanged(JNIEnv *env, jobject obj) |
| { |
| WebCache::get(true)->certTrustChanged(); |
| WebCache::get(false)->certTrustChanged(); |
| } |
| |
| static void ScrollRenderLayer(JNIEnv* env, jobject obj, jint nativeClass, |
| jint layer, jobject jRect) |
| { |
| SkRect rect; |
| GraphicsJNI::jrect_to_rect(env, jRect, &rect); |
| reinterpret_cast<WebViewCore*>(nativeClass)->scrollRenderLayer(layer, rect); |
| } |
| |
| static void DeleteText(JNIEnv* env, jobject obj, jint nativeClass, |
| jint startX, jint startY, jint endX, jint endY) |
| { |
| WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); |
| viewImpl->deleteText(startX, startY, endX, endY); |
| } |
| |
| static void InsertText(JNIEnv* env, jobject obj, jint nativeClass, |
| jstring text) |
| { |
| WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); |
| WTF::String wtfText = jstringToWtfString(env, text); |
| viewImpl->insertText(wtfText); |
| } |
| |
| static jobject GetText(JNIEnv* env, jobject obj, jint nativeClass, |
| jint startX, jint startY, jint endX, jint endY) |
| { |
| WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); |
| WTF::String text = viewImpl->getText(startX, startY, endX, endY); |
| return text.isEmpty() ? 0 : wtfStringToJstring(env, text); |
| } |
| |
| static void SelectText(JNIEnv* env, jobject obj, jint nativeClass, |
| jint handleId, jint x, jint y) |
| { |
| WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); |
| viewImpl->selectText(static_cast<SelectText::HandleId>(handleId), x, y); |
| } |
| |
| static void ClearSelection(JNIEnv* env, jobject obj, jint nativeClass) |
| { |
| WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); |
| viewImpl->focusedFrame()->selection()->clear(); |
| } |
| |
| static bool SelectWordAt(JNIEnv* env, jobject obj, jint nativeClass, jint x, jint y) |
| { |
| WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); |
| return viewImpl->selectWordAt(x, y); |
| } |
| |
| static void SelectAll(JNIEnv* env, jobject obj, jint nativeClass) |
| { |
| WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); |
| viewImpl->focusedFrame()->selection()->selectAll(); |
| } |
| |
| static int FindAll(JNIEnv* env, jobject obj, jint nativeClass, |
| jstring text) |
| { |
| WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); |
| WTF::String wtfText = jstringToWtfString(env, text); |
| return viewImpl->findTextOnPage(wtfText); |
| } |
| |
| static int FindNext(JNIEnv* env, jobject obj, jint nativeClass, |
| jboolean forward) |
| { |
| WebViewCore* viewImpl = reinterpret_cast<WebViewCore*>(nativeClass); |
| return viewImpl->findNextOnPage(forward); |
| } |
| |
| // ---------------------------------------------------------------------------- |
| |
| /* |
| * JNI registration. |
| */ |
| static JNINativeMethod gJavaWebViewCoreMethods[] = { |
| { "nativeClearContent", "(I)V", |
| (void*) ClearContent }, |
| { "nativeKey", "(IIIIZZZZ)Z", |
| (void*) Key }, |
| { "nativeContentInvalidateAll", "(I)V", |
| (void*) ContentInvalidateAll }, |
| { "nativeSendListBoxChoices", "(I[ZI)V", |
| (void*) SendListBoxChoices }, |
| { "nativeSendListBoxChoice", "(II)V", |
| (void*) SendListBoxChoice }, |
| { "nativeSetSize", "(IIIIFIIIIZ)V", |
| (void*) SetSize }, |
| { "nativeSetScrollOffset", "(IZII)V", |
| (void*) SetScrollOffset }, |
| { "nativeSetGlobalBounds", "(IIIII)V", |
| (void*) SetGlobalBounds }, |
| { "nativeSetSelection", "(III)V", |
| (void*) SetSelection } , |
| { "nativeModifySelection", "(III)Ljava/lang/String;", |
| (void*) ModifySelection }, |
| { "nativeDeleteSelection", "(IIII)V", |
| (void*) DeleteSelection } , |
| { "nativeReplaceTextfieldText", "(IIILjava/lang/String;III)V", |
| (void*) ReplaceTextfieldText } , |
| { "nativeMoveMouse", "(III)V", |
| (void*) MoveMouse }, |
| { "passToJs", "(IILjava/lang/String;IIZZZZ)V", |
| (void*) PassToJs }, |
| { "nativeScrollFocusedTextInput", "(IFI)V", |
| (void*) ScrollFocusedTextInput }, |
| { "nativeSetFocusControllerActive", "(IZ)V", |
| (void*) SetFocusControllerActive }, |
| { "nativeSaveDocumentState", "(I)V", |
| (void*) SaveDocumentState }, |
| { "nativeFindAddress", "(Ljava/lang/String;Z)Ljava/lang/String;", |
| (void*) FindAddress }, |
| { "nativeHandleTouchEvent", "(II[I[I[IIII)I", |
| (void*) HandleTouchEvent }, |
| { "nativeMouseClick", "(I)Z", |
| (void*) MouseClick }, |
| { "nativeRetrieveHref", "(III)Ljava/lang/String;", |
| (void*) RetrieveHref }, |
| { "nativeRetrieveAnchorText", "(III)Ljava/lang/String;", |
| (void*) RetrieveAnchorText }, |
| { "nativeRetrieveImageSource", "(III)Ljava/lang/String;", |
| (void*) RetrieveImageSource }, |
| { "nativeGetContentMinPrefWidth", "(I)I", |
| (void*) GetContentMinPrefWidth }, |
| { "nativeNotifyAnimationStarted", "(I)V", |
| (void*) NotifyAnimationStarted }, |
| { "nativeRecordContent", "(ILandroid/graphics/Point;)I", |
| (void*) RecordContent }, |
| { "setViewportSettingsFromNative", "(I)V", |
| (void*) SetViewportSettingsFromNative }, |
| { "nativeSetBackgroundColor", "(II)V", |
| (void*) SetBackgroundColor }, |
| { "nativeRegisterURLSchemeAsLocal", "(ILjava/lang/String;)V", |
| (void*) RegisterURLSchemeAsLocal }, |
| { "nativeDumpDomTree", "(IZ)V", |
| (void*) DumpDomTree }, |
| { "nativeDumpRenderTree", "(IZ)V", |
| (void*) DumpRenderTree }, |
| { "nativeSetNewStorageLimit", "(IJ)V", |
| (void*) SetNewStorageLimit }, |
| { "nativeGeolocationPermissionsProvide", "(ILjava/lang/String;ZZ)V", |
| (void*) GeolocationPermissionsProvide }, |
| { "nativePause", "(I)V", (void*) Pause }, |
| { "nativeResume", "(I)V", (void*) Resume }, |
| { "nativeFreeMemory", "(I)V", (void*) FreeMemory }, |
| { "nativeSetJsFlags", "(ILjava/lang/String;)V", (void*) SetJsFlags }, |
| { "nativeRequestLabel", "(III)Ljava/lang/String;", |
| (void*) RequestLabel }, |
| { "nativeRevealSelection", "(I)V", (void*) RevealSelection }, |
| { "nativeProvideVisitedHistory", "(I[Ljava/lang/String;)V", |
| (void*) ProvideVisitedHistory }, |
| { "nativeFullScreenPluginHidden", "(II)V", |
| (void*) FullScreenPluginHidden }, |
| { "nativePluginSurfaceReady", "(I)V", |
| (void*) PluginSurfaceReady }, |
| { "nativeHitTest", "(IIIIZ)Landroid/webkit/WebViewCore$WebKitHitTest;", |
| (void*) HitTest }, |
| { "nativeAutoFillForm", "(II)V", |
| (void*) AutoFillForm }, |
| { "nativeScrollLayer", "(IILandroid/graphics/Rect;)V", |
| (void*) ScrollRenderLayer }, |
| { "nativeCloseIdleConnections", "(I)V", |
| (void*) CloseIdleConnections }, |
| { "nativeDeleteText", "(IIIII)V", |
| (void*) DeleteText }, |
| { "nativeInsertText", "(ILjava/lang/String;)V", |
| (void*) InsertText }, |
| { "nativeGetText", "(IIIII)Ljava/lang/String;", |
| (void*) GetText }, |
| { "nativeSelectText", "(IIII)V", |
| (void*) SelectText }, |
| { "nativeClearTextSelection", "(I)V", |
| (void*) ClearSelection }, |
| { "nativeSelectWordAt", "(III)Z", |
| (void*) SelectWordAt }, |
| { "nativeSelectAll", "(I)V", |
| (void*) SelectAll }, |
| { "nativeCertTrustChanged","()V", |
| (void*) nativeCertTrustChanged }, |
| { "nativeFindAll", "(ILjava/lang/String;)I", |
| (void*) FindAll }, |
| { "nativeFindNext", "(IZ)I", |
| (void*) FindNext }, |
| { "nativeSetInitialFocus", "(II)V", (void*) SetInitialFocus }, |
| }; |
| |
| int registerWebViewCore(JNIEnv* env) |
| { |
| jclass widget = env->FindClass("android/webkit/WebViewCore"); |
| ALOG_ASSERT(widget, |
| "Unable to find class android/webkit/WebViewCore"); |
| gWebViewCoreFields.m_nativeClass = env->GetFieldID(widget, "mNativeClass", |
| "I"); |
| ALOG_ASSERT(gWebViewCoreFields.m_nativeClass, |
| "Unable to find android/webkit/WebViewCore.mNativeClass"); |
| gWebViewCoreFields.m_viewportWidth = env->GetFieldID(widget, |
| "mViewportWidth", "I"); |
| ALOG_ASSERT(gWebViewCoreFields.m_viewportWidth, |
| "Unable to find android/webkit/WebViewCore.mViewportWidth"); |
| gWebViewCoreFields.m_viewportHeight = env->GetFieldID(widget, |
| "mViewportHeight", "I"); |
| ALOG_ASSERT(gWebViewCoreFields.m_viewportHeight, |
| "Unable to find android/webkit/WebViewCore.mViewportHeight"); |
| gWebViewCoreFields.m_viewportInitialScale = env->GetFieldID(widget, |
| "mViewportInitialScale", "I"); |
| ALOG_ASSERT(gWebViewCoreFields.m_viewportInitialScale, |
| "Unable to find android/webkit/WebViewCore.mViewportInitialScale"); |
| gWebViewCoreFields.m_viewportMinimumScale = env->GetFieldID(widget, |
| "mViewportMinimumScale", "I"); |
| ALOG_ASSERT(gWebViewCoreFields.m_viewportMinimumScale, |
| "Unable to find android/webkit/WebViewCore.mViewportMinimumScale"); |
| gWebViewCoreFields.m_viewportMaximumScale = env->GetFieldID(widget, |
| "mViewportMaximumScale", "I"); |
| ALOG_ASSERT(gWebViewCoreFields.m_viewportMaximumScale, |
| "Unable to find android/webkit/WebViewCore.mViewportMaximumScale"); |
| gWebViewCoreFields.m_viewportUserScalable = env->GetFieldID(widget, |
| "mViewportUserScalable", "Z"); |
| ALOG_ASSERT(gWebViewCoreFields.m_viewportUserScalable, |
| "Unable to find android/webkit/WebViewCore.mViewportUserScalable"); |
| gWebViewCoreFields.m_viewportDensityDpi = env->GetFieldID(widget, |
| "mViewportDensityDpi", "I"); |
| ALOG_ASSERT(gWebViewCoreFields.m_viewportDensityDpi, |
| "Unable to find android/webkit/WebViewCore.mViewportDensityDpi"); |
| gWebViewCoreFields.m_drawIsPaused = env->GetFieldID(widget, |
| "mDrawIsPaused", "Z"); |
| ALOG_ASSERT(gWebViewCoreFields.m_drawIsPaused, |
| "Unable to find android/webkit/WebViewCore.mDrawIsPaused"); |
| gWebViewCoreFields.m_lowMemoryUsageMb = env->GetFieldID(widget, "mLowMemoryUsageThresholdMb", "I"); |
| gWebViewCoreFields.m_highMemoryUsageMb = env->GetFieldID(widget, "mHighMemoryUsageThresholdMb", "I"); |
| gWebViewCoreFields.m_highUsageDeltaMb = env->GetFieldID(widget, "mHighUsageDeltaMb", "I"); |
| |
| gWebViewCoreStaticMethods.m_isSupportedMediaMimeType = |
| env->GetStaticMethodID(widget, "isSupportedMediaMimeType", "(Ljava/lang/String;)Z"); |
| LOG_FATAL_IF(!gWebViewCoreStaticMethods.m_isSupportedMediaMimeType, |
| "Could not find static method isSupportedMediaMimeType from WebViewCore"); |
| |
| env->DeleteLocalRef(widget); |
| |
| return jniRegisterNativeMethods(env, "android/webkit/WebViewCore", |
| gJavaWebViewCoreMethods, NELEM(gJavaWebViewCoreMethods)); |
| } |
| |
| } /* namespace android */ |